farming-code 2.2.8 → 2.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -28
- package/README.zh_cn.md +42 -28
- package/THIRD_PARTY_NOTICES.md +11 -2
- package/backend/acp-checkpoint-store.js +198 -0
- package/backend/acp-runtime.js +306 -83
- package/backend/acp-session-state.js +202 -6
- package/backend/acp-transcript.js +112 -0
- package/backend/agent-activity.js +6 -157
- package/backend/agent-manager.js +1592 -741
- package/backend/agent-provider-session.js +17 -242
- package/backend/agent-runtime-binding.js +219 -0
- package/backend/agent-session-history.js +66 -1
- package/backend/auth.js +79 -6
- package/backend/codex-models.js +81 -84
- package/backend/codex-session-archive.js +54 -0
- package/backend/codex-terminal-profile.js +500 -0
- package/backend/codex-transcript-sanitizer.js +12 -0
- package/backend/codex-transcript.js +230 -8
- package/backend/config-manager.js +35 -0
- package/backend/control-api.js +192 -17
- package/backend/executable-discovery.js +2 -0
- package/backend/farming-net-pass.js +285 -0
- package/backend/farming-net-registry.js +112 -0
- package/backend/farming-net-server.js +298 -0
- package/backend/farming-session-store.js +5 -13
- package/backend/git-worktree-info.js +181 -0
- package/backend/local-session-engine.js +411 -186
- package/backend/main-page-session.js +5 -2
- package/backend/native-pty-controller-generation.js +106 -0
- package/backend/native-pty-host-client.js +275 -7
- package/backend/native-pty-host-identity.js +86 -0
- package/backend/native-pty-host.js +813 -114
- package/backend/native-session-engine.js +100 -28
- package/backend/packaged-node-pty.js +22 -2
- package/backend/provider-adapters.js +253 -0
- package/backend/provider-session-service.js +241 -0
- package/backend/runtime-observation.js +81 -0
- package/backend/server.js +354 -84
- package/backend/session-engine-bridge.js +21 -2
- package/backend/session-engine-router.js +1 -1
- package/backend/session-engine.js +1 -1
- package/backend/session-stream-protocol.js +185 -0
- package/backend/storage-layout.js +55 -0
- package/backend/terminal-attach-checkpoint.js +74 -0
- package/backend/terminal-exit-quiescence.js +39 -0
- package/backend/terminal-reducer-flow-control.js +97 -0
- package/backend/terminal-screen-state.js +11 -2
- package/backend/terminal-screen-worker-pool.js +59 -6
- package/backend/terminal-screen-worker-thread.js +97 -57
- package/backend/terminal-screen-worker.js +133 -51
- package/backend/terminal-state-serialization.js +127 -0
- package/backend/terminal-status.js +23 -4
- package/backend/usage-monitor.js +176 -17
- package/backend/workspace-directory.js +232 -0
- package/backend/workspace-file-router.js +182 -76
- package/backend/workspace-file-service.js +319 -4
- package/backend/workspace-root-registry.js +164 -0
- package/dist/assets/App-DWsa7DXG.js +206 -0
- package/dist/assets/{FileEditorMarkdownPreview-elKWc8Im.js → FileEditorMarkdownPreview-UlVd0O4D.js} +92 -92
- package/dist/assets/FileEditorPane-Dxx4rKOg.js +2 -0
- package/dist/assets/IconGlyphs-AU22gPDY.js +1 -0
- package/dist/assets/ProjectFilesSection-DE7Nb9Jk.js +12 -0
- package/dist/assets/ReviewPage-CeDKDyZh.js +3 -0
- package/dist/assets/code-dark-DPiuyeSp.css +1 -0
- package/dist/assets/file-icons-Bw2qd5iT.js +1 -0
- package/dist/assets/{index-BrbljRqn.js → index-CUFPJoa5.js} +3 -3
- package/dist/assets/main-Bqtb5kuz.css +1 -0
- package/dist/assets/{monaco.contribution-CyLosfZI.js → monaco.contribution-BXz-lfFB.js} +2 -2
- package/dist/assets/{tsMode-DGZTlTj8.js → tsMode-B0oUTcXQ.js} +1 -1
- package/dist/assets/workspace-editor-model-BQol4qbA.js +1 -0
- package/dist/assets/workspace-editor-monaco-BICJESG0.js +4 -0
- package/dist/assets/workspace-editor-monaco-DRJ7IaXk.js +1 -0
- package/dist/assets/workspace-view-state-DvYG_9PH.js +7 -0
- package/dist/assets/workspace-working-copy-D8-s_Sgh.js +1 -0
- package/dist/farming-2/site.webmanifest +2 -0
- package/dist/index.html +1 -1
- package/frontend/farming-net/app.css +625 -0
- package/frontend/farming-net/app.js +268 -0
- package/frontend/farming-net/index.html +86 -0
- package/frontend/reading-anchor.js +198 -0
- package/frontend/session-bridge.js +12 -3
- package/frontend/session-modal-bridge.js +5 -12
- package/frontend/skins/crt/app.js +1978 -793
- package/frontend/skins/crt/index.html +313 -23
- package/frontend/skins/crt/styles/billing.css +294 -223
- package/frontend/skins/crt/styles/monochrome-green.css +7 -2
- package/frontend/terminal-replay.js +372 -0
- package/package.json +10 -3
- package/shared/browser-protocol.d.ts +5 -0
- package/shared/browser-protocol.js +130 -0
- package/dist/assets/App-8dYAM6ql.js +0 -124
- package/dist/assets/FileEditorPane-RWiFD2cq.js +0 -5
- package/dist/assets/IconGlyphs-DfL0EBnj.js +0 -1
- package/dist/assets/ProjectFilesSection-Q4PDsWmM.js +0 -12
- package/dist/assets/ReviewPage-BaXu1ZdX.js +0 -3
- package/dist/assets/code-dark-CDkOQAtK.css +0 -1
- package/dist/assets/file-icons-EFUGSSwf.js +0 -1
- package/dist/assets/main-D073SnW4.css +0 -1
- package/dist/assets/qoder-C9LmmOSf.svg +0 -1
- package/dist/assets/qoder-Cf9gl0Y5.svg +0 -1
- package/dist/assets/qoder-gHCinseV.svg +0 -1
- package/dist/assets/workspace-view-state-CTyDzk2D.js +0 -1
- package/dist/assets/zsh-CLpveKlF.svg +0 -1
- package/dist/assets/zsh-FxSpMPbz.svg +0 -1
- /package/dist/assets/{api-D1lyBYIQ.js → api-D8nyOEbz.js} +0 -0
- /package/dist/assets/{core-ZlAPicox.js → core-D0LFJkDt.js} +0 -0
- /package/dist/assets/{useWorkspaceMenuKeyboard-CneKAZUJ.js → useWorkspaceMenuKeyboard-Brws6Ar9.js} +0 -0
|
@@ -5,6 +5,7 @@ let focusedAgentId = null;
|
|
|
5
5
|
let keyMap = {};
|
|
6
6
|
let agents = [];
|
|
7
7
|
let waitingForAgent = false;
|
|
8
|
+
let workspaceDirectoryPrompt = null;
|
|
8
9
|
let selectedAgentIndex = null;
|
|
9
10
|
let terminal = null;
|
|
10
11
|
let fitAddon = null;
|
|
@@ -53,15 +54,23 @@ let billingError = '';
|
|
|
53
54
|
let billingRequestSequence = 0;
|
|
54
55
|
let billingAbortController = null;
|
|
55
56
|
let billingRefreshTimer = null;
|
|
57
|
+
let billingLiveDayRefreshTimer = null;
|
|
56
58
|
let billingCanvasFrame = null;
|
|
57
59
|
let billingMode = 'days';
|
|
58
60
|
let billingSelectedDate = '';
|
|
61
|
+
let billingSelectedHour = null;
|
|
59
62
|
let billingDailyRenderSignature = '';
|
|
60
63
|
let billingDayDetail = null;
|
|
61
64
|
let billingDayDetailLoading = false;
|
|
62
65
|
let billingDayDetailError = '';
|
|
63
66
|
let billingDayDetailRequestSequence = 0;
|
|
64
67
|
let billingDayDetailAbortController = null;
|
|
68
|
+
let billingDayDetailRetryTimer = null;
|
|
69
|
+
let billingDisplayedTotalDate = '';
|
|
70
|
+
let billingDisplayedTotalValue = null;
|
|
71
|
+
let billingTotalAnimationFrame = null;
|
|
72
|
+
let billingTotalAnimationTarget = null;
|
|
73
|
+
const billingAnimatedMetrics = new Map();
|
|
65
74
|
const billingDayDetailCache = new Map();
|
|
66
75
|
let crtAgentPage = 0;
|
|
67
76
|
let crtAgentPageSize = 1;
|
|
@@ -71,8 +80,9 @@ let dashboardRenderDeferred = false;
|
|
|
71
80
|
let lastCrtDashboardSignature = '';
|
|
72
81
|
let crtPreviewRenderTimer = null;
|
|
73
82
|
const pendingCrtPreviewRenders = new Map();
|
|
83
|
+
const crtStructuredPreviewCache = new Map();
|
|
84
|
+
const crtStructuredPreviewTimers = new Map();
|
|
74
85
|
let sessionRuntime = null;
|
|
75
|
-
let legacySessionPoller = null;
|
|
76
86
|
let structuredSessionPoller = null;
|
|
77
87
|
let structuredSessionLoading = false;
|
|
78
88
|
let structuredSessionRenderedAt = '';
|
|
@@ -92,19 +102,13 @@ let structuredComposerRestoreFocusAfterInterrupt = false;
|
|
|
92
102
|
let runtimeSwitchPending = false;
|
|
93
103
|
let pendingRuntimeSwitchAgentId = '';
|
|
94
104
|
let runtimeSwitchRequestSequence = 0;
|
|
95
|
-
let
|
|
96
|
-
let terminalInputComposing = false;
|
|
97
|
-
let terminalInputLastBackspaceAt = 0;
|
|
98
|
-
let terminalInputLastDeleteAt = 0;
|
|
99
|
-
let terminalInputPendingTexts = [];
|
|
105
|
+
let crtTerminalReplication = null;
|
|
100
106
|
const terminalPreviewSnapshots = new Map();
|
|
101
107
|
const crtBrandPulseTimers = new Map();
|
|
102
|
-
const SESSION_INPUT_SETTINGS = {
|
|
103
|
-
// xterm's native textarea is the same low-latency input path used by Farming Code.
|
|
104
|
-
imeEnabled: false
|
|
105
|
-
};
|
|
106
108
|
const SESSION_LINK_LIMIT = 6;
|
|
109
|
+
const CRT_PROTOCOL_VERSION = 2;
|
|
107
110
|
const CRT_PREVIEW_RENDER_INTERVAL_MS = 1000;
|
|
111
|
+
const CRT_STRUCTURED_PREVIEW_REFRESH_MS = 240;
|
|
108
112
|
const CRT_AGENT_CARD_MIN_WIDTH = 200;
|
|
109
113
|
const CRT_AGENT_CARD_MIN_HEIGHT = 160;
|
|
110
114
|
const CRT_AGENT_GRID_GAP = 15;
|
|
@@ -112,7 +116,15 @@ const CRT_AGENT_GRID_PADDING = 20;
|
|
|
112
116
|
const CRT_SEARCH_DEBOUNCE_MS = 180;
|
|
113
117
|
const CRT_SEARCH_RESULT_LIMIT = 100;
|
|
114
118
|
const CRT_BILLING_REFRESH_MS = 30_000;
|
|
119
|
+
const CRT_BILLING_LIVE_DAY_REFRESH_MS = 5_000;
|
|
115
120
|
const CRT_BILLING_DAY_DETAIL_CACHE_MS = 30_000;
|
|
121
|
+
const CRT_BILLING_TOTAL_ANIMATION_MS = 900;
|
|
122
|
+
const CRT_BILLING_DAY_DETAIL_RETRY_MS = 750;
|
|
123
|
+
const CRT_BILLING_DAY_DETAIL_MAX_RETRIES = 4;
|
|
124
|
+
const CRT_TERMINAL_CHECKPOINT_REQUEST_TIMEOUT_MS = 5_000;
|
|
125
|
+
const CRT_TERMINAL_RESIZE_SETTLE_MS = 250;
|
|
126
|
+
const CRT_TERMINAL_MIN_COLS = 40;
|
|
127
|
+
const CRT_TERMINAL_MIN_ROWS = 10;
|
|
116
128
|
const CRT_AGENT_DISPLAY_NAMES = {
|
|
117
129
|
qwen: 'Qwen Code',
|
|
118
130
|
codex: 'Codex',
|
|
@@ -129,6 +141,7 @@ const CRT_AGENT_DISPLAY_NAMES = {
|
|
|
129
141
|
const CRT_TITLE_STATUS_PREFIX_PATTERN = /^[\s**✳✱✲✶·•◇✋✦⏲\u2800-\u28FF]+/u;
|
|
130
142
|
const CRT_QODER_RUNTIME_TITLE_PATTERN = /^[◇✋✦⏲]/u;
|
|
131
143
|
const RUNTIME_PATHS = typeof window !== 'undefined' ? window.FarmingRuntimePaths : null;
|
|
144
|
+
const TERMINAL_REPLAY = typeof window !== 'undefined' ? window.FarmingTerminalReplay : null;
|
|
132
145
|
|
|
133
146
|
function farmingApiPath(path) {
|
|
134
147
|
return RUNTIME_PATHS ? RUNTIME_PATHS.apiPath(path) : `/api${path.startsWith('/') ? path : `/${path}`}`;
|
|
@@ -362,9 +375,7 @@ function crtHistorySessionDisplayKey(item) {
|
|
|
362
375
|
function buildCrtHistoryItems({ taskHistory = [], agents: agentRecords = [], sessions = [], mainPageSessionKeys = [] } = {}) {
|
|
363
376
|
const liveAgents = agentRecords.filter((agent) => (
|
|
364
377
|
agent.isMain !== true
|
|
365
|
-
&& agent
|
|
366
|
-
&& agent.status !== 'dead'
|
|
367
|
-
&& agent.status !== 'stopped'
|
|
378
|
+
&& isCrtLiveAgent(agent)
|
|
368
379
|
));
|
|
369
380
|
const claimedSessionKeys = new Set(liveAgents.map((agent) => (
|
|
370
381
|
agent.providerSessionKey || (() => {
|
|
@@ -427,9 +438,7 @@ function buildCrtSearchResults({ query = '', agents: agentRecords = [], sessions
|
|
|
427
438
|
agent
|
|
428
439
|
&& agent.id !== mainAgentId
|
|
429
440
|
&& agent.isMain !== true
|
|
430
|
-
&& agent
|
|
431
|
-
&& agent.status !== 'dead'
|
|
432
|
-
&& agent.status !== 'stopped'
|
|
441
|
+
&& isCrtLiveAgent(agent)
|
|
433
442
|
));
|
|
434
443
|
const claimedSessionKeys = new Set(liveAgents.map((agent) => {
|
|
435
444
|
if (agent.providerSessionKey) return agent.providerSessionKey;
|
|
@@ -478,6 +487,8 @@ function buildCrtSearchResults({ query = '', agents: agentRecords = [], sessions
|
|
|
478
487
|
}
|
|
479
488
|
|
|
480
489
|
function getCrtNavigationScope() {
|
|
490
|
+
const sessionModal = document.getElementById('session-modal');
|
|
491
|
+
if (sessionModal && sessionModal.classList.contains('active')) return sessionModal;
|
|
481
492
|
const settingsModal = document.getElementById('settings-modal');
|
|
482
493
|
if (settingsModal && settingsModal.classList.contains('active')) return settingsModal;
|
|
483
494
|
const inputDialog = document.getElementById('input-dialog');
|
|
@@ -669,72 +680,10 @@ function disposeTerminal() {
|
|
|
669
680
|
fitAddon = null;
|
|
670
681
|
}
|
|
671
682
|
|
|
672
|
-
function destroyTerminalInputBridge() {
|
|
673
|
-
if (terminalInputBridge) {
|
|
674
|
-
terminalInputBridge.remove();
|
|
675
|
-
terminalInputBridge = null;
|
|
676
|
-
}
|
|
677
|
-
clearPendingPrintableInput();
|
|
678
|
-
terminalInputComposing = false;
|
|
679
|
-
document.body.removeAttribute('data-ime-input-focused');
|
|
680
|
-
document.body.removeAttribute('data-ime-composing');
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
function clearPendingPrintableInput() {
|
|
684
|
-
terminalInputPendingTexts.forEach((pending) => {
|
|
685
|
-
clearTimeout(pending.timeoutId);
|
|
686
|
-
});
|
|
687
|
-
terminalInputPendingTexts = [];
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function resetTerminalInputBridgeValue() {
|
|
691
|
-
if (!terminalInputBridge) return;
|
|
692
|
-
terminalInputBridge.value = ' ';
|
|
693
|
-
terminalInputBridge.setSelectionRange(1, 1);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
function schedulePrintableInput(text) {
|
|
697
|
-
const pending = {
|
|
698
|
-
text,
|
|
699
|
-
timeoutId: setTimeout(() => {
|
|
700
|
-
if (!terminalInputComposing) {
|
|
701
|
-
sendTerminalInput(text);
|
|
702
|
-
resetTerminalInputBridgeValue();
|
|
703
|
-
}
|
|
704
|
-
terminalInputPendingTexts = terminalInputPendingTexts.filter((item) => item !== pending);
|
|
705
|
-
}, 0)
|
|
706
|
-
};
|
|
707
|
-
terminalInputPendingTexts.push(pending);
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
function focusTerminalInputBridge() {
|
|
711
|
-
if (!SESSION_INPUT_SETTINGS.imeEnabled) return;
|
|
712
|
-
if (!terminalInputBridge) return;
|
|
713
|
-
if (isOverlayBlockingTerminalInput()) return;
|
|
714
|
-
syncTerminalInputBridgePosition();
|
|
715
|
-
terminalInputBridge.focus();
|
|
716
|
-
resetTerminalInputBridgeValue();
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
function isOverlayBlockingTerminalInput() {
|
|
720
|
-
const inputDialog = document.getElementById('input-dialog');
|
|
721
|
-
if (inputDialog && inputDialog.classList.contains('active')) {
|
|
722
|
-
return true;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
const settingsModal = document.getElementById('settings-modal');
|
|
726
|
-
if (settingsModal && settingsModal.classList.contains('active')) {
|
|
727
|
-
return true;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
return false;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
683
|
function focusSessionTerminal() {
|
|
734
684
|
if (terminal && typeof terminal.focus === 'function') {
|
|
735
685
|
terminal.focus();
|
|
736
686
|
}
|
|
737
|
-
focusTerminalInputBridge();
|
|
738
687
|
}
|
|
739
688
|
|
|
740
689
|
function getDocumentSelectionText() {
|
|
@@ -899,11 +848,24 @@ async function pasteTerminalText(text) {
|
|
|
899
848
|
return false;
|
|
900
849
|
}
|
|
901
850
|
|
|
902
|
-
|
|
851
|
+
const normalized = text.replace(/\r\n/g, '\n');
|
|
852
|
+
if (terminal && typeof terminal.paste === 'function') {
|
|
853
|
+
terminal.paste(normalized);
|
|
854
|
+
} else {
|
|
855
|
+
sendTerminalInput(normalized);
|
|
856
|
+
}
|
|
903
857
|
focusSessionTerminal();
|
|
904
858
|
return true;
|
|
905
859
|
}
|
|
906
860
|
|
|
861
|
+
function isCrtNativeTerminalPasteTarget(target) {
|
|
862
|
+
return Boolean(
|
|
863
|
+
target &&
|
|
864
|
+
typeof target.closest === 'function' &&
|
|
865
|
+
target.closest('#terminal-output .xterm')
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
|
|
907
869
|
async function pasteFromClipboard() {
|
|
908
870
|
if (!navigator.clipboard || !navigator.clipboard.readText) {
|
|
909
871
|
return false;
|
|
@@ -918,34 +880,6 @@ async function pasteFromClipboard() {
|
|
|
918
880
|
}
|
|
919
881
|
}
|
|
920
882
|
|
|
921
|
-
function routeSessionKey(event) {
|
|
922
|
-
if (!focusedAgentId || terminalInputComposing || isBrowserShortcut(event)) {
|
|
923
|
-
return false;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
if (event.ctrlKey && !event.metaKey && !event.altKey && event.key.length === 1 && event.key !== 'Enter') {
|
|
927
|
-
const controlChar = getControlChar(event.key);
|
|
928
|
-
if (controlChar) {
|
|
929
|
-
sendTerminalInput(controlChar);
|
|
930
|
-
return true;
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
const sequence = getTerminalSequenceForKey(event);
|
|
935
|
-
if (sequence) {
|
|
936
|
-
sendTerminalInput(sequence);
|
|
937
|
-
return true;
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
return false;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
function getControlChar(key) {
|
|
944
|
-
const lower = key.toLowerCase();
|
|
945
|
-
if (!/^[a-z]$/.test(lower)) return null;
|
|
946
|
-
return String.fromCharCode(lower.charCodeAt(0) - 96);
|
|
947
|
-
}
|
|
948
|
-
|
|
949
883
|
function getSessionClient() {
|
|
950
884
|
if (!window.FarmingSessionBridge || !window.FarmingSessionBridge.createClient) {
|
|
951
885
|
return null;
|
|
@@ -961,291 +895,6 @@ function getSessionClient() {
|
|
|
961
895
|
return sessionClient;
|
|
962
896
|
}
|
|
963
897
|
|
|
964
|
-
function getTerminalSequenceForKey(event) {
|
|
965
|
-
const { key, shiftKey, altKey, ctrlKey, metaKey } = event;
|
|
966
|
-
|
|
967
|
-
if (metaKey) return null;
|
|
968
|
-
|
|
969
|
-
if (altKey && !ctrlKey) {
|
|
970
|
-
if (key === 'ArrowLeft') return '\x1bb';
|
|
971
|
-
if (key === 'ArrowRight') return '\x1bf';
|
|
972
|
-
if (key === 'Backspace') return '\x17';
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
switch (key) {
|
|
976
|
-
case 'Enter':
|
|
977
|
-
return '\r';
|
|
978
|
-
case 'Backspace':
|
|
979
|
-
return '\x7f';
|
|
980
|
-
case 'Tab':
|
|
981
|
-
return shiftKey ? '\x1b[Z' : '\t';
|
|
982
|
-
case 'Delete':
|
|
983
|
-
return '\x1b[3~';
|
|
984
|
-
case 'ArrowUp':
|
|
985
|
-
return '\x1b[A';
|
|
986
|
-
case 'ArrowDown':
|
|
987
|
-
return '\x1b[B';
|
|
988
|
-
case 'ArrowRight':
|
|
989
|
-
return '\x1b[C';
|
|
990
|
-
case 'ArrowLeft':
|
|
991
|
-
return '\x1b[D';
|
|
992
|
-
case 'Home':
|
|
993
|
-
return '\x1b[H';
|
|
994
|
-
case 'End':
|
|
995
|
-
return '\x1b[F';
|
|
996
|
-
case 'PageUp':
|
|
997
|
-
return '\x1b[5~';
|
|
998
|
-
case 'PageDown':
|
|
999
|
-
return '\x1b[6~';
|
|
1000
|
-
default:
|
|
1001
|
-
return null;
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
function setupTerminalInputBridge() {
|
|
1006
|
-
destroyTerminalInputBridge();
|
|
1007
|
-
|
|
1008
|
-
if (!SESSION_INPUT_SETTINGS.imeEnabled) {
|
|
1009
|
-
return;
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
const input = document.createElement('input');
|
|
1013
|
-
input.type = 'text';
|
|
1014
|
-
input.setAttribute('autocomplete', 'off');
|
|
1015
|
-
input.setAttribute('autocorrect', 'off');
|
|
1016
|
-
input.setAttribute('autocapitalize', 'off');
|
|
1017
|
-
input.setAttribute('spellcheck', 'false');
|
|
1018
|
-
input.setAttribute('inputmode', 'text');
|
|
1019
|
-
input.setAttribute('aria-hidden', 'true');
|
|
1020
|
-
input.style.position = 'absolute';
|
|
1021
|
-
input.style.top = '0';
|
|
1022
|
-
input.style.left = '0';
|
|
1023
|
-
input.style.width = '200px';
|
|
1024
|
-
input.style.height = '24px';
|
|
1025
|
-
input.style.opacity = '0.01';
|
|
1026
|
-
input.style.background = 'transparent';
|
|
1027
|
-
input.style.color = 'transparent';
|
|
1028
|
-
input.style.caretColor = 'transparent';
|
|
1029
|
-
input.style.border = 'none';
|
|
1030
|
-
input.style.outline = 'none';
|
|
1031
|
-
input.style.fontSize = `${getCrtTerminalFontSize()}px`;
|
|
1032
|
-
input.style.fontFamily = TERMINAL_FONT_FAMILY;
|
|
1033
|
-
input.style.pointerEvents = 'none';
|
|
1034
|
-
input.style.zIndex = '2';
|
|
1035
|
-
|
|
1036
|
-
input.addEventListener('compositionstart', () => {
|
|
1037
|
-
syncTerminalInputBridgePosition();
|
|
1038
|
-
clearPendingPrintableInput();
|
|
1039
|
-
terminalInputComposing = true;
|
|
1040
|
-
document.body.setAttribute('data-ime-composing', 'true');
|
|
1041
|
-
});
|
|
1042
|
-
|
|
1043
|
-
input.addEventListener('compositionend', (event) => {
|
|
1044
|
-
terminalInputComposing = false;
|
|
1045
|
-
document.body.removeAttribute('data-ime-composing');
|
|
1046
|
-
clearPendingPrintableInput();
|
|
1047
|
-
if (event.data) {
|
|
1048
|
-
sendTerminalInput(event.data);
|
|
1049
|
-
}
|
|
1050
|
-
resetTerminalInputBridgeValue();
|
|
1051
|
-
});
|
|
1052
|
-
|
|
1053
|
-
input.addEventListener('compositionupdate', syncTerminalInputBridgePosition);
|
|
1054
|
-
|
|
1055
|
-
input.addEventListener('beforeinput', (event) => {
|
|
1056
|
-
if (terminalInputComposing) {
|
|
1057
|
-
return;
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
const inputEvent = event;
|
|
1061
|
-
if (inputEvent.inputType === 'insertText' && inputEvent.data) {
|
|
1062
|
-
clearPendingPrintableInput();
|
|
1063
|
-
event.preventDefault();
|
|
1064
|
-
sendTerminalInput(inputEvent.data);
|
|
1065
|
-
resetTerminalInputBridgeValue();
|
|
1066
|
-
}
|
|
1067
|
-
});
|
|
1068
|
-
|
|
1069
|
-
input.addEventListener('input', (event) => {
|
|
1070
|
-
if (terminalInputComposing) {
|
|
1071
|
-
return;
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
const inputEvent = event;
|
|
1075
|
-
if (inputEvent.inputType === 'deleteContentBackward') {
|
|
1076
|
-
clearPendingPrintableInput();
|
|
1077
|
-
const now = Date.now();
|
|
1078
|
-
if (now - terminalInputLastBackspaceAt > 50) {
|
|
1079
|
-
sendTerminalInput('\x7f');
|
|
1080
|
-
}
|
|
1081
|
-
terminalInputLastBackspaceAt = now;
|
|
1082
|
-
requestAnimationFrame(() => {
|
|
1083
|
-
if (document.activeElement === input) {
|
|
1084
|
-
resetTerminalInputBridgeValue();
|
|
1085
|
-
}
|
|
1086
|
-
});
|
|
1087
|
-
return;
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
if (inputEvent.inputType === 'deleteContentForward') {
|
|
1091
|
-
clearPendingPrintableInput();
|
|
1092
|
-
const now = Date.now();
|
|
1093
|
-
if (now - terminalInputLastDeleteAt > 50) {
|
|
1094
|
-
sendTerminalInput('\x1b[3~');
|
|
1095
|
-
}
|
|
1096
|
-
terminalInputLastDeleteAt = now;
|
|
1097
|
-
requestAnimationFrame(() => {
|
|
1098
|
-
if (document.activeElement === input) {
|
|
1099
|
-
resetTerminalInputBridgeValue();
|
|
1100
|
-
}
|
|
1101
|
-
});
|
|
1102
|
-
return;
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
clearPendingPrintableInput();
|
|
1106
|
-
resetTerminalInputBridgeValue();
|
|
1107
|
-
});
|
|
1108
|
-
|
|
1109
|
-
input.addEventListener('keydown', (event) => {
|
|
1110
|
-
if (event.defaultPrevented) {
|
|
1111
|
-
return;
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
if (isOverlayBlockingTerminalInput()) {
|
|
1115
|
-
return;
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
if (isBrowserShortcut(event)) {
|
|
1119
|
-
return;
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
if ((event.ctrlKey || event.metaKey) && event.key === 'Escape') {
|
|
1123
|
-
return;
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
if ((event.ctrlKey || event.metaKey) && (event.key === 'k' || event.key === 'K')) {
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
if (terminalInputComposing) {
|
|
1131
|
-
return;
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
if (['Enter', 'Tab', 'Escape'].includes(event.key)) {
|
|
1135
|
-
event.preventDefault();
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
if (event.ctrlKey && !event.metaKey && !event.altKey && event.key.length === 1 && event.key !== 'Enter') {
|
|
1139
|
-
clearPendingPrintableInput();
|
|
1140
|
-
const controlChar = getControlChar(event.key);
|
|
1141
|
-
if (controlChar) {
|
|
1142
|
-
event.preventDefault();
|
|
1143
|
-
sendTerminalInput(controlChar);
|
|
1144
|
-
resetTerminalInputBridgeValue();
|
|
1145
|
-
}
|
|
1146
|
-
return;
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
if (!event.ctrlKey && !event.metaKey && !event.altKey && event.key.length === 1) {
|
|
1150
|
-
schedulePrintableInput(event.key);
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
if (event.key === 'Backspace') {
|
|
1155
|
-
clearPendingPrintableInput();
|
|
1156
|
-
terminalInputLastBackspaceAt = Date.now();
|
|
1157
|
-
sendTerminalInput('\x7f');
|
|
1158
|
-
requestAnimationFrame(() => {
|
|
1159
|
-
if (document.activeElement === input) {
|
|
1160
|
-
resetTerminalInputBridgeValue();
|
|
1161
|
-
}
|
|
1162
|
-
});
|
|
1163
|
-
return;
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
const sequence = getTerminalSequenceForKey(event);
|
|
1167
|
-
if (!sequence) {
|
|
1168
|
-
return;
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
if (event.key === 'Delete') {
|
|
1172
|
-
terminalInputLastDeleteAt = Date.now();
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
event.preventDefault();
|
|
1176
|
-
sendTerminalInput(sequence);
|
|
1177
|
-
if (event.key !== 'Tab') {
|
|
1178
|
-
resetTerminalInputBridgeValue();
|
|
1179
|
-
}
|
|
1180
|
-
});
|
|
1181
|
-
|
|
1182
|
-
input.addEventListener('focus', () => {
|
|
1183
|
-
document.body.setAttribute('data-ime-input-focused', 'true');
|
|
1184
|
-
requestAnimationFrame(() => {
|
|
1185
|
-
if (document.activeElement === input) {
|
|
1186
|
-
resetTerminalInputBridgeValue();
|
|
1187
|
-
}
|
|
1188
|
-
});
|
|
1189
|
-
});
|
|
1190
|
-
|
|
1191
|
-
input.addEventListener('blur', () => {
|
|
1192
|
-
document.body.removeAttribute('data-ime-input-focused');
|
|
1193
|
-
setTimeout(() => {
|
|
1194
|
-
const sessionActive = document.getElementById('session-modal')?.classList.contains('active');
|
|
1195
|
-
if (!sessionActive || terminalInputComposing || !terminalInputBridge || isOverlayBlockingTerminalInput()) {
|
|
1196
|
-
return;
|
|
1197
|
-
}
|
|
1198
|
-
focusTerminalInputBridge();
|
|
1199
|
-
}, 0);
|
|
1200
|
-
});
|
|
1201
|
-
|
|
1202
|
-
const sessionModal = document.getElementById('session-modal');
|
|
1203
|
-
if (sessionModal) {
|
|
1204
|
-
sessionModal.appendChild(input);
|
|
1205
|
-
} else {
|
|
1206
|
-
document.body.appendChild(input);
|
|
1207
|
-
}
|
|
1208
|
-
terminalInputBridge = input;
|
|
1209
|
-
syncTerminalInputBridgePosition();
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
function calculateTerminalInputBridgePosition(cursor, dimensions, screenRect, parentRect) {
|
|
1213
|
-
if (!cursor || !dimensions || !screenRect || !parentRect) return null;
|
|
1214
|
-
if (dimensions.cols <= 0 || dimensions.rows <= 0) return null;
|
|
1215
|
-
|
|
1216
|
-
const cellWidth = screenRect.width / dimensions.cols;
|
|
1217
|
-
const cellHeight = screenRect.height / dimensions.rows;
|
|
1218
|
-
return {
|
|
1219
|
-
left: Math.max(0, screenRect.left - parentRect.left + cursor.x * cellWidth),
|
|
1220
|
-
top: Math.max(0, screenRect.top - parentRect.top + cursor.y * cellHeight),
|
|
1221
|
-
height: Math.max(getCrtTerminalFontSize() + 2, cellHeight)
|
|
1222
|
-
};
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
function syncTerminalInputBridgePosition() {
|
|
1226
|
-
if (!terminalInputBridge || !terminal) return;
|
|
1227
|
-
const sessionModal = document.getElementById('session-modal');
|
|
1228
|
-
const terminalElement = terminal.element;
|
|
1229
|
-
const screen = terminalElement && terminalElement.querySelector
|
|
1230
|
-
? terminalElement.querySelector('.xterm-screen')
|
|
1231
|
-
: null;
|
|
1232
|
-
const activeBuffer = terminal.buffer && terminal.buffer.active;
|
|
1233
|
-
if (!sessionModal || !(screen instanceof HTMLElement) || !activeBuffer) return;
|
|
1234
|
-
|
|
1235
|
-
const position = calculateTerminalInputBridgePosition(
|
|
1236
|
-
{ x: activeBuffer.cursorX, y: activeBuffer.cursorY },
|
|
1237
|
-
{ cols: terminal.cols, rows: terminal.rows },
|
|
1238
|
-
screen.getBoundingClientRect(),
|
|
1239
|
-
sessionModal.getBoundingClientRect()
|
|
1240
|
-
);
|
|
1241
|
-
if (!position) return;
|
|
1242
|
-
|
|
1243
|
-
terminalInputBridge.style.left = `${position.left}px`;
|
|
1244
|
-
terminalInputBridge.style.top = `${position.top}px`;
|
|
1245
|
-
terminalInputBridge.style.height = `${position.height}px`;
|
|
1246
|
-
terminalInputBridge.style.lineHeight = `${position.height}px`;
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
898
|
function normalizeCrtTerminalFontSize(value) {
|
|
1250
899
|
const fontSize = Number(value);
|
|
1251
900
|
if (!Number.isFinite(fontSize)) return DEFAULT_TERMINAL_FONT_SIZE;
|
|
@@ -1281,24 +930,32 @@ async function createTerminalInstance(options = {}) {
|
|
|
1281
930
|
return null;
|
|
1282
931
|
}
|
|
1283
932
|
|
|
1284
|
-
function
|
|
933
|
+
function showCrtTerminalFailure(titleText, error, detailText) {
|
|
1285
934
|
const terminalContainer = document.getElementById('terminal-output');
|
|
1286
935
|
if (!terminalContainer) return;
|
|
1287
936
|
terminalContainer.querySelector('.crt-webgl-error')?.remove();
|
|
1288
937
|
const panel = document.createElement('div');
|
|
1289
938
|
panel.className = 'crt-webgl-error';
|
|
1290
939
|
const title = document.createElement('strong');
|
|
1291
|
-
title.textContent =
|
|
940
|
+
title.textContent = titleText;
|
|
1292
941
|
const message = document.createElement('span');
|
|
1293
942
|
message.textContent = error && error.message
|
|
1294
943
|
? error.message
|
|
1295
|
-
:
|
|
944
|
+
: String(error || 'Terminal unavailable');
|
|
1296
945
|
const detail = document.createElement('small');
|
|
1297
|
-
detail.textContent =
|
|
946
|
+
detail.textContent = detailText;
|
|
1298
947
|
panel.append(title, message, detail);
|
|
1299
948
|
terminalContainer.appendChild(panel);
|
|
1300
949
|
}
|
|
1301
950
|
|
|
951
|
+
function showCrtWebglFailure(error) {
|
|
952
|
+
showCrtTerminalFailure(
|
|
953
|
+
'CRT WEBGL ERROR',
|
|
954
|
+
error,
|
|
955
|
+
'The Agent is still running. Close and reopen this terminal after WebGL is available.'
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
|
|
1302
959
|
function shouldUseLiveSessionText(agent) {
|
|
1303
960
|
return Boolean(agent && agent.sessionSource === 'live-text');
|
|
1304
961
|
}
|
|
@@ -1353,10 +1010,32 @@ function getCrtPreviewCellStyle(cell) {
|
|
|
1353
1010
|
};
|
|
1354
1011
|
}
|
|
1355
1012
|
|
|
1013
|
+
function getCrtTerminalSnapshotRows(snapshot) {
|
|
1014
|
+
if (!snapshot || !Array.isArray(snapshot.cells)) return [];
|
|
1015
|
+
let lastMeaningfulRow = -1;
|
|
1016
|
+
snapshot.cells.forEach((cells, rowIndex) => {
|
|
1017
|
+
if (!Array.isArray(cells)) return;
|
|
1018
|
+
const meaningful = cells.some((cell) => {
|
|
1019
|
+
if (!cell || cell.width === 0) return false;
|
|
1020
|
+
const attributes = cell.attributes || 0;
|
|
1021
|
+
const hasBackground = Number.isFinite(cell.bg) && cell.bg >= 0;
|
|
1022
|
+
return String(cell.char || '').trim() !== '' || hasBackground || Boolean(attributes & 0x10);
|
|
1023
|
+
});
|
|
1024
|
+
if (meaningful) lastMeaningfulRow = rowIndex;
|
|
1025
|
+
});
|
|
1026
|
+
const cursorRow = snapshot.cursorVisible === true && Number.isInteger(snapshot.cursorY)
|
|
1027
|
+
? Math.min(snapshot.cells.length - 1, Math.max(0, snapshot.cursorY))
|
|
1028
|
+
: -1;
|
|
1029
|
+
const lastVisibleRow = Math.max(lastMeaningfulRow, cursorRow);
|
|
1030
|
+
return lastVisibleRow >= 0 ? snapshot.cells.slice(0, lastVisibleRow + 1) : [];
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1356
1033
|
function renderCrtTerminalSnapshot(container, snapshot) {
|
|
1357
|
-
if (!container
|
|
1034
|
+
if (!container) return false;
|
|
1035
|
+
const rows = getCrtTerminalSnapshotRows(snapshot);
|
|
1036
|
+
if (rows.length === 0) return false;
|
|
1358
1037
|
container.classList.add('terminal-snapshot');
|
|
1359
|
-
|
|
1038
|
+
rows.forEach((cells) => {
|
|
1360
1039
|
const row = document.createElement('div');
|
|
1361
1040
|
row.className = 'terminal-snapshot-row';
|
|
1362
1041
|
let currentSpan = null;
|
|
@@ -1378,6 +1057,104 @@ function renderCrtTerminalSnapshot(container, snapshot) {
|
|
|
1378
1057
|
return true;
|
|
1379
1058
|
}
|
|
1380
1059
|
|
|
1060
|
+
function crtStructuredPreviewStatus(agent, cached) {
|
|
1061
|
+
if (cached && cached.error && !cached.preview) return 'PREVIEW OFFLINE';
|
|
1062
|
+
if ((!cached || !cached.preview) && (!cached || cached.loading)) return 'SYNCING';
|
|
1063
|
+
const status = String(structuredRuntimeStatus(agent) || 'idle').toUpperCase().replaceAll('-', ' ');
|
|
1064
|
+
if (status === 'WAITING FOR PERMISSION') return 'PERMISSION NEEDED';
|
|
1065
|
+
if (status === 'WAITING FOR INPUT') return 'INPUT NEEDED';
|
|
1066
|
+
return status;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function appendCrtStructuredPreviewLine(container, role, label, text) {
|
|
1070
|
+
if (!text) return;
|
|
1071
|
+
const line = document.createElement('div');
|
|
1072
|
+
line.className = `agent-chat-preview-line ${role}`;
|
|
1073
|
+
line.dataset.previewRole = role;
|
|
1074
|
+
const roleLabel = document.createElement('span');
|
|
1075
|
+
roleLabel.className = 'agent-chat-preview-role';
|
|
1076
|
+
roleLabel.textContent = label;
|
|
1077
|
+
const content = document.createElement('span');
|
|
1078
|
+
content.className = 'agent-chat-preview-text';
|
|
1079
|
+
content.textContent = text;
|
|
1080
|
+
line.append(roleLabel, content);
|
|
1081
|
+
container.appendChild(line);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function renderCrtStructuredPreview(output, agent) {
|
|
1085
|
+
output.classList.add('structured-preview');
|
|
1086
|
+
const cached = crtStructuredPreviewCache.get(agent.id) || null;
|
|
1087
|
+
const preview = cached && cached.preview;
|
|
1088
|
+
const runtimeStatus = String(structuredRuntimeStatus(agent) || '').toLowerCase();
|
|
1089
|
+
const active = ['connecting', 'working', 'waiting-for-permission', 'waiting-for-input', 'interrupting']
|
|
1090
|
+
.includes(runtimeStatus);
|
|
1091
|
+
const panel = document.createElement('div');
|
|
1092
|
+
panel.className = `agent-chat-preview${active ? ' active' : ''}`;
|
|
1093
|
+
panel.dataset.previewKind = 'chat';
|
|
1094
|
+
|
|
1095
|
+
const meta = document.createElement('div');
|
|
1096
|
+
meta.className = 'agent-chat-preview-meta';
|
|
1097
|
+
const channel = document.createElement('span');
|
|
1098
|
+
channel.textContent = `CHAT / ${structuredRuntimeKind(agent)}`;
|
|
1099
|
+
const stateLabel = document.createElement('span');
|
|
1100
|
+
stateLabel.className = 'agent-chat-preview-state';
|
|
1101
|
+
const signal = document.createElement('i');
|
|
1102
|
+
signal.setAttribute('aria-hidden', 'true');
|
|
1103
|
+
const stateText = document.createElement('span');
|
|
1104
|
+
stateText.textContent = crtStructuredPreviewStatus(agent, cached);
|
|
1105
|
+
stateLabel.append(signal, stateText);
|
|
1106
|
+
meta.append(channel, stateLabel);
|
|
1107
|
+
panel.appendChild(meta);
|
|
1108
|
+
|
|
1109
|
+
const trail = document.createElement('div');
|
|
1110
|
+
trail.className = 'agent-chat-preview-trail';
|
|
1111
|
+
panel.appendChild(trail);
|
|
1112
|
+
|
|
1113
|
+
if (preview) {
|
|
1114
|
+
const messageLines = Array.isArray(preview.messageLines) && preview.messageLines.length > 0
|
|
1115
|
+
? preview.messageLines
|
|
1116
|
+
: [
|
|
1117
|
+
{ role: 'user', label: 'YOU', text: preview.userText },
|
|
1118
|
+
{ role: 'assistant', label: 'AGENT', text: preview.assistantText },
|
|
1119
|
+
];
|
|
1120
|
+
messageLines.forEach((line) => {
|
|
1121
|
+
appendCrtStructuredPreviewLine(trail, line.role, line.label, line.text);
|
|
1122
|
+
});
|
|
1123
|
+
if (active && preview.activityText && preview.activityText !== preview.assistantText) {
|
|
1124
|
+
appendCrtStructuredPreviewLine(trail, 'activity', 'NOW', preview.activityText);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
if (!trail.querySelector('.agent-chat-preview-line')) {
|
|
1129
|
+
const empty = document.createElement('div');
|
|
1130
|
+
empty.className = 'agent-chat-preview-empty';
|
|
1131
|
+
empty.textContent = cached && cached.error
|
|
1132
|
+
? 'Conversation preview unavailable'
|
|
1133
|
+
: active ? 'Establishing conversation stream…' : 'Ready for the first message';
|
|
1134
|
+
trail.appendChild(empty);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
output.replaceChildren(panel);
|
|
1138
|
+
scheduleCrtStructuredPreviewRefresh(agent);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
function renderCrtAgentOutput(output, agent, { main = false } = {}) {
|
|
1142
|
+
output.classList.remove('structured-preview');
|
|
1143
|
+
if (isStructuredRuntimeAgent(agent)) {
|
|
1144
|
+
renderCrtStructuredPreview(output, agent);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
const outputTail = document.createElement('div');
|
|
1148
|
+
outputTail.className = 'agent-output-tail';
|
|
1149
|
+
const cleanOutput = getAgentDisplayText(agent);
|
|
1150
|
+
if (!renderCrtTerminalSnapshot(outputTail, agent.previewSnapshot)) {
|
|
1151
|
+
outputTail.textContent = main
|
|
1152
|
+
? cleanOutput.slice(-150) || 'No output yet...'
|
|
1153
|
+
: cleanOutput || 'No output yet...';
|
|
1154
|
+
}
|
|
1155
|
+
output.replaceChildren(outputTail);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1381
1158
|
function crtDashboardStateSignature(value) {
|
|
1382
1159
|
if (!value || !Array.isArray(value.agents)) return '';
|
|
1383
1160
|
return JSON.stringify([
|
|
@@ -1417,8 +1194,17 @@ function renderCrtDashboardIfNeeded(force = false) {
|
|
|
1417
1194
|
return true;
|
|
1418
1195
|
}
|
|
1419
1196
|
|
|
1197
|
+
function scheduleCrtRenderedStructuredPreviews() {
|
|
1198
|
+
if (typeof document === 'undefined' || !state) return;
|
|
1199
|
+
document.querySelectorAll('#map-area .agent-block[data-agent-id], #main-agent-block[data-agent-id]')
|
|
1200
|
+
.forEach((block) => {
|
|
1201
|
+
const agent = state.agents.find((candidate) => candidate.id === block.dataset.agentId);
|
|
1202
|
+
if (agent) scheduleCrtStructuredPreviewRefresh(agent);
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1420
1206
|
function updateCrtAgentPreviewCard(agent) {
|
|
1421
|
-
if (typeof document === 'undefined' || !agent) return false;
|
|
1207
|
+
if (typeof document === 'undefined' || !isCrtLiveAgent(agent)) return false;
|
|
1422
1208
|
const block = Array.from(document.querySelectorAll('[data-agent-id]'))
|
|
1423
1209
|
.find((candidate) => candidate.dataset.agentId === agent.id);
|
|
1424
1210
|
if (!block) return false;
|
|
@@ -1441,15 +1227,7 @@ function updateCrtAgentPreviewCard(agent) {
|
|
|
1441
1227
|
status.textContent = [agent.status, agent.activityLevel, projectName].filter(Boolean).join(' | ');
|
|
1442
1228
|
}
|
|
1443
1229
|
|
|
1444
|
-
|
|
1445
|
-
outputTail.className = 'agent-output-tail';
|
|
1446
|
-
const cleanOutput = getAgentDisplayText(agent);
|
|
1447
|
-
if (!renderCrtTerminalSnapshot(outputTail, agent.previewSnapshot)) {
|
|
1448
|
-
outputTail.textContent = isMain
|
|
1449
|
-
? cleanOutput.slice(-150) || 'No output yet...'
|
|
1450
|
-
: cleanOutput || 'No output yet...';
|
|
1451
|
-
}
|
|
1452
|
-
output.replaceChildren(outputTail);
|
|
1230
|
+
renderCrtAgentOutput(output, agent, { main: isMain });
|
|
1453
1231
|
lastCrtDashboardSignature = crtDashboardStateSignature(state);
|
|
1454
1232
|
return true;
|
|
1455
1233
|
}
|
|
@@ -1465,8 +1243,10 @@ function flushCrtPreviewCardRenders() {
|
|
|
1465
1243
|
}
|
|
1466
1244
|
|
|
1467
1245
|
pending.forEach(({ agent, previousSnapshot, previousText, previewChanged }) => {
|
|
1468
|
-
|
|
1469
|
-
|
|
1246
|
+
const currentAgent = state && state.agents.find((candidate) => candidate.id === agent.id);
|
|
1247
|
+
if (!isCrtLiveAgent(currentAgent)) return;
|
|
1248
|
+
if (!updateCrtAgentPreviewCard(currentAgent)) {
|
|
1249
|
+
if (currentAgent.id !== state.mainAgentId && !isCrtAgentOnCurrentPage(currentAgent.id)) {
|
|
1470
1250
|
if (previewChanged) pulseCrtBrandForAgent(agent.id);
|
|
1471
1251
|
return;
|
|
1472
1252
|
}
|
|
@@ -1481,6 +1261,7 @@ function flushCrtPreviewCardRenders() {
|
|
|
1481
1261
|
}
|
|
1482
1262
|
|
|
1483
1263
|
function scheduleCrtPreviewCardRender(agent, previousSnapshot, previousText, previewChanged) {
|
|
1264
|
+
if (!isCrtLiveAgent(agent)) return;
|
|
1484
1265
|
const existing = pendingCrtPreviewRenders.get(agent.id);
|
|
1485
1266
|
pendingCrtPreviewRenders.set(agent.id, {
|
|
1486
1267
|
agent,
|
|
@@ -1617,10 +1398,10 @@ function isCrtAgentWorking(agent) {
|
|
|
1617
1398
|
if (activity === 'busy') return true;
|
|
1618
1399
|
if (activity === 'idle' || activity === 'exited') return false;
|
|
1619
1400
|
if (agent.terminalStatus && agent.terminalStatus.busy === true) return true;
|
|
1401
|
+
const runtime = agent.runtimeBinding || { kind: 'terminal' };
|
|
1620
1402
|
if (
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
['working', 'waiting-for-input', 'interrupting'].includes(agent.codexAppServerState || '')
|
|
1403
|
+
runtime.kind === 'app-server' &&
|
|
1404
|
+
['working', 'waiting-for-input', 'interrupting'].includes(runtime.state || '')
|
|
1624
1405
|
) {
|
|
1625
1406
|
return true;
|
|
1626
1407
|
}
|
|
@@ -1850,6 +1631,150 @@ function buildTerminalLineProjection(line) {
|
|
|
1850
1631
|
return { text, offsetToCell };
|
|
1851
1632
|
}
|
|
1852
1633
|
|
|
1634
|
+
const CRT_READING_ANCHOR_LINE_COUNT = 3;
|
|
1635
|
+
|
|
1636
|
+
function crtReadingAnchorApi() {
|
|
1637
|
+
return window.FarmingReadingAnchors || null;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
function crtTerminalVisibleBufferBase(currentTerminal) {
|
|
1641
|
+
if (!currentTerminal) return 0;
|
|
1642
|
+
if (typeof currentTerminal.getVisibleBufferBase === 'function') {
|
|
1643
|
+
return Math.max(0, Number(currentTerminal.getVisibleBufferBase()) || 0);
|
|
1644
|
+
}
|
|
1645
|
+
return Math.max(0, Number(currentTerminal.buffer && currentTerminal.buffer.active && currentTerminal.buffer.active.viewportY) || 0);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
function crtLogicalTerminalLineAtRow(currentTerminal, row) {
|
|
1649
|
+
const buffer = currentTerminal && currentTerminal.buffer && currentTerminal.buffer.active;
|
|
1650
|
+
if (!buffer || typeof buffer.getLine !== 'function' || !Number.isFinite(row) || row < 0) return null;
|
|
1651
|
+
let startRow = row;
|
|
1652
|
+
while (startRow > 0 && buffer.getLine(startRow)?.isWrapped) startRow -= 1;
|
|
1653
|
+
let endRow = row;
|
|
1654
|
+
while (buffer.getLine(endRow + 1)?.isWrapped) endRow += 1;
|
|
1655
|
+
const lines = [];
|
|
1656
|
+
for (let index = startRow; index <= endRow; index += 1) {
|
|
1657
|
+
lines.push(buildTerminalLineProjection(buffer.getLine(index)).text.trimEnd());
|
|
1658
|
+
}
|
|
1659
|
+
return { startRow, endRow, text: lines.join('') };
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
function saveCrtTerminalReadingAnchor(agentId, currentTerminal = terminal) {
|
|
1663
|
+
const api = crtReadingAnchorApi();
|
|
1664
|
+
if (!api || !agentId || !currentTerminal) return;
|
|
1665
|
+
const key = api.agentKey(agentId, 'terminal');
|
|
1666
|
+
const buffer = currentTerminal.buffer && currentTerminal.buffer.active;
|
|
1667
|
+
const base = crtTerminalVisibleBufferBase(currentTerminal);
|
|
1668
|
+
const maxRow = Math.max(0, Number(buffer && buffer.length) || 0);
|
|
1669
|
+
if (!buffer || base >= maxRow - Math.max(1, Number(currentTerminal.rows) || 1)) {
|
|
1670
|
+
api.remove(key);
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
const firstLine = crtLogicalTerminalLineAtRow(currentTerminal, base);
|
|
1674
|
+
if (!firstLine) return;
|
|
1675
|
+
const lines = [];
|
|
1676
|
+
let row = firstLine.startRow;
|
|
1677
|
+
for (let index = 0; index < CRT_READING_ANCHOR_LINE_COUNT; index += 1) {
|
|
1678
|
+
const line = crtLogicalTerminalLineAtRow(currentTerminal, row);
|
|
1679
|
+
if (!line) break;
|
|
1680
|
+
lines.push(line.text);
|
|
1681
|
+
row = line.endRow + 1;
|
|
1682
|
+
}
|
|
1683
|
+
if (!lines.length) return;
|
|
1684
|
+
api.save({
|
|
1685
|
+
version: 1,
|
|
1686
|
+
surface: 'terminal',
|
|
1687
|
+
resource: { kind: 'agent', id: agentId },
|
|
1688
|
+
locator: { kind: 'terminal-lines', id: api.fingerprint(lines), lineCount: lines.length },
|
|
1689
|
+
position: { unit: 'row', value: Math.max(0, base - firstLine.startRow) },
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
function restoreCrtTerminalReadingAnchor(agentId, currentTerminal = terminal) {
|
|
1694
|
+
const api = crtReadingAnchorApi();
|
|
1695
|
+
if (!api || !agentId || !currentTerminal) return false;
|
|
1696
|
+
const key = api.agentKey(agentId, 'terminal');
|
|
1697
|
+
const anchor = api.read(key);
|
|
1698
|
+
if (!anchor || anchor.surface !== 'terminal') return false;
|
|
1699
|
+
const buffer = currentTerminal.buffer && currentTerminal.buffer.active;
|
|
1700
|
+
const lineCount = Math.max(1, Number(anchor.locator && anchor.locator.lineCount) || 1);
|
|
1701
|
+
const lastRow = Math.max(0, Number(buffer && buffer.length) || 0);
|
|
1702
|
+
let closest = null;
|
|
1703
|
+
let closestDistance = Number.POSITIVE_INFINITY;
|
|
1704
|
+
for (let row = 0; row <= lastRow;) {
|
|
1705
|
+
const firstLine = crtLogicalTerminalLineAtRow(currentTerminal, row);
|
|
1706
|
+
if (!firstLine) {
|
|
1707
|
+
row += 1;
|
|
1708
|
+
continue;
|
|
1709
|
+
}
|
|
1710
|
+
const lines = [firstLine.text];
|
|
1711
|
+
let nextRow = firstLine.endRow + 1;
|
|
1712
|
+
for (let index = 1; index < lineCount; index += 1) {
|
|
1713
|
+
const line = crtLogicalTerminalLineAtRow(currentTerminal, nextRow);
|
|
1714
|
+
if (!line) break;
|
|
1715
|
+
lines.push(line.text);
|
|
1716
|
+
nextRow = line.endRow + 1;
|
|
1717
|
+
}
|
|
1718
|
+
if (lines.length === lineCount && api.fingerprint(lines) === anchor.locator.id) {
|
|
1719
|
+
const distance = Math.abs(firstLine.startRow - crtTerminalVisibleBufferBase(currentTerminal));
|
|
1720
|
+
if (distance < closestDistance) {
|
|
1721
|
+
closest = firstLine;
|
|
1722
|
+
closestDistance = distance;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
row = Math.max(row + 1, firstLine.endRow + 1);
|
|
1726
|
+
}
|
|
1727
|
+
if (!closest || typeof currentTerminal.scrollToLine !== 'function') {
|
|
1728
|
+
api.remove(key);
|
|
1729
|
+
currentTerminal.scrollToBottom?.();
|
|
1730
|
+
return false;
|
|
1731
|
+
}
|
|
1732
|
+
currentTerminal.scrollToLine(Math.min(closest.endRow, closest.startRow + Math.max(0, Number(anchor.position.value) || 0)));
|
|
1733
|
+
return true;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
function saveCrtStructuredReadingAnchor(agentId) {
|
|
1737
|
+
const api = crtReadingAnchorApi();
|
|
1738
|
+
const container = document.getElementById('terminal-output');
|
|
1739
|
+
if (!api || !agentId || !container) return;
|
|
1740
|
+
const key = api.agentKey(agentId, 'chat');
|
|
1741
|
+
if (container.scrollHeight - container.scrollTop - container.clientHeight < 80) {
|
|
1742
|
+
api.remove(key);
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
const containerRect = container.getBoundingClientRect();
|
|
1746
|
+
const turn = Array.from(container.querySelectorAll('[data-reading-anchor-id]'))
|
|
1747
|
+
.find((candidate) => candidate.getBoundingClientRect().bottom > containerRect.top);
|
|
1748
|
+
if (!turn) return;
|
|
1749
|
+
const turnRect = turn.getBoundingClientRect();
|
|
1750
|
+
api.save({
|
|
1751
|
+
version: 1,
|
|
1752
|
+
surface: 'chat',
|
|
1753
|
+
resource: { kind: 'agent', id: agentId },
|
|
1754
|
+
locator: { kind: 'message', id: turn.dataset.readingAnchorId },
|
|
1755
|
+
position: { unit: 'fraction', value: Math.max(0, Math.min(1, (containerRect.top - turnRect.top) / Math.max(1, turnRect.height))) },
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
function restoreCrtStructuredReadingAnchor(agentId) {
|
|
1760
|
+
const api = crtReadingAnchorApi();
|
|
1761
|
+
const container = document.getElementById('terminal-output');
|
|
1762
|
+
if (!api || !agentId || !container) return false;
|
|
1763
|
+
const key = api.agentKey(agentId, 'chat');
|
|
1764
|
+
const anchor = api.read(key);
|
|
1765
|
+
if (!anchor || anchor.surface !== 'chat') return false;
|
|
1766
|
+
const target = container.querySelector(`[data-reading-anchor-id="${CSS.escape(anchor.locator.id)}"]`);
|
|
1767
|
+
if (!target) {
|
|
1768
|
+
api.remove(key);
|
|
1769
|
+
container.scrollTop = container.scrollHeight;
|
|
1770
|
+
return false;
|
|
1771
|
+
}
|
|
1772
|
+
const containerRect = container.getBoundingClientRect();
|
|
1773
|
+
const targetRect = target.getBoundingClientRect();
|
|
1774
|
+
container.scrollTop += targetRect.top + targetRect.height * anchor.position.value - containerRect.top;
|
|
1775
|
+
return true;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1853
1778
|
function collectWrappedLinkContext(buffer, row) {
|
|
1854
1779
|
if (!buffer || typeof buffer.getLine !== 'function') {
|
|
1855
1780
|
return null;
|
|
@@ -2267,7 +2192,9 @@ function normalizeSessionViewPayload(payload, fallbackAgent = null) {
|
|
|
2267
2192
|
sessionSource: session.sessionSource || (fallbackAgent && fallbackAgent.sessionSource) || 'buffer',
|
|
2268
2193
|
output: typeof session.output === 'string' ? session.output : ((fallbackAgent && fallbackAgent.output) || ''),
|
|
2269
2194
|
renderOutput: typeof session.renderOutput === 'string' ? session.renderOutput : '',
|
|
2195
|
+
runtimeEpoch: typeof session.runtimeEpoch === 'string' ? session.runtimeEpoch : '',
|
|
2270
2196
|
outputSeq: Number.isFinite(session.outputSeq) ? session.outputSeq : null,
|
|
2197
|
+
stateRevision: Number.isFinite(session.stateRevision) ? session.stateRevision : null,
|
|
2271
2198
|
previewCols: Number.isFinite(session.previewCols) ? session.previewCols : null,
|
|
2272
2199
|
previewRows: Number.isFinite(session.previewRows) ? session.previewRows : null,
|
|
2273
2200
|
previewText: typeof session.previewText === 'string' ? session.previewText : ((fallbackAgent && fallbackAgent.previewText) || ''),
|
|
@@ -2282,25 +2209,440 @@ function normalizeSessionViewPayload(payload, fallbackAgent = null) {
|
|
|
2282
2209
|
};
|
|
2283
2210
|
}
|
|
2284
2211
|
|
|
2285
|
-
function
|
|
2286
|
-
|
|
2287
|
-
|
|
2212
|
+
function createCrtTerminalReplication(agentId) {
|
|
2213
|
+
return {
|
|
2214
|
+
agentId,
|
|
2215
|
+
lastResizeCols: null,
|
|
2216
|
+
lastResizeRows: null,
|
|
2217
|
+
pendingFitResize: null,
|
|
2218
|
+
fitResizeTimer: null,
|
|
2219
|
+
applyingLocalResize: false,
|
|
2220
|
+
replayState: TERMINAL_REPLAY.createState(),
|
|
2221
|
+
checkpointInFlight: false,
|
|
2222
|
+
checkpointSeq: 0,
|
|
2223
|
+
checkpointAbortController: null,
|
|
2224
|
+
checkpointRetryTimer: null,
|
|
2225
|
+
installSeq: 0,
|
|
2226
|
+
installInProgress: false,
|
|
2227
|
+
pendingCheckpoint: null,
|
|
2228
|
+
writeInProgress: false,
|
|
2229
|
+
disposed: false
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
function installCrtTerminalTestApi() {
|
|
2234
|
+
if (
|
|
2235
|
+
typeof window === 'undefined' ||
|
|
2236
|
+
!window.__FARMING_E2E__ ||
|
|
2237
|
+
window.__farmingCrtTerminalTest
|
|
2238
|
+
) return;
|
|
2239
|
+
window.__farmingCrtTerminalTest = {
|
|
2240
|
+
getState() {
|
|
2241
|
+
const replication = crtTerminalReplication;
|
|
2242
|
+
if (!replication || replication.disposed) return null;
|
|
2243
|
+
return {
|
|
2244
|
+
runtimeEpoch: replication.replayState.runtimeEpoch,
|
|
2245
|
+
outputSeq: replication.replayState.outputSeq,
|
|
2246
|
+
stateRevision: replication.replayState.stateRevision,
|
|
2247
|
+
cols: terminal?.cols || 0,
|
|
2248
|
+
rows: terminal?.rows || 0,
|
|
2249
|
+
replaying: replication.replayState.recovering,
|
|
2250
|
+
checkpointHalted: replication.replayState.halted,
|
|
2251
|
+
writeInProgress: replication.writeInProgress,
|
|
2252
|
+
checkpointInFlight: replication.checkpointInFlight,
|
|
2253
|
+
checkpointInstallInProgress: replication.installInProgress,
|
|
2254
|
+
pendingFitResize: replication.pendingFitResize,
|
|
2255
|
+
fitResizeTimerPending: replication.fitResizeTimer !== null
|
|
2256
|
+
};
|
|
2257
|
+
},
|
|
2258
|
+
getRows() {
|
|
2259
|
+
const buffer = terminal && terminal.buffer && terminal.buffer.active;
|
|
2260
|
+
if (!buffer || typeof buffer.getLine !== 'function') return [];
|
|
2261
|
+
const rows = [];
|
|
2262
|
+
for (let index = 0; index < buffer.length; index += 1) {
|
|
2263
|
+
rows.push(buffer.getLine(index)?.translateToString(true) || '');
|
|
2264
|
+
}
|
|
2265
|
+
return rows;
|
|
2266
|
+
},
|
|
2267
|
+
notifyResizeForTest(cols, rows) {
|
|
2268
|
+
sendSessionResize(focusedAgentId, { cols, rows });
|
|
2269
|
+
},
|
|
2270
|
+
streamSequenced(data, outputSeq, runtimeEpoch, stateRevision) {
|
|
2271
|
+
const replication = crtTerminalReplication;
|
|
2272
|
+
if (!replication || replication.disposed) return false;
|
|
2273
|
+
handleCrtTerminalStream({
|
|
2274
|
+
agentId: replication.agentId,
|
|
2275
|
+
kind: 'output',
|
|
2276
|
+
data: String(data || ''),
|
|
2277
|
+
outputSeq,
|
|
2278
|
+
runtimeEpoch,
|
|
2279
|
+
stateRevision
|
|
2280
|
+
});
|
|
2281
|
+
return true;
|
|
2282
|
+
},
|
|
2283
|
+
replaceStream(stream) {
|
|
2284
|
+
const replication = crtTerminalReplication;
|
|
2285
|
+
if (!replication || replication.disposed || !stream) return false;
|
|
2286
|
+
handleCrtTerminalStream({
|
|
2287
|
+
...stream,
|
|
2288
|
+
agentId: replication.agentId,
|
|
2289
|
+
replace: true
|
|
2290
|
+
});
|
|
2291
|
+
return true;
|
|
2292
|
+
}
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
function disposeCrtTerminalReplication() {
|
|
2297
|
+
const replication = crtTerminalReplication;
|
|
2298
|
+
if (!replication) return;
|
|
2299
|
+
replication.disposed = true;
|
|
2300
|
+
if (replication.fitResizeTimer) clearTimeout(replication.fitResizeTimer);
|
|
2301
|
+
replication.fitResizeTimer = null;
|
|
2302
|
+
replication.pendingFitResize = null;
|
|
2303
|
+
if (replication.checkpointRetryTimer) clearTimeout(replication.checkpointRetryTimer);
|
|
2304
|
+
replication.checkpointRetryTimer = null;
|
|
2305
|
+
replication.checkpointAbortController?.abort();
|
|
2306
|
+
replication.checkpointAbortController = null;
|
|
2307
|
+
document.getElementById('terminal-output')?.classList.remove('crt-terminal-checkpoint-installing');
|
|
2308
|
+
crtTerminalReplication = null;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
function queueCrtTerminalInput(input) {
|
|
2312
|
+
const replication = crtTerminalReplication;
|
|
2313
|
+
if (!replication || replication.disposed) return false;
|
|
2314
|
+
const text = String(input || '');
|
|
2315
|
+
if (!text) return false;
|
|
2316
|
+
return Boolean(getSessionClient()?.sendTerminalInput(replication.agentId, text));
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
function requestCrtTerminalReplay() {
|
|
2320
|
+
const replication = crtTerminalReplication;
|
|
2321
|
+
if (
|
|
2322
|
+
!replication ||
|
|
2323
|
+
replication.disposed ||
|
|
2324
|
+
replication.replayState.halted ||
|
|
2325
|
+
replication.checkpointRetryTimer ||
|
|
2326
|
+
replication.checkpointInFlight ||
|
|
2327
|
+
replication.installInProgress
|
|
2328
|
+
) return;
|
|
2329
|
+
TERMINAL_REPLAY.beginRecovery(replication.replayState);
|
|
2330
|
+
void refreshSessionView(true, replication.agentId, getCurrentSessionToken());
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
function queueCrtTerminalTransition(event) {
|
|
2334
|
+
const replication = crtTerminalReplication;
|
|
2335
|
+
if (!replication || replication.disposed) return;
|
|
2336
|
+
const result = TERMINAL_REPLAY.queueTransition(replication.replayState, event);
|
|
2337
|
+
if (!result.queued) {
|
|
2338
|
+
requestCrtTerminalReplay();
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
function scheduleCrtTerminalCheckpointRetry(replication, delay) {
|
|
2343
|
+
if (
|
|
2344
|
+
!replication ||
|
|
2345
|
+
replication.disposed ||
|
|
2346
|
+
replication.replayState.halted ||
|
|
2347
|
+
replication.checkpointRetryTimer
|
|
2348
|
+
) return;
|
|
2349
|
+
replication.checkpointRetryTimer = setTimeout(() => {
|
|
2350
|
+
replication.checkpointRetryTimer = null;
|
|
2351
|
+
if (!crtTerminalReplication || crtTerminalReplication !== replication || replication.disposed) return;
|
|
2352
|
+
requestCrtTerminalReplay();
|
|
2353
|
+
}, delay);
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
function retryCrtTerminalReplayAfterFailure(replication, failure, error = null) {
|
|
2357
|
+
if (!replication || replication.disposed) return;
|
|
2358
|
+
replication.checkpointInFlight = false;
|
|
2359
|
+
if (failure.halted) {
|
|
2360
|
+
stopCrtTerminalReplay(replication, failure.message);
|
|
2361
|
+
return;
|
|
2362
|
+
}
|
|
2363
|
+
if (error) console.warn('Terminal replay request failed; retrying:', error);
|
|
2364
|
+
scheduleCrtTerminalCheckpointRetry(replication, failure.delay);
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
function finishCrtTerminalReplay(replication = crtTerminalReplication) {
|
|
2368
|
+
if (
|
|
2369
|
+
!replication ||
|
|
2370
|
+
replication.disposed ||
|
|
2371
|
+
replication.checkpointInFlight ||
|
|
2372
|
+
replication.installInProgress ||
|
|
2373
|
+
replication.pendingCheckpoint ||
|
|
2374
|
+
replication.writeInProgress
|
|
2375
|
+
) return;
|
|
2376
|
+
if (replication.replayState.queuedTransitions.length > 0 && !replication.replayState.recovering) {
|
|
2377
|
+
flushCrtTerminalTransitions();
|
|
2378
|
+
if (
|
|
2379
|
+
replication.replayState.queuedTransitions.length > 0 ||
|
|
2380
|
+
replication.writeInProgress ||
|
|
2381
|
+
replication.checkpointInFlight ||
|
|
2382
|
+
replication.replayState.recovering
|
|
2383
|
+
) return;
|
|
2384
|
+
}
|
|
2385
|
+
if (
|
|
2386
|
+
replication.replayState.recovering ||
|
|
2387
|
+
TERMINAL_REPLAY.isReplayTargetPending(replication.replayState)
|
|
2388
|
+
) {
|
|
2389
|
+
requestCrtTerminalReplay();
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
requestAnimationFrame(() => {
|
|
2393
|
+
if (!crtTerminalReplication || crtTerminalReplication !== replication || replication.disposed) return;
|
|
2394
|
+
document.getElementById('terminal-output')?.classList.remove('crt-terminal-checkpoint-installing');
|
|
2395
|
+
sendSessionResize(replication.agentId);
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
function stopCrtTerminalReplay(replication, error) {
|
|
2400
|
+
if (!replication || replication.disposed) return;
|
|
2401
|
+
replication.checkpointInFlight = false;
|
|
2402
|
+
replication.installInProgress = false;
|
|
2403
|
+
replication.pendingCheckpoint = null;
|
|
2404
|
+
TERMINAL_REPLAY.clearQueuedTransitions(replication.replayState);
|
|
2405
|
+
document.getElementById('terminal-output')?.classList.add('crt-terminal-checkpoint-installing');
|
|
2406
|
+
console.error('Terminal replay failed:', error);
|
|
2407
|
+
showCrtTerminalFailure(
|
|
2408
|
+
'CRT TERMINAL SYNC ERROR',
|
|
2409
|
+
error,
|
|
2410
|
+
'The Agent is still running. Close and reopen this terminal to retry recovery.'
|
|
2411
|
+
);
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
function applyCrtTerminalTransition(event) {
|
|
2415
|
+
const replication = crtTerminalReplication;
|
|
2416
|
+
if (!replication || !terminal || replication.disposed) return;
|
|
2417
|
+
const decision = TERMINAL_REPLAY.classifyTransition(replication.replayState, event);
|
|
2418
|
+
if (decision.action === 'drop') return;
|
|
2419
|
+
if (decision.action === 'recover') {
|
|
2420
|
+
queueCrtTerminalTransition(event);
|
|
2421
|
+
requestCrtTerminalReplay();
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
if (event.kind === 'resize') {
|
|
2426
|
+
replication.applyingLocalResize = true;
|
|
2427
|
+
try {
|
|
2428
|
+
terminal.resize(Math.floor(event.cols), Math.floor(event.rows));
|
|
2429
|
+
} finally {
|
|
2430
|
+
replication.applyingLocalResize = false;
|
|
2431
|
+
}
|
|
2432
|
+
TERMINAL_REPLAY.commitTransition(replication.replayState, event);
|
|
2433
|
+
refreshSessionTerminalUi({ preserveSearchIndex: true });
|
|
2434
|
+
flushCrtTerminalTransitions();
|
|
2435
|
+
return;
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
const transitionData = event.kind === 'clear' ? '\x1b[2J\x1b[3J\x1b[H' : event.data;
|
|
2439
|
+
if (!transitionData) {
|
|
2440
|
+
TERMINAL_REPLAY.commitTransition(replication.replayState, event);
|
|
2441
|
+
flushCrtTerminalTransitions();
|
|
2442
|
+
return;
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
replication.writeInProgress = true;
|
|
2446
|
+
terminal.write(transitionData, () => {
|
|
2447
|
+
if (!crtTerminalReplication || crtTerminalReplication !== replication || replication.disposed) return;
|
|
2448
|
+
TERMINAL_REPLAY.commitTransition(replication.replayState, event);
|
|
2449
|
+
if (event.kind === 'clear') terminal.clearSelection?.();
|
|
2450
|
+
replication.writeInProgress = false;
|
|
2451
|
+
refreshSessionTerminalUi({ preserveSearchIndex: true });
|
|
2452
|
+
if (drainCrtTerminalCheckpointInstall(replication)) return;
|
|
2453
|
+
flushCrtTerminalTransitions();
|
|
2454
|
+
finishCrtTerminalReplay(replication);
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
function flushCrtTerminalTransitions() {
|
|
2459
|
+
const replication = crtTerminalReplication;
|
|
2460
|
+
if (
|
|
2461
|
+
!replication ||
|
|
2462
|
+
replication.disposed ||
|
|
2463
|
+
replication.replayState.recovering ||
|
|
2464
|
+
replication.checkpointInFlight ||
|
|
2465
|
+
replication.installInProgress ||
|
|
2466
|
+
replication.writeInProgress
|
|
2467
|
+
) return;
|
|
2468
|
+
|
|
2469
|
+
while (
|
|
2470
|
+
!replication.replayState.recovering &&
|
|
2471
|
+
!replication.checkpointInFlight &&
|
|
2472
|
+
!replication.installInProgress &&
|
|
2473
|
+
!replication.writeInProgress
|
|
2474
|
+
) {
|
|
2475
|
+
const next = TERMINAL_REPLAY.takeQueuedTransition(replication.replayState);
|
|
2476
|
+
if (!next) break;
|
|
2477
|
+
applyCrtTerminalTransition(next);
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
function performCrtTerminalCheckpointInstall(replication, sessionView) {
|
|
2482
|
+
if (
|
|
2483
|
+
!crtTerminalReplication ||
|
|
2484
|
+
crtTerminalReplication !== replication ||
|
|
2485
|
+
!terminal ||
|
|
2486
|
+
replication.disposed
|
|
2487
|
+
) return false;
|
|
2488
|
+
|
|
2489
|
+
const installSeq = replication.installSeq + 1;
|
|
2490
|
+
replication.installSeq = installSeq;
|
|
2491
|
+
replication.installInProgress = true;
|
|
2492
|
+
TERMINAL_REPLAY.beginRecovery(replication.replayState, sessionView);
|
|
2493
|
+
const container = document.getElementById('terminal-output');
|
|
2494
|
+
container?.classList.add('crt-terminal-checkpoint-installing');
|
|
2495
|
+
|
|
2496
|
+
replication.applyingLocalResize = true;
|
|
2497
|
+
try {
|
|
2498
|
+
terminal.resize(sessionView.previewCols, sessionView.previewRows);
|
|
2499
|
+
} finally {
|
|
2500
|
+
replication.applyingLocalResize = false;
|
|
2501
|
+
}
|
|
2502
|
+
terminal.reset();
|
|
2503
|
+
|
|
2504
|
+
const finishInstall = () => {
|
|
2505
|
+
if (
|
|
2506
|
+
!crtTerminalReplication ||
|
|
2507
|
+
crtTerminalReplication !== replication ||
|
|
2508
|
+
replication.disposed ||
|
|
2509
|
+
replication.installSeq !== installSeq
|
|
2510
|
+
) return;
|
|
2511
|
+
|
|
2512
|
+
TERMINAL_REPLAY.commitCheckpoint(replication.replayState, {
|
|
2513
|
+
runtimeEpoch: sessionView.runtimeEpoch,
|
|
2514
|
+
outputSeq: sessionView.outputSeq,
|
|
2515
|
+
stateRevision: sessionView.stateRevision,
|
|
2516
|
+
cols: sessionView.previewCols,
|
|
2517
|
+
rows: sessionView.previewRows
|
|
2518
|
+
});
|
|
2519
|
+
replication.installInProgress = false;
|
|
2520
|
+
refreshSessionTerminalUi({ preserveSearchIndex: true });
|
|
2521
|
+
const runtime = getSessionRuntime();
|
|
2522
|
+
if (runtime) {
|
|
2523
|
+
runtime.markHydrated(sessionView.renderOutput.length);
|
|
2524
|
+
syncSessionRuntimeState();
|
|
2525
|
+
}
|
|
2526
|
+
if (drainCrtTerminalCheckpointInstall(replication)) return;
|
|
2527
|
+
flushCrtTerminalTransitions();
|
|
2528
|
+
finishCrtTerminalReplay(replication);
|
|
2529
|
+
};
|
|
2530
|
+
|
|
2531
|
+
if (sessionView.renderOutput) {
|
|
2532
|
+
terminal.write(sessionView.renderOutput, finishInstall);
|
|
2533
|
+
} else {
|
|
2534
|
+
finishInstall();
|
|
2535
|
+
}
|
|
2536
|
+
return true;
|
|
2537
|
+
}
|
|
2288
2538
|
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2539
|
+
function drainCrtTerminalCheckpointInstall(replication = crtTerminalReplication) {
|
|
2540
|
+
if (
|
|
2541
|
+
!replication ||
|
|
2542
|
+
replication.disposed ||
|
|
2543
|
+
replication.installInProgress ||
|
|
2544
|
+
replication.writeInProgress ||
|
|
2545
|
+
!replication.pendingCheckpoint
|
|
2546
|
+
) return false;
|
|
2547
|
+
const sessionView = replication.pendingCheckpoint;
|
|
2548
|
+
replication.pendingCheckpoint = null;
|
|
2549
|
+
return performCrtTerminalCheckpointInstall(replication, sessionView);
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
function installCrtTerminalCheckpoint(sessionView) {
|
|
2553
|
+
const replication = crtTerminalReplication;
|
|
2554
|
+
if (!replication || !terminal || replication.disposed) return false;
|
|
2555
|
+
const checkpoint = {
|
|
2556
|
+
runtimeEpoch: sessionView.runtimeEpoch,
|
|
2557
|
+
outputSeq: sessionView.outputSeq,
|
|
2558
|
+
stateRevision: sessionView.stateRevision,
|
|
2559
|
+
cols: sessionView.previewCols,
|
|
2560
|
+
rows: sessionView.previewRows
|
|
2561
|
+
};
|
|
2562
|
+
const decision = TERMINAL_REPLAY.evaluateCheckpoint(replication.replayState, checkpoint);
|
|
2563
|
+
if (decision.action === 'reject') {
|
|
2564
|
+
retryCrtTerminalReplayAfterFailure(
|
|
2565
|
+
replication,
|
|
2566
|
+
TERMINAL_REPLAY.recordInvariantFailure(
|
|
2567
|
+
replication.replayState,
|
|
2568
|
+
decision.signature || 'invalid-checkpoint',
|
|
2569
|
+
decision.message || 'Terminal replay returned an invalid screen state'
|
|
2570
|
+
)
|
|
2571
|
+
);
|
|
2572
|
+
return false;
|
|
2573
|
+
}
|
|
2574
|
+
if (
|
|
2575
|
+
decision.action === 'current' &&
|
|
2576
|
+
terminal.cols === sessionView.previewCols &&
|
|
2577
|
+
terminal.rows === sessionView.previewRows
|
|
2578
|
+
) {
|
|
2579
|
+
TERMINAL_REPLAY.commitCheckpoint(replication.replayState, checkpoint);
|
|
2580
|
+
flushCrtTerminalTransitions();
|
|
2581
|
+
finishCrtTerminalReplay(replication);
|
|
2582
|
+
return true;
|
|
2294
2583
|
}
|
|
2295
2584
|
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
return
|
|
2300
|
-
? `${text}\x1b[?25l`
|
|
2301
|
-
: text;
|
|
2585
|
+
replication.pendingCheckpoint = sessionView;
|
|
2586
|
+
TERMINAL_REPLAY.beginRecovery(replication.replayState, sessionView);
|
|
2587
|
+
drainCrtTerminalCheckpointInstall(replication);
|
|
2588
|
+
return true;
|
|
2302
2589
|
}
|
|
2303
2590
|
|
|
2591
|
+
function handleCrtTerminalStream(stream) {
|
|
2592
|
+
const replication = crtTerminalReplication;
|
|
2593
|
+
if (!replication || !stream || stream.agentId !== replication.agentId) return;
|
|
2594
|
+
if (stream.replace === true) {
|
|
2595
|
+
replication.checkpointSeq += 1;
|
|
2596
|
+
replication.checkpointAbortController?.abort();
|
|
2597
|
+
replication.checkpointAbortController = null;
|
|
2598
|
+
replication.checkpointInFlight = false;
|
|
2599
|
+
installCrtTerminalCheckpoint({
|
|
2600
|
+
runtimeEpoch: stream.runtimeEpoch,
|
|
2601
|
+
outputSeq: stream.outputSeq,
|
|
2602
|
+
stateRevision: stream.stateRevision,
|
|
2603
|
+
previewCols: stream.cols,
|
|
2604
|
+
previewRows: stream.rows,
|
|
2605
|
+
renderOutput: typeof stream.data === 'string' ? stream.data : ''
|
|
2606
|
+
});
|
|
2607
|
+
if (Array.isArray(stream.chunks)) {
|
|
2608
|
+
stream.chunks.forEach((chunk) => queueCrtTerminalTransition({
|
|
2609
|
+
kind: chunk.kind || 'output',
|
|
2610
|
+
data: typeof chunk.data === 'string' ? chunk.data : '',
|
|
2611
|
+
runtimeEpoch: chunk.runtimeEpoch || stream.runtimeEpoch,
|
|
2612
|
+
outputSeq: chunk.outputSeq,
|
|
2613
|
+
stateRevision: chunk.stateRevision,
|
|
2614
|
+
cols: chunk.cols,
|
|
2615
|
+
rows: chunk.rows
|
|
2616
|
+
}));
|
|
2617
|
+
flushCrtTerminalTransitions();
|
|
2618
|
+
finishCrtTerminalReplay(replication);
|
|
2619
|
+
}
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
const chunks = Array.isArray(stream.chunks) ? stream.chunks : [stream];
|
|
2624
|
+
chunks.forEach((chunk) => {
|
|
2625
|
+
const event = {
|
|
2626
|
+
kind: chunk.kind || 'output',
|
|
2627
|
+
data: typeof chunk.data === 'string' ? chunk.data : '',
|
|
2628
|
+
runtimeEpoch: chunk.runtimeEpoch || stream.runtimeEpoch,
|
|
2629
|
+
outputSeq: chunk.outputSeq,
|
|
2630
|
+
stateRevision: chunk.stateRevision,
|
|
2631
|
+
cols: chunk.cols,
|
|
2632
|
+
rows: chunk.rows
|
|
2633
|
+
};
|
|
2634
|
+
if (
|
|
2635
|
+
replication.replayState.recovering ||
|
|
2636
|
+
replication.checkpointInFlight ||
|
|
2637
|
+
replication.installInProgress ||
|
|
2638
|
+
replication.writeInProgress
|
|
2639
|
+
) {
|
|
2640
|
+
queueCrtTerminalTransition(event);
|
|
2641
|
+
} else {
|
|
2642
|
+
applyCrtTerminalTransition(event);
|
|
2643
|
+
}
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2304
2646
|
function deriveSessionStreamPatch(stream, currentFocusedAgentId, currentSessionSource) {
|
|
2305
2647
|
if (!stream) return null;
|
|
2306
2648
|
if (stream.agentId !== currentFocusedAgentId) return null;
|
|
@@ -2335,7 +2677,7 @@ function shouldPollSessionView(sessionSource) {
|
|
|
2335
2677
|
if (SESSION_MODAL_BRIDGE && SESSION_MODAL_BRIDGE.shouldPollSessionView) {
|
|
2336
2678
|
return SESSION_MODAL_BRIDGE.shouldPollSessionView(sessionSource);
|
|
2337
2679
|
}
|
|
2338
|
-
return
|
|
2680
|
+
return false;
|
|
2339
2681
|
}
|
|
2340
2682
|
|
|
2341
2683
|
function getSessionModalDomState(documentRef) {
|
|
@@ -2354,8 +2696,6 @@ function getSessionRuntime() {
|
|
|
2354
2696
|
sessionRuntime = SESSION_MODAL_BRIDGE.createRuntime({
|
|
2355
2697
|
deriveSessionStreamPatch,
|
|
2356
2698
|
refreshSessionView,
|
|
2357
|
-
schedulePoll: (handler) => setInterval(handler, 350),
|
|
2358
|
-
clearPoll: (poller) => clearInterval(poller)
|
|
2359
2699
|
});
|
|
2360
2700
|
}
|
|
2361
2701
|
|
|
@@ -2384,11 +2724,6 @@ function getCurrentSessionToken() {
|
|
|
2384
2724
|
return runtime ? runtime.getSessionToken() : 0;
|
|
2385
2725
|
}
|
|
2386
2726
|
|
|
2387
|
-
function isAwaitingInitialSessionSync() {
|
|
2388
|
-
const runtime = getSessionRuntime();
|
|
2389
|
-
return runtime ? runtime.isAwaitingInitialSync() : false;
|
|
2390
|
-
}
|
|
2391
|
-
|
|
2392
2727
|
function setSessionOutputLength(length) {
|
|
2393
2728
|
const runtime = getSessionRuntime();
|
|
2394
2729
|
if (runtime) {
|
|
@@ -2550,12 +2885,9 @@ function applyCrtTerminalFontSize(value) {
|
|
|
2550
2885
|
if (input) input.value = String(fontSize);
|
|
2551
2886
|
if (output) output.textContent = `${fontSize} px`;
|
|
2552
2887
|
if (terminal && terminal.options) terminal.options.fontSize = fontSize;
|
|
2553
|
-
if (terminalInputBridge) terminalInputBridge.style.fontSize = `${fontSize}px`;
|
|
2554
2888
|
if (terminal && fitAddon && focusedAgentId && typeof requestAnimationFrame === 'function') {
|
|
2555
2889
|
requestAnimationFrame(() => {
|
|
2556
2890
|
if (!terminal || !fitAddon || !focusedAgentId) return;
|
|
2557
|
-
fitAddon.fit();
|
|
2558
|
-
syncTerminalInputBridgePosition();
|
|
2559
2891
|
sendSessionResize();
|
|
2560
2892
|
});
|
|
2561
2893
|
}
|
|
@@ -3064,6 +3396,7 @@ function formatCrtExactUsageValue(value) {
|
|
|
3064
3396
|
}
|
|
3065
3397
|
|
|
3066
3398
|
function formatCrtCompactTotalValue(value) {
|
|
3399
|
+
if (value === null || value === undefined || value === '') return '--';
|
|
3067
3400
|
const numberValue = Number(value);
|
|
3068
3401
|
if (!Number.isFinite(numberValue) || numberValue < 0) return '--';
|
|
3069
3402
|
const units = [
|
|
@@ -3098,26 +3431,199 @@ function crtBillingDayPoint(dateValue = billingSelectedDate) {
|
|
|
3098
3431
|
return points.find(point => point && point.date === dateValue) || null;
|
|
3099
3432
|
}
|
|
3100
3433
|
|
|
3101
|
-
function
|
|
3434
|
+
function crtBillingSelectedDayIsCurrent(point = crtBillingDayPoint()) {
|
|
3435
|
+
const daily = billingSummary && billingSummary.daily;
|
|
3436
|
+
return Boolean(daily && point && point.date === daily.endDate);
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
function cancelCrtBillingTotalAnimation() {
|
|
3440
|
+
if (billingTotalAnimationFrame !== null) {
|
|
3441
|
+
window.cancelAnimationFrame(billingTotalAnimationFrame);
|
|
3442
|
+
billingTotalAnimationFrame = null;
|
|
3443
|
+
}
|
|
3444
|
+
billingTotalAnimationTarget = null;
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3447
|
+
function cancelCrtBillingMetricAnimations() {
|
|
3448
|
+
billingAnimatedMetrics.forEach((metric) => {
|
|
3449
|
+
if (metric.frame !== null) window.cancelAnimationFrame(metric.frame);
|
|
3450
|
+
});
|
|
3451
|
+
billingAnimatedMetrics.clear();
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
function updateCrtBillingAnimatedMetric(key, value, {
|
|
3455
|
+
date = '',
|
|
3456
|
+
live = false,
|
|
3457
|
+
write,
|
|
3458
|
+
} = {}) {
|
|
3459
|
+
const target = Number(value);
|
|
3460
|
+
const existing = billingAnimatedMetrics.get(key);
|
|
3461
|
+
if (!Number.isFinite(target) || target < 0 || typeof write !== 'function') {
|
|
3462
|
+
if (existing && existing.frame !== null) window.cancelAnimationFrame(existing.frame);
|
|
3463
|
+
billingAnimatedMetrics.delete(key);
|
|
3464
|
+
write?.(null, null);
|
|
3465
|
+
return;
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
const roundedTarget = Math.round(target);
|
|
3469
|
+
if (existing && existing.frame !== null && existing.target === roundedTarget && existing.date === date) {
|
|
3470
|
+
existing.write = write;
|
|
3471
|
+
return;
|
|
3472
|
+
}
|
|
3473
|
+
if (existing && existing.frame !== null) window.cancelAnimationFrame(existing.frame);
|
|
3474
|
+
const shouldSnap = !live
|
|
3475
|
+
|| !existing
|
|
3476
|
+
|| existing.date !== date
|
|
3477
|
+
|| !Number.isFinite(existing.value)
|
|
3478
|
+
|| roundedTarget <= existing.value;
|
|
3479
|
+
if (shouldSnap) {
|
|
3480
|
+
const metric = { date, value: roundedTarget, target: roundedTarget, frame: null, write };
|
|
3481
|
+
billingAnimatedMetrics.set(key, metric);
|
|
3482
|
+
write(roundedTarget, roundedTarget);
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3486
|
+
const metric = {
|
|
3487
|
+
date,
|
|
3488
|
+
value: existing.value,
|
|
3489
|
+
target: roundedTarget,
|
|
3490
|
+
frame: null,
|
|
3491
|
+
write,
|
|
3492
|
+
};
|
|
3493
|
+
billingAnimatedMetrics.set(key, metric);
|
|
3494
|
+
const startValue = existing.value;
|
|
3495
|
+
const startedAt = window.performance.now();
|
|
3496
|
+
const step = (now) => {
|
|
3497
|
+
const current = billingAnimatedMetrics.get(key);
|
|
3498
|
+
if (current !== metric) return;
|
|
3499
|
+
const progress = Math.min(1, Math.max(0, (now - startedAt) / CRT_BILLING_TOTAL_ANIMATION_MS));
|
|
3500
|
+
const steppedProgress = Math.min(1, Math.floor(progress * 18) / 18);
|
|
3501
|
+
const easedProgress = 1 - ((1 - steppedProgress) ** 3);
|
|
3502
|
+
metric.value = Math.round(startValue + (roundedTarget - startValue) * easedProgress);
|
|
3503
|
+
metric.write(metric.value, roundedTarget);
|
|
3504
|
+
if (progress < 1) {
|
|
3505
|
+
metric.frame = window.requestAnimationFrame(step);
|
|
3506
|
+
return;
|
|
3507
|
+
}
|
|
3508
|
+
metric.value = roundedTarget;
|
|
3509
|
+
metric.frame = null;
|
|
3510
|
+
metric.write(roundedTarget, roundedTarget);
|
|
3511
|
+
};
|
|
3512
|
+
metric.frame = window.requestAnimationFrame(step);
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
function updateCrtBillingExactMetric(id, value, { date = '', live = false } = {}) {
|
|
3516
|
+
const element = document.getElementById(id);
|
|
3517
|
+
if (!element) return;
|
|
3518
|
+
updateCrtBillingAnimatedMetric(id, value, {
|
|
3519
|
+
date,
|
|
3520
|
+
live,
|
|
3521
|
+
write: (displayed, target) => {
|
|
3522
|
+
element.textContent = formatCrtExactUsageValue(displayed);
|
|
3523
|
+
element.dataset.displayedValue = displayed === null ? '' : String(displayed);
|
|
3524
|
+
element.dataset.targetValue = target === null ? '' : String(target);
|
|
3525
|
+
},
|
|
3526
|
+
});
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
function writeCrtBillingTotalDisplay(value, target, { live = false } = {}) {
|
|
3530
|
+
const total = document.getElementById('billing-day-total');
|
|
3531
|
+
const compact = document.getElementById('billing-day-total-compact');
|
|
3532
|
+
const meter = document.getElementById('billing-day-total-meter');
|
|
3533
|
+
const numericValue = Number.isFinite(Number(value)) ? Math.max(0, Math.round(Number(value))) : null;
|
|
3534
|
+
const numericTarget = Number.isFinite(Number(target)) ? Math.max(0, Math.round(Number(target))) : null;
|
|
3535
|
+
if (total) total.textContent = formatCrtExactUsageValue(numericValue);
|
|
3536
|
+
if (compact) compact.textContent = formatCrtCompactTotalValue(numericValue);
|
|
3537
|
+
if (meter) {
|
|
3538
|
+
meter.classList.toggle('is-live', live);
|
|
3539
|
+
meter.dataset.displayedTotal = numericValue === null ? '' : String(numericValue);
|
|
3540
|
+
meter.dataset.targetTotal = numericTarget === null ? '' : String(numericTarget);
|
|
3541
|
+
meter.setAttribute('aria-label', numericTarget === null
|
|
3542
|
+
? 'Total tokens unavailable'
|
|
3543
|
+
: `${formatCrtExactUsageValue(numericTarget)} total tokens${live ? ', live refresh every 5 seconds' : ''}`);
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
function updateCrtBillingTotalDisplay(value, { date = '', live = false } = {}) {
|
|
3548
|
+
const target = Number(value);
|
|
3549
|
+
if (!Number.isFinite(target) || target < 0) {
|
|
3550
|
+
cancelCrtBillingTotalAnimation();
|
|
3551
|
+
billingDisplayedTotalDate = date;
|
|
3552
|
+
billingDisplayedTotalValue = null;
|
|
3553
|
+
writeCrtBillingTotalDisplay(null, null, { live });
|
|
3554
|
+
return;
|
|
3555
|
+
}
|
|
3556
|
+
|
|
3557
|
+
const roundedTarget = Math.round(target);
|
|
3558
|
+
const shouldSnap = !live
|
|
3559
|
+
|| billingDisplayedTotalDate !== date
|
|
3560
|
+
|| !Number.isFinite(billingDisplayedTotalValue)
|
|
3561
|
+
|| roundedTarget <= billingDisplayedTotalValue;
|
|
3562
|
+
if (shouldSnap) {
|
|
3563
|
+
cancelCrtBillingTotalAnimation();
|
|
3564
|
+
billingDisplayedTotalDate = date;
|
|
3565
|
+
billingDisplayedTotalValue = roundedTarget;
|
|
3566
|
+
writeCrtBillingTotalDisplay(roundedTarget, roundedTarget, { live });
|
|
3567
|
+
return;
|
|
3568
|
+
}
|
|
3569
|
+
if (billingTotalAnimationFrame !== null && billingTotalAnimationTarget === roundedTarget) return;
|
|
3570
|
+
|
|
3571
|
+
cancelCrtBillingTotalAnimation();
|
|
3572
|
+
const startValue = billingDisplayedTotalValue;
|
|
3573
|
+
const startedAt = window.performance.now();
|
|
3574
|
+
billingTotalAnimationTarget = roundedTarget;
|
|
3575
|
+
const step = (now) => {
|
|
3576
|
+
const progress = Math.min(1, Math.max(0, (now - startedAt) / CRT_BILLING_TOTAL_ANIMATION_MS));
|
|
3577
|
+
const steppedProgress = Math.min(1, Math.floor(progress * 18) / 18);
|
|
3578
|
+
const easedProgress = 1 - ((1 - steppedProgress) ** 3);
|
|
3579
|
+
billingDisplayedTotalValue = Math.round(startValue + (roundedTarget - startValue) * easedProgress);
|
|
3580
|
+
writeCrtBillingTotalDisplay(billingDisplayedTotalValue, roundedTarget, { live: true });
|
|
3581
|
+
if (progress < 1) {
|
|
3582
|
+
billingTotalAnimationFrame = window.requestAnimationFrame(step);
|
|
3583
|
+
return;
|
|
3584
|
+
}
|
|
3585
|
+
billingDisplayedTotalValue = roundedTarget;
|
|
3586
|
+
billingTotalAnimationFrame = null;
|
|
3587
|
+
billingTotalAnimationTarget = null;
|
|
3588
|
+
writeCrtBillingTotalDisplay(roundedTarget, roundedTarget, { live: true });
|
|
3589
|
+
};
|
|
3590
|
+
billingTotalAnimationFrame = window.requestAnimationFrame(step);
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
const CRT_BILLING_OVERRANGE_BASE = 1_000_000_000;
|
|
3594
|
+
|
|
3595
|
+
function crtBillingOverrangeTier(value) {
|
|
3596
|
+
const total = Math.max(0, Number(value) || 0);
|
|
3597
|
+
if (total < CRT_BILLING_OVERRANGE_BASE) return 0;
|
|
3598
|
+
return Math.min(4, Math.floor(Math.log2(total / CRT_BILLING_OVERRANGE_BASE)) + 1);
|
|
3599
|
+
}
|
|
3600
|
+
|
|
3601
|
+
function crtBillingOverrangeLabel(tier) {
|
|
3602
|
+
return tier > 0 ? `${2 ** (tier - 1)}B+ OVERRANGE` : '';
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
function crtBillingHeatThresholds(values) {
|
|
3606
|
+
const activeValues = (Array.isArray(values) ? values : [])
|
|
3607
|
+
.map(value => Math.max(0, Number(value) || 0))
|
|
3608
|
+
.filter(value => value > 0 && value < CRT_BILLING_OVERRANGE_BASE)
|
|
3609
|
+
.sort((left, right) => left - right);
|
|
3610
|
+
if (activeValues.length === 0) return [];
|
|
3611
|
+
return [0.2, 0.4, 0.6, 0.8].map(quantile => (
|
|
3612
|
+
activeValues[Math.min(activeValues.length - 1, Math.ceil(activeValues.length * quantile) - 1)]
|
|
3613
|
+
));
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
function crtBillingHeatLevel(value, thresholds) {
|
|
3102
3617
|
const total = Math.max(0, Number(value) || 0);
|
|
3103
3618
|
if (total <= 0) return 0;
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
/ (Math.log10(maximum) - Math.log10(minimum)) * 100;
|
|
3107
|
-
return Math.max(2, Math.min(100, position));
|
|
3619
|
+
const bands = Array.isArray(thresholds) ? thresholds : [];
|
|
3620
|
+
return Math.max(1, Math.min(5, 1 + bands.filter(threshold => total > threshold).length));
|
|
3108
3621
|
}
|
|
3109
3622
|
|
|
3110
|
-
function
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
for (let exponent = firstExponent; exponent <= lastExponent; exponent += 1) {
|
|
3115
|
-
const value = 10 ** exponent;
|
|
3116
|
-
if (value > minimum && value < maximum) values.push(value);
|
|
3117
|
-
}
|
|
3118
|
-
const upperThird = 3 * (10 ** lastExponent);
|
|
3119
|
-
if (upperThird > minimum && upperThird < maximum) values.push(upperThird);
|
|
3120
|
-
return Array.from(new Set(values)).sort((left, right) => left - right);
|
|
3623
|
+
function crtBillingDayDetailHasHourlyActivity(detail) {
|
|
3624
|
+
return Boolean(detail && Array.isArray(detail.hours) && detail.hours.some(hour => (
|
|
3625
|
+
Math.max(0, Number(hour && hour.totalTokens) || 0) > 0
|
|
3626
|
+
)));
|
|
3121
3627
|
}
|
|
3122
3628
|
|
|
3123
3629
|
function crtBillingHourlyPath(hours, valueForHour, maximum) {
|
|
@@ -3126,10 +3632,11 @@ function crtBillingHourlyPath(hours, valueForHour, maximum) {
|
|
|
3126
3632
|
const points = Array.isArray(hours) ? hours : [];
|
|
3127
3633
|
if (points.length === 0 || maximum <= 0) return '';
|
|
3128
3634
|
return points.map((hour, index) => {
|
|
3129
|
-
const
|
|
3635
|
+
const startX = index / points.length * width;
|
|
3636
|
+
const endX = (index + 1) / points.length * width;
|
|
3130
3637
|
const value = Math.max(0, Number(valueForHour(hour)) || 0);
|
|
3131
3638
|
const y = height - Math.min(1, value / maximum) * height;
|
|
3132
|
-
return `${index === 0 ? 'M' : 'L'}${
|
|
3639
|
+
return `${index === 0 ? 'M' : 'L'}${startX.toFixed(1)} ${y.toFixed(1)} L${endX.toFixed(1)} ${y.toFixed(1)}`;
|
|
3133
3640
|
}).join(' ');
|
|
3134
3641
|
}
|
|
3135
3642
|
|
|
@@ -3145,6 +3652,8 @@ function renderCrtBillingDayInsight() {
|
|
|
3145
3652
|
const cachePath = document.getElementById('billing-day-cache-path');
|
|
3146
3653
|
const scale = document.getElementById('billing-day-curve-scale');
|
|
3147
3654
|
const maximumLabel = document.getElementById('billing-day-curve-max');
|
|
3655
|
+
const hourStrip = document.getElementById('billing-day-hour-strip');
|
|
3656
|
+
const hourReadout = document.getElementById('billing-day-hour-readout');
|
|
3148
3657
|
const shares = document.getElementById('billing-day-provider-shares');
|
|
3149
3658
|
const maximum = Math.max(0, ...hours.map(hour => Math.max(0, Number(hour && hour.totalTokens) || 0)));
|
|
3150
3659
|
|
|
@@ -3154,19 +3663,94 @@ function renderCrtBillingDayInsight() {
|
|
|
3154
3663
|
hour => (Number(hour.cacheReadTokens) || 0) + (Number(hour.cacheWriteTokens) || 0),
|
|
3155
3664
|
maximum,
|
|
3156
3665
|
));
|
|
3157
|
-
|
|
3158
|
-
|
|
3666
|
+
const isToday = crtBillingSelectedDayIsCurrent(point);
|
|
3667
|
+
updateCrtBillingAnimatedMetric('billing-day-curve-scale', maximum > 0 ? maximum : null, {
|
|
3668
|
+
date: point && point.date || '',
|
|
3669
|
+
live: isToday,
|
|
3670
|
+
write: displayed => {
|
|
3671
|
+
if (scale) scale.textContent = displayed === null ? '-- TOK/H PEAK' : `${formatCrtUsageValue(displayed)} TOK/H PEAK`;
|
|
3672
|
+
},
|
|
3673
|
+
});
|
|
3674
|
+
updateCrtBillingAnimatedMetric('billing-day-curve-max', maximum > 0 ? maximum : null, {
|
|
3675
|
+
date: point && point.date || '',
|
|
3676
|
+
live: isToday,
|
|
3677
|
+
write: displayed => {
|
|
3678
|
+
if (maximumLabel) maximumLabel.textContent = displayed === null ? '--' : formatCrtUsageValue(displayed);
|
|
3679
|
+
},
|
|
3680
|
+
});
|
|
3681
|
+
if (hourStrip) {
|
|
3682
|
+
hourStrip.replaceChildren();
|
|
3683
|
+
if (hours.length > 0) {
|
|
3684
|
+
const heatThresholds = crtBillingHeatThresholds(hours.map(hour => hour && hour.totalTokens));
|
|
3685
|
+
if (!Number.isInteger(billingSelectedHour) || billingSelectedHour < 0 || billingSelectedHour >= hours.length) {
|
|
3686
|
+
billingSelectedHour = hours.reduce((peakIndex, hour, index) => (
|
|
3687
|
+
Number(hour && hour.totalTokens) > Number(hours[peakIndex] && hours[peakIndex].totalTokens) ? index : peakIndex
|
|
3688
|
+
), 0);
|
|
3689
|
+
}
|
|
3690
|
+
const selectHour = (index, { focus = false } = {}) => {
|
|
3691
|
+
billingSelectedHour = index;
|
|
3692
|
+
hourStrip.querySelectorAll('.billing-day-hour-cell').forEach((cell, cellIndex) => {
|
|
3693
|
+
const selected = cellIndex === index;
|
|
3694
|
+
cell.classList.toggle('selected', selected);
|
|
3695
|
+
cell.setAttribute('aria-selected', selected ? 'true' : 'false');
|
|
3696
|
+
cell.tabIndex = selected ? 0 : -1;
|
|
3697
|
+
});
|
|
3698
|
+
const hour = hours[index] || {};
|
|
3699
|
+
const hourValue = Number.isFinite(Number(hour.hour)) ? Number(hour.hour) : index;
|
|
3700
|
+
const total = Math.max(0, Number(hour.totalTokens) || 0);
|
|
3701
|
+
const cache = Math.max(0, (Number(hour.cacheReadTokens) || 0) + (Number(hour.cacheWriteTokens) || 0));
|
|
3702
|
+
if (hourReadout) {
|
|
3703
|
+
const endHour = hourValue + 1;
|
|
3704
|
+
const cacheShare = total > 0 ? `${(cache / total * 100).toFixed(1)}% CACHE` : 'NO ACTIVITY';
|
|
3705
|
+
hourReadout.textContent = `[${String(hourValue).padStart(2, '0')}:00—${String(endHour).padStart(2, '0')}:00] TOTAL ${formatCrtUsageValue(total)} // CACHE ${formatCrtUsageValue(cache)} // ${cacheShare}`;
|
|
3706
|
+
hourReadout.title = `${String(hourValue).padStart(2, '0')}:00—${String(endHour).padStart(2, '0')}:00 · ${formatCrtExactUsageValue(total)} total tokens · ${formatCrtExactUsageValue(cache)} cache tokens`;
|
|
3707
|
+
}
|
|
3708
|
+
if (focus) hourStrip.children[index]?.focus();
|
|
3709
|
+
};
|
|
3710
|
+
hours.forEach((hour, index) => {
|
|
3711
|
+
const total = Math.max(0, Number(hour && hour.totalTokens) || 0);
|
|
3712
|
+
const cache = Math.max(0, (Number(hour && hour.cacheReadTokens) || 0) + (Number(hour && hour.cacheWriteTokens) || 0));
|
|
3713
|
+
const hourValue = Number.isFinite(Number(hour && hour.hour)) ? Number(hour.hour) : index;
|
|
3714
|
+
const overrangeTier = crtBillingOverrangeTier(total);
|
|
3715
|
+
const cell = document.createElement('button');
|
|
3716
|
+
cell.type = 'button';
|
|
3717
|
+
cell.className = 'billing-day-hour-cell';
|
|
3718
|
+
cell.dataset.level = overrangeTier ? 'overrange' : String(crtBillingHeatLevel(total, heatThresholds));
|
|
3719
|
+
if (overrangeTier) cell.dataset.overrange = String(overrangeTier);
|
|
3720
|
+
cell.dataset.hour = String(hourValue);
|
|
3721
|
+
cell.setAttribute('role', 'gridcell');
|
|
3722
|
+
cell.setAttribute('aria-label', `${String(hourValue).padStart(2, '0')}:00 to ${String(hourValue + 1).padStart(2, '0')}:00, ${formatCrtExactUsageValue(total)} total tokens, ${formatCrtExactUsageValue(cache)} cache tokens`);
|
|
3723
|
+
cell.tabIndex = index === billingSelectedHour ? 0 : -1;
|
|
3724
|
+
cell.addEventListener('click', () => selectHour(index));
|
|
3725
|
+
cell.addEventListener('mouseenter', () => selectHour(index));
|
|
3726
|
+
cell.addEventListener('focus', () => selectHour(index));
|
|
3727
|
+
cell.addEventListener('keydown', (event) => {
|
|
3728
|
+
if (!['ArrowLeft', 'ArrowRight'].includes(event.key)) return;
|
|
3729
|
+
event.preventDefault();
|
|
3730
|
+
event.stopPropagation();
|
|
3731
|
+
selectHour(Math.max(0, Math.min(hours.length - 1, index + (event.key === 'ArrowLeft' ? -1 : 1))), { focus: true });
|
|
3732
|
+
});
|
|
3733
|
+
hourStrip.appendChild(cell);
|
|
3734
|
+
});
|
|
3735
|
+
selectHour(billingSelectedHour);
|
|
3736
|
+
} else if (hourReadout) {
|
|
3737
|
+
hourReadout.textContent = billingDayDetailLoading ? 'READING HOURLY COORDINATES' : 'NO HOURLY ACTIVITY';
|
|
3738
|
+
hourReadout.removeAttribute('title');
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3159
3741
|
if (state) {
|
|
3160
|
-
state.classList.toggle('is-error', Boolean(billingDayDetailError));
|
|
3161
|
-
state.textContent = billingDayDetailError
|
|
3162
|
-
? '
|
|
3163
|
-
:
|
|
3164
|
-
? '
|
|
3165
|
-
:
|
|
3166
|
-
? '24 HOURLY BINS
|
|
3167
|
-
: selectedDetail
|
|
3168
|
-
? '
|
|
3169
|
-
:
|
|
3742
|
+
state.classList.toggle('is-error', Boolean(billingDayDetailError && !selectedDetail));
|
|
3743
|
+
state.textContent = billingDayDetailError && selectedDetail
|
|
3744
|
+
? '24 HOURLY BINS READY · STALE'
|
|
3745
|
+
: billingDayDetailError
|
|
3746
|
+
? 'DAY SIGNAL LOST'
|
|
3747
|
+
: billingDayDetailLoading && !selectedDetail
|
|
3748
|
+
? 'READING 24 HOURLY BINS'
|
|
3749
|
+
: selectedDetail && maximum > 0
|
|
3750
|
+
? '24 HOURLY BINS READY'
|
|
3751
|
+
: selectedDetail
|
|
3752
|
+
? 'NO HOURLY ACTIVITY'
|
|
3753
|
+
: 'SELECTED DAY DETAIL';
|
|
3170
3754
|
}
|
|
3171
3755
|
|
|
3172
3756
|
if (!shares) return;
|
|
@@ -3209,42 +3793,46 @@ function renderCrtBillingDayInsight() {
|
|
|
3209
3793
|
});
|
|
3210
3794
|
}
|
|
3211
3795
|
|
|
3212
|
-
function renderCrtBillingSelectedDay() {
|
|
3796
|
+
function renderCrtBillingSelectedDay({ preferSummary = false } = {}) {
|
|
3213
3797
|
const daily = billingSummary && billingSummary.daily;
|
|
3214
3798
|
const point = crtBillingDayPoint();
|
|
3799
|
+
const cachedEntry = billingDayDetailCache.get(billingSelectedDate);
|
|
3800
|
+
const selectedDetail = billingDayDetail && billingDayDetail.date === billingSelectedDate
|
|
3801
|
+
? billingDayDetail
|
|
3802
|
+
: cachedEntry && cachedEntry.detail || null;
|
|
3803
|
+
const displayDetail = preferSummary ? null : selectedDetail;
|
|
3804
|
+
const displayPoint = displayDetail && displayDetail.total || point;
|
|
3805
|
+
const displayProviders = displayDetail && displayDetail.providers || point && point.providers;
|
|
3806
|
+
const isToday = crtBillingSelectedDayIsCurrent(point);
|
|
3215
3807
|
const date = document.getElementById('billing-day-date');
|
|
3216
3808
|
const stateLabel = document.getElementById('billing-day-state');
|
|
3217
|
-
const total = document.getElementById('billing-day-total');
|
|
3218
|
-
const compactTotal = document.getElementById('billing-day-total-compact');
|
|
3219
|
-
const input = document.getElementById('billing-day-input');
|
|
3220
|
-
const output = document.getElementById('billing-day-output');
|
|
3221
|
-
const cacheRead = document.getElementById('billing-day-cache-read');
|
|
3222
|
-
const cacheWrite = document.getElementById('billing-day-cache-write');
|
|
3223
3809
|
const providers = document.getElementById('billing-day-providers');
|
|
3224
3810
|
if (date) date.textContent = crtBillingDayLabel(point && point.date);
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3811
|
+
updateCrtBillingTotalDisplay(displayPoint && displayPoint.totalTokens, {
|
|
3812
|
+
date: point && point.date || '',
|
|
3813
|
+
live: isToday,
|
|
3814
|
+
});
|
|
3815
|
+
const selectedDate = point && point.date || '';
|
|
3816
|
+
updateCrtBillingExactMetric('billing-day-input', displayPoint && displayPoint.inputTokens, { date: selectedDate, live: isToday });
|
|
3817
|
+
updateCrtBillingExactMetric('billing-day-output', displayPoint && displayPoint.outputTokens, { date: selectedDate, live: isToday });
|
|
3818
|
+
updateCrtBillingExactMetric('billing-day-cache-read', displayPoint && displayPoint.cacheReadTokens, { date: selectedDate, live: isToday });
|
|
3819
|
+
updateCrtBillingExactMetric('billing-day-cache-write', displayPoint && displayPoint.cacheWriteTokens, { date: selectedDate, live: isToday });
|
|
3231
3820
|
if (providers) {
|
|
3232
|
-
const providerTotals =
|
|
3821
|
+
const providerTotals = displayProviders ? Object.entries(displayProviders) : [];
|
|
3233
3822
|
providers.textContent = providerTotals
|
|
3234
3823
|
.map(([provider, usage]) => `${provider.toUpperCase()} ${formatCrtExactUsageValue(usage && usage.totalTokens)}`)
|
|
3235
3824
|
.join(' · ') || '--';
|
|
3236
3825
|
providers.title = providers.textContent;
|
|
3237
3826
|
}
|
|
3238
3827
|
if (stateLabel) {
|
|
3239
|
-
const
|
|
3240
|
-
const notes = [isToday ? 'PARTIAL DAY' : 'COMPLETE DAY', 'INCL CACHE'];
|
|
3828
|
+
const notes = isToday ? ['LIVE 5S', 'PARTIAL DAY', 'INCL CACHE'] : ['COMPLETE DAY', 'INCL CACHE'];
|
|
3241
3829
|
if (daily && daily.partial) notes.push('PARTIAL SOURCE');
|
|
3242
3830
|
if (point && Number(point.unattributedTokens) > 0) {
|
|
3243
3831
|
notes.push(`${formatCrtUsageValue(point.unattributedTokens)} UNCLASSIFIED`);
|
|
3244
3832
|
}
|
|
3245
3833
|
stateLabel.textContent = point ? notes.join(' · ') : 'LOCAL HISTORY';
|
|
3246
3834
|
}
|
|
3247
|
-
document.querySelectorAll('#billing-
|
|
3835
|
+
document.querySelectorAll('#billing-calendar-grid .billing-calendar-day').forEach((cell) => {
|
|
3248
3836
|
const selected = cell.dataset.date === billingSelectedDate;
|
|
3249
3837
|
cell.classList.toggle('selected', selected);
|
|
3250
3838
|
cell.setAttribute('aria-selected', selected ? 'true' : 'false');
|
|
@@ -3254,13 +3842,16 @@ function renderCrtBillingSelectedDay() {
|
|
|
3254
3842
|
|
|
3255
3843
|
function selectCrtBillingDay(dateValue, { focus = false } = {}) {
|
|
3256
3844
|
if (!crtBillingDayPoint(dateValue)) return false;
|
|
3845
|
+
cancelCrtBillingDayDetailRetry();
|
|
3257
3846
|
billingSelectedDate = dateValue;
|
|
3847
|
+
billingSelectedHour = null;
|
|
3848
|
+
const isToday = crtBillingSelectedDayIsCurrent(crtBillingDayPoint(dateValue));
|
|
3258
3849
|
billingDayDetail = billingDayDetailCache.get(dateValue)?.detail || null;
|
|
3259
3850
|
billingDayDetailError = '';
|
|
3260
|
-
renderCrtBillingSelectedDay();
|
|
3261
|
-
void loadCrtBillingDayDetail(dateValue);
|
|
3851
|
+
renderCrtBillingSelectedDay({ preferSummary: isToday });
|
|
3852
|
+
void loadCrtBillingDayDetail(dateValue, { force: isToday, live: isToday });
|
|
3262
3853
|
if (focus) {
|
|
3263
|
-
const cell = document.querySelector(`#billing-
|
|
3854
|
+
const cell = document.querySelector(`#billing-calendar-grid .billing-calendar-day[data-date="${dateValue}"]`);
|
|
3264
3855
|
if (cell) {
|
|
3265
3856
|
cell.focus({ preventScroll: true });
|
|
3266
3857
|
scrollCrtBillingSelectedDayIntoView();
|
|
@@ -3269,18 +3860,27 @@ function selectCrtBillingDay(dateValue, { focus = false } = {}) {
|
|
|
3269
3860
|
return true;
|
|
3270
3861
|
}
|
|
3271
3862
|
|
|
3272
|
-
|
|
3863
|
+
function cancelCrtBillingDayDetailRetry() {
|
|
3864
|
+
if (billingDayDetailRetryTimer !== null) {
|
|
3865
|
+
clearTimeout(billingDayDetailRetryTimer);
|
|
3866
|
+
billingDayDetailRetryTimer = null;
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
|
|
3870
|
+
async function loadCrtBillingDayDetail(dateValue, { force = false, live = false, retryCount = 0 } = {}) {
|
|
3273
3871
|
const date = String(dateValue || '').trim();
|
|
3274
3872
|
if (!crtBillingDayPoint(date)) return;
|
|
3873
|
+
if (retryCount === 0) cancelCrtBillingDayDetailRetry();
|
|
3275
3874
|
const cachedEntry = billingDayDetailCache.get(date);
|
|
3276
3875
|
const cached = cachedEntry && cachedEntry.detail;
|
|
3277
|
-
const
|
|
3876
|
+
const cacheMaxAge = live ? CRT_BILLING_LIVE_DAY_REFRESH_MS : CRT_BILLING_DAY_DETAIL_CACHE_MS;
|
|
3877
|
+
const cacheFresh = cachedEntry && Date.now() - cachedEntry.fetchedAt <= cacheMaxAge;
|
|
3278
3878
|
if (cached && cacheFresh && !force) {
|
|
3279
3879
|
if (billingSelectedDate === date) {
|
|
3280
3880
|
billingDayDetail = cached;
|
|
3281
3881
|
billingDayDetailLoading = false;
|
|
3282
3882
|
billingDayDetailError = '';
|
|
3283
|
-
|
|
3883
|
+
renderCrtBillingSelectedDay();
|
|
3284
3884
|
}
|
|
3285
3885
|
return;
|
|
3286
3886
|
}
|
|
@@ -3293,11 +3893,12 @@ async function loadCrtBillingDayDetail(dateValue, { force = false } = {}) {
|
|
|
3293
3893
|
billingDayDetailLoading = true;
|
|
3294
3894
|
billingDayDetailError = '';
|
|
3295
3895
|
if (billingSelectedDate === date) {
|
|
3296
|
-
billingDayDetail = null;
|
|
3896
|
+
if (!cached) billingDayDetail = null;
|
|
3297
3897
|
renderCrtBillingDayInsight();
|
|
3298
3898
|
}
|
|
3899
|
+
let shouldRetry = false;
|
|
3299
3900
|
try {
|
|
3300
|
-
const response = await fetch(farmingApiPath(`/usage/day?date=${encodeURIComponent(date)}`), {
|
|
3901
|
+
const response = await fetch(farmingApiPath(`/usage/day?date=${encodeURIComponent(date)}${live ? '&live=1' : ''}`), {
|
|
3301
3902
|
signal: controller.signal,
|
|
3302
3903
|
cache: 'no-store',
|
|
3303
3904
|
});
|
|
@@ -3306,18 +3907,44 @@ async function loadCrtBillingDayDetail(dateValue, { force = false } = {}) {
|
|
|
3306
3907
|
throw new Error(data && data.error ? data.error : `Usage day request failed (${response.status})`);
|
|
3307
3908
|
}
|
|
3308
3909
|
if (requestSequence !== billingDayDetailRequestSequence) return;
|
|
3910
|
+
const previousDetail = cached || (billingDayDetail && billingDayDetail.date === date ? billingDayDetail : null);
|
|
3911
|
+
const nextTotal = Math.max(0, Number(data.detail.total && data.detail.total.totalTokens) || 0);
|
|
3912
|
+
if (
|
|
3913
|
+
nextTotal > 0
|
|
3914
|
+
&& crtBillingDayDetailHasHourlyActivity(previousDetail)
|
|
3915
|
+
&& !crtBillingDayDetailHasHourlyActivity(data.detail)
|
|
3916
|
+
) {
|
|
3917
|
+
throw new Error('Usage day response omitted previously available hourly bins');
|
|
3918
|
+
}
|
|
3309
3919
|
billingDayDetailCache.set(date, { detail: data.detail, fetchedAt: Date.now() });
|
|
3310
3920
|
if (billingSelectedDate === date) billingDayDetail = data.detail;
|
|
3921
|
+
if (live) renderCrtBillingDaily();
|
|
3311
3922
|
} catch (error) {
|
|
3312
3923
|
if (controller.signal.aborted || requestSequence !== billingDayDetailRequestSequence) return;
|
|
3313
|
-
if (billingSelectedDate === date) {
|
|
3924
|
+
if (billingSelectedDate === date && retryCount < CRT_BILLING_DAY_DETAIL_MAX_RETRIES) {
|
|
3925
|
+
shouldRetry = true;
|
|
3926
|
+
} else if (billingSelectedDate === date) {
|
|
3314
3927
|
billingDayDetailError = error instanceof Error ? error.message : 'Failed to load selected day';
|
|
3315
3928
|
}
|
|
3316
3929
|
} finally {
|
|
3317
3930
|
if (requestSequence === billingDayDetailRequestSequence) {
|
|
3318
|
-
billingDayDetailLoading = false;
|
|
3319
3931
|
billingDayDetailAbortController = null;
|
|
3320
|
-
if (
|
|
3932
|
+
if (shouldRetry) {
|
|
3933
|
+
billingDayDetailLoading = true;
|
|
3934
|
+
cancelCrtBillingDayDetailRetry();
|
|
3935
|
+
const retryDelay = CRT_BILLING_DAY_DETAIL_RETRY_MS * (2 ** retryCount);
|
|
3936
|
+
billingDayDetailRetryTimer = setTimeout(() => {
|
|
3937
|
+
billingDayDetailRetryTimer = null;
|
|
3938
|
+
if (billingSelectedDate !== date || crtMainView !== 'billing') {
|
|
3939
|
+
billingDayDetailLoading = false;
|
|
3940
|
+
return;
|
|
3941
|
+
}
|
|
3942
|
+
void loadCrtBillingDayDetail(date, { force: true, live, retryCount: retryCount + 1 });
|
|
3943
|
+
}, retryDelay);
|
|
3944
|
+
} else {
|
|
3945
|
+
billingDayDetailLoading = false;
|
|
3946
|
+
}
|
|
3947
|
+
if (billingSelectedDate === date) renderCrtBillingSelectedDay();
|
|
3321
3948
|
}
|
|
3322
3949
|
}
|
|
3323
3950
|
}
|
|
@@ -3325,7 +3952,7 @@ async function loadCrtBillingDayDetail(dateValue, { force = false } = {}) {
|
|
|
3325
3952
|
function scrollCrtBillingSelectedDayIntoView() {
|
|
3326
3953
|
const scroll = document.getElementById('billing-daily-scroll');
|
|
3327
3954
|
const cell = billingSelectedDate
|
|
3328
|
-
? document.querySelector(`#billing-
|
|
3955
|
+
? document.querySelector(`#billing-calendar-grid .billing-calendar-day[data-date="${billingSelectedDate}"]`)
|
|
3329
3956
|
: null;
|
|
3330
3957
|
if (!scroll || !cell) return;
|
|
3331
3958
|
const left = cell.offsetLeft;
|
|
@@ -3355,7 +3982,19 @@ function renderCrtBillingDaily(summary = billingSummary) {
|
|
|
3355
3982
|
const element = document.getElementById(id);
|
|
3356
3983
|
if (element) element.textContent = formatCrtUsageValue(value);
|
|
3357
3984
|
};
|
|
3358
|
-
|
|
3985
|
+
const todayDetail = daily && billingDayDetailCache.get(daily.endDate)?.detail;
|
|
3986
|
+
const todayTokens = todayDetail && todayDetail.total ? todayDetail.total.totalTokens : totals.todayTokens;
|
|
3987
|
+
updateCrtBillingAnimatedMetric('billing-today-total', todayTokens, {
|
|
3988
|
+
date: daily && daily.endDate || '',
|
|
3989
|
+
live: true,
|
|
3990
|
+
write: (displayed, target) => {
|
|
3991
|
+
const element = document.getElementById('billing-today-total');
|
|
3992
|
+
if (!element) return;
|
|
3993
|
+
element.textContent = formatCrtUsageValue(displayed);
|
|
3994
|
+
element.dataset.displayedValue = displayed === null ? '' : String(displayed);
|
|
3995
|
+
element.dataset.targetValue = target === null ? '' : String(target);
|
|
3996
|
+
},
|
|
3997
|
+
});
|
|
3359
3998
|
setValue('billing-7d-total', totals.sevenDayTokens);
|
|
3360
3999
|
setValue('billing-30d-total', totals.thirtyDayTokens);
|
|
3361
4000
|
setValue('billing-period-total', totals.periodTokens);
|
|
@@ -3376,12 +4015,9 @@ function renderCrtBillingDaily(summary = billingSummary) {
|
|
|
3376
4015
|
: 'LOCAL TIME';
|
|
3377
4016
|
}
|
|
3378
4017
|
|
|
3379
|
-
const
|
|
3380
|
-
const
|
|
3381
|
-
|
|
3382
|
-
const guides = document.getElementById('billing-log-guides');
|
|
3383
|
-
const activity = document.getElementById('billing-activity-strip');
|
|
3384
|
-
if (!bars || !xAxis || !yAxis || !guides || !activity) return;
|
|
4018
|
+
const calendar = document.getElementById('billing-calendar-grid');
|
|
4019
|
+
const months = document.getElementById('billing-calendar-months');
|
|
4020
|
+
if (!calendar || !months) return;
|
|
3385
4021
|
const signature = points.map(point => [
|
|
3386
4022
|
point.date,
|
|
3387
4023
|
point.totalTokens,
|
|
@@ -3390,95 +4026,62 @@ function renderCrtBillingDaily(summary = billingSummary) {
|
|
|
3390
4026
|
].join(':')).join('|');
|
|
3391
4027
|
if (signature !== billingDailyRenderSignature) {
|
|
3392
4028
|
billingDailyRenderSignature = signature;
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
const
|
|
3400
|
-
const
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
const
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
guides.appendChild(guide);
|
|
3409
|
-
|
|
3410
|
-
const axisLabel = document.createElement('span');
|
|
3411
|
-
axisLabel.className = 'billing-y-axis-label';
|
|
3412
|
-
axisLabel.style.bottom = `${position}%`;
|
|
3413
|
-
axisLabel.textContent = formatCrtUsageValue(value);
|
|
3414
|
-
yAxis.appendChild(axisLabel);
|
|
4029
|
+
calendar.replaceChildren();
|
|
4030
|
+
months.replaceChildren();
|
|
4031
|
+
|
|
4032
|
+
const chartPoints = points.slice(-(52 * 7));
|
|
4033
|
+
const heatThresholds = crtBillingHeatThresholds(chartPoints.map(point => point && point.totalTokens));
|
|
4034
|
+
const firstDate = parseCrtBillingDate(chartPoints[0] && chartPoints[0].date);
|
|
4035
|
+
const leadingDays = firstDate ? (firstDate.getDay() + 6) % 7 : 0;
|
|
4036
|
+
const weekCount = Math.max(1, Math.ceil((leadingDays + chartPoints.length) / 7));
|
|
4037
|
+
calendar.style.setProperty('--billing-calendar-weeks', String(weekCount));
|
|
4038
|
+
months.style.setProperty('--billing-calendar-weeks', String(weekCount));
|
|
4039
|
+
Array.from({ length: leadingDays }).forEach(() => {
|
|
4040
|
+
const spacer = document.createElement('span');
|
|
4041
|
+
spacer.className = 'billing-calendar-spacer';
|
|
4042
|
+
spacer.setAttribute('aria-hidden', 'true');
|
|
4043
|
+
calendar.appendChild(spacer);
|
|
3415
4044
|
});
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
floorLabel.style.bottom = '0';
|
|
3419
|
-
floorLabel.textContent = `≤${formatCrtUsageValue(minimum)}`;
|
|
3420
|
-
yAxis.appendChild(floorLabel);
|
|
3421
|
-
|
|
3422
|
-
bars.style.setProperty('--billing-chart-days', String(Math.max(1, chartPoints.length)));
|
|
3423
|
-
xAxis.style.setProperty('--billing-chart-days', String(Math.max(1, chartPoints.length)));
|
|
4045
|
+
|
|
4046
|
+
const monthLabels = Array.from({ length: weekCount }, () => '');
|
|
3424
4047
|
chartPoints.forEach((point, index) => {
|
|
3425
4048
|
const pointDate = parseCrtBillingDate(point.date);
|
|
3426
|
-
const
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
const isHalfMonth = pointDate && [1, 15].includes(pointDate.getDate());
|
|
3430
|
-
if (pointDate && (isFirst || isLast || isHalfMonth)) {
|
|
3431
|
-
const month = pointDate.toLocaleDateString('en-US', { month: 'short' }).toUpperCase();
|
|
3432
|
-
axisTick.textContent = `${month} ${String(pointDate.getDate()).padStart(2, '0')}`;
|
|
3433
|
-
axisTick.className = `has-label${isLast ? ' is-end' : ''}`;
|
|
4049
|
+
const weekIndex = Math.floor((leadingDays + index) / 7);
|
|
4050
|
+
if (pointDate && (index === 0 || pointDate.getDate() === 1) && !monthLabels[weekIndex]) {
|
|
4051
|
+
monthLabels[weekIndex] = pointDate.toLocaleDateString('en-US', { month: 'short' }).toUpperCase();
|
|
3434
4052
|
}
|
|
3435
|
-
xAxis.appendChild(axisTick);
|
|
3436
|
-
|
|
3437
4053
|
const total = Math.max(0, Number(point.totalTokens) || 0);
|
|
3438
4054
|
const cache = Math.min(total, Math.max(0, Number(point.cacheReadTokens) || 0)
|
|
3439
4055
|
+ Math.max(0, Number(point.cacheWriteTokens) || 0));
|
|
3440
|
-
const
|
|
3441
|
-
const
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
bar.style.height = `${crtBillingLogPosition(total, minimum, maximum)}%`;
|
|
3456
|
-
if (direct > 0) {
|
|
3457
|
-
const directSegment = document.createElement('span');
|
|
3458
|
-
directSegment.className = 'billing-daily-direct';
|
|
3459
|
-
directSegment.style.height = `${direct / total * 100}%`;
|
|
3460
|
-
bar.appendChild(directSegment);
|
|
3461
|
-
}
|
|
3462
|
-
if (cache > 0) {
|
|
3463
|
-
const cacheSegment = document.createElement('span');
|
|
3464
|
-
cacheSegment.className = 'billing-daily-cache';
|
|
3465
|
-
cacheSegment.style.height = `${cache / total * 100}%`;
|
|
3466
|
-
bar.appendChild(cacheSegment);
|
|
3467
|
-
}
|
|
3468
|
-
column.appendChild(bar);
|
|
3469
|
-
}
|
|
3470
|
-
bars.appendChild(column);
|
|
4056
|
+
const overrangeTier = crtBillingOverrangeTier(total);
|
|
4057
|
+
const overrangeLabel = crtBillingOverrangeLabel(overrangeTier);
|
|
4058
|
+
const day = document.createElement('button');
|
|
4059
|
+
day.type = 'button';
|
|
4060
|
+
day.className = 'billing-calendar-day';
|
|
4061
|
+
day.dataset.date = point.date;
|
|
4062
|
+
day.dataset.level = overrangeTier ? 'overrange' : String(crtBillingHeatLevel(total, heatThresholds));
|
|
4063
|
+
if (overrangeTier) day.dataset.overrange = String(overrangeTier);
|
|
4064
|
+
day.setAttribute('role', 'gridcell');
|
|
4065
|
+
day.setAttribute('aria-label', `${point.date}: ${formatCrtExactUsageValue(total)} tokens, ${formatCrtExactUsageValue(cache)} cache tokens${overrangeLabel ? `, ${overrangeLabel}` : ''}`);
|
|
4066
|
+
day.setAttribute('aria-selected', 'false');
|
|
4067
|
+
day.tabIndex = -1;
|
|
4068
|
+
day.title = `${point.date} · ${formatCrtExactUsageValue(total)} total · ${formatCrtExactUsageValue(cache)} cache${overrangeLabel ? ` · ${overrangeLabel}` : ''}`;
|
|
4069
|
+
day.addEventListener('click', () => selectCrtBillingDay(point.date));
|
|
4070
|
+
calendar.appendChild(day);
|
|
3471
4071
|
});
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
tick.className = `billing-activity-tick${total > 0 ? ' is-active' : ''}${total >= 1_000_000_000 ? ' is-billion' : ''}`;
|
|
3479
|
-
tick.title = `${point.date} · ${formatCrtExactUsageValue(total)} tokens`;
|
|
3480
|
-
activity.appendChild(tick);
|
|
4072
|
+
const trailingDays = weekCount * 7 - leadingDays - chartPoints.length;
|
|
4073
|
+
Array.from({ length: trailingDays }).forEach(() => {
|
|
4074
|
+
const spacer = document.createElement('span');
|
|
4075
|
+
spacer.className = 'billing-calendar-spacer';
|
|
4076
|
+
spacer.setAttribute('aria-hidden', 'true');
|
|
4077
|
+
calendar.appendChild(spacer);
|
|
3481
4078
|
});
|
|
4079
|
+
monthLabels.forEach((label) => {
|
|
4080
|
+
const month = document.createElement('span');
|
|
4081
|
+
month.textContent = label;
|
|
4082
|
+
months.appendChild(month);
|
|
4083
|
+
});
|
|
4084
|
+
calendar.setAttribute('aria-label', `${chartPoints.length}-day token activity: ${activeDays} active days, ${billionDays} days at or above one billion tokens`);
|
|
3482
4085
|
|
|
3483
4086
|
if (!billingSelectedDate || !points.some(point => point.date === billingSelectedDate)) {
|
|
3484
4087
|
billingSelectedDate = daily && daily.endDate || points.at(-1)?.date || '';
|
|
@@ -3765,10 +4368,6 @@ async function loadCrtBilling({ fresh = false } = {}) {
|
|
|
3765
4368
|
billingAbortController = controller;
|
|
3766
4369
|
billingLoading = true;
|
|
3767
4370
|
billingError = '';
|
|
3768
|
-
if (fresh) {
|
|
3769
|
-
billingDayDetailCache.clear();
|
|
3770
|
-
billingDayDetail = null;
|
|
3771
|
-
}
|
|
3772
4371
|
renderCrtBilling();
|
|
3773
4372
|
try {
|
|
3774
4373
|
const response = await fetch(farmingApiPath(`/usage${fresh ? '?fresh=1' : ''}`), {
|
|
@@ -3788,7 +4387,10 @@ async function loadCrtBilling({ fresh = false } = {}) {
|
|
|
3788
4387
|
billingAbortController = null;
|
|
3789
4388
|
renderCrtBilling();
|
|
3790
4389
|
if (!billingError && billingSelectedDate) {
|
|
3791
|
-
void loadCrtBillingDayDetail(billingSelectedDate, {
|
|
4390
|
+
void loadCrtBillingDayDetail(billingSelectedDate, {
|
|
4391
|
+
force: fresh,
|
|
4392
|
+
live: crtBillingSelectedDayIsCurrent(),
|
|
4393
|
+
});
|
|
3792
4394
|
}
|
|
3793
4395
|
}
|
|
3794
4396
|
}
|
|
@@ -3799,6 +4401,10 @@ function stopCrtBillingRefresh({ abort = false } = {}) {
|
|
|
3799
4401
|
clearInterval(billingRefreshTimer);
|
|
3800
4402
|
billingRefreshTimer = null;
|
|
3801
4403
|
}
|
|
4404
|
+
if (billingLiveDayRefreshTimer !== null) {
|
|
4405
|
+
clearInterval(billingLiveDayRefreshTimer);
|
|
4406
|
+
billingLiveDayRefreshTimer = null;
|
|
4407
|
+
}
|
|
3802
4408
|
if (abort && billingAbortController) {
|
|
3803
4409
|
billingRequestSequence += 1;
|
|
3804
4410
|
billingAbortController.abort();
|
|
@@ -3811,6 +4417,11 @@ function stopCrtBillingRefresh({ abort = false } = {}) {
|
|
|
3811
4417
|
billingDayDetailAbortController = null;
|
|
3812
4418
|
billingDayDetailLoading = false;
|
|
3813
4419
|
}
|
|
4420
|
+
if (abort) {
|
|
4421
|
+
cancelCrtBillingDayDetailRetry();
|
|
4422
|
+
cancelCrtBillingTotalAnimation();
|
|
4423
|
+
cancelCrtBillingMetricAnimations();
|
|
4424
|
+
}
|
|
3814
4425
|
}
|
|
3815
4426
|
|
|
3816
4427
|
function startCrtBillingRefresh() {
|
|
@@ -3818,6 +4429,16 @@ function startCrtBillingRefresh() {
|
|
|
3818
4429
|
billingRefreshTimer = setInterval(() => {
|
|
3819
4430
|
if (crtMainView === 'billing' && document.visibilityState !== 'hidden') void loadCrtBilling();
|
|
3820
4431
|
}, CRT_BILLING_REFRESH_MS);
|
|
4432
|
+
billingLiveDayRefreshTimer = setInterval(() => {
|
|
4433
|
+
if (
|
|
4434
|
+
crtMainView !== 'billing'
|
|
4435
|
+
|| billingMode !== 'days'
|
|
4436
|
+
|| document.visibilityState === 'hidden'
|
|
4437
|
+
|| billingDayDetailLoading
|
|
4438
|
+
|| !crtBillingSelectedDayIsCurrent()
|
|
4439
|
+
) return;
|
|
4440
|
+
void loadCrtBillingDayDetail(billingSelectedDate, { force: true, live: true });
|
|
4441
|
+
}, CRT_BILLING_LIVE_DAY_REFRESH_MS);
|
|
3821
4442
|
}
|
|
3822
4443
|
|
|
3823
4444
|
function refreshCrtBilling() {
|
|
@@ -4140,7 +4761,7 @@ function needsMainAgent(currentState = state) {
|
|
|
4140
4761
|
const mainAgent = currentState && currentState.mainAgentId
|
|
4141
4762
|
? currentState.agents.find((agent) => agent.id === currentState.mainAgentId)
|
|
4142
4763
|
: null;
|
|
4143
|
-
return !currentState || !currentState.mainAgentId || (mainAgent
|
|
4764
|
+
return !currentState || !currentState.mainAgentId || !isCrtLiveAgent(mainAgent);
|
|
4144
4765
|
}
|
|
4145
4766
|
|
|
4146
4767
|
function getDefaultWorkspaceForDialog(asMainAgent) {
|
|
@@ -4337,6 +4958,7 @@ function refreshWorkspaceMemoryUI() {
|
|
|
4337
4958
|
function seedWorkspaceInput() {
|
|
4338
4959
|
const workspaceInput = document.getElementById('workspace-input');
|
|
4339
4960
|
if (!workspaceInput) return;
|
|
4961
|
+
hideWorkspaceDirectoryPrompt({ focusInput: false });
|
|
4340
4962
|
workspaceInput.value = '';
|
|
4341
4963
|
workspaceInput.placeholder = pendingMainAgentLaunch
|
|
4342
4964
|
? formatWorkspaceForDisplay(getDefaultWorkspaceForDialog(true))
|
|
@@ -4400,13 +5022,129 @@ function setupWorkspaceHistoryControls() {
|
|
|
4400
5022
|
}
|
|
4401
5023
|
|
|
4402
5024
|
async function confirmStartAgent() {
|
|
4403
|
-
if (waitingForAgent || selectedAgentIndex === null || selectedAgentIndex < 0 || selectedAgentIndex >= agents.length) return;
|
|
5025
|
+
if (waitingForAgent || workspaceDirectoryPrompt || selectedAgentIndex === null || selectedAgentIndex < 0 || selectedAgentIndex >= agents.length) return;
|
|
4404
5026
|
|
|
4405
5027
|
const agent = agents[selectedAgentIndex];
|
|
4406
5028
|
const workspaceInput = normalizeWorkspaceValue(document.getElementById('workspace-input').value);
|
|
4407
5029
|
const asMainAgent = pendingMainAgentLaunch;
|
|
4408
5030
|
const workspaceToUse = resolveWorkspaceToStart(workspaceInput, asMainAgent);
|
|
4409
5031
|
|
|
5032
|
+
if (asMainAgent || !workspaceToUse) {
|
|
5033
|
+
await startCrtAgent(agent, workspaceToUse, asMainAgent);
|
|
5034
|
+
return;
|
|
5035
|
+
}
|
|
5036
|
+
|
|
5037
|
+
try {
|
|
5038
|
+
const result = await prepareCrtWorkspaceDirectory(workspaceToUse, false);
|
|
5039
|
+
if (result.status === 'ready') {
|
|
5040
|
+
await startCrtAgent(agent, result.workspace, false);
|
|
5041
|
+
return;
|
|
5042
|
+
}
|
|
5043
|
+
showWorkspaceDirectoryPrompt(result.status === 'missing' ? 'confirm' : 'error', {
|
|
5044
|
+
workspace: result.workspace || workspaceToUse,
|
|
5045
|
+
code: result.code || ''
|
|
5046
|
+
});
|
|
5047
|
+
} catch {
|
|
5048
|
+
showWorkspaceDirectoryPrompt('error', { workspace: workspaceToUse, code: '' });
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
5051
|
+
|
|
5052
|
+
async function prepareCrtWorkspaceDirectory(workspace, create) {
|
|
5053
|
+
const response = await fetch(farmingApiPath('/workspaces/prepare'), {
|
|
5054
|
+
method: 'POST',
|
|
5055
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5056
|
+
body: JSON.stringify({ workspace, create: create === true })
|
|
5057
|
+
});
|
|
5058
|
+
const result = await response.json().catch(() => null);
|
|
5059
|
+
if (result && typeof result.status === 'string' && typeof result.workspace === 'string') {
|
|
5060
|
+
return result;
|
|
5061
|
+
}
|
|
5062
|
+
throw new Error(result && result.message ? result.message : `Failed to prepare workspace (${response.status})`);
|
|
5063
|
+
}
|
|
5064
|
+
|
|
5065
|
+
function showWorkspaceDirectoryPrompt(kind, { workspace, code = '' }) {
|
|
5066
|
+
const prompt = document.getElementById('workspace-directory-prompt');
|
|
5067
|
+
const input = document.getElementById('workspace-input');
|
|
5068
|
+
const history = document.getElementById('workspace-history');
|
|
5069
|
+
const startActions = document.getElementById('workspace-start-actions');
|
|
5070
|
+
const createButton = document.getElementById('workspace-directory-create');
|
|
5071
|
+
const cancelButton = document.getElementById('workspace-directory-cancel');
|
|
5072
|
+
const errorBackButton = document.getElementById('workspace-directory-error-back');
|
|
5073
|
+
if (!prompt || !input || !createButton || !cancelButton || !errorBackButton) return;
|
|
5074
|
+
|
|
5075
|
+
workspaceDirectoryPrompt = { kind, workspace, code };
|
|
5076
|
+
const isError = kind === 'error';
|
|
5077
|
+
const isCreating = kind === 'creating';
|
|
5078
|
+
prompt.hidden = false;
|
|
5079
|
+
prompt.classList.toggle('error', isError);
|
|
5080
|
+
input.disabled = true;
|
|
5081
|
+
if (history) history.style.display = 'none';
|
|
5082
|
+
if (startActions) startActions.style.display = 'none';
|
|
5083
|
+
|
|
5084
|
+
document.getElementById('workspace-directory-prompt-icon').textContent = isError ? '[!]' : '[+]';
|
|
5085
|
+
document.getElementById('workspace-directory-prompt-title').textContent = isError
|
|
5086
|
+
? 'Could not create workspace'
|
|
5087
|
+
: isCreating
|
|
5088
|
+
? 'Creating workspace...'
|
|
5089
|
+
: 'Create this workspace?';
|
|
5090
|
+
document.getElementById('workspace-directory-prompt-description').textContent = isError
|
|
5091
|
+
? code === 'workspace-create-forbidden'
|
|
5092
|
+
? 'Farming does not have permission to create this directory. Choose another location or update the parent directory permissions.'
|
|
5093
|
+
: 'Farming could not create this directory. Check the path and try again.'
|
|
5094
|
+
: 'This directory does not exist yet. Farming can create it and start the Agent there.';
|
|
5095
|
+
document.getElementById('workspace-directory-prompt-path').textContent = formatWorkspaceForDisplay(workspace);
|
|
5096
|
+
|
|
5097
|
+
createButton.hidden = isError;
|
|
5098
|
+
cancelButton.hidden = isError;
|
|
5099
|
+
errorBackButton.hidden = !isError;
|
|
5100
|
+
createButton.disabled = isCreating;
|
|
5101
|
+
cancelButton.disabled = isCreating;
|
|
5102
|
+
createButton.textContent = isCreating ? 'Creating...' : 'Create & Start [Enter]';
|
|
5103
|
+
clearCrtNavigationSelection();
|
|
5104
|
+
const primary = isError ? errorBackButton : createButton;
|
|
5105
|
+
if (!isCreating) {
|
|
5106
|
+
setCrtNavigationSelection(primary);
|
|
5107
|
+
primary.focus();
|
|
5108
|
+
}
|
|
5109
|
+
}
|
|
5110
|
+
|
|
5111
|
+
function hideWorkspaceDirectoryPrompt({ focusInput = true } = {}) {
|
|
5112
|
+
const prompt = document.getElementById('workspace-directory-prompt');
|
|
5113
|
+
const input = document.getElementById('workspace-input');
|
|
5114
|
+
const startActions = document.getElementById('workspace-start-actions');
|
|
5115
|
+
workspaceDirectoryPrompt = null;
|
|
5116
|
+
if (prompt) prompt.hidden = true;
|
|
5117
|
+
if (input) input.disabled = false;
|
|
5118
|
+
if (startActions) startActions.style.display = 'block';
|
|
5119
|
+
renderWorkspaceHistoryUI();
|
|
5120
|
+
if (focusInput && input) {
|
|
5121
|
+
input.focus();
|
|
5122
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
|
5123
|
+
}
|
|
5124
|
+
}
|
|
5125
|
+
|
|
5126
|
+
async function createCrtWorkspaceAndStart() {
|
|
5127
|
+
if (!workspaceDirectoryPrompt || workspaceDirectoryPrompt.kind !== 'confirm') return;
|
|
5128
|
+
const target = workspaceDirectoryPrompt.workspace;
|
|
5129
|
+
const agent = selectedAgentIndex === null ? null : agents[selectedAgentIndex];
|
|
5130
|
+
if (!agent) return;
|
|
5131
|
+
showWorkspaceDirectoryPrompt('creating', { workspace: target });
|
|
5132
|
+
try {
|
|
5133
|
+
const result = await prepareCrtWorkspaceDirectory(target, true);
|
|
5134
|
+
if (result.status === 'created' || result.status === 'ready') {
|
|
5135
|
+
await startCrtAgent(agent, result.workspace, false);
|
|
5136
|
+
return;
|
|
5137
|
+
}
|
|
5138
|
+
showWorkspaceDirectoryPrompt('error', {
|
|
5139
|
+
workspace: result.workspace || target,
|
|
5140
|
+
code: result.code || ''
|
|
5141
|
+
});
|
|
5142
|
+
} catch {
|
|
5143
|
+
showWorkspaceDirectoryPrompt('error', { workspace: target, code: '' });
|
|
5144
|
+
}
|
|
5145
|
+
}
|
|
5146
|
+
|
|
5147
|
+
async function startCrtAgent(agent, workspaceToUse, asMainAgent) {
|
|
4410
5148
|
console.log('Starting agent:', agent.name, 'workspace:', workspaceToUse || 'default');
|
|
4411
5149
|
|
|
4412
5150
|
waitingForAgent = true;
|
|
@@ -4436,7 +5174,10 @@ async function confirmStartAgent() {
|
|
|
4436
5174
|
}));
|
|
4437
5175
|
}
|
|
4438
5176
|
|
|
5177
|
+
globalThis.createCrtWorkspaceAndStart = createCrtWorkspaceAndStart;
|
|
5178
|
+
|
|
4439
5179
|
function backToAgentList() {
|
|
5180
|
+
hideWorkspaceDirectoryPrompt({ focusInput: false });
|
|
4440
5181
|
clearCrtNavigationSelection();
|
|
4441
5182
|
selectedAgentIndex = null;
|
|
4442
5183
|
document.getElementById('agent-list').style.display = 'block';
|
|
@@ -4449,6 +5190,7 @@ function selectAgent(index) {
|
|
|
4449
5190
|
if (index < 0 || index >= agents.length) return;
|
|
4450
5191
|
|
|
4451
5192
|
const agent = agents[index];
|
|
5193
|
+
hideWorkspaceDirectoryPrompt({ focusInput: false });
|
|
4452
5194
|
|
|
4453
5195
|
console.log('Selected agent:', agent.name);
|
|
4454
5196
|
clearCrtNavigationSelection();
|
|
@@ -4478,10 +5220,11 @@ function requestedCrtAgentId(search = typeof window !== 'undefined' ? window.loc
|
|
|
4478
5220
|
function openCrtAgentDeeplinkIfReady() {
|
|
4479
5221
|
if (didApplyAgentDeeplink || !state) return false;
|
|
4480
5222
|
didApplyAgentDeeplink = true;
|
|
5223
|
+
crtReadingAnchorApi()?.importFromSearch(window.location.search);
|
|
4481
5224
|
|
|
4482
5225
|
const agentId = requestedCrtAgentId();
|
|
4483
5226
|
const agent = agentId
|
|
4484
|
-
? state.agents.find((candidate) => candidate.id === agentId && candidate
|
|
5227
|
+
? state.agents.find((candidate) => candidate.id === agentId && isCrtLiveAgent(candidate))
|
|
4485
5228
|
: null;
|
|
4486
5229
|
if (!agent) return false;
|
|
4487
5230
|
|
|
@@ -4505,13 +5248,25 @@ function connect() {
|
|
|
4505
5248
|
socket.onopen = () => {
|
|
4506
5249
|
if (ws !== socket) return;
|
|
4507
5250
|
console.log('Connected to server');
|
|
5251
|
+
socket.send(JSON.stringify({ type: 'protocol-hello', protocolVersion: CRT_PROTOCOL_VERSION }));
|
|
4508
5252
|
const activeAgentId = isCrtSessionOpen() ? focusedAgentId : null;
|
|
4509
5253
|
getSessionClient()?.focusAgent(activeAgentId, {
|
|
4510
5254
|
streamScope: 'focused',
|
|
4511
5255
|
previewScope: activeAgentId ? 'none' : 'all',
|
|
4512
5256
|
});
|
|
4513
5257
|
if (activeAgentId && terminal) {
|
|
4514
|
-
|
|
5258
|
+
if (crtTerminalReplication) {
|
|
5259
|
+
clearPendingCrtTerminalFitResize(crtTerminalReplication);
|
|
5260
|
+
crtTerminalReplication.lastResizeCols = null;
|
|
5261
|
+
crtTerminalReplication.lastResizeRows = null;
|
|
5262
|
+
if (crtTerminalReplication.checkpointRetryTimer) {
|
|
5263
|
+
clearTimeout(crtTerminalReplication.checkpointRetryTimer);
|
|
5264
|
+
crtTerminalReplication.checkpointRetryTimer = null;
|
|
5265
|
+
}
|
|
5266
|
+
TERMINAL_REPLAY.resetRecovery(crtTerminalReplication.replayState);
|
|
5267
|
+
TERMINAL_REPLAY.beginRecovery(crtTerminalReplication.replayState);
|
|
5268
|
+
requestCrtTerminalReplay();
|
|
5269
|
+
}
|
|
4515
5270
|
}
|
|
4516
5271
|
loadAgents();
|
|
4517
5272
|
};
|
|
@@ -4519,9 +5274,20 @@ function connect() {
|
|
|
4519
5274
|
socket.onmessage = (event) => {
|
|
4520
5275
|
if (ws !== socket) return;
|
|
4521
5276
|
const data = JSON.parse(event.data);
|
|
5277
|
+
if (data.type === 'protocol-hello') {
|
|
5278
|
+
if (data.protocolVersion !== CRT_PROTOCOL_VERSION) {
|
|
5279
|
+
socket.close(4002, `Unsupported Farming protocol version ${data.protocolVersion}`);
|
|
5280
|
+
}
|
|
5281
|
+
return;
|
|
5282
|
+
}
|
|
5283
|
+
if (data.type === 'protocol-error') {
|
|
5284
|
+
console.error(data.message || 'Farming protocol error');
|
|
5285
|
+
return;
|
|
5286
|
+
}
|
|
4522
5287
|
if (data.type === 'state') {
|
|
4523
5288
|
const prevAgentCount = state ? state.agents.length : 0;
|
|
4524
5289
|
state = data.state;
|
|
5290
|
+
pruneCrtStructuredPreviews(state);
|
|
4525
5291
|
const activeAgentIds = new Set(state.agents.map((agent) => agent.id));
|
|
4526
5292
|
terminalPreviewSnapshots.forEach((_snapshot, agentId) => {
|
|
4527
5293
|
if (!activeAgentIds.has(agentId)) terminalPreviewSnapshots.delete(agentId);
|
|
@@ -4532,6 +5298,7 @@ function connect() {
|
|
|
4532
5298
|
}
|
|
4533
5299
|
});
|
|
4534
5300
|
const dashboardRendered = renderCrtDashboardIfNeeded();
|
|
5301
|
+
scheduleCrtRenderedStructuredPreviews();
|
|
4535
5302
|
if (dashboardRendered && crtMainView === 'history') renderCrtHistory();
|
|
4536
5303
|
if (crtMainView === 'search') renderCrtSearch();
|
|
4537
5304
|
generateKeyMap();
|
|
@@ -4558,6 +5325,14 @@ function connect() {
|
|
|
4558
5325
|
openCrtAgentDeeplinkIfReady();
|
|
4559
5326
|
} else if (data.type === 'agent-started') {
|
|
4560
5327
|
selectCrtStartedAgent(data.agentId);
|
|
5328
|
+
} else if (data.type === 'agent-update') {
|
|
5329
|
+
const update = data.update;
|
|
5330
|
+
const agent = update && state && state.agents.find(candidate => candidate.id === update.agentId);
|
|
5331
|
+
if (agent && update.patch && typeof update.patch === 'object') {
|
|
5332
|
+
Object.assign(agent, update.patch);
|
|
5333
|
+
renderCrtDashboardIfNeeded();
|
|
5334
|
+
if (agent.id === focusedAgentId) updateCrtRuntimeSwitchControl(agent);
|
|
5335
|
+
}
|
|
4561
5336
|
} else if (data.type === 'session-preview') {
|
|
4562
5337
|
const preview = data.preview;
|
|
4563
5338
|
if (preview && preview.agentId) {
|
|
@@ -4573,11 +5348,16 @@ function connect() {
|
|
|
4573
5348
|
agent.previewRows = preview.rows || agent.previewRows;
|
|
4574
5349
|
agent.previewSnapshot = preview.previewSnapshot || null;
|
|
4575
5350
|
if (preview.terminalStatus) agent.terminalStatus = preview.terminalStatus;
|
|
5351
|
+
if (preview.runtimeObservation) agent.runtimeObservation = preview.runtimeObservation;
|
|
4576
5352
|
if (isCrtSessionOpen()) dashboardRenderDeferred = true;
|
|
4577
5353
|
else scheduleCrtPreviewCardRender(agent, previousSnapshot, previousText, previewChanged);
|
|
4578
5354
|
}
|
|
4579
5355
|
}
|
|
4580
5356
|
} else if (data.type === 'session-output') {
|
|
5357
|
+
if (crtTerminalReplication) {
|
|
5358
|
+
handleCrtTerminalStream(data.stream);
|
|
5359
|
+
return;
|
|
5360
|
+
}
|
|
4581
5361
|
const runtime = getSessionRuntime();
|
|
4582
5362
|
const sessionToken = runtime ? runtime.getSessionToken() : 0;
|
|
4583
5363
|
const runtimeResult = runtime ? runtime.handleStreamMessage(data.stream) : null;
|
|
@@ -4596,10 +5376,20 @@ function connect() {
|
|
|
4596
5376
|
setSessionOutputLength(getSessionOutputLength() + patch.nextLengthDelta);
|
|
4597
5377
|
}
|
|
4598
5378
|
}
|
|
5379
|
+
} else if (data.type === 'agent-activity') {
|
|
5380
|
+
const activity = data.activity;
|
|
5381
|
+
const agent = activity && state && state.agents.find((candidate) => candidate.id === activity.agentId);
|
|
5382
|
+
if (agent) {
|
|
5383
|
+
Object.assign(agent, activity);
|
|
5384
|
+
renderCrtDashboardIfNeeded();
|
|
5385
|
+
}
|
|
4599
5386
|
} else if (data.type === 'system-stats') {
|
|
4600
5387
|
updateSystemStats(data.stats, data.uptime, data.usageRate);
|
|
4601
5388
|
} else if (data.type === 'error') {
|
|
4602
5389
|
waitingForAgent = false;
|
|
5390
|
+
if (workspaceDirectoryPrompt?.kind === 'creating') {
|
|
5391
|
+
hideWorkspaceDirectoryPrompt({ focusInput: false });
|
|
5392
|
+
}
|
|
4603
5393
|
alert('Error: ' + data.message);
|
|
4604
5394
|
}
|
|
4605
5395
|
};
|
|
@@ -4608,6 +5398,21 @@ function connect() {
|
|
|
4608
5398
|
if (ws !== socket) return;
|
|
4609
5399
|
ws = null;
|
|
4610
5400
|
console.log('Disconnected from server');
|
|
5401
|
+
if (crtTerminalReplication) {
|
|
5402
|
+
clearPendingCrtTerminalFitResize(crtTerminalReplication);
|
|
5403
|
+
crtTerminalReplication.lastResizeCols = null;
|
|
5404
|
+
crtTerminalReplication.lastResizeRows = null;
|
|
5405
|
+
crtTerminalReplication.checkpointSeq += 1;
|
|
5406
|
+
crtTerminalReplication.checkpointAbortController?.abort();
|
|
5407
|
+
crtTerminalReplication.checkpointAbortController = null;
|
|
5408
|
+
crtTerminalReplication.checkpointInFlight = false;
|
|
5409
|
+
if (crtTerminalReplication.checkpointRetryTimer) {
|
|
5410
|
+
clearTimeout(crtTerminalReplication.checkpointRetryTimer);
|
|
5411
|
+
crtTerminalReplication.checkpointRetryTimer = null;
|
|
5412
|
+
}
|
|
5413
|
+
TERMINAL_REPLAY.resetRecovery(crtTerminalReplication.replayState);
|
|
5414
|
+
TERMINAL_REPLAY.beginRecovery(crtTerminalReplication.replayState);
|
|
5415
|
+
}
|
|
4611
5416
|
if (typeof document === 'undefined' || document.visibilityState !== 'hidden') {
|
|
4612
5417
|
wsReconnectTimer = setTimeout(() => {
|
|
4613
5418
|
wsReconnectTimer = null;
|
|
@@ -4651,7 +5456,7 @@ function checkMainAgentStatus() {
|
|
|
4651
5456
|
? state.agents.find(a => a.id === state.mainAgentId)
|
|
4652
5457
|
: null;
|
|
4653
5458
|
|
|
4654
|
-
if (!state.mainAgentId || (mainAgent
|
|
5459
|
+
if (!state.mainAgentId || !isCrtLiveAgent(mainAgent)) {
|
|
4655
5460
|
showInputDialog();
|
|
4656
5461
|
}
|
|
4657
5462
|
}
|
|
@@ -4729,9 +5534,34 @@ function updateSystemStats(stats, uptime, usageRate) {
|
|
|
4729
5534
|
}
|
|
4730
5535
|
}
|
|
4731
5536
|
|
|
5537
|
+
function isCrtLiveAgent(agent) {
|
|
5538
|
+
return Boolean(
|
|
5539
|
+
agent
|
|
5540
|
+
&& agent.archived !== true
|
|
5541
|
+
&& agent.status !== 'dead'
|
|
5542
|
+
&& agent.status !== 'stopped'
|
|
5543
|
+
);
|
|
5544
|
+
}
|
|
5545
|
+
|
|
5546
|
+
function getCrtLiveAgents(currentState = state) {
|
|
5547
|
+
if (!currentState || !Array.isArray(currentState.agents)) return [];
|
|
5548
|
+
return currentState.agents.filter(isCrtLiveAgent);
|
|
5549
|
+
}
|
|
5550
|
+
|
|
4732
5551
|
function getCrtRegularAgents(currentState = state) {
|
|
4733
5552
|
if (!currentState || !Array.isArray(currentState.agents)) return [];
|
|
4734
|
-
return currentState.
|
|
5553
|
+
return getCrtLiveAgents(currentState).filter((agent) => (
|
|
5554
|
+
agent.id !== currentState.mainAgentId && agent.isMain !== true
|
|
5555
|
+
));
|
|
5556
|
+
}
|
|
5557
|
+
|
|
5558
|
+
function getCrtAgentRemovalFallback(currentState, removedAgentId) {
|
|
5559
|
+
const liveAgents = getCrtLiveAgents(currentState);
|
|
5560
|
+
const removedIndex = liveAgents.findIndex((agent) => agent.id === removedAgentId);
|
|
5561
|
+
const remaining = liveAgents.filter((agent) => agent.id !== removedAgentId);
|
|
5562
|
+
if (!remaining.length) return '';
|
|
5563
|
+
if (removedIndex < 0) return remaining[0].id;
|
|
5564
|
+
return remaining[Math.min(removedIndex, remaining.length - 1)].id;
|
|
4735
5565
|
}
|
|
4736
5566
|
|
|
4737
5567
|
function updateCrtAgentPageStatus(pageState) {
|
|
@@ -4774,8 +5604,9 @@ function renderState() {
|
|
|
4774
5604
|
lastCrtDashboardSignature = crtDashboardStateSignature(state);
|
|
4775
5605
|
|
|
4776
5606
|
// 更新吊顶的 Agent 数量
|
|
4777
|
-
const
|
|
4778
|
-
const
|
|
5607
|
+
const visibleAgents = getCrtLiveAgents(state);
|
|
5608
|
+
const activeAgents = visibleAgents.filter(a => a.status === 'running').length;
|
|
5609
|
+
const totalAgents = visibleAgents.length;
|
|
4779
5610
|
document.getElementById('active-agents').textContent = activeAgents;
|
|
4780
5611
|
document.getElementById('total-agents').textContent = totalAgents;
|
|
4781
5612
|
updateCrtBrandState(state);
|
|
@@ -4789,18 +5620,21 @@ function renderState() {
|
|
|
4789
5620
|
agentBlocks.forEach(block => block.remove());
|
|
4790
5621
|
|
|
4791
5622
|
mainAgentBlock.innerHTML = '';
|
|
5623
|
+
mainAgentBlock.removeAttribute('data-agent-id');
|
|
5624
|
+
mainAgentBlock.removeAttribute('data-crt-nav-key');
|
|
4792
5625
|
|
|
4793
|
-
|
|
4794
|
-
|
|
5626
|
+
const regularAgents = getCrtRegularAgents(state);
|
|
5627
|
+
const hasRegularAgents = regularAgents.length > 0;
|
|
5628
|
+
mapArea.classList.toggle('empty', !hasRegularAgents);
|
|
5629
|
+
emptyState.style.display = hasRegularAgents ? 'none' : 'flex';
|
|
5630
|
+
|
|
5631
|
+
if (visibleAgents.length === 0) {
|
|
4795
5632
|
mainAgentPanel.style.display = 'none';
|
|
4796
5633
|
updateCrtAgentPageStatus(getCrtAgentPage([], 0, 1));
|
|
4797
5634
|
showInputDialog();
|
|
4798
5635
|
return;
|
|
4799
5636
|
}
|
|
4800
5637
|
|
|
4801
|
-
emptyState.style.display = 'none';
|
|
4802
|
-
|
|
4803
|
-
const regularAgents = getCrtRegularAgents(state);
|
|
4804
5638
|
const pageLayout = calculateCrtAgentPageLayout(
|
|
4805
5639
|
mapArea.clientWidth,
|
|
4806
5640
|
mapArea.clientHeight,
|
|
@@ -4823,7 +5657,7 @@ function renderState() {
|
|
|
4823
5657
|
// 渲染 Main Agent 到右下角
|
|
4824
5658
|
if (state.mainAgentId) {
|
|
4825
5659
|
const mainAgent = state.agents.find(a => a.id === state.mainAgentId);
|
|
4826
|
-
if (mainAgent) {
|
|
5660
|
+
if (isCrtLiveAgent(mainAgent)) {
|
|
4827
5661
|
mainAgentPanel.style.display = 'block';
|
|
4828
5662
|
mainAgentBlock.dataset.crtNavKey = `agent:${mainAgent.id}`;
|
|
4829
5663
|
mainAgentBlock.dataset.agentId = mainAgent.id;
|
|
@@ -4843,13 +5677,7 @@ function renderState() {
|
|
|
4843
5677
|
const output = document.createElement('div');
|
|
4844
5678
|
output.className = 'agent-output';
|
|
4845
5679
|
output.style.height = '80px';
|
|
4846
|
-
|
|
4847
|
-
const outputTail = document.createElement('div');
|
|
4848
|
-
outputTail.className = 'agent-output-tail';
|
|
4849
|
-
if (!renderCrtTerminalSnapshot(outputTail, mainAgent.previewSnapshot)) {
|
|
4850
|
-
outputTail.textContent = cleanOutput.slice(-150) || 'No output yet...';
|
|
4851
|
-
}
|
|
4852
|
-
output.appendChild(outputTail);
|
|
5680
|
+
renderCrtAgentOutput(output, mainAgent, { main: true });
|
|
4853
5681
|
mainAgentBlock.appendChild(output);
|
|
4854
5682
|
|
|
4855
5683
|
mainAgentBlock.onclick = () => openSession(mainAgent.id);
|
|
@@ -4893,13 +5721,7 @@ function renderState() {
|
|
|
4893
5721
|
|
|
4894
5722
|
const output = document.createElement('div');
|
|
4895
5723
|
output.className = 'agent-output';
|
|
4896
|
-
|
|
4897
|
-
const outputTail = document.createElement('div');
|
|
4898
|
-
outputTail.className = 'agent-output-tail';
|
|
4899
|
-
if (!renderCrtTerminalSnapshot(outputTail, agent.previewSnapshot)) {
|
|
4900
|
-
outputTail.textContent = cleanOutput || 'No output yet...';
|
|
4901
|
-
}
|
|
4902
|
-
output.appendChild(outputTail);
|
|
5724
|
+
renderCrtAgentOutput(output, agent);
|
|
4903
5725
|
block.appendChild(output);
|
|
4904
5726
|
|
|
4905
5727
|
block.onclick = () => openSession(agent.id);
|
|
@@ -4914,14 +5736,14 @@ function generateKeyMap() {
|
|
|
4914
5736
|
|
|
4915
5737
|
keyMap = {};
|
|
4916
5738
|
let keyIndex = 1;
|
|
4917
|
-
state.
|
|
4918
|
-
if (agent.id === state.mainAgentId) return; // 跳过 Main Agent
|
|
5739
|
+
getCrtRegularAgents(state).forEach((agent) => {
|
|
4919
5740
|
keyMap[keyIndex] = agent.id;
|
|
4920
5741
|
keyIndex++;
|
|
4921
5742
|
});
|
|
4922
5743
|
}
|
|
4923
5744
|
|
|
4924
5745
|
function showInputDialog(prefill = null) {
|
|
5746
|
+
hideWorkspaceDirectoryPrompt({ focusInput: false });
|
|
4925
5747
|
clearCrtNavigationSelection();
|
|
4926
5748
|
void loadAgents();
|
|
4927
5749
|
const title = document.getElementById('dialog-title');
|
|
@@ -4972,6 +5794,7 @@ function hideInputDialog() {
|
|
|
4972
5794
|
pendingMainAgentLaunch = false;
|
|
4973
5795
|
pendingAgentLaunchPrefill = null;
|
|
4974
5796
|
waitingForAgent = false;
|
|
5797
|
+
hideWorkspaceDirectoryPrompt({ focusInput: false });
|
|
4975
5798
|
document.getElementById('agent-list').style.display = 'block';
|
|
4976
5799
|
document.getElementById('workspace-input-container').style.display = 'none';
|
|
4977
5800
|
document.getElementById('input-dialog').classList.remove('active');
|
|
@@ -4993,10 +5816,10 @@ function isCrtAgentInteractive(agent) {
|
|
|
4993
5816
|
}
|
|
4994
5817
|
|
|
4995
5818
|
function structuredRuntimeKind(agent) {
|
|
4996
|
-
|
|
4997
|
-
if (
|
|
4998
|
-
if (
|
|
4999
|
-
if (
|
|
5819
|
+
const kind = agent && agent.runtimeBinding && agent.runtimeBinding.kind;
|
|
5820
|
+
if (kind === 'acp') return 'ACP';
|
|
5821
|
+
if (kind === 'json') return 'JSON';
|
|
5822
|
+
if (kind === 'app-server') return 'APP SERVER';
|
|
5000
5823
|
return '';
|
|
5001
5824
|
}
|
|
5002
5825
|
|
|
@@ -5011,7 +5834,7 @@ function crtRuntimeView(agent) {
|
|
|
5011
5834
|
function canSwitchCrtAgentRuntime(agent) {
|
|
5012
5835
|
const freshCodexTerminal = agent
|
|
5013
5836
|
&& agent.providerSessionProvider === 'codex'
|
|
5014
|
-
&& agent.
|
|
5837
|
+
&& agent.runtimeBinding?.kind === 'terminal'
|
|
5015
5838
|
&& agent.providerSessionTemporary === true
|
|
5016
5839
|
&& agent.terminalInputReceived !== true;
|
|
5017
5840
|
return Boolean(
|
|
@@ -5033,6 +5856,43 @@ function isCrtRuntimeSwitchShortcut(event) {
|
|
|
5033
5856
|
);
|
|
5034
5857
|
}
|
|
5035
5858
|
|
|
5859
|
+
function hasCrtStructuredLocalEscapeAction(context = {}) {
|
|
5860
|
+
return Boolean(
|
|
5861
|
+
context.structuredTranscriptFocused
|
|
5862
|
+
|| context.structuredToolFocused
|
|
5863
|
+
|| context.structuredMenuItemFocused
|
|
5864
|
+
|| context.structuredInterruptFocused
|
|
5865
|
+
|| (context.structuredInputFocused && context.structuredComposerMenuOpen)
|
|
5866
|
+
);
|
|
5867
|
+
}
|
|
5868
|
+
|
|
5869
|
+
function resolveCrtSessionKeyboardCommand(event, context = {}) {
|
|
5870
|
+
if (!event) return '';
|
|
5871
|
+
const key = String(event.key || '').toLowerCase();
|
|
5872
|
+
const primaryModifier = Boolean(event.ctrlKey || event.metaKey);
|
|
5873
|
+
|
|
5874
|
+
// Session-wide commands must remain reachable from every focus owner,
|
|
5875
|
+
// including the hidden Terminal IME bridge and every structured Chat control.
|
|
5876
|
+
if (primaryModifier && key === 'k') return 'kill';
|
|
5877
|
+
if (primaryModifier && key === 'escape') return 'close';
|
|
5878
|
+
|
|
5879
|
+
// Plain Escape closes an idle Chat only when a more local transition does
|
|
5880
|
+
// not own it. Terminal keeps plain Escape for the running TUI.
|
|
5881
|
+
if (
|
|
5882
|
+
context.structuredSessionActive === true
|
|
5883
|
+
&& key === 'escape'
|
|
5884
|
+
&& !event.altKey
|
|
5885
|
+
&& !event.shiftKey
|
|
5886
|
+
&& event.isComposing !== true
|
|
5887
|
+
&& context.composing !== true
|
|
5888
|
+
&& !hasCrtStructuredLocalEscapeAction(context)
|
|
5889
|
+
) {
|
|
5890
|
+
return 'close';
|
|
5891
|
+
}
|
|
5892
|
+
|
|
5893
|
+
return '';
|
|
5894
|
+
}
|
|
5895
|
+
|
|
5036
5896
|
function setCrtRuntimeSwitchStatus(message = '', error = false) {
|
|
5037
5897
|
const status = document.getElementById('crt-runtime-switch-status');
|
|
5038
5898
|
const messageNode = document.getElementById('crt-runtime-switch-message');
|
|
@@ -5128,23 +5988,19 @@ function toggleCrtSessionRuntimeMode() {
|
|
|
5128
5988
|
}
|
|
5129
5989
|
|
|
5130
5990
|
function structuredTranscriptEndpoint(agent) {
|
|
5131
|
-
if (agent.
|
|
5132
|
-
if (agent.
|
|
5991
|
+
if (agent.runtimeBinding.kind === 'acp') return 'acp-transcript';
|
|
5992
|
+
if (agent.runtimeBinding.kind === 'json') return 'json-cli-transcript';
|
|
5133
5993
|
return 'codex-app-server-transcript';
|
|
5134
5994
|
}
|
|
5135
5995
|
|
|
5136
5996
|
function structuredRuntimeStatus(agent) {
|
|
5137
5997
|
if (!agent) return '';
|
|
5138
|
-
|
|
5139
|
-
if (agent.agentRuntimeMode === 'json') return agent.jsonCliState || 'idle';
|
|
5140
|
-
return agent.codexAppServerState || 'idle';
|
|
5998
|
+
return agent.runtimeBinding?.state || 'idle';
|
|
5141
5999
|
}
|
|
5142
6000
|
|
|
5143
6001
|
function structuredRuntimeError(agent) {
|
|
5144
6002
|
if (!agent) return '';
|
|
5145
|
-
|
|
5146
|
-
if (agent.agentRuntimeMode === 'json') return agent.jsonCliError || '';
|
|
5147
|
-
return agent.codexAppServerError || '';
|
|
6003
|
+
return agent.runtimeBinding?.error || '';
|
|
5148
6004
|
}
|
|
5149
6005
|
|
|
5150
6006
|
function structuredComposerAction(agent, draft = '') {
|
|
@@ -5153,12 +6009,8 @@ function structuredComposerAction(agent, draft = '') {
|
|
|
5153
6009
|
const status = String(structuredRuntimeStatus(agent) || 'idle');
|
|
5154
6010
|
const working = ['working', 'waiting-for-permission'].includes(status);
|
|
5155
6011
|
if (working) {
|
|
5156
|
-
if (agent.
|
|
5157
|
-
if (
|
|
5158
|
-
agent.providerSessionProvider === 'codex'
|
|
5159
|
-
&& agent.codexRuntimeMode === 'app-server'
|
|
5160
|
-
&& String(draft || '').trim()
|
|
5161
|
-
) return 'steer';
|
|
6012
|
+
if (agent.runtimeBinding?.kind === 'acp' && String(draft || '').trim()) return 'send';
|
|
6013
|
+
if (agent.runtimeBinding?.kind === 'app-server' && String(draft || '').trim()) return 'steer';
|
|
5162
6014
|
return 'interrupt';
|
|
5163
6015
|
}
|
|
5164
6016
|
if (['starting', 'interrupting'].includes(status)) return 'disabled';
|
|
@@ -5295,6 +6147,37 @@ function focusStructuredComposerMenuButton(current, offset = 0) {
|
|
|
5295
6147
|
return true;
|
|
5296
6148
|
}
|
|
5297
6149
|
|
|
6150
|
+
function structuredComposerMenuPath() {
|
|
6151
|
+
return `${structuredComposerMenu}:${structuredComposerConfigId}`;
|
|
6152
|
+
}
|
|
6153
|
+
|
|
6154
|
+
function captureStructuredComposerMenuFocus(menu) {
|
|
6155
|
+
const focused = document.activeElement && document.activeElement.closest
|
|
6156
|
+
? document.activeElement.closest('.crt-structured-menu-item')
|
|
6157
|
+
: null;
|
|
6158
|
+
if (!focused || !menu.contains(focused)) return null;
|
|
6159
|
+
const buttons = structuredComposerMenuButtons();
|
|
6160
|
+
return {
|
|
6161
|
+
path: structuredComposerMenuPath(),
|
|
6162
|
+
key: focused.dataset.menuKey || '',
|
|
6163
|
+
index: buttons.indexOf(focused)
|
|
6164
|
+
};
|
|
6165
|
+
}
|
|
6166
|
+
|
|
6167
|
+
function restoreStructuredComposerMenuFocus(snapshot) {
|
|
6168
|
+
if (!snapshot || snapshot.path !== structuredComposerMenuPath()) return false;
|
|
6169
|
+
const buttons = structuredComposerMenuButtons();
|
|
6170
|
+
if (!buttons.length) return false;
|
|
6171
|
+
const matching = snapshot.key
|
|
6172
|
+
? buttons.find((button) => button.dataset.menuKey === snapshot.key)
|
|
6173
|
+
: null;
|
|
6174
|
+
const fallbackIndex = Math.min(Math.max(snapshot.index, 0), buttons.length - 1);
|
|
6175
|
+
const target = matching || buttons[fallbackIndex];
|
|
6176
|
+
target.focus();
|
|
6177
|
+
target.scrollIntoView({ block: 'nearest' });
|
|
6178
|
+
return true;
|
|
6179
|
+
}
|
|
6180
|
+
|
|
5298
6181
|
function closeStructuredComposerMenu({ restoreFocus = true } = {}) {
|
|
5299
6182
|
structuredComposerMenu = '';
|
|
5300
6183
|
structuredComposerConfigId = '';
|
|
@@ -5334,10 +6217,11 @@ async function patchStructuredAcpSession(patch) {
|
|
|
5334
6217
|
if (opener && !opener.hidden && !opener.disabled) opener.focus();
|
|
5335
6218
|
}
|
|
5336
6219
|
|
|
5337
|
-
function structuredMenuButton(label, description, active, onClick) {
|
|
6220
|
+
function structuredMenuButton(label, description, active, onClick, menuKey = '') {
|
|
5338
6221
|
const button = document.createElement('button');
|
|
5339
6222
|
button.type = 'button';
|
|
5340
6223
|
button.className = `crt-structured-menu-item${active ? ' active' : ''}`;
|
|
6224
|
+
button.dataset.menuKey = menuKey;
|
|
5341
6225
|
const title = document.createElement('span');
|
|
5342
6226
|
title.textContent = label;
|
|
5343
6227
|
button.appendChild(title);
|
|
@@ -5353,6 +6237,7 @@ function structuredMenuButton(label, description, active, onClick) {
|
|
|
5353
6237
|
function renderStructuredComposerMenu() {
|
|
5354
6238
|
const menu = document.getElementById('crt-structured-composer-menu');
|
|
5355
6239
|
if (!menu) return;
|
|
6240
|
+
const retainedFocus = captureStructuredComposerMenuFocus(menu);
|
|
5356
6241
|
menu.replaceChildren();
|
|
5357
6242
|
const session = structuredSessionSnapshot;
|
|
5358
6243
|
if (!structuredComposerMenu || !session) {
|
|
@@ -5389,7 +6274,7 @@ function renderStructuredComposerMenu() {
|
|
|
5389
6274
|
renderStructuredSessionControls();
|
|
5390
6275
|
updateStructuredComposerState(state && state.agents.find((agent) => agent.id === focusedAgentId));
|
|
5391
6276
|
input.focus();
|
|
5392
|
-
}));
|
|
6277
|
+
}, `command:${command.name}`));
|
|
5393
6278
|
});
|
|
5394
6279
|
} else if (structuredComposerMenu === 'mode') {
|
|
5395
6280
|
const modes = session.modes && Array.isArray(session.modes.availableModes) ? session.modes.availableModes : [];
|
|
@@ -5397,7 +6282,7 @@ function renderStructuredComposerMenu() {
|
|
|
5397
6282
|
modes.forEach((mode) => {
|
|
5398
6283
|
items.appendChild(structuredMenuButton(mode.name || mode.id, mode.description || '', mode.id === current, () => {
|
|
5399
6284
|
void patchStructuredAcpSession({ modeId: mode.id }).catch(showStructuredComposerError);
|
|
5400
|
-
}));
|
|
6285
|
+
}, `mode:${mode.id}`));
|
|
5401
6286
|
});
|
|
5402
6287
|
} else if (!selectedConfig) {
|
|
5403
6288
|
structuredVisibleConfigOptions(session).forEach((option) => {
|
|
@@ -5409,20 +6294,21 @@ function renderStructuredComposerMenu() {
|
|
|
5409
6294
|
structuredComposerConfigId = option.id;
|
|
5410
6295
|
structuredComposerMenuFocusPending = true;
|
|
5411
6296
|
renderStructuredSessionControls();
|
|
5412
|
-
}
|
|
6297
|
+
},
|
|
6298
|
+
`config:${option.id}`
|
|
5413
6299
|
));
|
|
5414
6300
|
});
|
|
5415
6301
|
} else if (selectedConfig.type === 'boolean') {
|
|
5416
6302
|
[false, true].forEach((value) => {
|
|
5417
6303
|
items.appendChild(structuredMenuButton(value ? 'ON' : 'OFF', selectedConfig.description || '', selectedConfig.currentValue === value, () => {
|
|
5418
6304
|
void patchStructuredAcpSession({ configId: selectedConfig.id, value }).catch(showStructuredComposerError);
|
|
5419
|
-
}));
|
|
6305
|
+
}, `boolean:${value}`));
|
|
5420
6306
|
});
|
|
5421
6307
|
} else {
|
|
5422
6308
|
structuredSelectOptions(selectedConfig).forEach((candidate) => {
|
|
5423
6309
|
items.appendChild(structuredMenuButton(candidate.name || candidate.value, candidate.description || '', candidate.value === selectedConfig.currentValue, () => {
|
|
5424
6310
|
void patchStructuredAcpSession({ configId: selectedConfig.id, value: candidate.value }).catch(showStructuredComposerError);
|
|
5425
|
-
}));
|
|
6311
|
+
}, `option:${candidate.value}`));
|
|
5426
6312
|
});
|
|
5427
6313
|
}
|
|
5428
6314
|
|
|
@@ -5431,6 +6317,8 @@ function renderStructuredComposerMenu() {
|
|
|
5431
6317
|
if (structuredComposerMenuFocusPending) {
|
|
5432
6318
|
structuredComposerMenuFocusPending = false;
|
|
5433
6319
|
window.requestAnimationFrame(() => focusStructuredComposerMenuButton(null, 0));
|
|
6320
|
+
} else {
|
|
6321
|
+
restoreStructuredComposerMenuFocus(retainedFocus);
|
|
5434
6322
|
}
|
|
5435
6323
|
}
|
|
5436
6324
|
|
|
@@ -5457,11 +6345,11 @@ function renderStructuredSessionControls() {
|
|
|
5457
6345
|
async function refreshStructuredSessionControls(agentId = focusedAgentId, force = false) {
|
|
5458
6346
|
if (!agentId || structuredSessionControlsLoading) return;
|
|
5459
6347
|
const agent = state && state.agents.find((candidate) => candidate.id === agentId);
|
|
5460
|
-
if (!agent || agent.
|
|
6348
|
+
if (!agent || agent.runtimeBinding?.kind !== 'acp') {
|
|
5461
6349
|
resetStructuredSessionControls();
|
|
5462
6350
|
return;
|
|
5463
6351
|
}
|
|
5464
|
-
const revision = String(agent.
|
|
6352
|
+
const revision = String(agent.runtimeBinding.sessionUpdatedAt || '');
|
|
5465
6353
|
if (!force && (
|
|
5466
6354
|
(revision && revision === structuredSessionControlsRevision)
|
|
5467
6355
|
|| (!revision && structuredSessionSnapshot)
|
|
@@ -5579,7 +6467,7 @@ function updateStructuredComposerState(agent) {
|
|
|
5579
6467
|
statusNode.textContent = error || `${kind} ${String(runtimeStatus || 'idle').toUpperCase()}`;
|
|
5580
6468
|
statusNode.classList.toggle('error', Boolean(error));
|
|
5581
6469
|
renderStructuredPermissions(agent);
|
|
5582
|
-
if (agent && agent.
|
|
6470
|
+
if (agent && agent.runtimeBinding?.kind === 'acp') void refreshStructuredSessionControls(agent.id);
|
|
5583
6471
|
}
|
|
5584
6472
|
|
|
5585
6473
|
function structuredAttachmentId(file) {
|
|
@@ -5716,10 +6604,11 @@ function renderStructuredPermissions(agent) {
|
|
|
5716
6604
|
const container = document.getElementById('crt-structured-composer-notices');
|
|
5717
6605
|
if (!container) return;
|
|
5718
6606
|
container.replaceChildren();
|
|
5719
|
-
if (!agent || agent.
|
|
5720
|
-
const
|
|
5721
|
-
|
|
5722
|
-
|
|
6607
|
+
if (!agent || agent.runtimeBinding?.kind !== 'acp') return;
|
|
6608
|
+
const runtime = agent.runtimeBinding;
|
|
6609
|
+
const requests = Array.isArray(runtime.pendingPermissions) && runtime.pendingPermissions.length
|
|
6610
|
+
? runtime.pendingPermissions
|
|
6611
|
+
: (runtime.pendingPermission ? [runtime.pendingPermission] : []);
|
|
5723
6612
|
requests.forEach((request) => {
|
|
5724
6613
|
const panel = document.createElement('section');
|
|
5725
6614
|
panel.className = 'crt-structured-permission';
|
|
@@ -5778,13 +6667,13 @@ function structuredTranscriptTurns(transcript) {
|
|
|
5778
6667
|
const text = structuredTranscriptContentText(entry.content);
|
|
5779
6668
|
if (!text) return;
|
|
5780
6669
|
if (entry.role === 'user') {
|
|
5781
|
-
current = { userMessage: text, finalMessage: '' };
|
|
6670
|
+
current = { id: entry.id || `user-${turns.length}`, userMessage: text, finalMessage: '' };
|
|
5782
6671
|
turns.push(current);
|
|
5783
6672
|
return;
|
|
5784
6673
|
}
|
|
5785
6674
|
if (entry.role !== 'assistant') return;
|
|
5786
6675
|
if (!current) {
|
|
5787
|
-
current = { userMessage: '', finalMessage: '' };
|
|
6676
|
+
current = { id: entry.id || `assistant-${turns.length}`, userMessage: '', finalMessage: '' };
|
|
5788
6677
|
turns.push(current);
|
|
5789
6678
|
}
|
|
5790
6679
|
current.finalMessage = text;
|
|
@@ -5792,12 +6681,215 @@ function structuredTranscriptTurns(transcript) {
|
|
|
5792
6681
|
return turns;
|
|
5793
6682
|
}
|
|
5794
6683
|
|
|
6684
|
+
function normalizeCrtStructuredPreviewText(value, limit = 240) {
|
|
6685
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
6686
|
+
if (text.length <= limit) return text;
|
|
6687
|
+
return `${text.slice(0, Math.max(1, limit - 1)).trimEnd()}…`;
|
|
6688
|
+
}
|
|
6689
|
+
|
|
6690
|
+
function structuredTranscriptAttachmentLabel(content) {
|
|
6691
|
+
const labels = (Array.isArray(content) ? content : []).flatMap((block) => {
|
|
6692
|
+
const type = String(block && block.type || '').toLowerCase();
|
|
6693
|
+
if (type.includes('image')) return ['Image attachment'];
|
|
6694
|
+
if (type.includes('audio')) return ['Audio attachment'];
|
|
6695
|
+
if (type.includes('file') || type.includes('resource')) return ['File attachment'];
|
|
6696
|
+
return [];
|
|
6697
|
+
});
|
|
6698
|
+
return Array.from(new Set(labels)).join(' + ');
|
|
6699
|
+
}
|
|
6700
|
+
|
|
6701
|
+
function crtStructuredPreviewMessageLines(transcript, limit = 8) {
|
|
6702
|
+
const maxLines = Math.max(1, Number(limit) || 8);
|
|
6703
|
+
const turnLines = structuredTranscriptTurns(transcript).flatMap((turn) => [
|
|
6704
|
+
turn && turn.userMessage
|
|
6705
|
+
? { role: 'user', label: 'YOU', text: normalizeCrtStructuredPreviewText(turn.userMessage) }
|
|
6706
|
+
: null,
|
|
6707
|
+
turn && turn.finalMessage
|
|
6708
|
+
? { role: 'assistant', label: 'AGENT', text: normalizeCrtStructuredPreviewText(turn.finalMessage) }
|
|
6709
|
+
: null,
|
|
6710
|
+
]).filter(Boolean);
|
|
6711
|
+
if (turnLines.length > 0) return turnLines.slice(-maxLines);
|
|
6712
|
+
|
|
6713
|
+
const entries = transcript && Array.isArray(transcript.entries)
|
|
6714
|
+
? transcript.entries
|
|
6715
|
+
.filter((entry) => (
|
|
6716
|
+
entry
|
|
6717
|
+
&& entry.internal !== true
|
|
6718
|
+
&& entry.type === 'message'
|
|
6719
|
+
&& (entry.role === 'user' || entry.role === 'assistant')
|
|
6720
|
+
))
|
|
6721
|
+
.map((entry) => ({
|
|
6722
|
+
role: entry.role,
|
|
6723
|
+
label: entry.role === 'user' ? 'YOU' : 'AGENT',
|
|
6724
|
+
text: normalizeCrtStructuredPreviewText(structuredTranscriptContentText(entry.content))
|
|
6725
|
+
|| structuredTranscriptAttachmentLabel(entry.content),
|
|
6726
|
+
}))
|
|
6727
|
+
.filter((line) => line.text)
|
|
6728
|
+
: [];
|
|
6729
|
+
return entries.slice(-maxLines);
|
|
6730
|
+
}
|
|
6731
|
+
|
|
6732
|
+
function structuredEntryActivityText(entry) {
|
|
6733
|
+
if (!entry || entry.internal === true) return '';
|
|
6734
|
+
if (entry.type === 'tool') {
|
|
6735
|
+
const title = normalizeCrtStructuredPreviewText(entry.title || entry.kind || 'Tool activity', 160);
|
|
6736
|
+
const status = normalizeCrtStructuredPreviewText(entry.status, 40).replaceAll('_', ' ');
|
|
6737
|
+
return [title, status].filter(Boolean).join(' · ');
|
|
6738
|
+
}
|
|
6739
|
+
if (entry.type === 'thought') {
|
|
6740
|
+
return normalizeCrtStructuredPreviewText(structuredTranscriptContentText(entry.content), 180);
|
|
6741
|
+
}
|
|
6742
|
+
if (entry.type === 'plan') {
|
|
6743
|
+
const steps = Array.isArray(entry.entries) ? entry.entries : [];
|
|
6744
|
+
const current = steps.find((step) => step && step.status === 'in_progress')
|
|
6745
|
+
|| [...steps].reverse().find((step) => step && step.status !== 'completed')
|
|
6746
|
+
|| steps.at(-1);
|
|
6747
|
+
return normalizeCrtStructuredPreviewText(current && (current.content || current.title), 180);
|
|
6748
|
+
}
|
|
6749
|
+
return '';
|
|
6750
|
+
}
|
|
6751
|
+
|
|
6752
|
+
function buildCrtStructuredPreview(transcript, agent = null) {
|
|
6753
|
+
const entries = transcript && Array.isArray(transcript.entries)
|
|
6754
|
+
? transcript.entries.filter((entry) => entry && entry.internal !== true)
|
|
6755
|
+
: [];
|
|
6756
|
+
const turns = structuredTranscriptTurns(transcript);
|
|
6757
|
+
const latestTurn = turns.at(-1) || null;
|
|
6758
|
+
const messageLines = crtStructuredPreviewMessageLines(transcript);
|
|
6759
|
+
let userText = normalizeCrtStructuredPreviewText(latestTurn && latestTurn.userMessage);
|
|
6760
|
+
let assistantText = normalizeCrtStructuredPreviewText(latestTurn && latestTurn.finalMessage);
|
|
6761
|
+
let activityText = '';
|
|
6762
|
+
|
|
6763
|
+
if (entries.length > 0) {
|
|
6764
|
+
const latestUserIndex = entries.findLastIndex((entry) => entry.type === 'message' && entry.role === 'user');
|
|
6765
|
+
const latestUser = latestUserIndex >= 0 ? entries[latestUserIndex] : null;
|
|
6766
|
+
if (latestUser) {
|
|
6767
|
+
userText = normalizeCrtStructuredPreviewText(structuredTranscriptContentText(latestUser.content))
|
|
6768
|
+
|| structuredTranscriptAttachmentLabel(latestUser.content);
|
|
6769
|
+
}
|
|
6770
|
+
const turnEntries = latestUserIndex >= 0 ? entries.slice(latestUserIndex + 1) : entries;
|
|
6771
|
+
const latestAssistant = [...turnEntries].reverse().find((entry) => (
|
|
6772
|
+
entry.type === 'message' && entry.role === 'assistant'
|
|
6773
|
+
));
|
|
6774
|
+
if (latestAssistant) {
|
|
6775
|
+
assistantText = normalizeCrtStructuredPreviewText(structuredTranscriptContentText(latestAssistant.content))
|
|
6776
|
+
|| structuredTranscriptAttachmentLabel(latestAssistant.content);
|
|
6777
|
+
}
|
|
6778
|
+
activityText = [...turnEntries].reverse().map(structuredEntryActivityText).find(Boolean) || '';
|
|
6779
|
+
} else if (latestTurn && Array.isArray(latestTurn.processItems)) {
|
|
6780
|
+
const processItem = [...latestTurn.processItems].reverse().find(Boolean);
|
|
6781
|
+
activityText = normalizeCrtStructuredPreviewText(
|
|
6782
|
+
processItem && (processItem.detail || processItem.title),
|
|
6783
|
+
180,
|
|
6784
|
+
);
|
|
6785
|
+
}
|
|
6786
|
+
|
|
6787
|
+
return {
|
|
6788
|
+
messageLines,
|
|
6789
|
+
userText,
|
|
6790
|
+
assistantText,
|
|
6791
|
+
activityText,
|
|
6792
|
+
state: String(structuredRuntimeStatus(agent) || (transcript && transcript.state) || ''),
|
|
6793
|
+
};
|
|
6794
|
+
}
|
|
6795
|
+
|
|
6796
|
+
function crtStructuredPreviewRevision(agent) {
|
|
6797
|
+
if (!agent || !isStructuredRuntimeAgent(agent)) return '';
|
|
6798
|
+
const runtime = agent.runtimeBinding;
|
|
6799
|
+
return JSON.stringify([
|
|
6800
|
+
structuredRuntimeKind(agent),
|
|
6801
|
+
structuredRuntimeStatus(agent),
|
|
6802
|
+
runtime.sessionRevision || 0,
|
|
6803
|
+
runtime.sessionUpdatedAt || '',
|
|
6804
|
+
runtime.transcriptUpdatedAt || '',
|
|
6805
|
+
runtime.turnId || '',
|
|
6806
|
+
agent.lastActivity || 0,
|
|
6807
|
+
]);
|
|
6808
|
+
}
|
|
6809
|
+
|
|
6810
|
+
function pruneCrtStructuredPreviews(currentState = state) {
|
|
6811
|
+
const visibleIds = new Set(getCrtLiveAgents(currentState).map((agent) => agent.id));
|
|
6812
|
+
crtStructuredPreviewCache.forEach((_value, agentId) => {
|
|
6813
|
+
if (!visibleIds.has(agentId)) crtStructuredPreviewCache.delete(agentId);
|
|
6814
|
+
});
|
|
6815
|
+
crtStructuredPreviewTimers.forEach((timer, agentId) => {
|
|
6816
|
+
if (visibleIds.has(agentId)) return;
|
|
6817
|
+
clearTimeout(timer);
|
|
6818
|
+
crtStructuredPreviewTimers.delete(agentId);
|
|
6819
|
+
});
|
|
6820
|
+
}
|
|
6821
|
+
|
|
6822
|
+
function scheduleCrtStructuredPreviewRefresh(agent) {
|
|
6823
|
+
if (!isCrtLiveAgent(agent) || !isStructuredRuntimeAgent(agent)) return;
|
|
6824
|
+
const revision = crtStructuredPreviewRevision(agent);
|
|
6825
|
+
const cached = crtStructuredPreviewCache.get(agent.id);
|
|
6826
|
+
if (cached && cached.revision === revision && (cached.loading || cached.ready)) return;
|
|
6827
|
+
if (crtStructuredPreviewTimers.has(agent.id)) return;
|
|
6828
|
+
const timer = setTimeout(() => {
|
|
6829
|
+
crtStructuredPreviewTimers.delete(agent.id);
|
|
6830
|
+
const currentAgent = state && state.agents.find((candidate) => candidate.id === agent.id);
|
|
6831
|
+
if (isCrtLiveAgent(currentAgent) && isStructuredRuntimeAgent(currentAgent)) {
|
|
6832
|
+
void refreshCrtStructuredPreview(currentAgent);
|
|
6833
|
+
}
|
|
6834
|
+
}, CRT_STRUCTURED_PREVIEW_REFRESH_MS);
|
|
6835
|
+
crtStructuredPreviewTimers.set(agent.id, timer);
|
|
6836
|
+
}
|
|
6837
|
+
|
|
6838
|
+
async function refreshCrtStructuredPreview(agent) {
|
|
6839
|
+
const revision = crtStructuredPreviewRevision(agent);
|
|
6840
|
+
const cached = crtStructuredPreviewCache.get(agent.id);
|
|
6841
|
+
if (cached && cached.revision === revision && (cached.loading || cached.ready)) return;
|
|
6842
|
+
crtStructuredPreviewCache.set(agent.id, {
|
|
6843
|
+
revision,
|
|
6844
|
+
loading: true,
|
|
6845
|
+
ready: false,
|
|
6846
|
+
preview: cached && cached.preview || null,
|
|
6847
|
+
error: '',
|
|
6848
|
+
});
|
|
6849
|
+
|
|
6850
|
+
try {
|
|
6851
|
+
const endpoint = structuredTranscriptEndpoint(agent);
|
|
6852
|
+
const response = await fetch(farmingApiPath(`/agents/${encodeURIComponent(agent.id)}/${endpoint}?maxTurns=20`));
|
|
6853
|
+
const body = await response.json().catch(() => null);
|
|
6854
|
+
if (!response.ok || !body || !body.transcript) {
|
|
6855
|
+
throw new Error(body && body.error ? body.error : `Conversation preview failed (${response.status})`);
|
|
6856
|
+
}
|
|
6857
|
+
const currentAgent = state && state.agents.find((candidate) => candidate.id === agent.id);
|
|
6858
|
+
if (!isCrtLiveAgent(currentAgent) || crtStructuredPreviewRevision(currentAgent) !== revision) {
|
|
6859
|
+
if (isCrtLiveAgent(currentAgent)) scheduleCrtStructuredPreviewRefresh(currentAgent);
|
|
6860
|
+
return;
|
|
6861
|
+
}
|
|
6862
|
+
crtStructuredPreviewCache.set(agent.id, {
|
|
6863
|
+
revision,
|
|
6864
|
+
loading: false,
|
|
6865
|
+
ready: true,
|
|
6866
|
+
preview: buildCrtStructuredPreview(body.transcript, currentAgent),
|
|
6867
|
+
error: '',
|
|
6868
|
+
});
|
|
6869
|
+
if (isCrtSessionOpen()) dashboardRenderDeferred = true;
|
|
6870
|
+
else updateCrtAgentPreviewCard(currentAgent);
|
|
6871
|
+
} catch (error) {
|
|
6872
|
+
const currentAgent = state && state.agents.find((candidate) => candidate.id === agent.id);
|
|
6873
|
+
if (!isCrtLiveAgent(currentAgent) || crtStructuredPreviewRevision(currentAgent) !== revision) return;
|
|
6874
|
+
crtStructuredPreviewCache.set(agent.id, {
|
|
6875
|
+
revision,
|
|
6876
|
+
loading: false,
|
|
6877
|
+
ready: true,
|
|
6878
|
+
preview: cached && cached.preview || null,
|
|
6879
|
+
error: error && error.message ? error.message : 'Conversation preview unavailable',
|
|
6880
|
+
});
|
|
6881
|
+
if (isCrtSessionOpen()) dashboardRenderDeferred = true;
|
|
6882
|
+
else updateCrtAgentPreviewCard(currentAgent);
|
|
6883
|
+
}
|
|
6884
|
+
}
|
|
6885
|
+
|
|
5795
6886
|
function renderStructuredTranscript(transcript, force = false) {
|
|
5796
6887
|
const container = document.getElementById('terminal-output');
|
|
5797
6888
|
if (!container) return;
|
|
5798
6889
|
const updatedAt = String(transcript && transcript.updatedAt || '');
|
|
5799
6890
|
if (!force && updatedAt && updatedAt === structuredSessionRenderedAt) return;
|
|
5800
6891
|
const nearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 80;
|
|
6892
|
+
if (!nearBottom && focusedAgentId) saveCrtStructuredReadingAnchor(focusedAgentId);
|
|
5801
6893
|
const transcriptNode = document.createElement('div');
|
|
5802
6894
|
transcriptNode.className = 'crt-structured-transcript';
|
|
5803
6895
|
const turns = structuredTranscriptTurns(transcript);
|
|
@@ -5811,6 +6903,7 @@ function renderStructuredTranscript(transcript, force = false) {
|
|
|
5811
6903
|
turns.forEach((turn) => {
|
|
5812
6904
|
const section = document.createElement('section');
|
|
5813
6905
|
section.className = 'crt-structured-turn';
|
|
6906
|
+
section.dataset.readingAnchorId = String(turn.id || `${turn.userMessage || ''}\n${turn.finalMessage || ''}`.slice(0, 160));
|
|
5814
6907
|
if (turn.userMessage) {
|
|
5815
6908
|
const user = document.createElement('p');
|
|
5816
6909
|
user.className = 'crt-structured-message user';
|
|
@@ -5829,7 +6922,10 @@ function renderStructuredTranscript(transcript, force = false) {
|
|
|
5829
6922
|
|
|
5830
6923
|
container.replaceChildren(transcriptNode);
|
|
5831
6924
|
structuredSessionRenderedAt = updatedAt;
|
|
5832
|
-
if (
|
|
6925
|
+
if (!focusedAgentId || !restoreCrtStructuredReadingAnchor(focusedAgentId)) {
|
|
6926
|
+
if (force || nearBottom) container.scrollTop = container.scrollHeight;
|
|
6927
|
+
}
|
|
6928
|
+
container.onscroll = () => saveCrtStructuredReadingAnchor(focusedAgentId);
|
|
5833
6929
|
requestAnimationFrame(updateStructuredTranscriptScrollState);
|
|
5834
6930
|
}
|
|
5835
6931
|
|
|
@@ -6104,8 +7200,8 @@ async function openStructuredSession(agent, sessionToken) {
|
|
|
6104
7200
|
function teardownSessionSurface() {
|
|
6105
7201
|
stopSessionViewPolling();
|
|
6106
7202
|
stopStructuredSessionPolling();
|
|
7203
|
+
disposeCrtTerminalReplication();
|
|
6107
7204
|
disposeTerminal();
|
|
6108
|
-
destroyTerminalInputBridge();
|
|
6109
7205
|
setStructuredComposerActive(false);
|
|
6110
7206
|
document.getElementById('terminal-output')?.classList.remove('crt-structured-session');
|
|
6111
7207
|
if (SESSION_MODAL_BRIDGE && SESSION_MODAL_BRIDGE.resetTerminalShell) {
|
|
@@ -6126,6 +7222,7 @@ async function openSession(agentId) {
|
|
|
6126
7222
|
|
|
6127
7223
|
const agent = state.agents.find(a => a.id === agentId);
|
|
6128
7224
|
if (!agent) return;
|
|
7225
|
+
crtNavigationKey = `agent:${agentId}`;
|
|
6129
7226
|
void markCrtAgentReadIfNeeded(agent);
|
|
6130
7227
|
|
|
6131
7228
|
const sessionModal = document.getElementById('session-modal');
|
|
@@ -6207,10 +7304,13 @@ async function openSession(agentId) {
|
|
|
6207
7304
|
}
|
|
6208
7305
|
|
|
6209
7306
|
disposeTerminal();
|
|
7307
|
+
disposeCrtTerminalReplication();
|
|
7308
|
+
crtTerminalReplication = createCrtTerminalReplication(agentId);
|
|
6210
7309
|
let mountedTerminal = null;
|
|
6211
7310
|
try {
|
|
6212
7311
|
mountedTerminal = SESSION_MODAL_BRIDGE && SESSION_MODAL_BRIDGE.mountTerminal
|
|
6213
7312
|
? SESSION_MODAL_BRIDGE.mountTerminal(document, terminalBundle, {
|
|
7313
|
+
authoritativeGeometry: true,
|
|
6214
7314
|
initialOutput: shouldUseLiveSessionText(agent)
|
|
6215
7315
|
? (runtime ? runtime.prepareInitialOutput(agent.output) : agent.output)
|
|
6216
7316
|
: '',
|
|
@@ -6220,11 +7320,10 @@ async function openSession(agentId) {
|
|
|
6220
7320
|
sendTerminalInput(data);
|
|
6221
7321
|
},
|
|
6222
7322
|
onResize: (cols, rows) => {
|
|
7323
|
+
void cols;
|
|
7324
|
+
void rows;
|
|
6223
7325
|
if (runtime && !runtime.isCurrentSession(agentId, sessionToken)) return;
|
|
6224
|
-
|
|
6225
|
-
const sessionClient = getSessionClient();
|
|
6226
|
-
if (!sessionClient) return;
|
|
6227
|
-
sessionClient.resizeAgent(focusedAgentId, cols, rows);
|
|
7326
|
+
sendSessionResize(agentId);
|
|
6228
7327
|
},
|
|
6229
7328
|
hasSelection: hasAnySelection,
|
|
6230
7329
|
focusTerminal: focusSessionTerminal,
|
|
@@ -6265,12 +7364,13 @@ async function openSession(agentId) {
|
|
|
6265
7364
|
updateSessionSelectionStatus();
|
|
6266
7365
|
});
|
|
6267
7366
|
}
|
|
7367
|
+
if (terminal && typeof terminal.onScroll === 'function') {
|
|
7368
|
+
terminal.onScroll(() => saveCrtTerminalReadingAnchor(agentId, terminal));
|
|
7369
|
+
}
|
|
6268
7370
|
if (runtime) {
|
|
6269
7371
|
runtime.setLastOutputLength(mountedTerminal ? mountedTerminal.outputLength : (runtime.prepareInitialOutput(agent.output)).length);
|
|
6270
7372
|
syncSessionRuntimeState();
|
|
6271
7373
|
}
|
|
6272
|
-
setupTerminalInputBridge();
|
|
6273
|
-
|
|
6274
7374
|
if (!mountedTerminal) {
|
|
6275
7375
|
terminal.loadAddon(fitAddon);
|
|
6276
7376
|
terminal.onData((data) => {
|
|
@@ -6279,11 +7379,11 @@ async function openSession(agentId) {
|
|
|
6279
7379
|
sendTerminalInput(data);
|
|
6280
7380
|
});
|
|
6281
7381
|
terminal.onResize(({ cols, rows }) => {
|
|
7382
|
+
void cols;
|
|
7383
|
+
void rows;
|
|
6282
7384
|
if (runtime && !runtime.isCurrentSession(agentId, sessionToken)) return;
|
|
6283
|
-
if (
|
|
6284
|
-
|
|
6285
|
-
if (!sessionClient) return;
|
|
6286
|
-
sessionClient.resizeAgent(focusedAgentId, cols, rows);
|
|
7385
|
+
if (crtTerminalReplication?.applyingLocalResize) return;
|
|
7386
|
+
sendSessionResize(agentId, { cols, rows });
|
|
6287
7387
|
});
|
|
6288
7388
|
|
|
6289
7389
|
terminalContainer.innerHTML = '';
|
|
@@ -6312,7 +7412,6 @@ async function openSession(agentId) {
|
|
|
6312
7412
|
if (!terminal || !fitAddon) return;
|
|
6313
7413
|
if (runtime && !runtime.isCurrentSession(agentId, sessionToken)) return;
|
|
6314
7414
|
if (!runtime && focusedAgentId !== agentId) return;
|
|
6315
|
-
fitAddon.fit();
|
|
6316
7415
|
const initialOutput = runtime ? runtime.prepareInitialOutput(agent.output) : agent.output;
|
|
6317
7416
|
if (initialOutput) {
|
|
6318
7417
|
terminal.write(initialOutput);
|
|
@@ -6336,13 +7435,20 @@ async function openSession(agentId) {
|
|
|
6336
7435
|
showCrtWebglFailure(error);
|
|
6337
7436
|
}
|
|
6338
7437
|
|
|
7438
|
+
// Hydrate one authoritative reducer cut before reducing live transitions.
|
|
6339
7439
|
await refreshSessionView(true, agentId, sessionToken);
|
|
6340
|
-
|
|
7440
|
+
restoreCrtTerminalReadingAnchor(agentId, terminal);
|
|
7441
|
+
sendSessionResize(agentId);
|
|
7442
|
+
if (!crtTerminalReplication && shouldPollSessionView(modalState.sessionSource)) {
|
|
6341
7443
|
startSessionViewPolling(agentId, sessionToken);
|
|
6342
7444
|
}
|
|
6343
7445
|
}
|
|
6344
7446
|
|
|
6345
7447
|
function closeSession() {
|
|
7448
|
+
if (focusedAgentId) {
|
|
7449
|
+
if (terminal) saveCrtTerminalReadingAnchor(focusedAgentId, terminal);
|
|
7450
|
+
else saveCrtStructuredReadingAnchor(focusedAgentId);
|
|
7451
|
+
}
|
|
6346
7452
|
resetCrtRuntimeSwitchState();
|
|
6347
7453
|
const runtime = getSessionRuntime();
|
|
6348
7454
|
if (runtime) {
|
|
@@ -6379,14 +7485,19 @@ function closeSession() {
|
|
|
6379
7485
|
if (crtMainView === 'history') renderCrtHistory();
|
|
6380
7486
|
generateKeyMap();
|
|
6381
7487
|
}
|
|
7488
|
+
restoreCrtNavigationSelection();
|
|
6382
7489
|
}
|
|
6383
7490
|
|
|
6384
7491
|
function killCurrentAgent() {
|
|
6385
7492
|
if (!focusedAgentId) return;
|
|
6386
7493
|
|
|
7494
|
+
const killedAgentId = focusedAgentId;
|
|
7495
|
+
const fallbackAgentId = getCrtAgentRemovalFallback(state, killedAgentId);
|
|
7496
|
+
crtNavigationKey = fallbackAgentId ? `agent:${fallbackAgentId}` : '';
|
|
7497
|
+
|
|
6387
7498
|
const sessionClient = getSessionClient();
|
|
6388
7499
|
if (sessionClient) {
|
|
6389
|
-
sessionClient.killAgent(
|
|
7500
|
+
sessionClient.killAgent(killedAgentId);
|
|
6390
7501
|
}
|
|
6391
7502
|
|
|
6392
7503
|
closeSession();
|
|
@@ -6394,97 +7505,178 @@ function killCurrentAgent() {
|
|
|
6394
7505
|
|
|
6395
7506
|
function sendTerminalInput(input) {
|
|
6396
7507
|
if (!focusedAgentId) return;
|
|
7508
|
+
queueCrtTerminalInput(input);
|
|
7509
|
+
}
|
|
6397
7510
|
|
|
6398
|
-
|
|
6399
|
-
if (
|
|
6400
|
-
|
|
7511
|
+
function clearPendingCrtTerminalFitResize(replication) {
|
|
7512
|
+
if (!replication) return;
|
|
7513
|
+
if (replication.fitResizeTimer) clearTimeout(replication.fitResizeTimer);
|
|
7514
|
+
replication.fitResizeTimer = null;
|
|
7515
|
+
replication.pendingFitResize = null;
|
|
7516
|
+
}
|
|
7517
|
+
|
|
7518
|
+
function commitCrtTerminalResize(agentId, normalizedDimensions) {
|
|
7519
|
+
const replication = crtTerminalReplication;
|
|
7520
|
+
if (
|
|
7521
|
+
!agentId ||
|
|
7522
|
+
!terminal ||
|
|
7523
|
+
!replication ||
|
|
7524
|
+
replication.disposed ||
|
|
7525
|
+
replication.agentId !== agentId ||
|
|
7526
|
+
replication.replayState.recovering ||
|
|
7527
|
+
(
|
|
7528
|
+
normalizedDimensions.cols === replication.lastResizeCols &&
|
|
7529
|
+
normalizedDimensions.rows === replication.lastResizeRows
|
|
7530
|
+
)
|
|
7531
|
+
) return;
|
|
7532
|
+
if (terminal.cols !== normalizedDimensions.cols || terminal.rows !== normalizedDimensions.rows) {
|
|
7533
|
+
replication.applyingLocalResize = true;
|
|
7534
|
+
try {
|
|
7535
|
+
terminal.resize(normalizedDimensions.cols, normalizedDimensions.rows);
|
|
7536
|
+
} finally {
|
|
7537
|
+
replication.applyingLocalResize = false;
|
|
7538
|
+
}
|
|
7539
|
+
}
|
|
7540
|
+
const delivered = getSessionClient()?.resizeAgent(
|
|
7541
|
+
agentId,
|
|
7542
|
+
normalizedDimensions.cols,
|
|
7543
|
+
normalizedDimensions.rows
|
|
7544
|
+
);
|
|
7545
|
+
if (delivered) {
|
|
7546
|
+
replication.lastResizeCols = normalizedDimensions.cols;
|
|
7547
|
+
replication.lastResizeRows = normalizedDimensions.rows;
|
|
6401
7548
|
}
|
|
6402
7549
|
}
|
|
6403
7550
|
|
|
6404
|
-
function
|
|
6405
|
-
if (
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
7551
|
+
function scheduleCrtTerminalFitResize(replication, agentId, normalizedDimensions) {
|
|
7552
|
+
if (replication.fitResizeTimer) clearTimeout(replication.fitResizeTimer);
|
|
7553
|
+
replication.pendingFitResize = normalizedDimensions;
|
|
7554
|
+
replication.fitResizeTimer = setTimeout(() => {
|
|
7555
|
+
replication.fitResizeTimer = null;
|
|
7556
|
+
const next = replication.pendingFitResize;
|
|
7557
|
+
replication.pendingFitResize = null;
|
|
7558
|
+
if (
|
|
7559
|
+
!next ||
|
|
7560
|
+
replication.disposed ||
|
|
7561
|
+
crtTerminalReplication !== replication ||
|
|
7562
|
+
replication.agentId !== agentId
|
|
7563
|
+
) return;
|
|
7564
|
+
commitCrtTerminalResize(agentId, next);
|
|
7565
|
+
}, CRT_TERMINAL_RESIZE_SETTLE_MS);
|
|
7566
|
+
}
|
|
7567
|
+
|
|
7568
|
+
function sendSessionResize(agentId = focusedAgentId, requestedDimensions = null) {
|
|
7569
|
+
const replication = crtTerminalReplication;
|
|
7570
|
+
if (
|
|
7571
|
+
!agentId ||
|
|
7572
|
+
!terminal ||
|
|
7573
|
+
!fitAddon ||
|
|
7574
|
+
!replication ||
|
|
7575
|
+
replication.agentId !== agentId ||
|
|
7576
|
+
replication.replayState.recovering
|
|
7577
|
+
) return;
|
|
7578
|
+
const dimensions = requestedDimensions || (
|
|
7579
|
+
typeof fitAddon.proposeDimensions === 'function'
|
|
7580
|
+
? fitAddon.proposeDimensions()
|
|
7581
|
+
: null
|
|
7582
|
+
);
|
|
7583
|
+
const normalizedDimensions = dimensions && {
|
|
7584
|
+
cols: Math.floor(Number(dimensions.cols)),
|
|
7585
|
+
rows: Math.floor(Number(dimensions.rows))
|
|
7586
|
+
};
|
|
7587
|
+
if (
|
|
7588
|
+
!normalizedDimensions ||
|
|
7589
|
+
!Number.isFinite(normalizedDimensions.cols) ||
|
|
7590
|
+
!Number.isFinite(normalizedDimensions.rows) ||
|
|
7591
|
+
normalizedDimensions.cols < CRT_TERMINAL_MIN_COLS ||
|
|
7592
|
+
normalizedDimensions.rows < CRT_TERMINAL_MIN_ROWS
|
|
7593
|
+
) return;
|
|
7594
|
+
if (!requestedDimensions) {
|
|
7595
|
+
if (
|
|
7596
|
+
normalizedDimensions.cols === replication.lastResizeCols &&
|
|
7597
|
+
normalizedDimensions.rows === replication.lastResizeRows
|
|
7598
|
+
) {
|
|
7599
|
+
clearPendingCrtTerminalFitResize(replication);
|
|
7600
|
+
return;
|
|
7601
|
+
}
|
|
7602
|
+
scheduleCrtTerminalFitResize(replication, agentId, normalizedDimensions);
|
|
7603
|
+
return;
|
|
7604
|
+
}
|
|
7605
|
+
clearPendingCrtTerminalFitResize(replication);
|
|
7606
|
+
commitCrtTerminalResize(agentId, normalizedDimensions);
|
|
6410
7607
|
}
|
|
6411
7608
|
|
|
6412
|
-
async function refreshSessionView(
|
|
7609
|
+
async function refreshSessionView(_forceReplace = false, expectedAgentId = focusedAgentId, expectedSessionToken = getCurrentSessionToken()) {
|
|
6413
7610
|
if (!expectedAgentId || !terminal) return;
|
|
6414
7611
|
|
|
6415
7612
|
const runtime = getSessionRuntime();
|
|
7613
|
+
const replication = crtTerminalReplication;
|
|
7614
|
+
if (
|
|
7615
|
+
!replication ||
|
|
7616
|
+
replication.agentId !== expectedAgentId ||
|
|
7617
|
+
replication.replayState.halted ||
|
|
7618
|
+
replication.checkpointRetryTimer ||
|
|
7619
|
+
replication.checkpointInFlight
|
|
7620
|
+
) return;
|
|
7621
|
+
|
|
7622
|
+
const checkpointSeq = replication.checkpointSeq + 1;
|
|
7623
|
+
replication.checkpointSeq = checkpointSeq;
|
|
7624
|
+
replication.checkpointInFlight = true;
|
|
7625
|
+
TERMINAL_REPLAY.beginRecovery(replication.replayState);
|
|
7626
|
+
const controller = new globalThis.AbortController();
|
|
7627
|
+
replication.checkpointAbortController = controller;
|
|
7628
|
+
const timeout = setTimeout(
|
|
7629
|
+
() => controller.abort(),
|
|
7630
|
+
CRT_TERMINAL_CHECKPOINT_REQUEST_TIMEOUT_MS
|
|
7631
|
+
);
|
|
7632
|
+
|
|
6416
7633
|
try {
|
|
6417
7634
|
const sessionClient = getSessionClient();
|
|
6418
|
-
if (!sessionClient)
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|| (candidate.previewCols === terminal.cols && candidate.previewRows === terminal.rows);
|
|
6430
|
-
if (dimensionsMatch) {
|
|
6431
|
-
break;
|
|
6432
|
-
}
|
|
6433
|
-
}
|
|
6434
|
-
if (runtime && !runtime.isCurrentSession(expectedAgentId, expectedSessionToken)) {
|
|
6435
|
-
return;
|
|
6436
|
-
}
|
|
7635
|
+
if (!sessionClient) throw new Error('Terminal session client is unavailable');
|
|
7636
|
+
const payload = await sessionClient.getSessionView(expectedAgentId, {
|
|
7637
|
+
signal: controller.signal
|
|
7638
|
+
});
|
|
7639
|
+
if (
|
|
7640
|
+
!crtTerminalReplication ||
|
|
7641
|
+
crtTerminalReplication !== replication ||
|
|
7642
|
+
replication.checkpointSeq !== checkpointSeq
|
|
7643
|
+
) return;
|
|
7644
|
+
if (runtime && !runtime.isCurrentSession(expectedAgentId, expectedSessionToken)) return;
|
|
7645
|
+
|
|
6437
7646
|
const currentAgent = state && state.agents
|
|
6438
7647
|
? state.agents.find((agent) => agent.id === expectedAgentId)
|
|
6439
7648
|
: null;
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
7649
|
+
installCrtTerminalCheckpoint(normalizeSessionViewPayload(payload, currentAgent));
|
|
7650
|
+
} catch (error) {
|
|
7651
|
+
if (
|
|
7652
|
+
crtTerminalReplication === replication &&
|
|
7653
|
+
replication.checkpointSeq === checkpointSeq
|
|
7654
|
+
) {
|
|
7655
|
+
retryCrtTerminalReplayAfterFailure(
|
|
7656
|
+
replication,
|
|
7657
|
+
TERMINAL_REPLAY.recordTransportFailure(replication.replayState),
|
|
7658
|
+
error
|
|
6448
7659
|
);
|
|
6449
|
-
refreshSessionTerminalUi({ preserveSearchIndex: true });
|
|
6450
|
-
if (runtime) {
|
|
6451
|
-
runtime.markHydrated(patch.nextLength);
|
|
6452
|
-
syncSessionRuntimeState();
|
|
6453
|
-
}
|
|
6454
|
-
return;
|
|
6455
7660
|
}
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
}
|
|
6466
|
-
syncSessionRuntimeState();
|
|
6467
|
-
}
|
|
6468
|
-
}
|
|
6469
|
-
} catch (error) {
|
|
6470
|
-
console.error('Failed to refresh session view:', error);
|
|
6471
|
-
if (runtime && runtime.isCurrentSession(expectedAgentId, expectedSessionToken) && runtime.isAwaitingInitialSync()) {
|
|
6472
|
-
runtime.markHydrated(getSessionOutputLength());
|
|
6473
|
-
syncSessionRuntimeState();
|
|
7661
|
+
} finally {
|
|
7662
|
+
clearTimeout(timeout);
|
|
7663
|
+
if (
|
|
7664
|
+
crtTerminalReplication === replication &&
|
|
7665
|
+
replication.checkpointSeq === checkpointSeq
|
|
7666
|
+
) {
|
|
7667
|
+
replication.checkpointInFlight = false;
|
|
7668
|
+
replication.checkpointAbortController = null;
|
|
7669
|
+
finishCrtTerminalReplay(replication);
|
|
6474
7670
|
}
|
|
6475
7671
|
}
|
|
6476
7672
|
}
|
|
6477
|
-
|
|
6478
7673
|
function startSessionViewPolling(agentId = focusedAgentId, sessionToken = getCurrentSessionToken()) {
|
|
6479
7674
|
const runtime = getSessionRuntime();
|
|
6480
7675
|
if (runtime) {
|
|
6481
7676
|
runtime.startPolling({ agentId, sessionToken });
|
|
6482
|
-
return;
|
|
7677
|
+
return null;
|
|
6483
7678
|
}
|
|
6484
|
-
|
|
6485
|
-
legacySessionPoller = setInterval(() => {
|
|
6486
|
-
refreshSessionView(false, agentId, sessionToken);
|
|
6487
|
-
}, 350);
|
|
7679
|
+
return null;
|
|
6488
7680
|
}
|
|
6489
7681
|
|
|
6490
7682
|
function stopSessionViewPolling() {
|
|
@@ -6493,10 +7685,6 @@ function stopSessionViewPolling() {
|
|
|
6493
7685
|
runtime.stopPolling();
|
|
6494
7686
|
return;
|
|
6495
7687
|
}
|
|
6496
|
-
if (legacySessionPoller) {
|
|
6497
|
-
clearInterval(legacySessionPoller);
|
|
6498
|
-
legacySessionPoller = null;
|
|
6499
|
-
}
|
|
6500
7688
|
}
|
|
6501
7689
|
|
|
6502
7690
|
if (typeof document !== 'undefined') {
|
|
@@ -6516,8 +7704,6 @@ if (typeof document !== 'undefined') {
|
|
|
6516
7704
|
}
|
|
6517
7705
|
if (!terminal || !fitAddon || !focusedAgentId) return;
|
|
6518
7706
|
|
|
6519
|
-
fitAddon.fit();
|
|
6520
|
-
syncTerminalInputBridgePosition();
|
|
6521
7707
|
sendSessionResize();
|
|
6522
7708
|
});
|
|
6523
7709
|
|
|
@@ -6542,9 +7728,12 @@ if (typeof document !== 'undefined') {
|
|
|
6542
7728
|
const searchInputFocused = crtMainView === 'search'
|
|
6543
7729
|
&& document.activeElement === document.getElementById('crt-search-input');
|
|
6544
7730
|
const navigationArrow = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key);
|
|
7731
|
+
const billingHourCellFocused = crtMainView === 'billing'
|
|
7732
|
+
&& document.activeElement?.classList?.contains('billing-day-hour-cell');
|
|
6545
7733
|
|
|
6546
7734
|
if (terminalFontSizeInputFocused && (e.key === 'ArrowLeft' || e.key === 'ArrowRight')) return;
|
|
6547
7735
|
if (searchInputFocused) return;
|
|
7736
|
+
if (billingHourCellFocused && (e.key === 'ArrowLeft' || e.key === 'ArrowRight')) return;
|
|
6548
7737
|
|
|
6549
7738
|
if (
|
|
6550
7739
|
crtMainView === 'billing'
|
|
@@ -6650,6 +7839,13 @@ if (typeof document !== 'undefined') {
|
|
|
6650
7839
|
}
|
|
6651
7840
|
|
|
6652
7841
|
if (dialogActive) {
|
|
7842
|
+
if (workspaceDirectoryPrompt) {
|
|
7843
|
+
if (e.key === 'Escape' && workspaceDirectoryPrompt.kind !== 'creating') {
|
|
7844
|
+
hideWorkspaceDirectoryPrompt();
|
|
7845
|
+
e.preventDefault();
|
|
7846
|
+
}
|
|
7847
|
+
return;
|
|
7848
|
+
}
|
|
6653
7849
|
if (workspaceInputVisible) {
|
|
6654
7850
|
if (workspaceInputFocused) {
|
|
6655
7851
|
return;
|
|
@@ -6771,14 +7967,28 @@ if (typeof document !== 'undefined') {
|
|
|
6771
7967
|
const structuredInputFocused = structuredInput && document.activeElement === structuredInput;
|
|
6772
7968
|
const structuredSessionActive = document.getElementById('crt-structured-composer')?.classList.contains('active');
|
|
6773
7969
|
const structuredTranscriptFocused = document.activeElement === document.getElementById('terminal-output');
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
7970
|
+
const structuredToolFocused = Boolean(document.activeElement?.closest?.('.crt-structured-tool'));
|
|
7971
|
+
const structuredMenuItemFocused = Boolean(document.activeElement?.closest?.('.crt-structured-menu-item'));
|
|
7972
|
+
const focusedAgent = state && state.agents.find((candidate) => candidate.id === focusedAgentId);
|
|
7973
|
+
const structuredInterruptFocused = Boolean(
|
|
7974
|
+
structuredInputFocused
|
|
7975
|
+
&& structuredComposerAction(focusedAgent, structuredInput.value) === 'interrupt'
|
|
7976
|
+
);
|
|
7977
|
+
const sessionCommand = resolveCrtSessionKeyboardCommand(e, {
|
|
7978
|
+
structuredSessionActive,
|
|
7979
|
+
composing: e.isComposing,
|
|
7980
|
+
structuredInputFocused,
|
|
7981
|
+
structuredTranscriptFocused,
|
|
7982
|
+
structuredToolFocused,
|
|
7983
|
+
structuredMenuItemFocused,
|
|
7984
|
+
structuredInterruptFocused,
|
|
7985
|
+
structuredComposerMenuOpen: Boolean(structuredComposerMenu),
|
|
7986
|
+
});
|
|
7987
|
+
if (sessionCommand) {
|
|
6781
7988
|
e.preventDefault();
|
|
7989
|
+
e.stopPropagation();
|
|
7990
|
+
if (sessionCommand === 'kill') killCurrentAgent();
|
|
7991
|
+
else closeSession();
|
|
6782
7992
|
return;
|
|
6783
7993
|
}
|
|
6784
7994
|
if (structuredInputFocused) {
|
|
@@ -6806,48 +8016,6 @@ if (typeof document !== 'undefined') {
|
|
|
6806
8016
|
if (isBrowserShortcut(e)) {
|
|
6807
8017
|
return;
|
|
6808
8018
|
}
|
|
6809
|
-
if (e.isComposing || terminalInputComposing) {
|
|
6810
|
-
if (SESSION_INPUT_SETTINGS.imeEnabled) {
|
|
6811
|
-
e.preventDefault();
|
|
6812
|
-
}
|
|
6813
|
-
return;
|
|
6814
|
-
}
|
|
6815
|
-
if ((e.ctrlKey || e.metaKey) && e.key === 'Escape') {
|
|
6816
|
-
closeSession();
|
|
6817
|
-
e.preventDefault();
|
|
6818
|
-
return;
|
|
6819
|
-
}
|
|
6820
|
-
if ((e.ctrlKey || e.metaKey) && (e.key === 'k' || e.key === 'K')) {
|
|
6821
|
-
killCurrentAgent();
|
|
6822
|
-
e.preventDefault();
|
|
6823
|
-
return;
|
|
6824
|
-
}
|
|
6825
|
-
if (!SESSION_INPUT_SETTINGS.imeEnabled) {
|
|
6826
|
-
return;
|
|
6827
|
-
}
|
|
6828
|
-
if (
|
|
6829
|
-
SESSION_INPUT_SETTINGS.imeEnabled &&
|
|
6830
|
-
!e.ctrlKey &&
|
|
6831
|
-
!e.metaKey &&
|
|
6832
|
-
!e.altKey &&
|
|
6833
|
-
e.key.length === 1 &&
|
|
6834
|
-
document.activeElement !== terminalInputBridge
|
|
6835
|
-
) {
|
|
6836
|
-
e.preventDefault();
|
|
6837
|
-
schedulePrintableInput(e.key);
|
|
6838
|
-
focusTerminalInputBridge();
|
|
6839
|
-
return;
|
|
6840
|
-
}
|
|
6841
|
-
if (routeSessionKey(e)) {
|
|
6842
|
-
e.preventDefault();
|
|
6843
|
-
focusTerminalInputBridge();
|
|
6844
|
-
return;
|
|
6845
|
-
}
|
|
6846
|
-
if (!e.ctrlKey && !e.metaKey && e.key === 'Escape') {
|
|
6847
|
-
sendTerminalInput('\x1b');
|
|
6848
|
-
e.preventDefault();
|
|
6849
|
-
return;
|
|
6850
|
-
}
|
|
6851
8019
|
return;
|
|
6852
8020
|
}
|
|
6853
8021
|
|
|
@@ -6912,6 +8080,12 @@ if (typeof document !== 'undefined') {
|
|
|
6912
8080
|
return;
|
|
6913
8081
|
}
|
|
6914
8082
|
|
|
8083
|
+
// xterm owns paste events targeting its textarea. Calling terminal.paste()
|
|
8084
|
+
// here as well would submit the same clipboard text twice.
|
|
8085
|
+
if (isCrtNativeTerminalPasteTarget(e.target)) {
|
|
8086
|
+
return;
|
|
8087
|
+
}
|
|
8088
|
+
|
|
6915
8089
|
const pastedText = e.clipboardData && e.clipboardData.getData
|
|
6916
8090
|
? e.clipboardData.getData('text/plain')
|
|
6917
8091
|
: '';
|
|
@@ -6921,7 +8095,9 @@ if (typeof document !== 'undefined') {
|
|
|
6921
8095
|
}
|
|
6922
8096
|
|
|
6923
8097
|
e.preventDefault();
|
|
6924
|
-
|
|
8098
|
+
e.stopPropagation();
|
|
8099
|
+
e.stopImmediatePropagation();
|
|
8100
|
+
void pasteTerminalText(pastedText);
|
|
6925
8101
|
}, true);
|
|
6926
8102
|
|
|
6927
8103
|
}
|
|
@@ -6930,6 +8106,7 @@ if (typeof module !== 'undefined' && module.exports) {
|
|
|
6930
8106
|
module.exports = {
|
|
6931
8107
|
buildCrtHistoryItems,
|
|
6932
8108
|
buildCrtSearchResults,
|
|
8109
|
+
buildCrtStructuredPreview,
|
|
6933
8110
|
calculateCrtAgentPageLayout,
|
|
6934
8111
|
calculateCrtHistoryPageSize,
|
|
6935
8112
|
crtHistoryAgentName,
|
|
@@ -6941,8 +8118,8 @@ if (typeof module !== 'undefined' && module.exports) {
|
|
|
6941
8118
|
findDirectionalNavigationIndex,
|
|
6942
8119
|
isBrowserShortcut,
|
|
6943
8120
|
isCopyShortcut,
|
|
8121
|
+
isCrtNativeTerminalPasteTarget,
|
|
6944
8122
|
isPasteShortcut,
|
|
6945
|
-
getTerminalSequenceForKey,
|
|
6946
8123
|
shouldUseLiveSessionText,
|
|
6947
8124
|
shouldPollSessionView,
|
|
6948
8125
|
deriveSessionTextPatch,
|
|
@@ -6956,19 +8133,26 @@ if (typeof module !== 'undefined' && module.exports) {
|
|
|
6956
8133
|
getCrtHistoryPage,
|
|
6957
8134
|
getCrtAgentPage,
|
|
6958
8135
|
getCrtAgentVerticalPageTarget,
|
|
8136
|
+
getCrtLiveAgents,
|
|
8137
|
+
getCrtRegularAgents,
|
|
8138
|
+
getCrtAgentRemovalFallback,
|
|
6959
8139
|
getAgentDisplayText,
|
|
6960
8140
|
getCrtPreviewCellStyle,
|
|
8141
|
+
getCrtTerminalSnapshotRows,
|
|
6961
8142
|
getCrtAgentTitle,
|
|
6962
8143
|
getCrtProjectName,
|
|
6963
|
-
calculateTerminalInputBridgePosition,
|
|
6964
8144
|
getCrtTerminalFontSize,
|
|
6965
8145
|
isCrtAgentWorking,
|
|
8146
|
+
isCrtLiveAgent,
|
|
6966
8147
|
getCrtAgentReadPatch,
|
|
6967
8148
|
crtRuntimeView,
|
|
6968
8149
|
canSwitchCrtAgentRuntime,
|
|
6969
8150
|
isCrtRuntimeSwitchShortcut,
|
|
8151
|
+
hasCrtStructuredLocalEscapeAction,
|
|
8152
|
+
resolveCrtSessionKeyboardCommand,
|
|
6970
8153
|
structuredComposerAction,
|
|
6971
8154
|
structuredTranscriptTurns,
|
|
8155
|
+
crtStructuredPreviewMessageLines,
|
|
6972
8156
|
getCrtBrandPaneKey,
|
|
6973
8157
|
extractSessionLinks,
|
|
6974
8158
|
formatSelectionStatus,
|
|
@@ -6984,6 +8168,7 @@ if (typeof module !== 'undefined' && module.exports) {
|
|
|
6984
8168
|
getSessionModalDomState
|
|
6985
8169
|
};
|
|
6986
8170
|
} else {
|
|
8171
|
+
installCrtTerminalTestApi();
|
|
6987
8172
|
setupWorkspaceHistoryControls();
|
|
6988
8173
|
setupCrtSearchControls();
|
|
6989
8174
|
document.addEventListener('visibilitychange', () => {
|