clay-server 2.31.0 → 2.32.0-beta.2

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 (74) hide show
  1. package/lib/browser-mcp-server.js +32 -44
  2. package/lib/debate-mcp-server.js +14 -31
  3. package/lib/mcp-local.js +31 -1
  4. package/lib/project-connection.js +4 -2
  5. package/lib/project-filesystem.js +47 -1
  6. package/lib/project-http.js +75 -8
  7. package/lib/project-mcp.js +4 -0
  8. package/lib/project-sessions.js +88 -51
  9. package/lib/project-user-message.js +12 -7
  10. package/lib/project.js +204 -90
  11. package/lib/public/app.js +123 -448
  12. package/lib/public/codex-avatar.png +0 -0
  13. package/lib/public/css/debate.css +3 -2
  14. package/lib/public/css/filebrowser.css +91 -1
  15. package/lib/public/css/icon-strip.css +21 -5
  16. package/lib/public/css/input.css +181 -100
  17. package/lib/public/css/mates.css +43 -0
  18. package/lib/public/css/mention.css +48 -4
  19. package/lib/public/css/menus.css +1 -1
  20. package/lib/public/css/messages.css +2 -0
  21. package/lib/public/css/notifications-center.css +19 -0
  22. package/lib/public/index.html +46 -24
  23. package/lib/public/modules/app-connection.js +138 -37
  24. package/lib/public/modules/app-cursors.js +18 -17
  25. package/lib/public/modules/app-debate-ui.js +9 -9
  26. package/lib/public/modules/app-dm.js +170 -131
  27. package/lib/public/modules/app-favicon.js +28 -26
  28. package/lib/public/modules/app-header.js +79 -68
  29. package/lib/public/modules/app-home-hub.js +55 -47
  30. package/lib/public/modules/app-loop-ui.js +34 -18
  31. package/lib/public/modules/app-loop-wizard.js +6 -6
  32. package/lib/public/modules/app-messages.js +195 -152
  33. package/lib/public/modules/app-misc.js +23 -12
  34. package/lib/public/modules/app-notifications.js +97 -3
  35. package/lib/public/modules/app-panels.js +203 -49
  36. package/lib/public/modules/app-projects.js +159 -150
  37. package/lib/public/modules/app-rate-limit.js +5 -4
  38. package/lib/public/modules/app-rendering.js +149 -101
  39. package/lib/public/modules/app-skills-install.js +4 -4
  40. package/lib/public/modules/context-sources.js +12 -41
  41. package/lib/public/modules/dom-refs.js +21 -0
  42. package/lib/public/modules/filebrowser.js +173 -2
  43. package/lib/public/modules/input.js +86 -0
  44. package/lib/public/modules/mate-sidebar.js +38 -0
  45. package/lib/public/modules/mention.js +24 -6
  46. package/lib/public/modules/scheduler.js +1 -1
  47. package/lib/public/modules/sidebar-mates.js +66 -34
  48. package/lib/public/modules/sidebar-mobile.js +34 -30
  49. package/lib/public/modules/sidebar-projects.js +60 -57
  50. package/lib/public/modules/sidebar-sessions.js +75 -69
  51. package/lib/public/modules/sidebar.js +12 -20
  52. package/lib/public/modules/skills.js +8 -9
  53. package/lib/public/modules/sticky-notes.js +1 -2
  54. package/lib/public/modules/store.js +9 -2
  55. package/lib/public/modules/stt.js +4 -1
  56. package/lib/public/modules/tools.js +14 -9
  57. package/lib/sdk-bridge.js +511 -1113
  58. package/lib/sdk-message-processor.js +123 -134
  59. package/lib/sdk-worker.js +4 -0
  60. package/lib/server-dm.js +1 -0
  61. package/lib/server.js +86 -1
  62. package/lib/sessions.js +47 -36
  63. package/lib/ws-schema.js +2 -0
  64. package/lib/yoke/adapters/claude-worker.js +559 -0
  65. package/lib/yoke/adapters/claude.js +1418 -0
  66. package/lib/yoke/adapters/codex.js +968 -0
  67. package/lib/yoke/adapters/gemini.js +668 -0
  68. package/lib/yoke/codex-app-server.js +307 -0
  69. package/lib/yoke/index.js +199 -0
  70. package/lib/yoke/instructions.js +62 -0
  71. package/lib/yoke/interface.js +92 -0
  72. package/lib/yoke/mcp-bridge-server.js +294 -0
  73. package/lib/yoke/package.json +7 -0
  74. package/package.json +3 -1
@@ -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 { renderUserStrip, updateDmBadge, renderSidebarPresence } from './sidebar-mates.js';
14
+ import { updateDmBadge, renderSidebarPresence } 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';
@@ -21,7 +21,7 @@ import { handleFindInSessionResults } from './session-search.js';
21
21
  import { handleInputSync, autoResize, builtinCommands, setScheduleBtnDisabled } from './input.js';
22
22
  import { startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, closeToolGroup, removeToolFromGroup, resetToolState, getTools, getPlanContent, setPlanContent, renderPlanBanner, renderPlanCard, getTodoTools, handleTodoWrite, handleTaskCreate, handleTaskUpdate, isPlanFilePath, enableMainInput, addTurnMeta, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, initSubagentStop, updateSubagentProgress, updateSubagentTaskStatus, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionCancelled, markPermissionResolved, renderElicitationRequest, markElicitationResolved } from './tools.js';
23
23
  import { showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './notifications.js';
24
- import { handleFsList, handleFsRead, handleFileChanged, handleDirChanged, handleFileHistory, handleGitDiff, handleFileAt, refreshIfOpen, getPendingNavigate } from './filebrowser.js';
24
+ import { handleFsList, handleFsRead, handleFileChanged, handleDirChanged, handleFileHistory, handleGitDiff, handleFileAt, refreshIfOpen, getPendingNavigate, handleFsSearch } from './filebrowser.js';
25
25
  import { isProjectSettingsOpen, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './project-settings.js';
26
26
  import { updateSettingsModels, updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './server-settings.js';
27
27
  import { handleTermList, handleTermCreated, sendTerminalCommand, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed } from './terminal.js';
@@ -40,17 +40,17 @@ import { handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunF
40
40
  import { scrollToBottom, addToMessages, addUserMessage, addSystemMessage, removeMatePreThinking, appendDelta, finalizeAssistantBlock, addConflictMessage, addContextOverflowMessage, addAuthRequiredMessage, showSuggestionChips } from './app-rendering.js';
41
41
  import { setActivity, startUrgentBlink, stopUrgentBlink, blinkSessionDot } from './app-favicon.js';
42
42
  import { setStatus } from './app-connection.js';
43
- import { updateConfigChip, getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
43
+ import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
44
44
  import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './app-projects.js';
45
45
  import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
46
46
  import { hideHomeHub, handleHubSchedules } from './app-home-hub.js';
47
47
  import { openDm, enterDmMode, exitDmMode, handleMateCreatedInApp, updateMateIconStatus, appendDmMessage, showDmTypingIndicator, buildMateInterviewPrompt } from './app-dm.js';
48
48
  import { handleRateLimitEvent, updateRateLimitUsage, addScheduledMessageBubble, removeScheduledMessageBubble, handleFastModeState } from './app-rate-limit.js';
49
49
  import { handleRemoteCursorMove, handleRemoteCursorLeave, handleRemoteSelection, clearRemoteCursors } from './app-cursors.js';
50
- import { updateLoopButton, showLoopBanner, updateLoopBanner, updateRalphBars, updateLoopInputVisibility, showRalphApprovalBar, updateRalphApprovalStatus, openRalphPreviewModal, showExecModal, updateExecModalStatus } from './app-loop-ui.js';
50
+ import { showLoopBanner, updateLoopBanner, updateLoopInputVisibility, showRalphApprovalBar, updateRalphApprovalStatus, openRalphPreviewModal, showExecModal, updateExecModalStatus } from './app-loop-ui.js';
51
51
  import { showDebateSticky, showDebateConcludeConfirm, showDebateUserFloor, exitDebateFloorMode, exitDebateConcludeMode, exitDebateEndedMode, updateDebateRound, renderDebateUserFloorDone } from './app-debate-ui.js';
52
52
  import { handleSkillInstallWs } from './app-skills-install.js';
53
- import { handleNotificationsState, handleNotificationCreated, handleNotificationDismissed, handleNotificationDismissedAll } from './app-notifications.js';
53
+ import { handleNotificationsState, handleNotificationCreated, handleNotificationDismissed, handleNotificationDismissedAll, showUpdateBanner } from './app-notifications.js';
54
54
  import { handleDebatePreparing, handleDebateBriefReady, renderDebateBriefReady, handleDebateStarted, renderDebateStarted, handleDebateTurn, handleDebateActivity, handleDebateStream, handleDebateTurnDone, handleDebateCommentQueued, handleDebateCommentInjected, renderDebateCommentInjected, handleDebateResumed, handleDebateEnded, renderDebateEnded, handleDebateError, isDebateActive, renderMcpDebateProposal, renderDebateUserResume } from './debate.js';
55
55
  import { handleMentionStart, handleMentionActivity, handleMentionStream, handleMentionDone, handleMentionError, renderMentionUser, renderMentionResponse } from './mention.js';
56
56
 
@@ -62,8 +62,8 @@ var connectOverlay = document.getElementById("connect-overlay");
62
62
 
63
63
  export function processMessage(msg) {
64
64
  // Preserve original timestamp from history replay
65
- store.setState({ currentMsgTs: msg._ts || null });
66
- var isMateDm = store.getState().dmMode && store.getState().dmTargetUser && store.getState().dmTargetUser.isMate;
65
+ store.set({ currentMsgTs: msg._ts || null });
66
+ var isMateDm = store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate;
67
67
 
68
68
  // Mate DM: update mate icon status indicators
69
69
  if (isMateDm) updateMateIconStatus(msg);
@@ -74,11 +74,11 @@ export function processMessage(msg) {
74
74
  renderMateSessionList(msg.sessions || []);
75
75
  refreshMobileChatSheet();
76
76
  // Override title bar with mate name and re-apply color
77
- var _mdn = (store.getState().dmTargetUser.displayName || "New Mate");
77
+ var _mdn = (store.get('dmTargetUser').displayName || "New Mate");
78
78
  if (headerTitleEl) headerTitleEl.textContent = _mdn;
79
79
  var _tbpn = document.getElementById("title-bar-project-name");
80
80
  if (_tbpn) _tbpn.textContent = _mdn;
81
- var _mc2 = (store.getState().dmTargetUser.profile && store.getState().dmTargetUser.profile.avatarColor) || store.getState().dmTargetUser.avatarColor || "#7c3aed";
81
+ var _mc2 = (store.get('dmTargetUser').profile && store.get('dmTargetUser').profile.avatarColor) || store.get('dmTargetUser').avatarColor || "#7c3aed";
82
82
  var _tbc2 = document.querySelector(".title-bar-content");
83
83
  if (_tbc2) { _tbc2.style.background = _mc2; _tbc2.classList.add("mate-dm-active"); }
84
84
  document.body.classList.add("mate-dm-active");
@@ -115,12 +115,12 @@ export function processMessage(msg) {
115
115
  var readyMatch = fullText.match(/\[\[MATE_READY:\s*(.+?)\]\]/);
116
116
  if (readyMatch) {
117
117
  var newName = readyMatch[1].trim();
118
- store.getState().dmTargetUser.displayName = newName;
119
- updateMateSidebarProfile({ profile: { displayName: newName, avatarColor: store.getState().dmTargetUser.avatarColor, avatarStyle: store.getState().dmTargetUser.avatarStyle, avatarSeed: store.getState().dmTargetUser.avatarSeed } });
118
+ store.get('dmTargetUser').displayName = newName;
119
+ updateMateSidebarProfile({ profile: { displayName: newName, avatarColor: store.get('dmTargetUser').avatarColor, avatarStyle: store.get('dmTargetUser').avatarStyle, avatarSeed: store.get('dmTargetUser').avatarSeed } });
120
120
  if (getWs() && getWs().readyState === 1) {
121
121
  getWs().send(JSON.stringify({
122
122
  type: "mate_update",
123
- mateId: store.getState().dmTargetUser.id,
123
+ mateId: store.get('dmTargetUser').id,
124
124
  updates: { name: newName, status: "ready", profile: { displayName: newName } },
125
125
  }));
126
126
  }
@@ -138,7 +138,7 @@ export function processMessage(msg) {
138
138
 
139
139
  switch (msg.type) {
140
140
  case "history_meta":
141
- store.setState({ historyFrom: msg.from, historyTotal: msg.total, replayingHistory: true });
141
+ store.set({ historyFrom: msg.from, historyTotal: msg.total, replayingHistory: true });
142
142
  updateHistorySentinel();
143
143
  break;
144
144
 
@@ -147,10 +147,14 @@ export function processMessage(msg) {
147
147
  break;
148
148
 
149
149
  case "history_done":
150
- store.setState({ replayingHistory: false });
150
+ store.set({ replayingHistory: false });
151
+ // Hide vendor toggle if session has history (vendor already locked)
152
+ var _hTotal = store.get('historyTotal') || 0;
153
+ var _vtw2 = document.getElementById("vendor-toggle-wrap");
154
+ if (_vtw2 && _hTotal > 0) { _vtw2.classList.remove("hidden"); _vtw2.classList.add("locked"); }
151
155
  // Restore cached rich context usage BEFORE updateContextPanel runs
152
156
  if (msg.contextUsage) {
153
- store.setState({ richContextUsage: msg.contextUsage });
157
+ store.set({ richContextUsage: msg.contextUsage });
154
158
  }
155
159
  // Restore accurate context data from the last result in full history
156
160
  if (msg.lastUsage || msg.lastModelUsage) {
@@ -159,7 +163,7 @@ export function processMessage(msg) {
159
163
  updateContextPanel();
160
164
  updateUsagePanel();
161
165
  // Render + finalize any incomplete turn from the replayed history
162
- var _hs = store.getState();
166
+ var _hs = store.snap();
163
167
  if (_hs.currentMsgEl && _hs.currentFullText) {
164
168
  var replayContentEl = _hs.currentMsgEl.querySelector(".md-content");
165
169
  if (replayContentEl) {
@@ -178,7 +182,7 @@ export function processMessage(msg) {
178
182
  var dbBadges = document.querySelectorAll(".debate-header-badge");
179
183
  for (var dbi = 0; dbi < dbBadges.length; dbi++) dbBadges[dbi].remove();
180
184
  // Clean up all debate mode banners if debate is not active on this session
181
- var _ds = store.getState();
185
+ var _ds = store.snap();
182
186
  if (_ds.debateFloorMode) exitDebateFloorMode();
183
187
  if (_ds.debateConcludeMode) exitDebateConcludeMode();
184
188
  if (_ds.debateEndedMode) exitDebateEndedMode();
@@ -208,20 +212,20 @@ export function processMessage(msg) {
208
212
  break;
209
213
 
210
214
  case "restore_mate_dm":
211
- if (msg.mateId && !store.getState().returningFromMateDm) {
215
+ if (msg.mateId && !store.get('returningFromMateDm')) {
212
216
  // Server-driven mate DM restore on reconnect
213
217
  // Note: do NOT remove mate-dm-active here; openDm is async (skill check)
214
218
  // and removing the class causes a flash where mate UI is lost.
215
219
  // enterDmMode will properly set/reset the class when DM is entered.
216
- if (store.getState().dmMode) {
217
- store.setState({ dmMode: false });
220
+ if (store.get('dmMode')) {
221
+ store.set({ dmMode: false });
218
222
  }
219
223
  messagesEl.innerHTML = "";
220
224
  openDm(msg.mateId);
221
225
  }
222
226
  // Clear the flag and notify server that mate DM is closed
223
- if (store.getState().returningFromMateDm) {
224
- store.setState({ returningFromMateDm: false });
227
+ if (store.get('returningFromMateDm')) {
228
+ store.set({ returningFromMateDm: false });
225
229
  if (getWs() && getWs().readyState === 1) {
226
230
  try { getWs().send(JSON.stringify({ type: "set_mate_dm", mateId: null })); } catch(e) {}
227
231
  }
@@ -233,25 +237,25 @@ export function processMessage(msg) {
233
237
  addSystemMessage(msg.text, false);
234
238
  break;
235
239
  }
236
- store.setState({ projectName: msg.project || msg.cwd });
237
- if (msg.cwd) store.setState({ cwd: msg.cwd });
238
- if (msg.slug) store.setState({ currentSlug: msg.slug });
239
- try { var _is = store.getState(); localStorage.setItem("clay-project-name-" + (_is.currentSlug || "default"), _is.projectName); } catch (e) {}
240
+ store.set({ projectName: msg.project || msg.cwd });
241
+ if (msg.cwd) store.set({ cwd: msg.cwd });
242
+ if (msg.slug) store.set({ currentSlug: msg.slug });
243
+ try { var _is = store.snap(); localStorage.setItem("clay-project-name-" + (_is.currentSlug || "default"), _is.projectName); } catch (e) {}
240
244
  // In mate DM, keep title as mate name and re-apply mate color
241
- if (store.getState().dmMode && store.getState().dmTargetUser && store.getState().dmTargetUser.isMate) {
242
- var _mateDN = store.getState().dmTargetUser.displayName || "New Mate";
245
+ if (store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate) {
246
+ var _mateDN = store.get('dmTargetUser').displayName || "New Mate";
243
247
  headerTitleEl.textContent = _mateDN;
244
248
  var tbProjectName = document.getElementById("title-bar-project-name");
245
249
  if (tbProjectName) tbProjectName.textContent = _mateDN;
246
250
  // Re-apply mate title bar styling (may be lost during project switch)
247
- var _mc = (store.getState().dmTargetUser.profile && store.getState().dmTargetUser.profile.avatarColor) || store.getState().dmTargetUser.avatarColor || "#7c3aed";
251
+ var _mc = (store.get('dmTargetUser').profile && store.get('dmTargetUser').profile.avatarColor) || store.get('dmTargetUser').avatarColor || "#7c3aed";
248
252
  var _tbc = document.querySelector(".title-bar-content");
249
253
  if (_tbc) { _tbc.style.background = _mc; _tbc.classList.add("mate-dm-active"); }
250
254
  document.body.classList.add("mate-dm-active");
251
255
  } else {
252
- headerTitleEl.textContent = store.getState().projectName;
256
+ headerTitleEl.textContent = store.get('projectName');
253
257
  var tbProjectName = document.getElementById("title-bar-project-name");
254
- if (tbProjectName) tbProjectName.textContent = msg.title || store.getState().projectName;
258
+ if (tbProjectName) tbProjectName.textContent = msg.title || store.get('projectName');
255
259
  }
256
260
  updatePageTitle();
257
261
  if (msg.version) {
@@ -259,12 +263,12 @@ export function processMessage(msg) {
259
263
  var serverVersionEl = document.getElementById("settings-server-version");
260
264
  if (serverVersionEl) serverVersionEl.textContent = msg.version;
261
265
  }
262
- if (msg.projectOwnerId !== undefined) store.setState({ currentProjectOwnerId: msg.projectOwnerId });
263
- if (msg.ownerLocked !== undefined) store.setState({ ownerLocked: !!msg.ownerLocked });
264
- if (msg.osUsers !== undefined) store.setState({ isOsUsers: !!msg.osUsers });
266
+ if (msg.projectOwnerId !== undefined) store.set({ currentProjectOwnerId: msg.projectOwnerId });
267
+ if (msg.ownerLocked !== undefined) store.set({ ownerLocked: !!msg.ownerLocked });
268
+ if (msg.osUsers !== undefined) store.set({ isOsUsers: !!msg.osUsers });
265
269
  if (msg.lanHost) window.__lanHost = msg.lanHost;
266
270
  if (msg.dangerouslySkipPermissions) {
267
- store.setState({ skipPermsEnabled: true });
271
+ store.set({ skipPermsEnabled: true });
268
272
  var spBanner = document.getElementById("skip-perms-pill");
269
273
  if (spBanner) spBanner.classList.remove("hidden");
270
274
  }
@@ -273,13 +277,15 @@ export function processMessage(msg) {
273
277
 
274
278
  case "update_available":
275
279
  // In multi-user mode, only show update UI to admins
276
- if (store.getState().isMultiUserMode) {
280
+ if (store.get('isMultiUserMode')) {
277
281
  checkAdminAccess().then(function (isAdmin) {
278
282
  if (!isAdmin) return;
279
283
  showUpdateAvailable(msg);
284
+ showUpdateBanner(msg);
280
285
  });
281
286
  } else {
282
287
  showUpdateAvailable(msg);
288
+ showUpdateBanner(msg);
283
289
  }
284
290
  break;
285
291
 
@@ -321,18 +327,24 @@ export function processMessage(msg) {
321
327
 
322
328
  case "slash_commands":
323
329
  var reserved = new Set(builtinCommands.map(function (c) { return c.name; }));
324
- store.setState({ slashCommands: (msg.commands || []).filter(function (name) {
330
+ store.set({ slashCommands: (msg.commands || []).filter(function (name) {
325
331
  return !reserved.has(name);
326
332
  }).map(function (name) {
327
333
  return { name: name, desc: "Skill" };
328
334
  }) });
329
335
  break;
330
336
 
331
- case "model_info":
332
- store.setState({ currentModel: msg.model || store.getState().currentModel, currentModels: msg.models || [] });
333
- updateConfigChip();
334
- updateSettingsModels(msg.model, msg.models || []);
337
+ case "model_info": {
338
+ var _modelVal = msg.model;
339
+ if (_modelVal && typeof _modelVal === "object") _modelVal = _modelVal.value || _modelVal.displayName || "";
340
+ var _miUpdate = { currentModel: _modelVal || store.get('currentModel'), currentModels: msg.models || [] };
341
+ if (msg.vendor) _miUpdate.currentVendor = msg.vendor;
342
+ if (msg.availableVendors) _miUpdate.availableVendors = msg.availableVendors;
343
+ if (msg.installedVendors) _miUpdate.installedVendors = msg.installedVendors;
344
+ store.set(_miUpdate);
345
+ updateSettingsModels(_modelVal, msg.models || []);
335
346
  break;
347
+ }
336
348
 
337
349
  case "config_state": {
338
350
  var _cs = {};
@@ -342,20 +354,27 @@ export function processMessage(msg) {
342
354
  if (msg.betas) _cs.currentBetas = msg.betas;
343
355
  if (msg.thinking) _cs.currentThinking = msg.thinking;
344
356
  if (msg.thinkingBudget) _cs.currentThinkingBudget = msg.thinkingBudget;
345
- store.setState(_cs);
357
+ store.set(_cs);
346
358
  // Validate effort against current model's supported levels
347
- var _csRead = store.getState();
359
+ var _csRead = store.snap();
348
360
  if (_csRead.currentModels.length > 0) {
349
361
  var levels = getModelEffortLevels();
350
362
  var effortValid = false;
351
363
  for (var ei = 0; ei < levels.length; ei++) {
352
364
  if (levels[ei] === _csRead.currentEffort) { effortValid = true; break; }
353
365
  }
354
- if (!effortValid) store.setState({ currentEffort: "medium" });
366
+ if (!effortValid) store.set({ currentEffort: "medium" });
355
367
  }
356
- updateConfigChip();
357
368
  } break;
358
369
 
370
+ case "codex_config":
371
+ store.set({
372
+ codexApproval: msg.approval || "on-failure",
373
+ codexSandbox: msg.sandbox || "workspace-write",
374
+ codexWebSearch: msg.webSearch || "disabled",
375
+ });
376
+ break;
377
+
359
378
  case "client_count":
360
379
  // Sidebar presence: current project's online users
361
380
  if (msg.users) {
@@ -382,13 +401,13 @@ export function processMessage(msg) {
382
401
 
383
402
  case "skill_installed":
384
403
  handleSkillInstalled(msg);
385
- if (msg.success) { var _kis = Object.assign({}, store.getState().knownInstalledSkills); _kis[msg.skill] = true; store.setState({ knownInstalledSkills: _kis }); }
404
+ if (msg.success) { var _kis = Object.assign({}, store.get('knownInstalledSkills')); _kis[msg.skill] = true; store.set({ knownInstalledSkills: _kis }); }
386
405
  handleSkillInstallWs(msg);
387
406
  break;
388
407
 
389
408
  case "skill_uninstalled":
390
409
  handleSkillUninstalled(msg);
391
- if (msg.success) { var _kis2 = Object.assign({}, store.getState().knownInstalledSkills); delete _kis2[msg.skill]; store.setState({ knownInstalledSkills: _kis2 }); }
410
+ if (msg.success) { var _kis2 = Object.assign({}, store.get('knownInstalledSkills')); delete _kis2[msg.skill]; store.set({ knownInstalledSkills: _kis2 }); }
392
411
  break;
393
412
 
394
413
  case "loop_registry_updated":
@@ -424,7 +443,7 @@ export function processMessage(msg) {
424
443
  break;
425
444
 
426
445
  case "input_sync":
427
- if (!store.getState().dmMode) handleInputSync(msg.text);
446
+ if (!store.get('dmMode')) handleInputSync(msg.text);
428
447
  break;
429
448
 
430
449
  case "session_list":
@@ -474,23 +493,47 @@ export function processMessage(msg) {
474
493
  case "session_switched":
475
494
  hideHomeHub();
476
495
  // Save draft from outgoing session
477
- var _prevSid = store.getState().activeSessionId;
496
+ var _prevSid = store.get('activeSessionId');
478
497
  if (_prevSid && inputEl.value) {
479
- store.getState().sessionDrafts[_prevSid] = inputEl.value;
498
+ store.get('sessionDrafts')[_prevSid] = inputEl.value;
480
499
  } else if (_prevSid) {
481
- delete store.getState().sessionDrafts[_prevSid];
500
+ delete store.get('sessionDrafts')[_prevSid];
501
+ }
502
+ store.set({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null, vendorCapabilities: msg.capabilities || {} });
503
+ if (msg.vendor) {
504
+ store.set({ currentVendor: msg.vendor });
505
+ } else if (msg.hasHistory) {
506
+ // Existing session without explicit vendor: reset to claude
507
+ store.set({ currentVendor: "claude" });
508
+ } else if (!msg.hasHistory) {
509
+ // New session without vendor: use mate's default vendor if in DM mode
510
+ var _dmTarget = store.get('dmTargetUser');
511
+ if (_dmTarget && _dmTarget.isMate) {
512
+ var _mateList = store.get('cachedMatesList') || [];
513
+ for (var _mi = 0; _mi < _mateList.length; _mi++) {
514
+ if (_mateList[_mi].id === _dmTarget.id && _mateList[_mi].vendor) {
515
+ store.set({ currentVendor: _mateList[_mi].vendor });
516
+ break;
517
+ }
518
+ }
519
+ }
520
+ }
521
+ // Show vendor toggle only for new sessions (no history)
522
+ var _vtw = document.getElementById("vendor-toggle-wrap");
523
+ if (_vtw) {
524
+ if (msg.hasHistory) { _vtw.classList.remove("hidden"); _vtw.classList.add("locked"); }
525
+ else { _vtw.classList.remove("locked"); _vtw.classList.remove("hidden"); }
482
526
  }
483
- store.setState({ activeSessionId: msg.id, cliSessionId: msg.cliSessionId || null });
484
527
  // Session presence is now tracked server-side (user-presence.json)
485
528
  clearRemoteCursors();
486
529
  resetClientState();
487
- updateRalphBars();
530
+
488
531
  updateLoopInputVisibility(msg.loop);
489
532
  // Restore input area visibility (may have been hidden by auth_required)
490
533
  var inputAreaSw = document.getElementById("input-area");
491
534
  if (inputAreaSw) inputAreaSw.classList.remove("hidden");
492
535
  // Restore draft for incoming session
493
- var draft = store.getState().sessionDrafts[store.getState().activeSessionId] || "";
536
+ var draft = store.get('sessionDrafts')[store.get('activeSessionId')] || "";
494
537
  inputEl.value = draft;
495
538
  autoResize();
496
539
  if (!("ontouchstart" in window)) {
@@ -499,7 +542,7 @@ export function processMessage(msg) {
499
542
  break;
500
543
 
501
544
  case "session_id":
502
- store.setState({ cliSessionId: msg.cliSessionId });
545
+ store.set({ cliSessionId: msg.cliSessionId });
503
546
  break;
504
547
 
505
548
  case "message_uuid":
@@ -513,9 +556,9 @@ export function processMessage(msg) {
513
556
  }
514
557
  if (uuidTarget) {
515
558
  uuidTarget.dataset.uuid = msg.uuid;
516
- if (msg.messageType === "user") addRewindButton(uuidTarget);
559
+ if (msg.messageType === "user" && (store.get('vendorCapabilities') || {}).rewind !== false) addRewindButton(uuidTarget);
517
560
  }
518
- store.getState().messageUuidMap.push({ uuid: msg.uuid, type: msg.messageType });
561
+ store.get('messageUuidMap').push({ uuid: msg.uuid, type: msg.messageType });
519
562
  break;
520
563
 
521
564
  case "user_message":
@@ -595,7 +638,7 @@ export function processMessage(msg) {
595
638
  case "status":
596
639
  if (msg.status === "processing") {
597
640
  setStatus("processing");
598
- if (!(store.getState().dmMode && store.getState().dmTargetUser && store.getState().dmTargetUser.isMate) && !store.getState().matePreThinkingEl) {
641
+ if (!(store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate) && !store.get('matePreThinkingEl')) {
599
642
  setActivity("thinking");
600
643
  }
601
644
  }
@@ -604,7 +647,7 @@ export function processMessage(msg) {
604
647
  case "compacting":
605
648
  if (msg.active) {
606
649
  setActivity("compacting");
607
- } else if (!(store.getState().dmMode && store.getState().dmTargetUser && store.getState().dmTargetUser.isMate)) {
650
+ } else if (!(store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate)) {
608
651
  setActivity("thinking");
609
652
  }
610
653
  break;
@@ -620,7 +663,7 @@ export function processMessage(msg) {
620
663
 
621
664
  case "thinking_stop":
622
665
  stopThinking(msg.duration);
623
- if (!(store.getState().dmMode && store.getState().dmTargetUser && store.getState().dmTargetUser.isMate)) {
666
+ if (!(store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate)) {
624
667
  setActivity("thinking");
625
668
  }
626
669
  break;
@@ -725,7 +768,7 @@ export function processMessage(msg) {
725
768
  break;
726
769
 
727
770
  case "permission_request":
728
- renderPermissionRequest(msg.requestId, msg.toolName, msg.toolInput, msg.decisionReason, msg.mateId);
771
+ renderPermissionRequest(msg.requestId, msg.toolName, msg.toolInput, msg.decisionReason, msg.mateId, msg.vendor);
729
772
  startUrgentBlink();
730
773
  break;
731
774
 
@@ -740,7 +783,7 @@ export function processMessage(msg) {
740
783
  break;
741
784
 
742
785
  case "permission_request_pending":
743
- renderPermissionRequest(msg.requestId, msg.toolName, msg.toolInput, msg.decisionReason, msg.mateId);
786
+ renderPermissionRequest(msg.requestId, msg.toolName, msg.toolInput, msg.decisionReason, msg.mateId, msg.vendor);
744
787
  startUrgentBlink();
745
788
  break;
746
789
 
@@ -805,23 +848,21 @@ export function processMessage(msg) {
805
848
  break;
806
849
 
807
850
  case "context_usage":
808
- if (msg.data && !store.getState().replayingHistory) {
809
- store.setState({ richContextUsage: msg.data });
810
- var _hce = store.getState().headerContextEl;
811
- if (_hce) _hce.removeAttribute("data-tip");
812
- if (store.getState().ctxPopoverVisible) renderCtxPopover();
851
+ if (msg.data && !store.get('replayingHistory')) {
852
+ store.set({ richContextUsage: msg.data });
853
+ // UI sync handled by store subscriber in app-panels.js
813
854
  }
814
855
  break;
815
856
 
816
857
  case "done":
858
+ removeMatePreThinking();
817
859
  setActivity(null);
818
860
  stopThinking();
819
861
  markAllToolsDone();
820
862
  closeToolGroup();
821
863
  finalizeAssistantBlock();
822
- store.setState({ processing: false });
823
864
  setStatus("connected");
824
- if (!store.getState().loopActive) enableMainInput();
865
+ if (!store.get('loopActive')) enableMainInput();
825
866
  resetToolState();
826
867
  stopUrgentBlink();
827
868
  if (document.hidden) {
@@ -835,6 +876,7 @@ export function processMessage(msg) {
835
876
  break;
836
877
 
837
878
  case "error":
879
+ removeMatePreThinking();
838
880
  setActivity(null);
839
881
  addSystemMessage(msg.text, true);
840
882
  break;
@@ -848,16 +890,19 @@ export function processMessage(msg) {
848
890
  break;
849
891
 
850
892
  case "process_conflict":
893
+ removeMatePreThinking();
851
894
  setActivity(null);
852
895
  addConflictMessage(msg);
853
896
  break;
854
897
 
855
898
  case "context_overflow":
899
+ removeMatePreThinking();
856
900
  setActivity(null);
857
901
  addContextOverflowMessage(msg);
858
902
  break;
859
903
 
860
904
  case "auth_required":
905
+ removeMatePreThinking();
861
906
  setActivity(null);
862
907
  addAuthRequiredMessage(msg);
863
908
  break;
@@ -878,7 +923,6 @@ export function processMessage(msg) {
878
923
  case "scheduled_message_sent":
879
924
  removeScheduledMessageBubble();
880
925
  setScheduleBtnDisabled(false);
881
- store.setState({ processing: true });
882
926
  setStatus("processing");
883
927
  break;
884
928
 
@@ -892,7 +936,6 @@ export function processMessage(msg) {
892
936
  break;
893
937
 
894
938
  case "auto_continue_fired":
895
- store.setState({ processing: true });
896
939
  setStatus("processing");
897
940
  break;
898
941
 
@@ -935,6 +978,10 @@ export function processMessage(msg) {
935
978
  handleFsList(msg);
936
979
  break;
937
980
 
981
+ case "fs_search_result":
982
+ handleFsSearch(msg);
983
+ break;
984
+
938
985
  case "fs_read_result":
939
986
  if (msg.path === "CLAUDE.md" && isProjectSettingsOpen()) {
940
987
  handleInstructionsRead(msg);
@@ -1041,9 +1088,9 @@ export function processMessage(msg) {
1041
1088
 
1042
1089
  case "term_created":
1043
1090
  handleTermCreated(msg);
1044
- if (store.getState().pendingTermCommand) {
1045
- var cmd = store.getState().pendingTermCommand;
1046
- store.setState({ pendingTermCommand: null });
1091
+ if (store.get('pendingTermCommand')) {
1092
+ var cmd = store.get('pendingTermCommand');
1093
+ store.set({ pendingTermCommand: null });
1047
1094
  // Small delay to let terminal initialize
1048
1095
  setTimeout(function() {
1049
1096
  sendTerminalCommand(cmd);
@@ -1127,7 +1174,7 @@ export function processMessage(msg) {
1127
1174
  break;
1128
1175
 
1129
1176
  case "project_owner_changed":
1130
- store.setState({ currentProjectOwnerId: msg.ownerId });
1177
+ store.set({ currentProjectOwnerId: msg.ownerId });
1131
1178
  handleProjectOwnerChanged(msg);
1132
1179
  break;
1133
1180
 
@@ -1139,12 +1186,12 @@ export function processMessage(msg) {
1139
1186
  }
1140
1187
  enterDmMode(msg.dmKey, msg.targetUser, msg.messages);
1141
1188
  // Auto-send first interview prompt after mate DM opens
1142
- if (store.getState().pendingMateInterview && msg.targetUser && msg.targetUser.isMate && msg.projectSlug) {
1143
- var interviewMate = store.getState().pendingMateInterview;
1144
- store.setState({ pendingMateInterview: null });
1189
+ if (store.get('pendingMateInterview') && msg.targetUser && msg.targetUser.isMate && msg.projectSlug) {
1190
+ var interviewMate = store.get('pendingMateInterview');
1191
+ store.set({ pendingMateInterview: null });
1145
1192
  // Wait for mate project WS to connect, then send interview prompt
1146
1193
  var checkMateReady = setInterval(function () {
1147
- if (getWs() && getWs().readyState === 1 && store.getState().mateProjectSlug) {
1194
+ if (getWs() && getWs().readyState === 1 && store.get('mateProjectSlug')) {
1148
1195
  clearInterval(checkMateReady);
1149
1196
  var interviewText = buildMateInterviewPrompt(interviewMate);
1150
1197
  getWs().send(JSON.stringify({ type: "message", text: interviewText }));
@@ -1155,25 +1202,25 @@ export function processMessage(msg) {
1155
1202
  break;
1156
1203
 
1157
1204
  case "dm_message":
1158
- if (store.getState().dmMode && msg.dmKey === store.getState().dmKey) {
1205
+ if (store.get('dmMode') && msg.dmKey === store.get('dmKey')) {
1159
1206
  showDmTypingIndicator(false); // hide typing when message arrives
1160
1207
  appendDmMessage(msg.message);
1161
1208
  scrollToBottom();
1162
1209
  } else if (msg.message) {
1163
1210
  // DM notification when not in that DM
1164
1211
  var fromId = msg.message.from;
1165
- var _s1 = store.getState();
1166
- if (fromId && fromId !== _s1.myUserId) {
1167
- _s1.dmUnread[fromId] = (_s1.dmUnread[fromId] || 0) + 1;
1168
- // Re-render strip so non-favorited sender appears
1169
- renderUserStrip(_s1.cachedAllUsers, _s1.cachedOnlineIds, _s1.myUserId, _s1.cachedDmFavorites, _s1.cachedDmConversations, _s1.dmUnread, _s1.dmRemovedUsers, _s1.cachedMatesList);
1170
- updateDmBadge(fromId, _s1.dmUnread[fromId]);
1212
+ if (fromId && fromId !== store.get('myUserId')) {
1213
+ var _du = Object.assign({}, store.get('dmUnread'));
1214
+ _du[fromId] = (_du[fromId] || 0) + 1;
1215
+ store.set({ dmUnread: _du });
1216
+ // renderUserStrip is handled by the store subscriber
1217
+ updateDmBadge(fromId, _du[fromId]);
1171
1218
  }
1172
1219
  }
1173
1220
  break;
1174
1221
 
1175
1222
  case "dm_typing":
1176
- if (store.getState().dmMode && msg.dmKey === store.getState().dmKey) {
1223
+ if (store.get('dmMode') && msg.dmKey === store.get('dmKey')) {
1177
1224
  showDmTypingIndicator(msg.typing);
1178
1225
  }
1179
1226
  break;
@@ -1184,23 +1231,22 @@ export function processMessage(msg) {
1184
1231
 
1185
1232
  case "dm_favorites_updated":
1186
1233
  // Track users explicitly removed from favorites
1187
- var _cdf = store.getState().cachedDmFavorites;
1234
+ var _cdf = store.get('cachedDmFavorites');
1188
1235
  if (_cdf && msg.dmFavorites) {
1189
1236
  for (var ri = 0; ri < _cdf.length; ri++) {
1190
1237
  if (msg.dmFavorites.indexOf(_cdf[ri]) === -1) {
1191
- store.getState().dmRemovedUsers[_cdf[ri]] = true;
1238
+ store.get('dmRemovedUsers')[_cdf[ri]] = true;
1192
1239
  }
1193
1240
  }
1194
1241
  }
1195
1242
  // Clear removed flag for users being added back
1196
1243
  if (msg.dmFavorites) {
1197
1244
  for (var ai = 0; ai < msg.dmFavorites.length; ai++) {
1198
- delete store.getState().dmRemovedUsers[msg.dmFavorites[ai]];
1245
+ delete store.get('dmRemovedUsers')[msg.dmFavorites[ai]];
1199
1246
  }
1200
1247
  }
1201
- store.setState({ cachedDmFavorites: msg.dmFavorites || [] });
1202
- var _s2 = store.getState();
1203
- renderUserStrip(_s2.cachedAllUsers, _s2.cachedOnlineIds, _s2.myUserId, _s2.cachedDmFavorites, _s2.cachedDmConversations, _s2.dmUnread, _s2.dmRemovedUsers, _s2.cachedMatesList);
1248
+ store.set({ cachedDmFavorites: msg.dmFavorites || [] });
1249
+ // renderUserStrip is handled by the store subscriber
1204
1250
  break;
1205
1251
 
1206
1252
  case "mate_created":
@@ -1208,41 +1254,39 @@ export function processMessage(msg) {
1208
1254
  break;
1209
1255
 
1210
1256
  case "mate_deleted":
1211
- store.setState({ cachedMatesList: store.getState().cachedMatesList.filter(function (m) { return m.id !== msg.mateId; }) });
1212
- if (msg.availableBuiltins) store.setState({ cachedAvailableBuiltins: msg.availableBuiltins });
1213
- var _s3 = store.getState();
1214
- renderUserStrip(_s3.cachedAllUsers, _s3.cachedOnlineIds, _s3.myUserId, _s3.cachedDmFavorites, _s3.cachedDmConversations, _s3.dmUnread, _s3.dmRemovedUsers, _s3.cachedMatesList);
1257
+ store.set({ cachedMatesList: store.get('cachedMatesList').filter(function (m) { return m.id !== msg.mateId; }) });
1258
+ if (msg.availableBuiltins) store.set({ cachedAvailableBuiltins: msg.availableBuiltins });
1259
+ // renderUserStrip is handled by the store subscriber
1215
1260
  // If currently in DM with this mate, exit DM mode
1216
- if (_s3.dmMode && store.getState().dmTargetUser && store.getState().dmTargetUser.id === msg.mateId) {
1261
+ if (store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').id === msg.mateId) {
1217
1262
  exitDmMode();
1218
1263
  }
1219
1264
  break;
1220
1265
 
1221
1266
  case "mate_updated":
1222
1267
  if (msg.mate) {
1223
- var _cml = store.getState().cachedMatesList.slice();
1268
+ var _cml = store.get('cachedMatesList').slice();
1224
1269
  for (var mi = 0; mi < _cml.length; mi++) {
1225
1270
  if (_cml[mi].id === msg.mate.id) {
1226
1271
  _cml[mi] = msg.mate;
1227
1272
  break;
1228
1273
  }
1229
1274
  }
1230
- store.setState({ cachedMatesList: _cml });
1231
- var _s4 = store.getState();
1232
- renderUserStrip(_s4.cachedAllUsers, _s4.cachedOnlineIds, _s4.myUserId, _s4.cachedDmFavorites, _s4.cachedDmConversations, _s4.dmUnread, _s4.dmRemovedUsers, _cml);
1275
+ store.set({ cachedMatesList: _cml });
1276
+ // renderUserStrip is handled by the store subscriber
1233
1277
  // Update mate sidebar if currently viewing this mate
1234
- if (store.getState().dmMode && store.getState().dmTargetUser && store.getState().dmTargetUser.isMate && store.getState().dmTargetUser.id === msg.mate.id) {
1278
+ if (store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate && store.get('dmTargetUser').id === msg.mate.id) {
1235
1279
  updateMateSidebarProfile(msg.mate);
1236
1280
  // Sync dmTargetUser so subsequent renders use fresh data
1237
1281
  var mp2 = msg.mate.profile || {};
1238
- store.getState().dmTargetUser.displayName = mp2.displayName || msg.mate.name || store.getState().dmTargetUser.displayName;
1239
- store.getState().dmTargetUser.avatarStyle = mp2.avatarStyle || store.getState().dmTargetUser.avatarStyle;
1240
- store.getState().dmTargetUser.avatarSeed = mp2.avatarSeed || store.getState().dmTargetUser.avatarSeed;
1241
- store.getState().dmTargetUser.avatarColor = mp2.avatarColor || store.getState().dmTargetUser.avatarColor;
1242
- store.getState().dmTargetUser.avatarCustom = mp2.avatarCustom || "";
1243
- store.getState().dmTargetUser.profile = mp2;
1282
+ store.get('dmTargetUser').displayName = mp2.displayName || msg.mate.name || store.get('dmTargetUser').displayName;
1283
+ store.get('dmTargetUser').avatarStyle = mp2.avatarStyle || store.get('dmTargetUser').avatarStyle;
1284
+ store.get('dmTargetUser').avatarSeed = mp2.avatarSeed || store.get('dmTargetUser').avatarSeed;
1285
+ store.get('dmTargetUser').avatarColor = mp2.avatarColor || store.get('dmTargetUser').avatarColor;
1286
+ store.get('dmTargetUser').avatarCustom = mp2.avatarCustom || "";
1287
+ store.get('dmTargetUser').profile = mp2;
1244
1288
  // Refresh body dataset so new chat bubbles use the updated avatar
1245
- document.body.dataset.mateAvatarUrl = mateAvatarUrl(store.getState().dmTargetUser, 36);
1289
+ document.body.dataset.mateAvatarUrl = mateAvatarUrl(store.get('dmTargetUser'), 36);
1246
1290
  document.body.dataset.mateName = mp2.displayName || msg.mate.name || "";
1247
1291
  // Update existing chat bubble avatars
1248
1292
  var mateAvis = document.querySelectorAll(".dm-bubble-avatar-mate");
@@ -1251,7 +1295,7 @@ export function processMessage(msg) {
1251
1295
  }
1252
1296
  }
1253
1297
  // Update DM header if currently chatting with this mate
1254
- if (store.getState().dmMode && store.getState().dmTargetUser && store.getState().dmTargetUser.id === msg.mate.id) {
1298
+ if (store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').id === msg.mate.id) {
1255
1299
  var updatedName = (msg.mate.profile && msg.mate.profile.displayName) || msg.mate.name;
1256
1300
  if (updatedName) {
1257
1301
  var dmHeaderName = document.getElementById("dm-header-name");
@@ -1264,9 +1308,8 @@ export function processMessage(msg) {
1264
1308
  break;
1265
1309
 
1266
1310
  case "mate_list":
1267
- store.setState({ cachedMatesList: msg.mates || [], cachedAvailableBuiltins: msg.availableBuiltins || [] });
1268
- var _s5 = store.getState();
1269
- renderUserStrip(_s5.cachedAllUsers, _s5.cachedOnlineIds, _s5.myUserId, _s5.cachedDmFavorites, _s5.cachedDmConversations, _s5.dmUnread, _s5.dmRemovedUsers, _s5.cachedMatesList);
1311
+ store.set({ cachedMatesList: msg.mates || [], cachedAvailableBuiltins: msg.availableBuiltins || [] });
1312
+ // renderUserStrip is handled by the store subscriber
1270
1313
  break;
1271
1314
 
1272
1315
  case "mate_available_builtins":
@@ -1329,12 +1372,12 @@ export function processMessage(msg) {
1329
1372
 
1330
1373
  // --- Debate ---
1331
1374
  case "debate_preparing":
1332
- if (!store.getState().replayingHistory) showDebateSticky("preparing", msg);
1375
+ if (!store.get('replayingHistory')) showDebateSticky("preparing", msg);
1333
1376
  handleDebatePreparing(msg);
1334
1377
  break;
1335
1378
 
1336
1379
  case "debate_brief_ready":
1337
- if (store.getState().replayingHistory) {
1380
+ if (store.get('replayingHistory')) {
1338
1381
  renderDebateBriefReady(msg);
1339
1382
  } else {
1340
1383
  handleDebateBriefReady(msg);
@@ -1342,8 +1385,8 @@ export function processMessage(msg) {
1342
1385
  break;
1343
1386
 
1344
1387
  case "debate_started":
1345
- if (!store.getState().replayingHistory) showDebateSticky("live", msg);
1346
- if (store.getState().replayingHistory) {
1388
+ if (!store.get('replayingHistory')) showDebateSticky("live", msg);
1389
+ if (store.get('replayingHistory')) {
1347
1390
  renderDebateStarted(msg);
1348
1391
  } else {
1349
1392
  handleDebateStarted(msg);
@@ -1377,7 +1420,7 @@ export function processMessage(msg) {
1377
1420
  break;
1378
1421
 
1379
1422
  case "debate_comment_injected":
1380
- if (store.getState().replayingHistory) {
1423
+ if (store.get('replayingHistory')) {
1381
1424
  renderDebateCommentInjected(msg);
1382
1425
  } else {
1383
1426
  handleDebateCommentInjected(msg);
@@ -1385,11 +1428,11 @@ export function processMessage(msg) {
1385
1428
  break;
1386
1429
 
1387
1430
  case "debate_conclude_confirm":
1388
- if (!store.getState().replayingHistory) showDebateConcludeConfirm(msg);
1431
+ if (!store.get('replayingHistory')) showDebateConcludeConfirm(msg);
1389
1432
  break;
1390
1433
 
1391
1434
  case "debate_user_floor":
1392
- if (!store.getState().replayingHistory) showDebateUserFloor(msg);
1435
+ if (!store.get('replayingHistory')) showDebateUserFloor(msg);
1393
1436
  break;
1394
1437
 
1395
1438
  case "debate_user_floor_done":
@@ -1402,12 +1445,12 @@ export function processMessage(msg) {
1402
1445
 
1403
1446
  case "debate_resumed":
1404
1447
  handleDebateResumed(msg);
1405
- if (!store.getState().replayingHistory) showDebateSticky("live", msg);
1448
+ if (!store.get('replayingHistory')) showDebateSticky("live", msg);
1406
1449
  break;
1407
1450
 
1408
1451
  case "debate_ended":
1409
- if (!store.getState().replayingHistory) showDebateSticky("ended", msg);
1410
- if (store.getState().replayingHistory) {
1452
+ if (!store.get('replayingHistory')) showDebateSticky("ended", msg);
1453
+ if (store.get('replayingHistory')) {
1411
1454
  renderDebateEnded(msg);
1412
1455
  } else {
1413
1456
  handleDebateEnded(msg);
@@ -1420,7 +1463,7 @@ export function processMessage(msg) {
1420
1463
  break;
1421
1464
 
1422
1465
  case "daemon_config":
1423
- if (msg.config && msg.config.headless) store.setState({ isHeadlessMode: true });
1466
+ if (msg.config && msg.config.headless) store.set({ isHeadlessMode: true });
1424
1467
  updateDaemonConfig(msg.config);
1425
1468
  break;
1426
1469
 
@@ -1451,9 +1494,9 @@ export function processMessage(msg) {
1451
1494
 
1452
1495
  // --- Ralph Loop ---
1453
1496
  case "loop_available":
1454
- store.setState({ loopAvailable: msg.available, loopActive: msg.active, loopIteration: msg.iteration || 0, loopMaxIterations: msg.maxIterations || 20, loopBannerName: msg.name || null });
1455
- updateLoopButton();
1456
- var _la = store.getState();
1497
+ store.set({ loopAvailable: msg.available, loopActive: msg.active, loopIteration: msg.iteration || 0, loopMaxIterations: msg.maxIterations || 20, loopBannerName: msg.name || null });
1498
+
1499
+ var _la = store.snap();
1457
1500
  if (_la.loopActive) {
1458
1501
  showLoopBanner(true);
1459
1502
  if (_la.loopIteration > 0) {
@@ -1465,27 +1508,27 @@ export function processMessage(msg) {
1465
1508
  break;
1466
1509
 
1467
1510
  case "loop_started":
1468
- store.setState({ loopActive: true, ralphPhase: "executing", loopIteration: 0, loopMaxIterations: msg.maxIterations, loopBannerName: msg.name || null });
1511
+ store.set({ loopActive: true, ralphPhase: "executing", loopIteration: 0, loopMaxIterations: msg.maxIterations, loopBannerName: msg.name || null });
1469
1512
  showLoopBanner(true);
1470
- updateLoopButton();
1471
- var _lbn = store.getState().loopBannerName;
1513
+
1514
+ var _lbn = store.get('loopBannerName');
1472
1515
  addSystemMessage((_lbn || "Loop") + " started (max " + msg.maxIterations + " iterations)", false);
1473
1516
  inputEl.disabled = true;
1474
1517
  inputEl.placeholder = (_lbn || "Loop") + " is running...";
1475
1518
  break;
1476
1519
 
1477
1520
  case "loop_iteration":
1478
- store.setState({ loopIteration: msg.iteration, loopMaxIterations: msg.maxIterations });
1521
+ store.set({ loopIteration: msg.iteration, loopMaxIterations: msg.maxIterations });
1479
1522
  updateLoopBanner(msg.iteration, msg.maxIterations, "running");
1480
- updateLoopButton();
1481
- var _libn = store.getState().loopBannerName;
1523
+
1524
+ var _libn = store.get('loopBannerName');
1482
1525
  addSystemMessage((_libn || "Loop") + " iteration #" + msg.iteration + " started", false);
1483
1526
  inputEl.disabled = true;
1484
1527
  inputEl.placeholder = (_libn || "Loop") + " is running...";
1485
1528
  break;
1486
1529
 
1487
1530
  case "loop_judging":
1488
- var _ljs = store.getState();
1531
+ var _ljs = store.snap();
1489
1532
  updateLoopBanner(_ljs.loopIteration, _ljs.loopMaxIterations, "judging");
1490
1533
  addSystemMessage("Judging iteration #" + msg.iteration + "...", false);
1491
1534
  inputEl.disabled = true;
@@ -1497,15 +1540,15 @@ export function processMessage(msg) {
1497
1540
  break;
1498
1541
 
1499
1542
  case "loop_stopping":
1500
- var _lss = store.getState();
1543
+ var _lss = store.snap();
1501
1544
  updateLoopBanner(_lss.loopIteration, _lss.loopMaxIterations, "stopping");
1502
1545
  break;
1503
1546
 
1504
1547
  case "loop_finished":
1505
- var _lfbn = store.getState().loopBannerName;
1506
- store.setState({ loopActive: false, ralphPhase: "done", loopBannerName: null });
1548
+ var _lfbn = store.get('loopBannerName');
1549
+ store.set({ loopActive: false, ralphPhase: "done", loopBannerName: null });
1507
1550
  showLoopBanner(false);
1508
- updateLoopButton();
1551
+
1509
1552
  enableMainInput();
1510
1553
  updateLoopInputVisibility(null);
1511
1554
  var loopLabel = _lfbn || "Loop";
@@ -1520,7 +1563,7 @@ export function processMessage(msg) {
1520
1563
  break;
1521
1564
 
1522
1565
  case "loop_error":
1523
- addSystemMessage((store.getState().loopBannerName || "Loop") + " error: " + msg.text, true);
1566
+ addSystemMessage((store.get('loopBannerName') || "Loop") + " error: " + msg.text, true);
1524
1567
  break;
1525
1568
 
1526
1569
  // --- Ralph Wizard / Crafting ---
@@ -1528,16 +1571,16 @@ export function processMessage(msg) {
1528
1571
  var _rps = { ralphPhase: msg.phase || "idle" };
1529
1572
  if (msg.craftingSessionId) _rps.ralphCraftingSessionId = msg.craftingSessionId;
1530
1573
  if (msg.source !== undefined) _rps.ralphCraftingSource = msg.source;
1531
- store.setState(_rps);
1532
- if (msg.wizardData) store.setState({ wizardData: msg.wizardData });
1533
- updateLoopButton();
1534
- updateRalphBars();
1574
+ store.set(_rps);
1575
+ if (msg.wizardData) store.set({ wizardData: msg.wizardData });
1576
+
1577
+
1535
1578
  break;
1536
1579
 
1537
1580
  case "ralph_crafting_started":
1538
- store.setState({ ralphPhase: "crafting", ralphCraftingSessionId: msg.sessionId || store.getState().activeSessionId, ralphCraftingSource: msg.source || null });
1539
- updateLoopButton();
1540
- updateRalphBars();
1581
+ store.set({ ralphPhase: "crafting", ralphCraftingSessionId: msg.sessionId || store.get('activeSessionId'), ralphCraftingSource: msg.source || null });
1582
+
1583
+
1541
1584
  if (msg.source !== "ralph") {
1542
1585
  // Task sessions open in the scheduler calendar window
1543
1586
  enterCraftingMode(msg.sessionId, msg.taskId);
@@ -1546,22 +1589,22 @@ export function processMessage(msg) {
1546
1589
  break;
1547
1590
 
1548
1591
  case "ralph_files_status":
1549
- store.setState({ ralphFilesReady: {
1592
+ store.set({ ralphFilesReady: {
1550
1593
  promptReady: msg.promptReady,
1551
1594
  judgeReady: msg.judgeReady,
1552
1595
  bothReady: msg.bothReady,
1553
1596
  } });
1554
1597
  if (msg.bothReady) {
1555
- var _rfs = store.getState();
1598
+ var _rfs = store.snap();
1556
1599
  if (_rfs.ralphPhase === "crafting" || _rfs.ralphPhase === "approval") {
1557
- store.setState({ ralphPhase: "approval" });
1600
+ store.set({ ralphPhase: "approval" });
1558
1601
  if (_rfs.ralphCraftingSource !== "ralph" || isSchedulerOpen()) {
1559
1602
  // Task crafting in scheduler: switch from crafting chat to detail view showing files
1560
1603
  exitCraftingMode(msg.taskId);
1561
1604
  } else {
1562
1605
  showRalphApprovalBar(true);
1563
1606
  // Auto-show execution modal (one-time) for Ralph source
1564
- if (!store.getState().execModalShown && _rfs.ralphCraftingSource === "ralph") {
1607
+ if (!store.get('execModalShown') && _rfs.ralphCraftingSource === "ralph") {
1565
1608
  showExecModal();
1566
1609
  }
1567
1610
  }
@@ -1576,7 +1619,7 @@ export function processMessage(msg) {
1576
1619
  break;
1577
1620
 
1578
1621
  case "ralph_files_content":
1579
- store.setState({ ralphPreviewContent: { prompt: msg.prompt || "", judge: msg.judge || "" } });
1622
+ store.set({ ralphPreviewContent: { prompt: msg.prompt || "", judge: msg.judge || "" } });
1580
1623
  openRalphPreviewModal();
1581
1624
  break;
1582
1625