clay-server 4.1.0-beta.9 → 4.2.0-beta.1

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/daemon-project-access.js +14 -0
  3. package/lib/daemon.js +7 -0
  4. package/lib/issues-mcp-server.js +1 -1
  5. package/lib/issues-service.js +3 -1
  6. package/lib/loop-registry-occurrence.js +1 -0
  7. package/lib/notes.js +20 -3
  8. package/lib/os-users.js +6 -0
  9. package/lib/project-connection.js +14 -3
  10. package/lib/project-file-path.js +13 -0
  11. package/lib/project-file-watch.js +19 -8
  12. package/lib/project-filesystem.js +10 -16
  13. package/lib/project-http.js +53 -48
  14. package/lib/project-issue-launch.js +3 -1
  15. package/lib/project-logs-mcp-server.js +8 -8
  16. package/lib/project-logs-schema.js +1 -1
  17. package/lib/project-logs-service.js +46 -13
  18. package/lib/project-logs-store.js +17 -15
  19. package/lib/project-logs.js +70 -0
  20. package/lib/project-loop.js +1 -0
  21. package/lib/project-notifications.js +1 -1
  22. package/lib/project-scheduled-result-store.js +131 -0
  23. package/lib/project-scheduled-task-execution.js +35 -2
  24. package/lib/project-session-notes.js +11 -4
  25. package/lib/project-sessions.js +39 -6
  26. package/lib/project-user-message.js +47 -0
  27. package/lib/project.js +57 -3
  28. package/lib/public/app.js +27 -9
  29. package/lib/public/css/avatar-imprints.css +1 -1
  30. package/lib/public/css/command-palette.css +57 -19
  31. package/lib/public/css/git-panel.css +13 -12
  32. package/lib/public/css/git-placard.css +4 -4
  33. package/lib/public/css/issues.css +79 -32
  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/project-logs.css +15 -0
  37. package/lib/public/css/sidebar.css +7 -0
  38. package/lib/public/css/sticky-notes.css +29 -0
  39. package/lib/public/index.html +10 -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 +50 -12
  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 +2 -0
  46. package/lib/public/modules/app-rate-limit.js +148 -61
  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 -0
  50. package/lib/public/modules/clay-issue-links.js +24 -2
  51. package/lib/public/modules/clay-log-links.js +12 -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/issues-render.js +36 -10
  57. package/lib/public/modules/issues.js +95 -13
  58. package/lib/public/modules/markdown.js +3 -1
  59. package/lib/public/modules/pane-bridge.js +14 -8
  60. package/lib/public/modules/pane-issue-bridge.js +27 -0
  61. package/lib/public/modules/permission-hints.js +13 -0
  62. package/lib/public/modules/profile.js +9 -9
  63. package/lib/public/modules/project-activation.js +49 -0
  64. package/lib/public/modules/project-logs-render.js +13 -3
  65. package/lib/public/modules/project-logs.js +45 -15
  66. package/lib/public/modules/project-session-visibility-settings.js +138 -0
  67. package/lib/public/modules/project-settings.js +5 -0
  68. package/lib/public/modules/right-workbench.js +44 -0
  69. package/lib/public/modules/scheduled-result-client.js +33 -0
  70. package/lib/public/modules/scheduled-result-display.js +50 -0
  71. package/lib/public/modules/scheduled-result-notification.js +31 -0
  72. package/lib/public/modules/scheduled-task-presentation.js +15 -0
  73. package/lib/public/modules/scheduled-tasks.js +9 -21
  74. package/lib/public/modules/sidebar-mobile.js +51 -3
  75. package/lib/public/modules/sidebar-projects.js +14 -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 +31 -7
  93. package/lib/session-notes-mcp-server.js +3 -5
  94. package/lib/session-pair-factory.js +2 -2
  95. package/lib/session-title-generator.js +89 -0
  96. package/lib/session-title-policy.js +8 -2
  97. package/lib/session-visibility.js +62 -1
  98. package/lib/sessions.js +28 -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 +13 -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';
@@ -36,6 +38,7 @@ import { handleFsList, handleFsRead, handleFileChanged, handleDirChanged, handle
36
38
  import { beginMarkdownTurn, finishMarkdownTurn, markdownPathFromToolInput } from './markdown-live-edit.js';
37
39
  import { forwardPaneMarkdownPresentation } from './pane-bridge.js';
38
40
  import { isProjectSettingsOpen, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './project-settings.js';
41
+ import { handleProjectSessionVisibilityDefault, refreshProjectSessionVisibilitySettings } from './project-session-visibility-settings.js';
39
42
  import { updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleInheritGroupsChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './server-settings.js';
40
43
  import { handleTermList, handleTermCreated, sendTerminalCommand, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed } from './terminal.js';
41
44
  import { attachTuiView, detachTuiView, setTuiSuspendedView, tuiHandleTermOutput, tuiHandleTermResized, tuiHandleTermExited, tuiHandleTermClosed } from './session-tui-view.js';
@@ -44,7 +47,8 @@ import { tuiModalHandleTermOutput, tuiModalHandleTermResized, tuiModalHandleTerm
44
47
  import { updateTerminalList, handleContextSourcesState, updateEmailAccountList, updateEmailUnreadCounts, handleEmailTestResult, handleEmailAddResult, handleEmailRemoveResult } from './context-sources.js';
45
48
  import { refreshEmailSettings } from './user-settings.js';
46
49
  import { handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, handleNoteWritten } from './sticky-notes.js';
47
- import { handleProjectLogsState, handleProjectLogEntry, handleProjectLogCommented, handleProjectLogCommentReviewed, handleProjectLogDeleted, handleProjectLogUpdated, handleProjectLogsError } from './project-logs.js';
50
+ import { handleNoteDeleteResult } from './sticky-notes-browser.js';
51
+ import { handleProjectLogsState, handleProjectLogEntry, handleProjectLogCommented, handleProjectLogCommentReviewed, handleProjectLogDeleted, handleProjectLogUpdated, handleProjectLogsError, handleScheduledTaskResultsState, handleScheduledTaskResultCreated, handleScheduledTaskResultSession } from './project-logs.js';
48
52
  import { handleSkillInstalled, handleSkillUninstalled } from './skills.js';
49
53
  import { showRewindModal, onRewindComplete, setRewindMode, onRewindError, clearPendingRewindUuid, addRewindButton } from './rewind.js';
50
54
  import { checkAdminAccess } from './admin.js';
@@ -63,9 +67,10 @@ import { setStatus } from './app-connection.js';
63
67
  import { handleWhatsNewState, handleWhatsNewSeenResult, setKnownEntries as setWhatsNewKnownEntries } from './whats-new.js';
64
68
  import { closeArticle as closeWhatsNewArticle } from './whats-new-article.js';
65
69
  import { resolvePaneSession, resolveSwitchedVendor } from './pane-session.js';
70
+ import { markProjectSessionListHydrated, applyProjectSplitGroups, clearProjectSplitState } from './split-session-boundary.js';
66
71
  import { selectDefaultVendorForBlankSession } from './vendor-selection.js';
67
72
  import { getModelInfoUpdate, modelEntryValue, modelEntryMatches, handleModelSelectionResult, requestVendorModels } from './model-picker.js';
68
- import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel } from './app-panels.js';
73
+ import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel, markContextUnavailable } from './app-panels.js';
69
74
  import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress, finishProjectSessionActivation } from './app-projects.js';
70
75
  import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
71
76
  import { hideHomeHub, showHomeHub } from './app-home-hub.js';
@@ -97,6 +102,12 @@ export function processMessage(msg) {
97
102
  if (handleAutonomousRunMessage(msg)) return;
98
103
  if (handleLoopInterviewMessage(msg)) return;
99
104
  if (handleScheduledTaskMessage(msg)) return;
105
+ if (handleDefaultVendorMessage(msg)) return;
106
+ if (handleContextViewMessage(msg)) return;
107
+ if (msg && msg.type === "set_project_session_visibility_default_result") {
108
+ handleProjectSessionVisibilityDefault(msg);
109
+ return;
110
+ }
100
111
  if (msg && msg.type === "schedule_message_result") {
101
112
  handleScheduleMessageResult(msg);
102
113
  return;
@@ -226,9 +237,8 @@ export function processMessage(msg) {
226
237
  applyDeadSessionTodoCompaction();
227
238
  }
228
239
  // Restore cached rich context usage BEFORE updateContextPanel runs
229
- if (msg.contextUsage) {
230
- store.set({ richContextUsage: msg.contextUsage });
231
- }
240
+ if (msg.contextUsage && !msg.contextUsage.unavailable) store.set({ richContextUsage: msg.contextUsage });
241
+ else markContextUnavailable();
232
242
  // Restore accurate context data from the last result in full history
233
243
  if (msg.lastUsage || msg.lastModelUsage) {
234
244
  accumulateContext(msg.lastCost, msg.lastUsage, msg.lastModelUsage, msg.lastStreamInputTokens);
@@ -330,10 +340,13 @@ export function processMessage(msg) {
330
340
  // start receiving the new project's session_switched. The TUI
331
341
  // host lives on document.body (it's position: fixed), so it
332
342
  // survives project navigation unless we detach explicitly here.
343
+ if (msg.slug && store.get('activeProjectSlug') && store.get('activeProjectSlug') !== msg.slug) {
344
+ clearProjectSplitState();
345
+ }
333
346
  detachTuiView();
334
347
  store.set({ projectName: msg.project || msg.cwd, vendorInfo: msg.vendors || {} });
335
348
  if (msg.cwd) store.set({ cwd: msg.cwd });
336
- if (msg.slug) store.set({ currentSlug: msg.slug, activeProjectSlug: msg.slug });
349
+ if (msg.slug) store.set({ currentSlug: msg.slug, activeProjectSlug: msg.slug, sessionListProjectSlug: null, splitGroupsProjectSlug: null });
337
350
  try { var _is = store.snap(); localStorage.setItem("clay-project-name-" + (_is.currentSlug || "default"), _is.projectName); } catch (e) {}
338
351
  // In mate DM, keep title as mate name and re-apply mate color
339
352
  if (store.get('dmMode') && store.get('dmTargetUser') && store.get('dmTargetUser').isMate) {
@@ -367,6 +380,7 @@ export function processMessage(msg) {
367
380
  if (spBanner) spBanner.classList.remove("hidden");
368
381
  }
369
382
  updateProjectList(msg);
383
+ refreshProjectSessionVisibilitySettings(msg.projects);
370
384
  break;
371
385
 
372
386
  case "update_available":
@@ -690,6 +704,7 @@ export function processMessage(msg) {
690
704
  break;
691
705
 
692
706
  case "session_list":
707
+ markProjectSessionListHydrated();
693
708
  renderMateSessionList(msg.sessions || []);
694
709
  renderSessionList(msg.sessions || []);
695
710
  syncPaneTitles();
@@ -701,13 +716,14 @@ export function processMessage(msg) {
701
716
  }
702
717
  }
703
718
  handlePaletteSessionSwitch();
719
+ maybeRestoreSplitGroup();
704
720
  break;
705
721
 
706
722
  case "split_groups":
707
- store.set({ splitGroups: msg.groups || [] });
723
+ applyProjectSplitGroups(msg.groups || []);
708
724
  renderSessionList(null);
709
- maybeRestoreSplitGroup();
710
725
  syncPaneTitles();
726
+ maybeRestoreSplitGroup();
711
727
  break;
712
728
 
713
729
  case "split_group_result":
@@ -888,8 +904,8 @@ export function processMessage(msg) {
888
904
  }
889
905
  // Reload survival: a restored active session that is a split-group
890
906
  // member reopens its group (no-op when a split is already open).
891
- maybeRestoreSplitGroup();
892
907
  if (finishProjectSessionActivation()) hideHomeHub();
908
+ maybeRestoreSplitGroup();
893
909
  break;
894
910
 
895
911
  case "session_full_access_changed":
@@ -1044,6 +1060,9 @@ export function processMessage(msg) {
1044
1060
  case "thinking_start":
1045
1061
  removeMatePreThinking();
1046
1062
  startThinking();
1063
+ // The live Thinking entry is the progress affordance while reasoning
1064
+ // is streaming; avoid a second bouncing indicator at the same tail.
1065
+ setActivity(null);
1047
1066
  break;
1048
1067
 
1049
1068
  case "thinking_delta":
@@ -1171,7 +1190,7 @@ export function processMessage(msg) {
1171
1190
 
1172
1191
  case "permission_request":
1173
1192
  setActivity(null);
1174
- renderPermissionRequest(msg.requestId, msg.toolName, msg.toolInput, msg.decisionReason, msg.mateId, msg.vendor);
1193
+ renderPermissionRequest(msg.requestId, msg.toolName, msg.toolInput, msg.decisionReason, msg.mateId, msg.vendor, msg);
1175
1194
  startUrgentBlink();
1176
1195
  break;
1177
1196
 
@@ -1187,7 +1206,7 @@ export function processMessage(msg) {
1187
1206
 
1188
1207
  case "permission_request_pending":
1189
1208
  setActivity(null);
1190
- renderPermissionRequest(msg.requestId, msg.toolName, msg.toolInput, msg.decisionReason, msg.mateId, msg.vendor);
1209
+ renderPermissionRequest(msg.requestId, msg.toolName, msg.toolInput, msg.decisionReason, msg.mateId, msg.vendor, msg);
1191
1210
  startUrgentBlink();
1192
1211
  break;
1193
1212
 
@@ -1271,8 +1290,10 @@ export function processMessage(msg) {
1271
1290
  break;
1272
1291
 
1273
1292
  case "context_usage":
1293
+ if (msg.sessionId != null && msg.sessionId !== store.get("activeSessionId")) break;
1274
1294
  if (msg.data && !store.get('replayingHistory')) {
1275
- store.set({ richContextUsage: msg.data });
1295
+ if (msg.data.unavailable) markContextUnavailable();
1296
+ else store.set({ richContextUsage: msg.data });
1276
1297
  // UI sync handled by store subscriber in app-panels.js
1277
1298
  }
1278
1299
  break;
@@ -1635,6 +1656,10 @@ export function processMessage(msg) {
1635
1656
  handleNoteDeleted(msg);
1636
1657
  break;
1637
1658
 
1659
+ case "note_delete_result":
1660
+ handleNoteDeleteResult(msg);
1661
+ break;
1662
+
1638
1663
  case "project_logs_state":
1639
1664
  handleProjectLogsState(msg);
1640
1665
  break;
@@ -1663,6 +1688,18 @@ export function processMessage(msg) {
1663
1688
  handleProjectLogsError(msg);
1664
1689
  break;
1665
1690
 
1691
+ case "scheduled_task_results_state":
1692
+ handleScheduledTaskResultsState(msg);
1693
+ break;
1694
+
1695
+ case "scheduled_task_result_created":
1696
+ handleScheduledTaskResultCreated(msg);
1697
+ break;
1698
+
1699
+ case "scheduled_task_result_session":
1700
+ handleScheduledTaskResultSession(msg);
1701
+ break;
1702
+
1666
1703
  case "process_stats":
1667
1704
  updateStatusPanel(msg);
1668
1705
  updateSettingsStats(msg);
@@ -1704,6 +1741,7 @@ export function processMessage(msg) {
1704
1741
 
1705
1742
  case "projects_updated":
1706
1743
  updateProjectList(msg);
1744
+ refreshProjectSessionVisibilitySettings(msg.projects);
1707
1745
  renderUserStrip();
1708
1746
  break;
1709
1747
 
@@ -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 = [];
@@ -447,6 +448,7 @@ export function switchProject(slug) {
447
448
  }
448
449
  if (homeVisible) store.set({ pendingHomeProjectSlug: slug });
449
450
  if (isProjectActivationPending(store.snap(), slug, ws)) return;
451
+ if (store.get('currentSlug') !== slug) clearProjectSplitState();
450
452
  resetFileBrowser();
451
453
  if (typeof closeScheduledTasks === "function") closeScheduledTasks();
452
454
  closeNotesBrowser();