clay-server 4.1.0-beta.8 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/lib/context-usage.js +47 -0
  2. package/lib/issues-mcp-server.js +1 -1
  3. package/lib/issues-service.js +3 -1
  4. package/lib/loop-registry-occurrence.js +1 -0
  5. package/lib/notes.js +20 -3
  6. package/lib/os-users.js +6 -0
  7. package/lib/project-connection.js +14 -3
  8. package/lib/project-file-path.js +13 -0
  9. package/lib/project-file-watch.js +19 -8
  10. package/lib/project-filesystem.js +10 -16
  11. package/lib/project-http.js +53 -48
  12. package/lib/project-logs-mcp-server.js +8 -8
  13. package/lib/project-logs-schema.js +1 -1
  14. package/lib/project-logs-service.js +46 -13
  15. package/lib/project-logs-store.js +17 -15
  16. package/lib/project-logs.js +70 -0
  17. package/lib/project-loop.js +1 -0
  18. package/lib/project-notifications.js +1 -1
  19. package/lib/project-pending-message-queue.js +71 -7
  20. package/lib/project-scheduled-messages.js +235 -52
  21. package/lib/project-scheduled-result-store.js +131 -0
  22. package/lib/project-scheduled-task-execution.js +35 -2
  23. package/lib/project-session-notes.js +11 -4
  24. package/lib/project-sessions.js +7 -0
  25. package/lib/project-user-message.js +72 -5
  26. package/lib/project.js +60 -5
  27. package/lib/public/app.js +27 -12
  28. package/lib/public/css/avatar-imprints.css +1 -1
  29. package/lib/public/css/command-palette.css +20 -0
  30. package/lib/public/css/git-panel.css +13 -12
  31. package/lib/public/css/git-placard.css +4 -4
  32. package/lib/public/css/input.css +0 -109
  33. package/lib/public/css/issues.css +78 -31
  34. package/lib/public/css/mobile-nav.css +7 -0
  35. package/lib/public/css/notifications-center.css +3 -3
  36. package/lib/public/css/pending-message-queue.css +3 -0
  37. package/lib/public/css/project-logs.css +15 -0
  38. package/lib/public/css/sidebar.css +7 -0
  39. package/lib/public/css/sticky-notes.css +29 -0
  40. package/lib/public/modules/app-connection.js +9 -1
  41. package/lib/public/modules/app-favicon.js +10 -0
  42. package/lib/public/modules/app-messages.js +49 -37
  43. package/lib/public/modules/app-notifications.js +2 -0
  44. package/lib/public/modules/app-panels.js +77 -23
  45. package/lib/public/modules/app-projects.js +3 -0
  46. package/lib/public/modules/app-rate-limit.js +148 -243
  47. package/lib/public/modules/avatar-imprint.js +25 -70
  48. package/lib/public/modules/avatar.js +12 -4
  49. package/lib/public/modules/chat-render-runtime.js +20 -2
  50. package/lib/public/modules/clay-issue-links.js +23 -2
  51. package/lib/public/modules/clay-log-links.js +11 -1
  52. package/lib/public/modules/context-view-preference.js +143 -0
  53. package/lib/public/modules/default-vendor.js +116 -0
  54. package/lib/public/modules/filebrowser-tabs.js +1 -0
  55. package/lib/public/modules/filebrowser.js +45 -18
  56. package/lib/public/modules/input.js +81 -20
  57. package/lib/public/modules/issues-render.js +36 -10
  58. package/lib/public/modules/issues.js +95 -13
  59. package/lib/public/modules/markdown.js +3 -1
  60. package/lib/public/modules/pane-bridge.js +14 -8
  61. package/lib/public/modules/pane-issue-bridge.js +27 -0
  62. package/lib/public/modules/pending-message-queue.js +43 -1
  63. package/lib/public/modules/permission-hints.js +13 -0
  64. package/lib/public/modules/profile.js +9 -9
  65. package/lib/public/modules/project-activation.js +49 -0
  66. package/lib/public/modules/project-logs-render.js +13 -3
  67. package/lib/public/modules/project-logs.js +45 -15
  68. package/lib/public/modules/right-workbench.js +44 -0
  69. package/lib/public/modules/scheduled-message-state.js +12 -36
  70. package/lib/public/modules/scheduled-result-client.js +33 -0
  71. package/lib/public/modules/scheduled-result-display.js +50 -0
  72. package/lib/public/modules/scheduled-result-notification.js +31 -0
  73. package/lib/public/modules/scheduled-task-presentation.js +15 -0
  74. package/lib/public/modules/scheduled-tasks.js +9 -21
  75. package/lib/public/modules/sidebar-mobile.js +51 -3
  76. package/lib/public/modules/sidebar-sessions.js +65 -5
  77. package/lib/public/modules/split-session-boundary.js +28 -0
  78. package/lib/public/modules/split-view.js +42 -7
  79. package/lib/public/modules/sticky-notes-browser.js +130 -18
  80. package/lib/public/modules/sticky-notes.js +2 -3
  81. package/lib/public/modules/terminal.js +4 -4
  82. package/lib/public/modules/thinking-lifecycle.js +4 -1
  83. package/lib/public/modules/tools.js +5 -1
  84. package/lib/scheduled-run-state.js +10 -2
  85. package/lib/scheduler.js +1 -0
  86. package/lib/sdk-bridge.js +97 -61
  87. package/lib/sdk-message-processor.js +44 -13
  88. package/lib/sdk-skill-discovery.js +6 -28
  89. package/lib/sdk-turn-queue.js +14 -0
  90. package/lib/server-context-view.js +53 -0
  91. package/lib/server-default-vendor.js +60 -0
  92. package/lib/server.js +23 -6
  93. package/lib/session-notes-mcp-server.js +3 -5
  94. package/lib/session-pair-factory.js +2 -2
  95. package/lib/session-pair-mcp-server.js +5 -4
  96. package/lib/session-title-generator.js +89 -0
  97. package/lib/session-title-policy.js +8 -2
  98. package/lib/sessions.js +26 -1
  99. package/lib/user-presence.js +6 -0
  100. package/lib/users-context-view-preferences.js +65 -0
  101. package/lib/users-default-vendor-preferences.js +80 -0
  102. package/lib/users.js +12 -0
  103. package/lib/ws-schema.js +11 -0
  104. package/lib/yoke/acp-agent-profiles.js +15 -2
  105. package/lib/yoke/adapters/acp.js +3 -2
  106. package/lib/yoke/adapters/antigravity.js +2 -2
  107. package/lib/yoke/adapters/claude-worker.js +11 -12
  108. package/lib/yoke/adapters/claude.js +75 -22
  109. package/lib/yoke/adapters/codex.js +216 -29
  110. package/lib/yoke/adapters/kiro.js +6 -5
  111. package/lib/yoke/claude-context-usage.js +57 -0
  112. package/lib/yoke/claude-message-correlation.js +31 -0
  113. package/lib/yoke/claude-model-switch.js +15 -0
  114. package/lib/yoke/index.js +5 -1
  115. package/lib/yoke/mcp-bridge-server.js +18 -5
  116. package/lib/yoke/session-skill-catalog.js +45 -0
  117. package/lib/yoke/skill-discovery.js +60 -20
  118. package/lib/yoke/vendor-registry.js +1 -1
  119. package/package.json +5 -7
@@ -19,6 +19,8 @@ import { refreshMobileChatSheet } from './sidebar-mobile.js';
19
19
  import { renderMateSessionList, handleMateSearchResults, updateMateSidebarProfile } from './mate-sidebar.js';
20
20
  import { openHomeChat, handleHomeMateHistory, handleHomeMateDelta, handleHomeMateSegment, handleHomeMateDone, handleHomeMateError, handleHomeMateSessionsState } from './home-mate-chat.js';
21
21
  import { handleHomeSurfaceState } from './home-surface.js';
22
+ import { handleDefaultVendorMessage } from './default-vendor.js';
23
+ import { handleContextViewMessage } from './context-view-preference.js';
22
24
  import { handleHomeMateMemoryState, handleHomeMateKnowledgeState } from './home-mate-settings.js';
23
25
  import { renderKnowledgeList, handleKnowledgeContent } from './mate-knowledge.js';
24
26
  import { renderMemoryList } from './mate-memory.js';
@@ -28,7 +30,7 @@ import { syncPaneTitles, maybeRestoreSplitGroup, openGroup } from './split-view.
28
30
  import { showPairDialog, handlePairCreated, handleSplitDelegation, showWorkerDelegationNotice, hideWorkerDelegationNotice } from './split-pair-ui.js';
29
31
  import { handleSessionActionMessage } from './session-actions.js';
30
32
  import { renderWorkerProposal, updateWorkerProposal } from './worker-proposal.js';
31
- import { handleInputSync, autoResize, builtinCommands, setScheduleBtnDisabled, sendShellResultToAgent, handleAcceptedOrdinaryMessage } from './input.js';
33
+ import { handleInputSync, autoResize, builtinCommands, setScheduleBtnDisabled, sendShellResultToAgent, handleAcceptedOrdinaryMessage, handleScheduleMessageResult } from './input.js';
32
34
  import { startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, closeToolGroup, removeToolFromGroup, resetToolState, getTools, getPlanContent, setPlanContent, renderPlanBanner, renderPlanCard, getTodoTools, handleTodoWrite, handleTaskCreate, handleTaskUpdate, applyDeadSessionTodoCompaction, isPlanFilePath, enableMainInput, addTurnMeta, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, initSubagentStop, updateSubagentProgress, updateSubagentTaskStatus, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionCancelled, markPermissionResolved, renderElicitationRequest, markElicitationResolved, renderUserDialogRequest, markUserDialogResolved, updateThinkingTokens } from './tools.js';
33
35
  import { finishThinkingTurn, resumeThinkingAfterReplay } from './thinking-lifecycle.js';
34
36
  import { showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './notifications.js';
@@ -44,7 +46,8 @@ import { tuiModalHandleTermOutput, tuiModalHandleTermResized, tuiModalHandleTerm
44
46
  import { updateTerminalList, handleContextSourcesState, updateEmailAccountList, updateEmailUnreadCounts, handleEmailTestResult, handleEmailAddResult, handleEmailRemoveResult } from './context-sources.js';
45
47
  import { refreshEmailSettings } from './user-settings.js';
46
48
  import { handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, handleNoteWritten } from './sticky-notes.js';
47
- import { handleProjectLogsState, handleProjectLogEntry, handleProjectLogCommented, handleProjectLogCommentReviewed, handleProjectLogDeleted, handleProjectLogUpdated, handleProjectLogsError } from './project-logs.js';
49
+ import { handleNoteDeleteResult } from './sticky-notes-browser.js';
50
+ import { handleProjectLogsState, handleProjectLogEntry, handleProjectLogCommented, handleProjectLogCommentReviewed, handleProjectLogDeleted, handleProjectLogUpdated, handleProjectLogsError, handleScheduledTaskResultsState, handleScheduledTaskResultCreated, handleScheduledTaskResultSession } from './project-logs.js';
48
51
  import { handleSkillInstalled, handleSkillUninstalled } from './skills.js';
49
52
  import { showRewindModal, onRewindComplete, setRewindMode, onRewindError, clearPendingRewindUuid, addRewindButton } from './rewind.js';
50
53
  import { checkAdminAccess } from './admin.js';
@@ -63,9 +66,10 @@ import { setStatus } from './app-connection.js';
63
66
  import { handleWhatsNewState, handleWhatsNewSeenResult, setKnownEntries as setWhatsNewKnownEntries } from './whats-new.js';
64
67
  import { closeArticle as closeWhatsNewArticle } from './whats-new-article.js';
65
68
  import { resolvePaneSession, resolveSwitchedVendor } from './pane-session.js';
69
+ import { markProjectSessionListHydrated, applyProjectSplitGroups, clearProjectSplitState } from './split-session-boundary.js';
66
70
  import { selectDefaultVendorForBlankSession } from './vendor-selection.js';
67
71
  import { getModelInfoUpdate, modelEntryValue, modelEntryMatches, handleModelSelectionResult, requestVendorModels } from './model-picker.js';
68
- import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
72
+ import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel, markContextUnavailable } from './app-panels.js';
69
73
  import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress, finishProjectSessionActivation } from './app-projects.js';
70
74
  import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
71
75
  import { hideHomeHub, showHomeHub } from './app-home-hub.js';
@@ -73,8 +77,7 @@ import { handleToolsState, handleToolInstalled, handleToolRemoved, handleToolSto
73
77
  import { handleHomeDockState } from './home-dock.js';
74
78
  import { handleToolFrameUrlState } from './home-tool-frame.js';
75
79
  import { enterDmMode, exitDmMode, handleMateCreatedInApp, updateMateIconStatus, appendDmMessage, showDmTypingIndicator, buildMateInterviewPrompt } from './app-dm.js';
76
- import { handleRateLimitEvent, updateRateLimitUsage, addScheduledMessageBubble, removeScheduledMessageBubble, handleFastModeState } from './app-rate-limit.js';
77
- import { applyScheduledMessageQueued, applyScheduledMessageState, applyScheduledMessageTerminal } from './scheduled-message-state.js';
80
+ import { handleRateLimitEvent, updateRateLimitUsage, handleFastModeState } from './app-rate-limit.js';
78
81
  import { handleRemoteCursorMove, handleRemoteCursorLeave, handleRemoteSelection, clearRemoteCursors } from './app-cursors.js';
79
82
  import { showLoopBanner, updateLoopBanner, updateLoopInputVisibility, showRalphApprovalBar, updateRalphApprovalStatus, openRalphPreviewModal, showExecModal, updateExecModalStatus } from './app-loop-ui.js';
80
83
  import { handleAutonomousRunMessage, syncAutonomousRunForSession } from './autonomous-run.js';
@@ -98,6 +101,12 @@ export function processMessage(msg) {
98
101
  if (handleAutonomousRunMessage(msg)) return;
99
102
  if (handleLoopInterviewMessage(msg)) return;
100
103
  if (handleScheduledTaskMessage(msg)) return;
104
+ if (handleDefaultVendorMessage(msg)) return;
105
+ if (handleContextViewMessage(msg)) return;
106
+ if (msg && msg.type === "schedule_message_result") {
107
+ handleScheduleMessageResult(msg);
108
+ return;
109
+ }
101
110
  if (msg.type === "access_revoked") {
102
111
  window.location.assign("/");
103
112
  return;
@@ -223,9 +232,8 @@ export function processMessage(msg) {
223
232
  applyDeadSessionTodoCompaction();
224
233
  }
225
234
  // Restore cached rich context usage BEFORE updateContextPanel runs
226
- if (msg.contextUsage) {
227
- store.set({ richContextUsage: msg.contextUsage });
228
- }
235
+ if (msg.contextUsage && !msg.contextUsage.unavailable) store.set({ richContextUsage: msg.contextUsage });
236
+ else markContextUnavailable();
229
237
  // Restore accurate context data from the last result in full history
230
238
  if (msg.lastUsage || msg.lastModelUsage) {
231
239
  accumulateContext(msg.lastCost, msg.lastUsage, msg.lastModelUsage, msg.lastStreamInputTokens);
@@ -327,10 +335,13 @@ export function processMessage(msg) {
327
335
  // start receiving the new project's session_switched. The TUI
328
336
  // host lives on document.body (it's position: fixed), so it
329
337
  // survives project navigation unless we detach explicitly here.
338
+ if (msg.slug && store.get('activeProjectSlug') && store.get('activeProjectSlug') !== msg.slug) {
339
+ clearProjectSplitState();
340
+ }
330
341
  detachTuiView();
331
342
  store.set({ projectName: msg.project || msg.cwd, vendorInfo: msg.vendors || {} });
332
343
  if (msg.cwd) store.set({ cwd: msg.cwd });
333
- if (msg.slug) store.set({ currentSlug: msg.slug, activeProjectSlug: msg.slug });
344
+ if (msg.slug) store.set({ currentSlug: msg.slug, activeProjectSlug: msg.slug, sessionListProjectSlug: null, splitGroupsProjectSlug: null });
334
345
  try { var _is = store.snap(); localStorage.setItem("clay-project-name-" + (_is.currentSlug || "default"), _is.projectName); } catch (e) {}
335
346
  // In mate DM, keep title as mate name and re-apply mate color
336
347
  if (store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate) {
@@ -687,6 +698,7 @@ export function processMessage(msg) {
687
698
  break;
688
699
 
689
700
  case "session_list":
701
+ markProjectSessionListHydrated();
690
702
  renderMateSessionList(msg.sessions || []);
691
703
  renderSessionList(msg.sessions || []);
692
704
  syncPaneTitles();
@@ -698,13 +710,14 @@ export function processMessage(msg) {
698
710
  }
699
711
  }
700
712
  handlePaletteSessionSwitch();
713
+ maybeRestoreSplitGroup();
701
714
  break;
702
715
 
703
716
  case "split_groups":
704
- store.set({ splitGroups: msg.groups || [] });
717
+ applyProjectSplitGroups(msg.groups || []);
705
718
  renderSessionList(null);
706
- maybeRestoreSplitGroup();
707
719
  syncPaneTitles();
720
+ maybeRestoreSplitGroup();
708
721
  break;
709
722
 
710
723
  case "split_group_result":
@@ -885,8 +898,8 @@ export function processMessage(msg) {
885
898
  }
886
899
  // Reload survival: a restored active session that is a split-group
887
900
  // member reopens its group (no-op when a split is already open).
888
- maybeRestoreSplitGroup();
889
901
  if (finishProjectSessionActivation()) hideHomeHub();
902
+ maybeRestoreSplitGroup();
890
903
  break;
891
904
 
892
905
  case "session_full_access_changed":
@@ -1041,6 +1054,9 @@ export function processMessage(msg) {
1041
1054
  case "thinking_start":
1042
1055
  removeMatePreThinking();
1043
1056
  startThinking();
1057
+ // The live Thinking entry is the progress affordance while reasoning
1058
+ // is streaming; avoid a second bouncing indicator at the same tail.
1059
+ setActivity(null);
1044
1060
  break;
1045
1061
 
1046
1062
  case "thinking_delta":
@@ -1168,7 +1184,7 @@ export function processMessage(msg) {
1168
1184
 
1169
1185
  case "permission_request":
1170
1186
  setActivity(null);
1171
- renderPermissionRequest(msg.requestId, msg.toolName, msg.toolInput, msg.decisionReason, msg.mateId, msg.vendor);
1187
+ renderPermissionRequest(msg.requestId, msg.toolName, msg.toolInput, msg.decisionReason, msg.mateId, msg.vendor, msg);
1172
1188
  startUrgentBlink();
1173
1189
  break;
1174
1190
 
@@ -1184,7 +1200,7 @@ export function processMessage(msg) {
1184
1200
 
1185
1201
  case "permission_request_pending":
1186
1202
  setActivity(null);
1187
- renderPermissionRequest(msg.requestId, msg.toolName, msg.toolInput, msg.decisionReason, msg.mateId, msg.vendor);
1203
+ renderPermissionRequest(msg.requestId, msg.toolName, msg.toolInput, msg.decisionReason, msg.mateId, msg.vendor, msg);
1188
1204
  startUrgentBlink();
1189
1205
  break;
1190
1206
 
@@ -1268,8 +1284,10 @@ export function processMessage(msg) {
1268
1284
  break;
1269
1285
 
1270
1286
  case "context_usage":
1287
+ if (msg.sessionId != null && msg.sessionId !== store.get("activeSessionId")) break;
1271
1288
  if (msg.data && !store.get('replayingHistory')) {
1272
- store.set({ richContextUsage: msg.data });
1289
+ if (msg.data.unavailable) markContextUnavailable();
1290
+ else store.set({ richContextUsage: msg.data });
1273
1291
  // UI sync handled by store subscriber in app-panels.js
1274
1292
  }
1275
1293
  break;
@@ -1411,28 +1429,6 @@ export function processMessage(msg) {
1411
1429
  updateRateLimitUsage(msg);
1412
1430
  break;
1413
1431
 
1414
- case "scheduled_message_queued":
1415
- applyScheduledMessageQueued(msg, { add: addScheduledMessageBubble, setDisabled: setScheduleBtnDisabled });
1416
- break;
1417
-
1418
- case "scheduled_message_sent":
1419
- applyScheduledMessageTerminal(msg, { remove: removeScheduledMessageBubble, setDisabled: setScheduleBtnDisabled });
1420
- setStatus("processing");
1421
- break;
1422
-
1423
- case "scheduled_message_cancelled":
1424
- applyScheduledMessageTerminal(msg, { remove: removeScheduledMessageBubble, setDisabled: setScheduleBtnDisabled });
1425
- break;
1426
-
1427
- case "scheduled_message_state":
1428
- applyScheduledMessageState(msg, {
1429
- remove: removeScheduledMessageBubble,
1430
- setDisabled: setScheduleBtnDisabled,
1431
- add: addScheduledMessageBubble,
1432
- interrupted: function () { addSystemMessage("A scheduled message was interrupted during server recovery and was not sent again automatically."); },
1433
- });
1434
- break;
1435
-
1436
1432
  case "auto_continue_scheduled":
1437
1433
  // Scheduler auto-continue, just show info
1438
1434
  break;
@@ -1654,6 +1650,10 @@ export function processMessage(msg) {
1654
1650
  handleNoteDeleted(msg);
1655
1651
  break;
1656
1652
 
1653
+ case "note_delete_result":
1654
+ handleNoteDeleteResult(msg);
1655
+ break;
1656
+
1657
1657
  case "project_logs_state":
1658
1658
  handleProjectLogsState(msg);
1659
1659
  break;
@@ -1682,6 +1682,18 @@ export function processMessage(msg) {
1682
1682
  handleProjectLogsError(msg);
1683
1683
  break;
1684
1684
 
1685
+ case "scheduled_task_results_state":
1686
+ handleScheduledTaskResultsState(msg);
1687
+ break;
1688
+
1689
+ case "scheduled_task_result_created":
1690
+ handleScheduledTaskResultCreated(msg);
1691
+ break;
1692
+
1693
+ case "scheduled_task_result_session":
1694
+ handleScheduledTaskResultSession(msg);
1695
+ break;
1696
+
1685
1697
  case "process_stats":
1686
1698
  updateStatusPanel(msg);
1687
1699
  updateSettingsStats(msg);
@@ -1,3 +1,4 @@
1
+ import { applyPermissionHints } from './permission-hints.js';
1
2
  // app-notifications.js - Notification banners (Apple banner style)
2
3
  // New notifications appear as individual banners top-right, auto-dismiss after 3s.
3
4
  // Bell button click shows all stored notifications as banners.
@@ -190,6 +191,7 @@ function showBanner(notif, autoDismissMs) {
190
191
  (!isEmpty ? '<button class="notif-banner-close">' + iconHtml("x") + '</button>' : '');
191
192
 
192
193
  bannerContainer.appendChild(banner);
194
+ if (isPermission) applyPermissionHints(banner, notif.meta);
193
195
  refreshIcons();
194
196
  ensureClearAllButton();
195
197
 
@@ -5,15 +5,17 @@ import { refreshIcons } from "./icons.js";
5
5
  import { escapeHtml, showToast } from "./utils.js";
6
6
  import { store } from './store.js';
7
7
  import { getWs } from './ws-ref.js';
8
+ import { getContextView as getStoredContextView, getEffectiveContextView, setContextView as setStoredContextView } from './context-view-preference.js';
8
9
  import { reportPaneContext } from './pane-bridge.js';
9
10
  import { setupModelPicker, renderModelPicker, requestVendorModels, prepareModelPickerOpen, modelDisplayName } from './model-picker.js';
10
11
 
11
12
  // --- Module-owned state (not in store) ---
12
13
  var sessionUsage = { cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0 };
13
- var contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0, thinking: null, costBasis: null };
14
+ var contextData = { contextWindow: 0, currentInput: null, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0, thinking: null, costBasis: null };
14
15
  var ctxPopoverEl = null;
15
16
  var ctxHoverTimer = null;
16
17
  var statusRefreshTimer = null;
18
+ var lastAppliedContextView = null;
17
19
 
18
20
  // --- DOM refs ---
19
21
  var configChipWrap = null;
@@ -374,6 +376,15 @@ export function initPanels() {
374
376
 
375
377
  // --- Reactive UI sync ---
376
378
  store.subscribe(function (state, prev) {
379
+ var effectiveContextView = getEffectiveContextView();
380
+ if (effectiveContextView !== lastAppliedContextView) {
381
+ lastAppliedContextView = effectiveContextView;
382
+ applyContextView(effectiveContextView);
383
+ }
384
+ if (state.contextViewPreferenceState && state.contextViewPreferenceState.error &&
385
+ state.contextViewPreferenceState.error !== (prev.contextViewPreferenceState && prev.contextViewPreferenceState.error)) {
386
+ showToast(state.contextViewPreferenceState.error, "error");
387
+ }
377
388
  // richContextUsage changed -> update popover + panel
378
389
  if (state.richContextUsage !== prev.richContextUsage) {
379
390
  if (state.richContextUsage) {
@@ -540,7 +551,6 @@ export function initPanels() {
540
551
  if (contextPanelClose) {
541
552
  contextPanelClose.addEventListener("click", function () {
542
553
  setContextView("off");
543
- applyContextView("off");
544
554
  });
545
555
  }
546
556
 
@@ -553,7 +563,8 @@ export function initPanels() {
553
563
  }
554
564
 
555
565
  // Restore context view on load
556
- applyContextView(getContextView());
566
+ lastAppliedContextView = getEffectiveContextView();
567
+ applyContextView(lastAppliedContextView);
557
568
  }
558
569
 
559
570
  // --- Config chip ---
@@ -718,9 +729,18 @@ export function contextPctClass(pct) {
718
729
 
719
730
  export function updateContextPanel() {
720
731
  if (!contextUsedEl) return;
721
- // Context window usage = input tokens only (includes cache read/write)
722
- var used = contextData.input;
723
- var win = contextData.contextWindow;
732
+ // Context occupancy is separate from generic per-turn accounting. Codex
733
+ // only supplies currentInput from a verified last-turn snapshot; rich
734
+ // context usage is more authoritative when available.
735
+ var rich = store.get('richContextUsage');
736
+ var hasRich = rich && (
737
+ (typeof rich.totalTokens === "number" && typeof rich.maxTokens === "number" && rich.maxTokens > 0) ||
738
+ (typeof rich.input_tokens === "number" && typeof rich.contextWindow === "number" && rich.contextWindow > 0)
739
+ );
740
+ var hasCurrent = typeof contextData.currentInput === "number";
741
+ var hasContext = !!(hasRich || hasCurrent);
742
+ var used = hasRich ? (typeof rich.totalTokens === "number" ? rich.totalTokens : rich.input_tokens) : (hasCurrent ? contextData.currentInput : 0);
743
+ var win = hasRich ? (typeof rich.maxTokens === "number" ? rich.maxTokens : rich.contextWindow) : (hasCurrent ? contextData.contextWindow : 0);
724
744
  var pct = win > 0 ? Math.min(100, (used / win) * 100) : 0;
725
745
  var cls = contextPctClass(pct);
726
746
  reportPaneContext({ pct: pct, used: used, win: win, cls: cls, model: contextData.model, cost: contextData.cost });
@@ -734,7 +754,7 @@ export function updateContextPanel() {
734
754
  contextMiniFill.className = "context-mini-fill" + cls;
735
755
  }
736
756
  if (contextMiniLabel) {
737
- contextMiniLabel.textContent = (win > 0 ? formatTokens(used) + "/" + formatTokens(win) : "0%");
757
+ contextMiniLabel.textContent = (win > 0 ? formatTokens(used) + "/" + formatTokens(win) : "-");
738
758
  }
739
759
  // Header bar. A split group has no meaningful group-level context usage:
740
760
  // the two members have separate windows, and the parent shell's own numbers
@@ -742,9 +762,9 @@ export function updateContextPanel() {
742
762
  // chip instead, so hide the title-bar gauge while a split is open.
743
763
  var splitOpen = !!store.get('splitPanes');
744
764
  var headerCtxEl = store.get('headerContextEl');
745
- if (headerCtxEl) headerCtxEl.classList.toggle("hidden", splitOpen);
746
- if (contextMini) contextMini.classList.toggle("split-hidden", splitOpen);
747
- if (pct > 0 && !splitOpen) {
765
+ if (headerCtxEl) headerCtxEl.classList.toggle("hidden", splitOpen || !hasContext);
766
+ if (contextMini) contextMini.classList.toggle("split-hidden", splitOpen || !hasContext);
767
+ if (hasContext && !splitOpen) {
748
768
  var statusArea = document.querySelector(".title-bar-content .status");
749
769
  var hCtxEl = store.get('headerContextEl');
750
770
  if (statusArea && !hCtxEl) {
@@ -808,7 +828,7 @@ export function accumulateContext(cost, usage, modelUsage, lastStreamInputTokens
808
828
  // when available -- result.usage.input_tokens sums all API calls in a turn,
809
829
  // inflating context usage when tools are involved.
810
830
  // Falls back to the summed value for setups that don't emit message_start.
811
- if (lastStreamInputTokens) {
831
+ if (typeof lastStreamInputTokens === "number") {
812
832
  contextData.input = lastStreamInputTokens;
813
833
  } else {
814
834
  contextData.input = (usage.input_tokens || usage.inputTokens || 0)
@@ -817,6 +837,11 @@ export function accumulateContext(cost, usage, modelUsage, lastStreamInputTokens
817
837
  contextData.output = usage.output_tokens || usage.outputTokens || 0;
818
838
  contextData.cacheRead = usage.cache_read_input_tokens || usage.cacheReadInputTokens || 0;
819
839
  contextData.cacheWrite = usage.cache_creation_input_tokens || usage.cacheCreationInputTokens || 0;
840
+ if (store.get('currentVendor') === "codex") {
841
+ contextData.currentInput = typeof lastStreamInputTokens === "number" ? lastStreamInputTokens : null;
842
+ } else {
843
+ contextData.currentInput = typeof lastStreamInputTokens === "number" ? lastStreamInputTokens : contextData.input;
844
+ }
820
845
  }
821
846
  contextData.turns++;
822
847
  if (modelUsage) {
@@ -830,6 +855,21 @@ export function accumulateContext(cost, usage, modelUsage, lastStreamInputTokens
830
855
  contextData.model = displayModel;
831
856
  contextData.contextWindow = resolveContextWindow(displayModel, mu.contextWindow);
832
857
  if (mu.maxOutputTokens) contextData.maxOutputTokens = mu.maxOutputTokens;
858
+ if (store.get('currentVendor') === "codex") {
859
+ var snapshot = mu.contextSnapshot;
860
+ if (snapshot) store.set({ richContextUsage: null });
861
+ if (snapshot && snapshot.valid === true
862
+ && typeof snapshot.inputTokens === "number"
863
+ && typeof snapshot.contextWindow === "number"
864
+ && snapshot.contextWindow > 0) {
865
+ contextData.currentInput = snapshot.inputTokens;
866
+ contextData.contextWindow = snapshot.contextWindow;
867
+ } else {
868
+ // An explicit invalid marker is authoritative: do not retain an
869
+ // occupancy value from the previous live turn or history replay.
870
+ contextData.currentInput = null;
871
+ }
872
+ }
833
873
 
834
874
  // thinkingTokens and costBasis are per-model and optional: absent on
835
875
  // older CLI builds and, for a resumed session, on the turns that ran
@@ -860,50 +900,56 @@ export function accumulateContext(cost, usage, modelUsage, lastStreamInputTokens
860
900
 
861
901
  // contextView: "off" | "mini" | "panel"
862
902
  export function getContextView() {
863
- try { return localStorage.getItem("clay-context-view") || "off"; } catch (e) { return "off"; }
903
+ return getStoredContextView();
864
904
  }
865
905
 
866
906
  export function setContextView(v) {
867
- try { localStorage.setItem("clay-context-view", v); } catch (e) {}
907
+ if (store.get("paneMode") === true) {
908
+ store.set({ contextViewOverride: v });
909
+ return true;
910
+ }
911
+ return setStoredContextView(v);
868
912
  }
869
913
 
870
914
  export function applyContextView(view) {
871
915
  if (contextPanel) contextPanel.classList.toggle("hidden", view !== "panel");
872
916
  if (contextMini) contextMini.classList.toggle("hidden", view !== "mini");
873
- if (view === "panel") refreshIcons();
917
+ if (view === "panel") { refreshIcons(); requestContextDetails(); }
874
918
  }
875
919
 
876
920
  export function resetContextData() {
877
- contextData = { contextWindow: 0, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0, thinking: null, costBasis: null };
921
+ contextData = { contextWindow: 0, currentInput: null, maxOutputTokens: 0, model: "-", cost: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, turns: 0, thinking: null, costBasis: null };
878
922
  store.set({ richContextUsage: null });
879
923
  // hideCtxPopover + updateContextPanel handled by store subscriber
880
924
  }
881
925
 
926
+ export function markContextUnavailable() {
927
+ contextData.currentInput = null;
928
+ store.set({ richContextUsage: null });
929
+ updateContextPanel();
930
+ }
931
+
882
932
  export function resetContext() {
883
933
  resetContextData();
884
934
  // Keep view state, just reset data
885
- applyContextView(getContextView());
935
+ applyContextView(getEffectiveContextView());
886
936
  }
887
937
 
888
938
  export function minimizeContext() {
889
939
  setContextView("mini");
890
- applyContextView("mini");
891
940
  }
892
941
 
893
942
  export function expandContext() {
894
943
  setContextView("panel");
895
- applyContextView("panel");
896
944
  }
897
945
 
898
946
  export function toggleContextPanel() {
899
947
  if (!contextPanel) return;
900
- var view = getContextView();
948
+ var view = getEffectiveContextView();
901
949
  if (view === "panel") {
902
950
  setContextView("mini");
903
- applyContextView("mini");
904
951
  } else {
905
952
  setContextView("panel");
906
- applyContextView("panel");
907
953
  }
908
954
  }
909
955
 
@@ -922,9 +968,17 @@ export function ensureCtxPopover() {
922
968
  });
923
969
  }
924
970
 
971
+ function requestContextDetails() {
972
+ var ws = getWs();
973
+ if (!store.get("replayingHistory") && ws && ws.readyState === 1) {
974
+ ws.send(JSON.stringify({ type: "context_usage_request", sessionId: store.get("activeSessionId") }));
975
+ }
976
+ }
977
+
925
978
  export function showCtxPopover() {
926
979
  var s = store.snap();
927
980
  if (!s.headerContextEl || !s.richContextUsage) return;
981
+ requestContextDetails();
928
982
  if (ctxHoverTimer) { clearTimeout(ctxHoverTimer); ctxHoverTimer = null; }
929
983
  ensureCtxPopover();
930
984
  s.headerContextEl.appendChild(ctxPopoverEl);
@@ -944,8 +998,8 @@ export function renderCtxPopover() {
944
998
  if (!ctxPopoverEl || !richContextUsage) return;
945
999
  var d = richContextUsage;
946
1000
  var cats = d.categories || [];
947
- var total = d.totalTokens || 0;
948
- var max = d.maxTokens || 0;
1001
+ var total = d.totalTokens != null ? d.totalTokens : (d.input_tokens || 0);
1002
+ var max = d.maxTokens != null ? d.maxTokens : (d.contextWindow || 0);
949
1003
  var pct = d.percentage != null ? d.percentage : (max > 0 ? (total / max) * 100 : 0);
950
1004
 
951
1005
  var html = "";
@@ -39,6 +39,7 @@ import { closeProjectSettings } from './project-settings.js';
39
39
  import { chooseProjectAfterRemoval } from './project-removal-target.js';
40
40
  import { isProjectActivated, isProjectActivationPending, isProjectContextConnected, projectWsPath } from './project-activation.js';
41
41
  import { rememberHomePrimarySurface } from './home-surface.js';
42
+ import { clearProjectSplitState } from './split-session-boundary.js';
42
43
 
43
44
  // --- Module-owned state ---
44
45
  var cachedProjects = [];
@@ -377,6 +378,7 @@ export function resetClientState() {
377
378
  store.set({ currentFullText: "" });
378
379
  resetToolState();
379
380
  clearPendingImages();
381
+ store.set({ pendingScheduleAdmissions: {}, pendingScheduleAdmission: null });
380
382
  resetShellCommand();
381
383
  clearAllMentionActive();
382
384
  setActivityEl(null);
@@ -446,6 +448,7 @@ export function switchProject(slug) {
446
448
  }
447
449
  if (homeVisible) store.set({ pendingHomeProjectSlug: slug });
448
450
  if (isProjectActivationPending(store.snap(), slug, ws)) return;
451
+ if (store.get('currentSlug') !== slug) clearProjectSplitState();
449
452
  resetFileBrowser();
450
453
  if (typeof closeScheduledTasks === "function") closeScheduledTasks();
451
454
  closeNotesBrowser();