farming-code 2.2.19 → 2.2.22

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 (53) hide show
  1. package/README.md +5 -5
  2. package/README.zh_cn.md +5 -5
  3. package/THIRD_PARTY_NOTICES.md +5 -8
  4. package/backend/acp/client-services.js +37 -5
  5. package/backend/acp/patch-decisions.js +19 -3
  6. package/backend/acp-runtime.js +1201 -198
  7. package/backend/acp-transcript.js +66 -0
  8. package/backend/agent-lifecycle-journal.js +133 -0
  9. package/backend/agent-manager.js +2715 -354
  10. package/backend/agent-order.js +8 -4
  11. package/backend/agent-runtime-binding.js +1 -0
  12. package/backend/bundle-update-helper.js +5 -1
  13. package/backend/config-manager.js +16 -0
  14. package/backend/control-api.js +146 -20
  15. package/backend/farming-app-cli.js +335 -10
  16. package/backend/farming-session-store.js +283 -36
  17. package/backend/json-cli-runtime.js +120 -6
  18. package/backend/npm-update-helper.js +97 -18
  19. package/backend/provider-adapters.js +18 -6
  20. package/backend/review-session-service.js +31 -17
  21. package/backend/server.js +337 -103
  22. package/backend/storage-layout.js +10 -0
  23. package/backend/terminal-screen-worker-pool.js +15 -6
  24. package/backend/theme-manager.js +23 -5
  25. package/backend/update-service.js +239 -27
  26. package/backend/usage-monitor.js +11 -0
  27. package/backend/workspace-file-router.js +9 -3
  28. package/backend/workspace-file-service.js +292 -62
  29. package/dist/acp/codex-acp-1.1.4.js +32 -1
  30. package/dist/assets/App-CmZdNavW.js +210 -0
  31. package/dist/assets/{FileEditorMarkdownPreview-R0jwwLME.js → FileEditorMarkdownPreview-KbnE8VC8.js} +1 -1
  32. package/dist/assets/FileEditorPane-5_HUejjd.js +2 -0
  33. package/dist/assets/IconGlyphs-C2WFcoJS.js +1 -0
  34. package/dist/assets/ProjectFilesSection-C1RAJWOE.js +12 -0
  35. package/dist/assets/{ReviewPage-DBKNOWsJ.js → ReviewPage-L1vh1Ggs.js} +3 -3
  36. package/dist/assets/code-dark-BKtvf0Xc.css +1 -0
  37. package/dist/assets/file-icons-CHWt5nSy.js +1 -0
  38. package/dist/assets/{index-DQdtPfCk.js → index-RvUiMpGP.js} +2 -2
  39. package/dist/assets/main-XMXgyJJt.css +1 -0
  40. package/dist/assets/workspace-view-state-BqB4HM7r.js +7 -0
  41. package/dist/index.html +1 -1
  42. package/frontend/skins/crt/app.js +365 -107
  43. package/frontend/skins/crt/index.html +5 -10
  44. package/frontend/skins/crt/styles/billing.css +6 -1
  45. package/package.json +9 -3
  46. package/dist/assets/App-CbXYBccJ.js +0 -210
  47. package/dist/assets/FileEditorPane-CJs6kfZO.js +0 -2
  48. package/dist/assets/IconGlyphs-CZzDr5o2.js +0 -1
  49. package/dist/assets/ProjectFilesSection-DWCV07lA.js +0 -12
  50. package/dist/assets/code-dark-qIBiLBWV.css +0 -1
  51. package/dist/assets/file-icons-DpbrD5ZP.js +0 -1
  52. package/dist/assets/main-yzZEfgft.css +0 -1
  53. package/dist/assets/workspace-view-state-BpPINu84.js +0 -7
@@ -55,6 +55,9 @@ let billingRequestSequence = 0;
55
55
  let billingAbortController = null;
56
56
  let billingRefreshTimer = null;
57
57
  let billingLiveDayRefreshTimer = null;
58
+ let crtTokenRateRefreshTimer = null;
59
+ let crtTokenRateFirstLoadTimer = null;
60
+ let crtTokenRateAbortController = null;
58
61
  let billingCanvasFrame = null;
59
62
  let billingMode = 'days';
60
63
  let billingSelectedDate = '';
@@ -86,6 +89,7 @@ const crtStructuredPreviewTimers = new Map();
86
89
  let sessionRuntime = null;
87
90
  let structuredSessionPoller = null;
88
91
  let structuredSessionLoading = false;
92
+ let structuredSessionGeneration = 0;
89
93
  let structuredSessionRenderedAt = '';
90
94
  let structuredSessionSnapshot = null;
91
95
  let structuredSessionControlsLoading = false;
@@ -104,6 +108,8 @@ let runtimeSwitchPending = false;
104
108
  let pendingRuntimeSwitchAgentId = '';
105
109
  let runtimeSwitchRequestSequence = 0;
106
110
  let crtTerminalReplication = null;
111
+ const globalSettingsSaveRevisions = new Map();
112
+ let globalSettingsSaveTail = Promise.resolve();
107
113
  const terminalPreviewSnapshots = new Map();
108
114
  const crtBrandPulseTimers = new Map();
109
115
  const SESSION_LINK_LIMIT = 6;
@@ -116,13 +122,16 @@ const CRT_AGENT_GRID_GAP = 15;
116
122
  const CRT_AGENT_GRID_PADDING = 20;
117
123
  const CRT_SEARCH_DEBOUNCE_MS = 180;
118
124
  const CRT_SEARCH_RESULT_LIMIT = 100;
119
- const CRT_BILLING_REFRESH_MS = 30_000;
125
+ const CRT_BILLING_REFRESH_MS = 15_000;
120
126
  const CRT_BILLING_LIVE_DAY_REFRESH_MS = 5_000;
127
+ const CRT_TOKEN_RATE_REFRESH_MS = 60_000;
128
+ const CRT_TOKEN_RATE_FIRST_LOAD_MS = 1_500;
121
129
  const CRT_BILLING_DAY_DETAIL_CACHE_MS = 30_000;
122
130
  const CRT_BILLING_TOTAL_ANIMATION_MS = 900;
123
131
  const CRT_BILLING_DAY_DETAIL_RETRY_MS = 750;
124
132
  const CRT_BILLING_DAY_DETAIL_MAX_RETRIES = 4;
125
133
  const CRT_TERMINAL_CHECKPOINT_REQUEST_TIMEOUT_MS = 5_000;
134
+ const CRT_SETTINGS_REQUEST_TIMEOUT_MS = 15_000;
126
135
  const CRT_TERMINAL_RESIZE_SETTLE_MS = 250;
127
136
  const CRT_TERMINAL_MIN_COLS = 40;
128
137
  const CRT_TERMINAL_MIN_ROWS = 10;
@@ -844,8 +853,14 @@ function fallbackCopyText(text) {
844
853
  return copied;
845
854
  }
846
855
 
847
- async function pasteTerminalText(text) {
848
- if (!text || !focusedAgentId) {
856
+ function isCurrentCrtSession(agentId, sessionToken = null) {
857
+ if (!agentId || focusedAgentId !== agentId) return false;
858
+ const runtime = getSessionRuntime();
859
+ return sessionToken === null || !runtime || runtime.isCurrentSession(agentId, sessionToken);
860
+ }
861
+
862
+ async function pasteTerminalText(text, agentId = focusedAgentId, sessionToken = null) {
863
+ if (!text || !isCurrentCrtSession(agentId, sessionToken)) {
849
864
  return false;
850
865
  }
851
866
 
@@ -872,9 +887,13 @@ async function pasteFromClipboard() {
872
887
  return false;
873
888
  }
874
889
 
890
+ const agentId = focusedAgentId;
891
+ const runtime = getSessionRuntime();
892
+ const sessionToken = runtime ? runtime.getSessionToken() : null;
893
+ if (!agentId) return false;
875
894
  try {
876
895
  const text = await navigator.clipboard.readText();
877
- return pasteTerminalText(text);
896
+ return pasteTerminalText(text, agentId, sessionToken);
878
897
  } catch (error) {
879
898
  console.warn('Clipboard API paste failed:', error);
880
899
  return false;
@@ -2787,9 +2806,19 @@ async function loadThemes() {
2787
2806
  }
2788
2807
  }
2789
2808
 
2809
+ async function fetchCrtSettingsRequest(url, init) {
2810
+ const controller = new window.AbortController();
2811
+ const timeout = setTimeout(() => controller.abort(), CRT_SETTINGS_REQUEST_TIMEOUT_MS);
2812
+ try {
2813
+ return await fetch(url, { ...init, signal: controller.signal });
2814
+ } finally {
2815
+ clearTimeout(timeout);
2816
+ }
2817
+ }
2818
+
2790
2819
  async function loadGlobalSettings() {
2791
2820
  try {
2792
- const response = await fetch(farmingApiPath('/settings'));
2821
+ const response = await fetchCrtSettingsRequest(farmingApiPath('/settings'));
2793
2822
  const data = await response.json();
2794
2823
  globalSettings = {
2795
2824
  ...globalSettings,
@@ -2808,31 +2837,48 @@ async function loadGlobalSettings() {
2808
2837
  }
2809
2838
  }
2810
2839
 
2811
- async function saveGlobalSettings() {
2812
- try {
2813
- syncWorkspaceSettings();
2814
- const response = await fetch(farmingApiPath('/settings'), {
2815
- method: 'POST',
2816
- headers: {
2817
- 'Content-Type': 'application/json'
2818
- },
2819
- body: JSON.stringify(globalSettings)
2820
- });
2840
+ async function saveGlobalSettings(settingsPatch) {
2841
+ const patch = settingsPatch && typeof settingsPatch === 'object'
2842
+ ? { ...settingsPatch }
2843
+ : {};
2844
+ const keys = Object.keys(patch);
2845
+ if (keys.length === 0) return;
2846
+ const revisions = new Map(keys.map((key) => {
2847
+ const revision = (globalSettingsSaveRevisions.get(key) || 0) + 1;
2848
+ globalSettingsSaveRevisions.set(key, revision);
2849
+ return [key, revision];
2850
+ }));
2851
+ const save = globalSettingsSaveTail.catch(() => {}).then(async () => {
2852
+ try {
2853
+ const response = await fetchCrtSettingsRequest(farmingApiPath('/settings'), {
2854
+ method: 'POST',
2855
+ headers: {
2856
+ 'Content-Type': 'application/json'
2857
+ },
2858
+ body: JSON.stringify(patch)
2859
+ });
2821
2860
 
2822
- const data = await response.json();
2823
- if (data.success) {
2824
- globalSettings = {
2825
- ...globalSettings,
2826
- ...(data.settings || {})
2827
- };
2828
- if (globalSettings.instanceName) {
2829
- document.title = `${globalSettings.instanceName} · Farming CRT`;
2861
+ const data = await response.json();
2862
+ if (data.success) {
2863
+ const savedSettings = data.settings || {};
2864
+ keys.forEach((key) => {
2865
+ if (globalSettingsSaveRevisions.get(key) !== revisions.get(key)) return;
2866
+ globalSettings[key] = Object.prototype.hasOwnProperty.call(savedSettings, key)
2867
+ ? savedSettings[key]
2868
+ : patch[key];
2869
+ });
2870
+ if (globalSettings.instanceName) {
2871
+ document.title = `${globalSettings.instanceName} · Farming CRT`;
2872
+ }
2873
+ syncWorkspaceSettings();
2830
2874
  }
2831
- syncWorkspaceSettings();
2875
+ } catch (error) {
2876
+ console.error('Failed to save global settings:', error);
2877
+ await loadGlobalSettings();
2832
2878
  }
2833
- } catch (error) {
2834
- console.error('Failed to save global settings:', error);
2835
- }
2879
+ });
2880
+ globalSettingsSaveTail = save;
2881
+ return save;
2836
2882
  }
2837
2883
 
2838
2884
  async function setTheme(themeId) {
@@ -2936,7 +2982,7 @@ function scheduleCrtTerminalFontSizeSave() {
2936
2982
  if (crtTerminalFontSizeSaveTimer) clearTimeout(crtTerminalFontSizeSaveTimer);
2937
2983
  crtTerminalFontSizeSaveTimer = setTimeout(() => {
2938
2984
  crtTerminalFontSizeSaveTimer = null;
2939
- void saveGlobalSettings();
2985
+ void saveGlobalSettings({ crtTerminalFontSize: globalSettings.crtTerminalFontSize });
2940
2986
  }, 150);
2941
2987
  }
2942
2988
 
@@ -2954,7 +3000,7 @@ function initDisplaySettings() {
2954
3000
  crtToggle.onchange = async () => {
2955
3001
  globalSettings.crtSkinEffectsEnabled = crtToggle.checked;
2956
3002
  applyCRTEffects(crtToggle.checked);
2957
- await saveGlobalSettings();
3003
+ await saveGlobalSettings({ crtSkinEffectsEnabled: crtToggle.checked });
2958
3004
  };
2959
3005
  }
2960
3006
  } else {
@@ -2969,7 +3015,7 @@ function initDisplaySettings() {
2969
3015
  dynamicHeatToggle.onchange = async () => {
2970
3016
  globalSettings.crtDynamicHeatEnabled = dynamicHeatToggle.checked;
2971
3017
  renderState();
2972
- await saveGlobalSettings();
3018
+ await saveGlobalSettings({ crtDynamicHeatEnabled: dynamicHeatToggle.checked });
2973
3019
  };
2974
3020
  }
2975
3021
 
@@ -2989,7 +3035,9 @@ function initSessionEngineSettings() {
2989
3035
  skipPermissionCheckToggle.checked = globalSettings.dangerouslySkipAgentPermissionsByDefault === true;
2990
3036
  skipPermissionCheckToggle.onchange = async () => {
2991
3037
  globalSettings.dangerouslySkipAgentPermissionsByDefault = skipPermissionCheckToggle.checked;
2992
- await saveGlobalSettings();
3038
+ await saveGlobalSettings({
3039
+ dangerouslySkipAgentPermissionsByDefault: skipPermissionCheckToggle.checked,
3040
+ });
2993
3041
  };
2994
3042
  }
2995
3043
 
@@ -3159,6 +3207,7 @@ function setCrtMainView(view) {
3159
3207
  if (historySidebarItem) historySidebarItem.classList.toggle('active', crtMainView === 'history');
3160
3208
  if (searchSidebarItem) searchSidebarItem.classList.toggle('active', crtMainView === 'search');
3161
3209
  if (billingSidebarItem) billingSidebarItem.classList.toggle('active', crtMainView === 'billing');
3210
+ if (previousView !== 'agents' && crtMainView === 'agents') checkMainAgentStatus();
3162
3211
  }
3163
3212
 
3164
3213
  function getCrtSearchResults() {
@@ -4006,6 +4055,30 @@ function scrollCrtBillingSelectedDayIntoView() {
4006
4055
  else if (right > scroll.scrollLeft + scroll.clientWidth) scroll.scrollLeft = right - scroll.clientWidth;
4007
4056
  }
4008
4057
 
4058
+ function crtBillingDayArrowTargetIndex(points, index, key) {
4059
+ if (!Array.isArray(points) || index < 0 || index >= points.length) return -1;
4060
+ const firstDate = parseCrtBillingDate(points[0] && points[0].date);
4061
+ const leadingDays = firstDate ? (firstDate.getDay() + 6) % 7 : 0;
4062
+ const position = leadingDays + index;
4063
+ const weekdayRow = position % 7;
4064
+ let targetPosition = position;
4065
+ if (key === 'ArrowUp') {
4066
+ if (weekdayRow === 0) return -1;
4067
+ targetPosition -= 1;
4068
+ } else if (key === 'ArrowDown') {
4069
+ if (weekdayRow === 6) return -1;
4070
+ targetPosition += 1;
4071
+ } else if (key === 'ArrowLeft') {
4072
+ targetPosition -= 7;
4073
+ } else if (key === 'ArrowRight') {
4074
+ targetPosition += 7;
4075
+ } else {
4076
+ return -1;
4077
+ }
4078
+ const targetIndex = targetPosition - leadingDays;
4079
+ return targetIndex >= 0 && targetIndex < points.length ? targetIndex : -1;
4080
+ }
4081
+
4009
4082
  function selectCrtBillingDayByArrow(key) {
4010
4083
  if (crtMainView !== 'billing' || billingMode !== 'days') return false;
4011
4084
  const points = billingSummary && billingSummary.daily && Array.isArray(billingSummary.daily.points)
@@ -4014,8 +4087,8 @@ function selectCrtBillingDayByArrow(key) {
4014
4087
  if (points.length === 0) return false;
4015
4088
  let index = points.findIndex(point => point.date === billingSelectedDate);
4016
4089
  if (index < 0) index = points.length - 1;
4017
- const delta = key === 'ArrowLeft' ? -1 : key === 'ArrowRight' ? 1 : key === 'ArrowUp' ? -7 : 7;
4018
- const nextIndex = Math.max(0, Math.min(points.length - 1, index + delta));
4090
+ const nextIndex = crtBillingDayArrowTargetIndex(points, index, key);
4091
+ if (nextIndex < 0) return false;
4019
4092
  return selectCrtBillingDay(points[nextIndex].date, { focus: true });
4020
4093
  }
4021
4094
 
@@ -4156,11 +4229,35 @@ function setCrtBillingMode(mode) {
4156
4229
  }
4157
4230
  const status = document.getElementById('billing-status');
4158
4231
  if (status && billingSummary && !billingLoading && !billingError) {
4159
- status.textContent = billingMode === 'days' ? 'HISTORY READY' : 'SIGNAL LOCKED';
4232
+ status.textContent = crtBillingStatusText();
4160
4233
  }
4161
4234
  if (billingMode === 'live') window.requestAnimationFrame(() => drawCrtBillingScope());
4162
4235
  }
4163
4236
 
4237
+ function selectCrtBillingMode(mode) {
4238
+ setCrtBillingMode(mode);
4239
+ const selectedTab = document.getElementById(
4240
+ billingMode === 'live' ? 'billing-live-tab' : 'billing-days-tab',
4241
+ );
4242
+ if (selectedTab) setCrtNavigationSelection(selectedTab);
4243
+ if (billingMode === 'live' && !billingLoading) void loadCrtBilling();
4244
+ }
4245
+
4246
+ function crtBillingTimeline(summary = billingSummary) {
4247
+ return summary && summary.liveTimeline || summary && summary.timeline || null;
4248
+ }
4249
+
4250
+ function crtBillingStatusText(summary = billingSummary) {
4251
+ if (billingMode === 'days') return 'HISTORY READY';
4252
+ const sampledAt = Number(summary && summary.sampledAt);
4253
+ if (!Number.isFinite(sampledAt) || sampledAt <= 0) return 'LIVE';
4254
+ const sampled = new Date(sampledAt);
4255
+ const time = [sampled.getHours(), sampled.getMinutes(), sampled.getSeconds()]
4256
+ .map(value => String(value).padStart(2, '0'))
4257
+ .join(':');
4258
+ return `LIVE ${time}`;
4259
+ }
4260
+
4164
4261
  function formatCrtBillingWindow(windowMinutes) {
4165
4262
  const minutes = Number(windowMinutes);
4166
4263
  if (!Number.isFinite(minutes) || minutes <= 0) return 'WINDOW';
@@ -4214,6 +4311,13 @@ function crtBillingCurrentRate(summary = billingSummary) {
4214
4311
  return hasRate ? total : null;
4215
4312
  }
4216
4313
 
4314
+ function renderCrtTopBarTokenRate(summary = billingSummary) {
4315
+ const tokensPerMinute = document.getElementById('tokens-per-minute');
4316
+ if (tokensPerMinute) {
4317
+ tokensPerMinute.textContent = formatCrtTokenRate(crtBillingCurrentRate(summary));
4318
+ }
4319
+ }
4320
+
4217
4321
  function appendCrtBillingMessage(container, text, isError = false) {
4218
4322
  const message = document.createElement('div');
4219
4323
  message.className = `billing-message${isError ? ' is-error' : ''}`;
@@ -4314,7 +4418,7 @@ function drawCrtBillingScope(summary = billingSummary) {
4314
4418
  context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
4315
4419
  context.clearRect(0, 0, rect.width, rect.height);
4316
4420
 
4317
- const timeline = summary && summary.timeline;
4421
+ const timeline = crtBillingTimeline(summary);
4318
4422
  const points = timeline && Array.isArray(timeline.points) ? timeline.points : [];
4319
4423
  const values = points.map(point => Math.max(0, Number(point.tokensPerMinute) || 0));
4320
4424
  const providerNames = points.length > 0 ? Object.keys(points[0].providers || {}) : [];
@@ -4392,7 +4496,7 @@ function renderCrtBilling() {
4392
4496
  const status = document.getElementById('billing-status');
4393
4497
  const refresh = document.getElementById('billing-refresh');
4394
4498
  const empty = document.getElementById('billing-scope-empty');
4395
- const timeline = billingSummary && billingSummary.timeline;
4499
+ const timeline = crtBillingTimeline();
4396
4500
  const hasSignal = Boolean(timeline && Number(timeline.totalTokens) > 0);
4397
4501
  if (status) {
4398
4502
  status.classList.toggle('is-busy', billingLoading);
@@ -4402,7 +4506,7 @@ function renderCrtBilling() {
4402
4506
  : billingLoading
4403
4507
  ? (billingSummary ? 'REFRESHING' : 'SCANNING LOGS')
4404
4508
  : billingSummary
4405
- ? (billingMode === 'days' ? 'HISTORY READY' : 'SIGNAL LOCKED')
4509
+ ? crtBillingStatusText()
4406
4510
  : 'STANDBY';
4407
4511
  }
4408
4512
  if (refresh) refresh.disabled = billingLoading;
@@ -4449,13 +4553,18 @@ async function loadCrtBilling({ fresh = false } = {}) {
4449
4553
  billingRequestSequence += 1;
4450
4554
  const requestSequence = billingRequestSequence;
4451
4555
  if (billingAbortController) billingAbortController.abort();
4556
+ if (crtTokenRateAbortController) {
4557
+ crtTokenRateAbortController.abort();
4558
+ crtTokenRateAbortController = null;
4559
+ }
4452
4560
  const controller = new window.AbortController();
4453
4561
  billingAbortController = controller;
4454
4562
  billingLoading = true;
4455
4563
  billingError = '';
4456
4564
  renderCrtBilling();
4457
4565
  try {
4458
- const response = await fetch(farmingApiPath(`/usage${fresh ? '?fresh=1' : ''}`), {
4566
+ const query = fresh ? '?fresh=1' : billingMode === 'live' ? '?live=1' : '';
4567
+ const response = await fetch(farmingApiPath(`/usage${query}`), {
4459
4568
  signal: controller.signal,
4460
4569
  cache: 'no-store',
4461
4570
  });
@@ -4463,6 +4572,7 @@ async function loadCrtBilling({ fresh = false } = {}) {
4463
4572
  if (!response.ok || !data || !data.usage) throw new Error(data && data.error ? data.error : `Usage request failed (${response.status})`);
4464
4573
  if (requestSequence !== billingRequestSequence) return;
4465
4574
  billingSummary = data.usage;
4575
+ renderCrtTopBarTokenRate();
4466
4576
  } catch (error) {
4467
4577
  if (controller.signal.aborted || requestSequence !== billingRequestSequence) return;
4468
4578
  billingError = error instanceof Error ? error.message : 'Failed to load token telemetry';
@@ -4481,6 +4591,58 @@ async function loadCrtBilling({ fresh = false } = {}) {
4481
4591
  }
4482
4592
  }
4483
4593
 
4594
+ async function loadCrtTopBarTokenRate() {
4595
+ if (billingLoading || crtTokenRateAbortController) return;
4596
+ const controller = new window.AbortController();
4597
+ crtTokenRateAbortController = controller;
4598
+ try {
4599
+ const response = await fetch(farmingApiPath('/usage'), {
4600
+ signal: controller.signal,
4601
+ cache: 'no-store',
4602
+ });
4603
+ const data = await response.json().catch(() => null);
4604
+ if (controller.signal.aborted) return;
4605
+ if (!response.ok || !data || !data.usage) {
4606
+ throw new Error(data && data.error ? data.error : `Usage request failed (${response.status})`);
4607
+ }
4608
+ renderCrtTopBarTokenRate(data.usage);
4609
+ } catch {
4610
+ if (!controller.signal.aborted) renderCrtTopBarTokenRate(null);
4611
+ } finally {
4612
+ if (crtTokenRateAbortController === controller) crtTokenRateAbortController = null;
4613
+ }
4614
+ }
4615
+
4616
+ function stopCrtTopBarTokenRateRefresh({ abort = false } = {}) {
4617
+ if (crtTokenRateFirstLoadTimer !== null) {
4618
+ clearTimeout(crtTokenRateFirstLoadTimer);
4619
+ crtTokenRateFirstLoadTimer = null;
4620
+ }
4621
+ if (crtTokenRateRefreshTimer !== null) {
4622
+ clearInterval(crtTokenRateRefreshTimer);
4623
+ crtTokenRateRefreshTimer = null;
4624
+ }
4625
+ if (abort && crtTokenRateAbortController) {
4626
+ crtTokenRateAbortController.abort();
4627
+ crtTokenRateAbortController = null;
4628
+ }
4629
+ }
4630
+
4631
+ function startCrtTopBarTokenRateRefresh({ immediate = false } = {}) {
4632
+ stopCrtTopBarTokenRateRefresh();
4633
+ if (immediate) {
4634
+ void loadCrtTopBarTokenRate();
4635
+ } else {
4636
+ crtTokenRateFirstLoadTimer = setTimeout(() => {
4637
+ crtTokenRateFirstLoadTimer = null;
4638
+ if (document.visibilityState !== 'hidden') void loadCrtTopBarTokenRate();
4639
+ }, CRT_TOKEN_RATE_FIRST_LOAD_MS);
4640
+ }
4641
+ crtTokenRateRefreshTimer = setInterval(() => {
4642
+ if (document.visibilityState !== 'hidden') void loadCrtTopBarTokenRate();
4643
+ }, CRT_TOKEN_RATE_REFRESH_MS);
4644
+ }
4645
+
4484
4646
  function stopCrtBillingRefresh({ abort = false } = {}) {
4485
4647
  if (billingRefreshTimer !== null) {
4486
4648
  clearInterval(billingRefreshTimer);
@@ -5239,11 +5401,16 @@ async function startCrtAgent(agent, workspaceToUse, asMainAgent) {
5239
5401
  }
5240
5402
  if (JSON.stringify(getWorkspaceHistory()) !== previousHistory) {
5241
5403
  refreshWorkspaceMemoryUI();
5242
- await saveGlobalSettings();
5404
+ syncWorkspaceSettings();
5405
+ await saveGlobalSettings({
5406
+ workspace: globalSettings.workspace,
5407
+ workspaceHistory: globalSettings.workspaceHistory,
5408
+ });
5243
5409
  }
5244
5410
 
5245
5411
  ws.send(JSON.stringify({
5246
5412
  type: 'start-agent',
5413
+ requestId: globalThis.crypto?.randomUUID?.(),
5247
5414
  command: agent.name,
5248
5415
  workspace: workspaceToUse,
5249
5416
  asMain: asMainAgent,
@@ -5485,7 +5652,7 @@ function connect() {
5485
5652
  else scheduleCrtPreviewCardRender(agent);
5486
5653
  }
5487
5654
  } else if (data.type === 'system-stats') {
5488
- updateSystemStats(data.stats, data.uptime, data.usageRate);
5655
+ updateSystemStats(data.stats, data.uptime);
5489
5656
  } else if (data.type === 'error') {
5490
5657
  waitingForAgent = false;
5491
5658
  if (workspaceDirectoryPrompt?.kind === 'creating') {
@@ -5532,6 +5699,7 @@ function connect() {
5532
5699
  function suspendCrtPageConnection() {
5533
5700
  document.body.classList.add('page-hidden');
5534
5701
  stopCrtBillingRefresh({ abort: true });
5702
+ stopCrtTopBarTokenRateRefresh({ abort: true });
5535
5703
  if (wsReconnectTimer) {
5536
5704
  clearTimeout(wsReconnectTimer);
5537
5705
  wsReconnectTimer = null;
@@ -5545,6 +5713,7 @@ function resumeCrtPageConnection() {
5545
5713
  if (document.visibilityState === 'hidden') return;
5546
5714
  document.body.classList.remove('page-hidden');
5547
5715
  connect();
5716
+ startCrtTopBarTokenRateRefresh({ immediate: true });
5548
5717
  if (crtMainView === 'billing') {
5549
5718
  startCrtBillingRefresh();
5550
5719
  void loadCrtBilling();
@@ -5554,15 +5723,33 @@ function resumeCrtPageConnection() {
5554
5723
  function checkMainAgentStatus() {
5555
5724
  if (!state) return;
5556
5725
 
5557
- const mainAgent = state.mainAgentId
5558
- ? state.agents.find(a => a.id === state.mainAgentId)
5559
- : null;
5560
-
5561
- if (!state.mainAgentId || !isCrtLiveAgent(mainAgent)) {
5726
+ const inputDialog = document.getElementById('input-dialog');
5727
+ const action = getCrtMainAgentDialogAction({
5728
+ currentState: state,
5729
+ mainView: crtMainView,
5730
+ dialogActive: Boolean(inputDialog && inputDialog.classList.contains('active')),
5731
+ pendingMainAgent: pendingMainAgentLaunch,
5732
+ });
5733
+ if (action === 'hide') {
5734
+ hideInputDialog();
5735
+ } else if (action === 'show') {
5562
5736
  showInputDialog();
5563
5737
  }
5564
5738
  }
5565
5739
 
5740
+ function getCrtMainAgentDialogAction({
5741
+ currentState,
5742
+ mainView = 'agents',
5743
+ dialogActive = false,
5744
+ pendingMainAgent = false,
5745
+ } = {}) {
5746
+ if (!needsMainAgent(currentState)) {
5747
+ return dialogActive && pendingMainAgent ? 'hide' : 'none';
5748
+ }
5749
+ if (mainView !== 'agents' || dialogActive) return 'none';
5750
+ return 'show';
5751
+ }
5752
+
5566
5753
  function formatSystemClock(timestamp, timeZone) {
5567
5754
  if (!Number.isFinite(timestamp)) return '--';
5568
5755
  try {
@@ -5582,21 +5769,22 @@ function formatSystemClock(timestamp, timeZone) {
5582
5769
  }
5583
5770
 
5584
5771
  function formatCrtTokenRate(value) {
5772
+ if (value === null || value === undefined || value === '') return '--';
5585
5773
  const rate = Number(value);
5586
5774
  if (!Number.isFinite(rate) || rate < 0) return '--';
5587
5775
  const rounded = rate < 10 ? Math.round(rate * 10) / 10 : Math.round(rate);
5588
5776
  if (rounded >= 1_000_000) {
5589
5777
  const compact = rounded / 1_000_000;
5590
- return `~${compact >= 10 ? Math.round(compact) : Math.round(compact * 10) / 10}M`;
5778
+ return `${compact >= 10 ? Math.round(compact) : Math.round(compact * 10) / 10}M`;
5591
5779
  }
5592
5780
  if (rounded >= 1_000) {
5593
5781
  const compact = rounded / 1_000;
5594
- return `~${compact >= 10 ? Math.round(compact) : Math.round(compact * 10) / 10}K`;
5782
+ return `${compact >= 10 ? Math.round(compact) : Math.round(compact * 10) / 10}K`;
5595
5783
  }
5596
- return `~${rounded}`;
5784
+ return String(rounded);
5597
5785
  }
5598
5786
 
5599
- function updateSystemStats(stats, uptime, usageRate) {
5787
+ function updateSystemStats(stats, uptime) {
5600
5788
  if (stats.cpu !== undefined) {
5601
5789
  document.getElementById('cpu-usage').textContent = stats.cpu;
5602
5790
  }
@@ -5613,11 +5801,6 @@ function updateSystemStats(stats, uptime, usageRate) {
5613
5801
  document.getElementById('system-time').textContent = formatSystemClock(stats.timestamp, stats.timeZone);
5614
5802
  }
5615
5803
 
5616
- const tokensPerMinute = document.getElementById('tokens-per-minute');
5617
- if (tokensPerMinute) {
5618
- tokensPerMinute.textContent = formatCrtTokenRate(usageRate && usageRate.estimatedTokensPerMinute);
5619
- }
5620
-
5621
5804
  if (uptime !== undefined) {
5622
5805
  const hours = Math.floor(uptime / 3600);
5623
5806
  const minutes = Math.floor((uptime % 3600) / 60);
@@ -6168,7 +6351,8 @@ function flushStructuredComposerFollowUp(agent) {
6168
6351
  structuredComposerPendingFollowUps.set(agent.id, queue);
6169
6352
  return;
6170
6353
  }
6171
- setTimeout(() => void refreshStructuredSession(agent.id, true), 160);
6354
+ const generation = structuredSessionGeneration;
6355
+ setTimeout(() => void refreshStructuredSession(agent.id, true, generation), 160);
6172
6356
  }
6173
6357
 
6174
6358
  function structuredContextUsage(session) {
@@ -6243,6 +6427,14 @@ function structuredConfigValueLabel(option) {
6243
6427
  return selected && selected.name ? selected.name : String(option.currentValue || '');
6244
6428
  }
6245
6429
 
6430
+ function isCurrentStructuredSession(agentId, generation) {
6431
+ return Boolean(
6432
+ agentId
6433
+ && agentId === focusedAgentId
6434
+ && generation === structuredSessionGeneration
6435
+ );
6436
+ }
6437
+
6246
6438
  function resetStructuredSessionControls() {
6247
6439
  structuredSessionSnapshot = null;
6248
6440
  structuredSessionControlsLoading = false;
@@ -6367,19 +6559,28 @@ function backStructuredComposerMenu() {
6367
6559
  closeStructuredComposerMenu();
6368
6560
  }
6369
6561
 
6370
- async function patchStructuredAcpSession(patch) {
6371
- if (!focusedAgentId) return;
6562
+ async function patchStructuredAcpSession(agentId, generation, patch) {
6563
+ if (!isCurrentStructuredSession(agentId, generation)) return;
6372
6564
  const openerId = structuredComposerMenuOpenerId;
6373
- const response = await fetch(farmingApiPath(`/agents/${encodeURIComponent(focusedAgentId)}/acp-session`), {
6374
- method: 'PATCH',
6375
- headers: { 'Content-Type': 'application/json' },
6376
- body: JSON.stringify(patch)
6377
- });
6378
- const body = await response.json().catch(() => null);
6565
+ let response;
6566
+ let body;
6567
+ try {
6568
+ response = await fetch(farmingApiPath(`/agents/${encodeURIComponent(agentId)}/acp-session`), {
6569
+ method: 'PATCH',
6570
+ headers: { 'Content-Type': 'application/json' },
6571
+ body: JSON.stringify(patch)
6572
+ });
6573
+ body = await response.json().catch(() => null);
6574
+ } catch (error) {
6575
+ if (!isCurrentStructuredSession(agentId, generation)) return;
6576
+ throw error;
6577
+ }
6578
+ if (!isCurrentStructuredSession(agentId, generation)) return;
6379
6579
  if (!response.ok) throw new Error(body && body.error ? body.error : `Failed to update ACP session (${response.status})`);
6380
6580
  structuredComposerMenu = '';
6381
6581
  structuredComposerConfigId = '';
6382
- await refreshStructuredSessionControls(focusedAgentId, true);
6582
+ await refreshStructuredSessionControls(agentId, true, generation);
6583
+ if (!isCurrentStructuredSession(agentId, generation)) return;
6383
6584
  const opener = openerId ? document.getElementById(openerId) : null;
6384
6585
  if (opener && !opener.hidden && !opener.disabled) opener.focus();
6385
6586
  }
@@ -6407,6 +6608,8 @@ function renderStructuredComposerMenu() {
6407
6608
  const retainedFocus = captureStructuredComposerMenuFocus(menu);
6408
6609
  menu.replaceChildren();
6409
6610
  const session = structuredSessionSnapshot;
6611
+ const agentId = focusedAgentId;
6612
+ const generation = structuredSessionGeneration;
6410
6613
  if (!structuredComposerMenu || !session) {
6411
6614
  menu.hidden = true;
6412
6615
  return;
@@ -6448,7 +6651,7 @@ function renderStructuredComposerMenu() {
6448
6651
  const current = session.currentModeId || (session.modes && session.modes.currentModeId) || '';
6449
6652
  modes.forEach((mode) => {
6450
6653
  items.appendChild(structuredMenuButton(mode.name || mode.id, mode.description || '', mode.id === current, () => {
6451
- void patchStructuredAcpSession({ modeId: mode.id }).catch(showStructuredComposerError);
6654
+ void patchStructuredAcpSession(agentId, generation, { modeId: mode.id }).catch(showStructuredComposerError);
6452
6655
  }, `mode:${mode.id}`));
6453
6656
  });
6454
6657
  } else if (!selectedConfig) {
@@ -6468,13 +6671,13 @@ function renderStructuredComposerMenu() {
6468
6671
  } else if (selectedConfig.type === 'boolean') {
6469
6672
  [false, true].forEach((value) => {
6470
6673
  items.appendChild(structuredMenuButton(value ? 'ON' : 'OFF', selectedConfig.description || '', selectedConfig.currentValue === value, () => {
6471
- void patchStructuredAcpSession({ configId: selectedConfig.id, value }).catch(showStructuredComposerError);
6674
+ void patchStructuredAcpSession(agentId, generation, { configId: selectedConfig.id, value }).catch(showStructuredComposerError);
6472
6675
  }, `boolean:${value}`));
6473
6676
  });
6474
6677
  } else {
6475
6678
  structuredSelectOptions(selectedConfig).forEach((candidate) => {
6476
6679
  items.appendChild(structuredMenuButton(candidate.name || candidate.value, candidate.description || '', candidate.value === selectedConfig.currentValue, () => {
6477
- void patchStructuredAcpSession({ configId: selectedConfig.id, value: candidate.value }).catch(showStructuredComposerError);
6680
+ void patchStructuredAcpSession(agentId, generation, { configId: selectedConfig.id, value: candidate.value }).catch(showStructuredComposerError);
6478
6681
  }, `option:${candidate.value}`));
6479
6682
  });
6480
6683
  }
@@ -6509,8 +6712,12 @@ function renderStructuredSessionControls() {
6509
6712
  renderStructuredComposerMenu();
6510
6713
  }
6511
6714
 
6512
- async function refreshStructuredSessionControls(agentId = focusedAgentId, force = false) {
6513
- if (!agentId || structuredSessionControlsLoading) return;
6715
+ async function refreshStructuredSessionControls(
6716
+ agentId = focusedAgentId,
6717
+ force = false,
6718
+ generation = structuredSessionGeneration
6719
+ ) {
6720
+ if (!isCurrentStructuredSession(agentId, generation) || structuredSessionControlsLoading) return;
6514
6721
  const agent = state && state.agents.find((candidate) => candidate.id === agentId);
6515
6722
  if (!agent || agent.runtimeBinding?.kind !== 'acp') {
6516
6723
  resetStructuredSessionControls();
@@ -6525,6 +6732,7 @@ async function refreshStructuredSessionControls(agentId = focusedAgentId, force
6525
6732
  try {
6526
6733
  const response = await fetch(farmingApiPath(`/agents/${encodeURIComponent(agentId)}/acp-session`));
6527
6734
  const body = await response.json().catch(() => null);
6735
+ if (!isCurrentStructuredSession(agentId, generation)) return;
6528
6736
  if (!response.ok || !body || !body.session) {
6529
6737
  throw new Error(body && body.error ? body.error : `Failed to read ACP session (${response.status})`);
6530
6738
  }
@@ -6532,9 +6740,12 @@ async function refreshStructuredSessionControls(agentId = focusedAgentId, force
6532
6740
  structuredSessionControlsRevision = revision || String(body.session.updatedAt || Date.now());
6533
6741
  renderStructuredSessionControls();
6534
6742
  } catch (error) {
6743
+ if (!isCurrentStructuredSession(agentId, generation)) return;
6535
6744
  showStructuredComposerError(error);
6536
6745
  } finally {
6537
- structuredSessionControlsLoading = false;
6746
+ if (isCurrentStructuredSession(agentId, generation)) {
6747
+ structuredSessionControlsLoading = false;
6748
+ }
6538
6749
  }
6539
6750
  }
6540
6751
 
@@ -6606,7 +6817,7 @@ function setStructuredComposerActive(active) {
6606
6817
  }
6607
6818
  }
6608
6819
 
6609
- function updateStructuredComposerState(agent) {
6820
+ function updateStructuredComposerState(agent, { refreshControls = true } = {}) {
6610
6821
  const input = document.getElementById('crt-structured-input');
6611
6822
  const sendButton = document.getElementById('crt-structured-send');
6612
6823
  const statusNode = document.getElementById('crt-structured-composer-status');
@@ -6632,7 +6843,9 @@ function updateStructuredComposerState(agent) {
6632
6843
  statusNode.textContent = error || `${kind} ${String(runtimeStatus || 'idle').toUpperCase()}`;
6633
6844
  statusNode.classList.toggle('error', Boolean(error));
6634
6845
  renderStructuredPermissions(agent);
6635
- if (agent && agent.runtimeBinding?.kind === 'acp') void refreshStructuredSessionControls(agent.id);
6846
+ if (refreshControls && agent && agent.runtimeBinding?.kind === 'acp') {
6847
+ void refreshStructuredSessionControls(agent.id);
6848
+ }
6636
6849
  }
6637
6850
 
6638
6851
  function structuredAttachmentId(file) {
@@ -6791,6 +7004,8 @@ function renderStructuredPermissions(agent) {
6791
7004
  if (!container) return;
6792
7005
  container.replaceChildren();
6793
7006
  if (!agent || agent.runtimeBinding?.kind !== 'acp') return;
7007
+ const agentId = agent.id;
7008
+ const generation = structuredSessionGeneration;
6794
7009
  const runtime = agent.runtimeBinding;
6795
7010
  const requests = Array.isArray(runtime.pendingPermissions) && runtime.pendingPermissions.length
6796
7011
  ? runtime.pendingPermissions
@@ -6809,27 +7024,47 @@ function renderStructuredPermissions(agent) {
6809
7024
  const button = document.createElement('button');
6810
7025
  button.type = 'button';
6811
7026
  button.textContent = `[${option.name}]`;
6812
- button.onclick = () => void respondToStructuredPermission(request.requestId, option.optionId, false);
7027
+ button.onclick = () => void respondToStructuredPermission(
7028
+ agentId,
7029
+ generation,
7030
+ request.requestId,
7031
+ option.optionId,
7032
+ false,
7033
+ );
6813
7034
  actions.appendChild(button);
6814
7035
  });
6815
7036
  const decline = document.createElement('button');
6816
7037
  decline.type = 'button';
6817
7038
  decline.textContent = '[DECLINE]';
6818
- decline.onclick = () => void respondToStructuredPermission(request.requestId, undefined, true);
7039
+ decline.onclick = () => void respondToStructuredPermission(
7040
+ agentId,
7041
+ generation,
7042
+ request.requestId,
7043
+ undefined,
7044
+ true,
7045
+ );
6819
7046
  actions.appendChild(decline);
6820
7047
  panel.append(title, description, actions);
6821
7048
  container.appendChild(panel);
6822
7049
  });
6823
7050
  }
6824
7051
 
6825
- async function respondToStructuredPermission(requestId, optionId, cancelled) {
6826
- if (!focusedAgentId) return;
6827
- const response = await fetch(farmingApiPath(`/agents/${encodeURIComponent(focusedAgentId)}/acp-permission`), {
6828
- method: 'POST',
6829
- headers: { 'Content-Type': 'application/json' },
6830
- body: JSON.stringify({ requestId, optionId, cancelled: cancelled === true })
6831
- });
6832
- const body = await response.json().catch(() => null);
7052
+ async function respondToStructuredPermission(agentId, generation, requestId, optionId, cancelled) {
7053
+ if (!isCurrentStructuredSession(agentId, generation)) return;
7054
+ let response;
7055
+ let body;
7056
+ try {
7057
+ response = await fetch(farmingApiPath(`/agents/${encodeURIComponent(agentId)}/acp-permission`), {
7058
+ method: 'POST',
7059
+ headers: { 'Content-Type': 'application/json' },
7060
+ body: JSON.stringify({ requestId, optionId, cancelled: cancelled === true })
7061
+ });
7062
+ body = await response.json().catch(() => null);
7063
+ } catch (error) {
7064
+ if (isCurrentStructuredSession(agentId, generation)) showStructuredComposerError(error);
7065
+ return;
7066
+ }
7067
+ if (!isCurrentStructuredSession(agentId, generation)) return;
6833
7068
  if (!response.ok) {
6834
7069
  showStructuredComposerError(new Error(body && body.error ? body.error : `Permission response failed (${response.status})`));
6835
7070
  }
@@ -7069,13 +7304,13 @@ async function refreshCrtStructuredPreview(agent) {
7069
7304
  }
7070
7305
  }
7071
7306
 
7072
- function renderStructuredTranscript(transcript, force = false) {
7307
+ function renderStructuredTranscript(transcript, force = false, agentId = focusedAgentId) {
7073
7308
  const container = document.getElementById('terminal-output');
7074
- if (!container) return;
7309
+ if (!container || agentId !== focusedAgentId) return;
7075
7310
  const updatedAt = String(transcript && transcript.updatedAt || '');
7076
7311
  if (!force && updatedAt && updatedAt === structuredSessionRenderedAt) return;
7077
7312
  const nearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 80;
7078
- if (!nearBottom && focusedAgentId) saveCrtStructuredReadingAnchor(focusedAgentId);
7313
+ if (!nearBottom) saveCrtStructuredReadingAnchor(agentId);
7079
7314
  const turns = structuredTranscriptTurns(transcript);
7080
7315
 
7081
7316
  const markdownRenderer = typeof window !== 'undefined' ? window.FarmingCrtMarkdownRenderer : null;
@@ -7099,15 +7334,21 @@ function renderStructuredTranscript(transcript, force = false) {
7099
7334
  return;
7100
7335
  }
7101
7336
  structuredSessionRenderedAt = updatedAt;
7102
- if (!focusedAgentId || !restoreCrtStructuredReadingAnchor(focusedAgentId)) {
7337
+ if (!restoreCrtStructuredReadingAnchor(agentId)) {
7103
7338
  if (force || nearBottom) container.scrollTop = container.scrollHeight;
7104
7339
  }
7105
- container.onscroll = () => saveCrtStructuredReadingAnchor(focusedAgentId);
7340
+ container.onscroll = () => {
7341
+ if (focusedAgentId === agentId) saveCrtStructuredReadingAnchor(agentId);
7342
+ };
7106
7343
  requestAnimationFrame(updateStructuredTranscriptScrollState);
7107
7344
  }
7108
7345
 
7109
- async function refreshStructuredSession(agentId = focusedAgentId, force = false) {
7110
- if (!agentId || structuredSessionLoading) return;
7346
+ async function refreshStructuredSession(
7347
+ agentId = focusedAgentId,
7348
+ force = false,
7349
+ generation = structuredSessionGeneration
7350
+ ) {
7351
+ if (!isCurrentStructuredSession(agentId, generation) || structuredSessionLoading) return;
7111
7352
  const agent = state && state.agents.find((candidate) => candidate.id === agentId);
7112
7353
  if (!agent || !isStructuredRuntimeAgent(agent)) return;
7113
7354
  structuredSessionLoading = true;
@@ -7115,12 +7356,14 @@ async function refreshStructuredSession(agentId = focusedAgentId, force = false)
7115
7356
  const endpoint = structuredTranscriptEndpoint(agent);
7116
7357
  const response = await fetch(farmingApiPath(`/agents/${encodeURIComponent(agentId)}/${endpoint}?maxTurns=80`));
7117
7358
  const body = await response.json().catch(() => null);
7359
+ if (!isCurrentStructuredSession(agentId, generation)) return;
7118
7360
  if (!response.ok || !body || !body.transcript) {
7119
7361
  throw new Error(body && body.error ? body.error : `Failed to read conversation (${response.status})`);
7120
7362
  }
7121
- renderStructuredTranscript(body.transcript, force);
7363
+ renderStructuredTranscript(body.transcript, force, agentId);
7122
7364
  updateStructuredComposerState(agent);
7123
7365
  } catch (error) {
7366
+ if (!isCurrentStructuredSession(agentId, generation)) return;
7124
7367
  const container = document.getElementById('terminal-output');
7125
7368
  if (container && !container.querySelector('.crt-structured-transcript')) {
7126
7369
  const message = document.createElement('div');
@@ -7130,23 +7373,28 @@ async function refreshStructuredSession(agentId = focusedAgentId, force = false)
7130
7373
  }
7131
7374
  updateStructuredComposerState(agent);
7132
7375
  } finally {
7133
- structuredSessionLoading = false;
7376
+ if (isCurrentStructuredSession(agentId, generation)) {
7377
+ structuredSessionLoading = false;
7378
+ }
7134
7379
  }
7135
7380
  }
7136
7381
 
7137
- function stopStructuredSessionPolling() {
7382
+ function stopStructuredSessionPolling(revokeOwnership = true) {
7138
7383
  if (structuredSessionPoller) {
7139
7384
  clearInterval(structuredSessionPoller);
7140
7385
  structuredSessionPoller = null;
7141
7386
  }
7142
7387
  structuredSessionLoading = false;
7143
7388
  structuredSessionRenderedAt = '';
7389
+ if (revokeOwnership) structuredSessionGeneration += 1;
7144
7390
  }
7145
7391
 
7146
- function startStructuredSessionPolling(agentId) {
7147
- stopStructuredSessionPolling();
7392
+ function startStructuredSessionPolling(agentId, generation) {
7393
+ stopStructuredSessionPolling(false);
7148
7394
  structuredSessionPoller = setInterval(() => {
7149
- if (focusedAgentId === agentId) void refreshStructuredSession(agentId);
7395
+ if (isCurrentStructuredSession(agentId, generation)) {
7396
+ void refreshStructuredSession(agentId, false, generation);
7397
+ }
7150
7398
  }, 1000);
7151
7399
  }
7152
7400
 
@@ -7317,9 +7565,11 @@ function setupStructuredSessionComposer() {
7317
7565
  const agent = state && state.agents.find((candidate) => candidate.id === focusedAgentId);
7318
7566
  const action = structuredComposerAction(agent, input.value);
7319
7567
  if (!agent || action === 'disabled') return;
7568
+ const agentId = agent.id;
7569
+ const generation = structuredSessionGeneration;
7320
7570
  if (action === 'interrupt') {
7321
7571
  structuredComposerRestoreFocusAfterInterrupt = true;
7322
- const interrupted = getSessionClient()?.interruptAgent(focusedAgentId);
7572
+ const interrupted = getSessionClient()?.interruptAgent(agentId);
7323
7573
  if (!interrupted) {
7324
7574
  structuredComposerRestoreFocusAfterInterrupt = false;
7325
7575
  showStructuredComposerError(new Error('Connection unavailable'));
@@ -7333,7 +7583,7 @@ function setupStructuredSessionComposer() {
7333
7583
  if (!message && promptAttachments.length === 0) return;
7334
7584
  const statusNode = document.getElementById('crt-structured-composer-status');
7335
7585
  const completeSubmission = () => {
7336
- addStructuredComposerHistory(focusedAgentId, draft);
7586
+ addStructuredComposerHistory(agentId, draft);
7337
7587
  input.value = '';
7338
7588
  structuredComposerAttachments = [];
7339
7589
  renderStructuredComposerAttachments();
@@ -7343,11 +7593,11 @@ function setupStructuredSessionComposer() {
7343
7593
  statusNode.classList.remove('error');
7344
7594
  }
7345
7595
  updateStructuredComposerState(agent);
7346
- setTimeout(() => void refreshStructuredSession(focusedAgentId, true), 160);
7596
+ setTimeout(() => void refreshStructuredSession(agentId, true, generation), 160);
7347
7597
  };
7348
7598
  const sent = action === 'send' && structuredRuntimeStatus(agent) !== 'idle'
7349
- ? (queueStructuredComposerFollowUp(focusedAgentId, message, promptAttachments), true)
7350
- : getSessionClient()?.sendComposerMessage(focusedAgentId, message, promptAttachments);
7599
+ ? (queueStructuredComposerFollowUp(agentId, message, promptAttachments), true)
7600
+ : getSessionClient()?.sendComposerMessage(agentId, message, promptAttachments);
7351
7601
  if (!sent) {
7352
7602
  if (statusNode) {
7353
7603
  statusNode.textContent = 'Connection unavailable';
@@ -7363,14 +7613,17 @@ async function openStructuredSession(agent, sessionToken) {
7363
7613
  const runtime = getSessionRuntime();
7364
7614
  const container = document.getElementById('terminal-output');
7365
7615
  if (!container) return;
7616
+ const generation = ++structuredSessionGeneration;
7366
7617
  container.classList.add('crt-structured-session');
7367
7618
  setStructuredComposerActive(true);
7368
7619
  setupStructuredSessionComposer();
7369
- updateStructuredComposerState(agent);
7370
- await refreshStructuredSessionControls(agent.id, true);
7371
- await refreshStructuredSession(agent.id, true);
7620
+ updateStructuredComposerState(agent, { refreshControls: false });
7621
+ await refreshStructuredSessionControls(agent.id, true, generation);
7622
+ if (!isCurrentStructuredSession(agent.id, generation)) return;
7623
+ await refreshStructuredSession(agent.id, true, generation);
7624
+ if (!isCurrentStructuredSession(agent.id, generation)) return;
7372
7625
  if (runtime && !runtime.isCurrentSession(agent.id, sessionToken)) return;
7373
- startStructuredSessionPolling(agent.id);
7626
+ startStructuredSessionPolling(agent.id, generation);
7374
7627
  const input = document.getElementById('crt-structured-input');
7375
7628
  if (input) {
7376
7629
  resizeStructuredComposerInput(input);
@@ -7952,13 +8205,14 @@ if (typeof document !== 'undefined') {
7952
8205
 
7953
8206
  if (
7954
8207
  crtMainView === 'billing'
8208
+ && billingMode === 'days'
7955
8209
  && !sessionActive
7956
8210
  && !e.ctrlKey
7957
8211
  && !e.metaKey
7958
8212
  && !e.altKey
7959
8213
  && navigationArrow
7960
- && selectCrtBillingDayByArrow(e.key)
7961
8214
  ) {
8215
+ selectCrtBillingDayByArrow(e.key);
7962
8216
  e.preventDefault();
7963
8217
  return;
7964
8218
  }
@@ -8152,13 +8406,13 @@ if (typeof document !== 'undefined') {
8152
8406
  }
8153
8407
 
8154
8408
  if ((e.key === 'd' || e.key === 'D') && crtMainView === 'billing') {
8155
- setCrtBillingMode('days');
8409
+ selectCrtBillingMode('days');
8156
8410
  e.preventDefault();
8157
8411
  return;
8158
8412
  }
8159
8413
 
8160
8414
  if ((e.key === 'l' || e.key === 'L') && crtMainView === 'billing') {
8161
- setCrtBillingMode('live');
8415
+ selectCrtBillingMode('live');
8162
8416
  e.preventDefault();
8163
8417
  return;
8164
8418
  }
@@ -8329,7 +8583,9 @@ if (typeof module !== 'undefined' && module.exports) {
8329
8583
  crtAgentSessionKey,
8330
8584
  crtResumedSessionFromSource,
8331
8585
  crtDashboardStateSignature,
8586
+ crtBillingDayArrowTargetIndex,
8332
8587
  crtBillingTimelineLabels,
8588
+ crtBillingTimeline,
8333
8589
  crtBillingCurrentRate,
8334
8590
  findDefaultNewAgentIndex,
8335
8591
  findDirectionalNavigationIndex,
@@ -8354,6 +8610,7 @@ if (typeof module !== 'undefined' && module.exports) {
8354
8610
  getCrtAgentVerticalPageTarget,
8355
8611
  getCrtLiveAgents,
8356
8612
  getCrtRegularAgents,
8613
+ getCrtMainAgentDialogAction,
8357
8614
  getCrtAgentRemovalFallback,
8358
8615
  getAgentDisplayText,
8359
8616
  getCrtPreviewCellStyle,
@@ -8398,5 +8655,6 @@ if (typeof module !== 'undefined' && module.exports) {
8398
8655
  });
8399
8656
  window.addEventListener('pagehide', suspendCrtPageConnection);
8400
8657
  window.addEventListener('pageshow', resumeCrtPageConnection);
8658
+ startCrtTopBarTokenRateRefresh();
8401
8659
  connect();
8402
8660
  }