farming-code 2.2.14 → 2.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +3 -3
  2. package/README.zh_cn.md +3 -3
  3. package/THIRD_PARTY_NOTICES.md +17 -1
  4. package/backend/acp/packaged-codex-acp.js +13 -0
  5. package/backend/acp-runtime.js +413 -24
  6. package/backend/agent-manager.js +572 -557
  7. package/backend/agent-provider-session.js +15 -1
  8. package/backend/agent-runtime-binding.js +9 -55
  9. package/backend/chat-runtime.js +28 -0
  10. package/backend/codex-rollout-follower.js +83 -0
  11. package/backend/codex-session-archive.js +12 -1
  12. package/backend/codex-session-history.js +94 -0
  13. package/backend/codex-transcript.js +2 -1
  14. package/backend/config-manager.js +25 -17
  15. package/backend/control-api.js +3 -1
  16. package/backend/farming-app-cli.js +21 -0
  17. package/backend/farming-session-store.js +2 -1
  18. package/backend/native-pty-host-client.js +59 -2
  19. package/backend/npm-update-helper.js +65 -7
  20. package/backend/provider-adapters.js +123 -38
  21. package/backend/provider-session-service.js +138 -34
  22. package/backend/server.js +94 -105
  23. package/backend/storage-layout.js +5 -16
  24. package/backend/update-service.js +94 -5
  25. package/backend/usage-history-client.js +305 -0
  26. package/backend/usage-history-scanner.generated.js +2250 -0
  27. package/backend/usage-history-scanner.ts +2718 -0
  28. package/backend/usage-history-smoke.js +64 -0
  29. package/backend/usage-history-worker.js +34 -0
  30. package/backend/usage-monitor.js +188 -588
  31. package/backend/vendor/usage-parsers/CC_STATISTICS_LICENSE +21 -0
  32. package/backend/vendor/usage-parsers/CODEXBAR_LICENSE +21 -0
  33. package/backend/vendor/usage-parsers/VENDOR.json +45 -0
  34. package/dist/acp/LICENSE.codex-acp +190 -0
  35. package/dist/acp/codex-acp-1.1.4.js +30718 -0
  36. package/dist/assets/App-6yWdwzj8.js +210 -0
  37. package/dist/assets/{FileEditorMarkdownPreview-UlVd0O4D.js → FileEditorMarkdownPreview-R0jwwLME.js} +4 -4
  38. package/dist/assets/FileEditorPane-DNy3LAqS.js +2 -0
  39. package/dist/assets/{IconGlyphs-AU22gPDY.js → IconGlyphs-BsUfqpKN.js} +1 -1
  40. package/dist/assets/ProjectFilesSection-BhDEUVHA.js +12 -0
  41. package/dist/assets/{ReviewPage-CeDKDyZh.js → ReviewPage-ChtByBe3.js} +1 -1
  42. package/dist/assets/{abnfDiagram-VRR7QNED-BgawzDQT.js → abnfDiagram-VRR7QNED-nnglrXFi.js} +1 -1
  43. package/dist/assets/{architectureDiagram-ZJ3FMSHR-DUShwPg0.js → architectureDiagram-ZJ3FMSHR-BEXMaFfY.js} +1 -1
  44. package/dist/assets/{blockDiagram-677ZJIJ3-CeGZviXR.js → blockDiagram-677ZJIJ3-1mCezSBk.js} +1 -1
  45. package/dist/assets/{c4Diagram-LMCZKHZV-CLhYht6S.js → c4Diagram-LMCZKHZV-DhTiI6Uv.js} +1 -1
  46. package/dist/assets/channel-BVWNatUB.js +1 -0
  47. package/dist/assets/{chunk-32BRIVSS-LFYpT-Cu.js → chunk-32BRIVSS-CCdsKd73.js} +1 -1
  48. package/dist/assets/{chunk-52WLFC77-BH6jDw9J.js → chunk-52WLFC77-Gncjk4LF.js} +1 -1
  49. package/dist/assets/{chunk-C7G6YPKG-B_mIUlb7.js → chunk-C7G6YPKG-CiDVUP82.js} +1 -1
  50. package/dist/assets/{chunk-EX3LRPZG-C63hVuAJ.js → chunk-EX3LRPZG-z6gXP2_N.js} +1 -1
  51. package/dist/assets/{chunk-FWX5IMBZ-CboX-WF_.js → chunk-FWX5IMBZ-DMZBccQa.js} +2 -2
  52. package/dist/assets/{chunk-HOUHSVGY-BOgSd6Un.js → chunk-HOUHSVGY-D8tIeoPk.js} +1 -1
  53. package/dist/assets/{chunk-ICXQ74PX-C0K-pgZt.js → chunk-ICXQ74PX-b6oQ2Q17.js} +1 -1
  54. package/dist/assets/{chunk-MOJQB5TN-CVMfwN_7.js → chunk-MOJQB5TN-DAVH_p2a.js} +1 -1
  55. package/dist/assets/{chunk-OGEWGWER-DLT3pxyw.js → chunk-OGEWGWER-DxoiMCI-.js} +1 -1
  56. package/dist/assets/{chunk-PUDLZKDR-Cd079yIQ.js → chunk-PUDLZKDR-NsOWh7HM.js} +1 -1
  57. package/dist/assets/{chunk-Q4XR5HBZ-D0jHa6SO.js → chunk-Q4XR5HBZ-Di4H7L21.js} +1 -1
  58. package/dist/assets/{chunk-V7JOEXUC-CVDtR75H.js → chunk-V7JOEXUC-DttpZg9Y.js} +1 -1
  59. package/dist/assets/{chunk-VAUOI2AC-B-W2_9Ot.js → chunk-VAUOI2AC-UHNr_yhX.js} +1 -1
  60. package/dist/assets/{chunk-VR4S4FIN-FD3kWh32.js → chunk-VR4S4FIN-D-Fmtd4Z.js} +1 -1
  61. package/dist/assets/{chunk-WYO6CB5R-C2bGaS5r.js → chunk-WYO6CB5R-CUxMtfK2.js} +4 -4
  62. package/dist/assets/{chunk-ZGVPDNZ5-ymgts0zo.js → chunk-ZGVPDNZ5-CoV2QEl0.js} +1 -1
  63. package/dist/assets/classDiagram-OUVF2IWQ-Db0ib5iE.js +1 -0
  64. package/dist/assets/classDiagram-v2-EOCWNBFH-Bkf69XF9.js +1 -0
  65. package/dist/assets/code-dark-5esj0bRu.css +1 -0
  66. package/dist/assets/{cynefinDiagram-TSTJHNR4-CwpobXIl.js → cynefinDiagram-TSTJHNR4-0xPI92Lg.js} +1 -1
  67. package/dist/assets/{dagre-VKFMJZFB-Dkhlfjna.js → dagre-VKFMJZFB-DfuMG4gU.js} +1 -1
  68. package/dist/assets/{diagram-FQU43EPY-hA8dicp6.js → diagram-FQU43EPY-CT00bLZ6.js} +1 -1
  69. package/dist/assets/{diagram-G47NLZAW-BTY6JTzO.js → diagram-G47NLZAW-Xbh7zXUx.js} +1 -1
  70. package/dist/assets/{diagram-NH7WQ7WH-xkaHHX9M.js → diagram-NH7WQ7WH-SRDVPfn7.js} +1 -1
  71. package/dist/assets/{diagram-OA4YK3LP-gk_fg9UU.js → diagram-OA4YK3LP-CQRli4Np.js} +1 -1
  72. package/dist/assets/{diagram-WEI45ONY-BvC8xd6T.js → diagram-WEI45ONY-DMf17WQi.js} +1 -1
  73. package/dist/assets/{ebnfDiagram-CCIWWBDH-CR7RaNat.js → ebnfDiagram-CCIWWBDH-DhpFkgJ5.js} +1 -1
  74. package/dist/assets/{erDiagram-Q63AITRT-_M-JFPbj.js → erDiagram-Q63AITRT-1IwxWmJo.js} +1 -1
  75. package/dist/assets/file-icons-DpbrD5ZP.js +1 -0
  76. package/dist/assets/flowDiagram-23GEKE2U-DYFo2xmB.js +1 -0
  77. package/dist/assets/{ganttDiagram-NO4QXBWP-Bl8P1yf_.js → ganttDiagram-NO4QXBWP-BNCdfyr1.js} +1 -1
  78. package/dist/assets/{gitGraphDiagram-IHSO6WYX-DoHa5ysz.js → gitGraphDiagram-IHSO6WYX-pbyWZuiX.js} +1 -1
  79. package/dist/assets/{index-BA-iH2Kg.js → index-C8ZFKye1.js} +2 -2
  80. package/dist/assets/{infoDiagram-FWYZ7A6U-DSFcSuUs.js → infoDiagram-FWYZ7A6U-BcTOgDbe.js} +1 -1
  81. package/dist/assets/{ishikawaDiagram-FXEZZL3T-DdkqullI.js → ishikawaDiagram-FXEZZL3T-DCdvGKrn.js} +1 -1
  82. package/dist/assets/{journeyDiagram-5HDEW3XC-B4hRnQJr.js → journeyDiagram-5HDEW3XC-BZ-1q_IT.js} +1 -1
  83. package/dist/assets/{kanban-definition-HUTT4EX6-tm2FZB2E.js → kanban-definition-HUTT4EX6-BIgejCjj.js} +1 -1
  84. package/dist/assets/{line-CmsjK4kn.js → line-CfptFMmX.js} +1 -1
  85. package/dist/assets/main-DSO3i_Gs.css +1 -0
  86. package/dist/assets/{mermaid.core-C7UT_EEL.js → mermaid.core-gSpX30pV.js} +3 -3
  87. package/dist/assets/{mindmap-definition-LN4V7U3C-DplfjGms.js → mindmap-definition-LN4V7U3C-BKBaI4LW.js} +1 -1
  88. package/dist/assets/{pegDiagram-2B236MQR-lu3SdkQs.js → pegDiagram-2B236MQR-sBvDY3VX.js} +1 -1
  89. package/dist/assets/{pieDiagram-ENE6RG2P-3fHbmDf9.js → pieDiagram-ENE6RG2P-DWlWcu08.js} +1 -1
  90. package/dist/assets/{quadrantDiagram-ABIIQ3AL-a5qoWz-X.js → quadrantDiagram-ABIIQ3AL-DZutUd3J.js} +1 -1
  91. package/dist/assets/{railroadDiagram-RFXS5EU6-D2TOuXW9.js → railroadDiagram-RFXS5EU6-DQb3lXGM.js} +1 -1
  92. package/dist/assets/{requirementDiagram-TGXJPOKE-LQBWpUvd.js → requirementDiagram-TGXJPOKE-DqFGXu2e.js} +1 -1
  93. package/dist/assets/{sankeyDiagram-HTMAVEWB-B1ohYoyK.js → sankeyDiagram-HTMAVEWB-d7ECahnO.js} +1 -1
  94. package/dist/assets/{sequenceDiagram-DBY2YBRQ-C3vM-hMq.js → sequenceDiagram-DBY2YBRQ-DdsbKqct.js} +1 -1
  95. package/dist/assets/{stateDiagram-2N3HPSRC-BvFhOryI.js → stateDiagram-2N3HPSRC-P6iSrgwR.js} +1 -1
  96. package/dist/assets/stateDiagram-v2-6OUMAXLB-DIYSf_qh.js +1 -0
  97. package/dist/assets/{swimlanes-5IMT3BWC-DKF_b-2s.js → swimlanes-5IMT3BWC-BGnZJx1M.js} +1 -1
  98. package/dist/assets/swimlanesDiagram-G3AALYLV-BIkrlKrZ.js +8 -0
  99. package/dist/assets/{timeline-definition-FHXFAJF6-vAs11ux_.js → timeline-definition-FHXFAJF6-rA-r3aUR.js} +1 -1
  100. package/dist/assets/{vennDiagram-L72KCM5P-BybTX_9B.js → vennDiagram-L72KCM5P-DiVKUjA9.js} +1 -1
  101. package/dist/assets/{wardleyDiagram-EHGQE667-CicUmMBR.js → wardleyDiagram-EHGQE667-Cfxh7UOn.js} +1 -1
  102. package/dist/assets/workspace-editor-model-D0Im34S5.js +1 -0
  103. package/dist/assets/{workspace-editor-monaco-BICJESG0.js → workspace-editor-monaco-BIpqcWf1.js} +1 -1
  104. package/dist/assets/workspace-editor-monaco-DLlws5Ig.js +1 -0
  105. package/dist/assets/{workspace-view-state-DvYG_9PH.js → workspace-view-state-BpPINu84.js} +1 -1
  106. package/dist/assets/{xychartDiagram-FW5EYKEG-BhWDyCX-.js → xychartDiagram-FW5EYKEG--hmryQeL.js} +1 -1
  107. package/dist/crt-mermaid-renderer.js +313 -313
  108. package/dist/index.html +1 -1
  109. package/frontend/session-bridge.js +24 -2
  110. package/frontend/skins/crt/app.js +203 -85
  111. package/frontend/skins/crt/index.html +2 -11
  112. package/frontend/skins/crt/styles/effects.css +2 -34
  113. package/package.json +30 -12
  114. package/shared/backend-connection-status.d.ts +25 -0
  115. package/shared/backend-connection-status.js +40 -0
  116. package/shared/browser-protocol.js +3 -3
  117. package/backend/app-server-api.js +0 -917
  118. package/backend/codex-app-server-home.js +0 -39
  119. package/backend/codex-app-server-runtime.js +0 -901
  120. package/dist/assets/App-Bumi772v.js +0 -206
  121. package/dist/assets/FileEditorPane-Dxx4rKOg.js +0 -2
  122. package/dist/assets/ProjectFilesSection-DE7Nb9Jk.js +0 -12
  123. package/dist/assets/channel-Uw90AO0b.js +0 -1
  124. package/dist/assets/classDiagram-OUVF2IWQ-DKW_dtku.js +0 -1
  125. package/dist/assets/classDiagram-v2-EOCWNBFH-C8Fi7-yv.js +0 -1
  126. package/dist/assets/code-dark-DPiuyeSp.css +0 -1
  127. package/dist/assets/code-mobile-3C2Rr-CN.css +0 -1
  128. package/dist/assets/file-icons-Bw2qd5iT.js +0 -1
  129. package/dist/assets/flowDiagram-23GEKE2U-DAiS5Y7G.js +0 -1
  130. package/dist/assets/main-Bqtb5kuz.css +0 -1
  131. package/dist/assets/stateDiagram-v2-6OUMAXLB-WMrz4PNo.js +0 -1
  132. package/dist/assets/swimlanesDiagram-G3AALYLV-BGswC9oC.js +0 -8
  133. package/dist/assets/workspace-editor-model-BQol4qbA.js +0 -1
  134. package/dist/assets/workspace-editor-monaco-DRJ7IaXk.js +0 -1
  135. package/scripts/sync-ghostty-vendor.js +0 -66
  136. /package/dist/assets/{core-D0LFJkDt.js → core-B9MwRjDE.js} +0 -0
package/dist/index.html CHANGED
@@ -15,7 +15,7 @@
15
15
  <link rel="apple-touch-icon" sizes="180x180" href="/farming/farming-2/app-icon-v2-180.png" />
16
16
  <link rel="manifest" href="/farming/farming-2/site.webmanifest" />
17
17
  <title>Farming 2</title>
18
- <script type="module" crossorigin src="/farming/assets/index-BA-iH2Kg.js"></script>
18
+ <script type="module" crossorigin src="/farming/assets/index-C8ZFKye1.js"></script>
19
19
  <link rel="modulepreload" crossorigin href="/farming/assets/chunk-DECur_0Z.js">
20
20
  <link rel="modulepreload" crossorigin href="/farming/assets/preload-helper-mJowh_hw.js">
21
21
  <link rel="modulepreload" crossorigin href="/farming/assets/jsx-runtime-BthL93pI.js">
@@ -2,6 +2,8 @@
2
2
  function createClient(options = {}) {
3
3
  const getSocket = options.getSocket || (() => null);
4
4
  const fetchImpl = options.fetchImpl || global.fetch.bind(global);
5
+ const composerResults = new Map();
6
+ let composerRequestSequence = 0;
5
7
 
6
8
  function send(message) {
7
9
  const ws = getSocket();
@@ -31,13 +33,33 @@
31
33
  });
32
34
  },
33
35
 
34
- sendComposerMessage(agentId, message, attachments = []) {
35
- return send({
36
+ sendComposerMessage(agentId, message, attachments = [], options = {}) {
37
+ const requestId = options.onResult
38
+ ? `composer-${Date.now().toString(36)}-${++composerRequestSequence}`
39
+ : '';
40
+ const sent = send({
36
41
  type: 'composer-input',
37
42
  agentId,
38
43
  message,
44
+ ...(requestId ? { requestId } : {}),
39
45
  ...(attachments.length > 0 ? { attachments } : {}),
40
46
  });
47
+ if (sent && requestId) composerResults.set(requestId, options.onResult);
48
+ return sent;
49
+ },
50
+
51
+ handleServerMessage(message) {
52
+ if (!message || message.type !== 'composer-input-result') return false;
53
+ const callback = composerResults.get(message.requestId);
54
+ if (!callback) return false;
55
+ composerResults.delete(message.requestId);
56
+ callback(message);
57
+ return true;
58
+ },
59
+
60
+ rejectPendingComposerMessages(message = 'Connection unavailable') {
61
+ composerResults.forEach(callback => callback({ accepted: false, message }));
62
+ composerResults.clear();
41
63
  },
42
64
 
43
65
  interruptAgent(agentId) {
@@ -79,6 +79,7 @@ let crtAgentPageResizeFrame = null;
79
79
  let dashboardRenderDeferred = false;
80
80
  let lastCrtDashboardSignature = '';
81
81
  let crtPreviewRenderTimer = null;
82
+ let crtPreviewRenderFrame = null;
82
83
  const pendingCrtPreviewRenders = new Map();
83
84
  const crtStructuredPreviewCache = new Map();
84
85
  const crtStructuredPreviewTimers = new Map();
@@ -1233,7 +1234,6 @@ function updateCrtAgentPreviewCard(agent) {
1233
1234
  }
1234
1235
 
1235
1236
  function flushCrtPreviewCardRenders() {
1236
- crtPreviewRenderTimer = null;
1237
1237
  const pending = Array.from(pendingCrtPreviewRenders.values());
1238
1238
  pendingCrtPreviewRenders.clear();
1239
1239
  if (pending.length === 0) return;
@@ -1242,9 +1242,12 @@ function flushCrtPreviewCardRenders() {
1242
1242
  return;
1243
1243
  }
1244
1244
 
1245
- pending.forEach(({ agent, previousSnapshot, previousText, previewChanged }) => {
1245
+ pending.forEach(({ agent, previewChanged }) => {
1246
1246
  const currentAgent = state && state.agents.find((candidate) => candidate.id === agent.id);
1247
1247
  if (!isCrtLiveAgent(currentAgent)) return;
1248
+ // updateCrtAgentPreviewCard prepares a detached preview tree and swaps it
1249
+ // into the existing card atomically. Keeping that card stable avoids the
1250
+ // flash caused by rebuilding or overlaying every sampled frame.
1248
1251
  if (!updateCrtAgentPreviewCard(currentAgent)) {
1249
1252
  if (currentAgent.id !== state.mainAgentId && !isCrtAgentOnCurrentPage(currentAgent.id)) {
1250
1253
  if (previewChanged) pulseCrtBrandForAgent(agent.id);
@@ -1253,44 +1256,30 @@ function flushCrtPreviewCardRenders() {
1253
1256
  renderCrtDashboardIfNeeded(true);
1254
1257
  return;
1255
1258
  }
1256
- if (previewChanged) {
1257
- appendCrtPreviewAfterimage(agent.id, previousSnapshot, previousText);
1258
- pulseCrtBrandForAgent(agent.id);
1259
- }
1260
1259
  });
1261
1260
  }
1262
1261
 
1263
- function scheduleCrtPreviewCardRender(agent, previousSnapshot, previousText, previewChanged) {
1262
+ function flushCrtPreviewCardRendersOnFrame() {
1263
+ crtPreviewRenderTimer = null;
1264
+ if (crtPreviewRenderFrame !== null) return;
1265
+ crtPreviewRenderFrame = requestAnimationFrame(() => {
1266
+ crtPreviewRenderFrame = null;
1267
+ flushCrtPreviewCardRenders();
1268
+ });
1269
+ }
1270
+
1271
+ function scheduleCrtPreviewCardRender(agent, previewChanged = false) {
1264
1272
  if (!isCrtLiveAgent(agent)) return;
1265
1273
  const existing = pendingCrtPreviewRenders.get(agent.id);
1266
1274
  pendingCrtPreviewRenders.set(agent.id, {
1267
1275
  agent,
1268
- previousSnapshot: existing ? existing.previousSnapshot : previousSnapshot,
1269
- previousText: existing ? existing.previousText : previousText,
1270
1276
  previewChanged: Boolean(previewChanged || (existing && existing.previewChanged)),
1271
1277
  });
1272
- if (!crtPreviewRenderTimer) {
1273
- crtPreviewRenderTimer = setTimeout(flushCrtPreviewCardRenders, CRT_PREVIEW_RENDER_INTERVAL_MS);
1278
+ if (!crtPreviewRenderTimer && crtPreviewRenderFrame === null) {
1279
+ crtPreviewRenderTimer = setTimeout(flushCrtPreviewCardRendersOnFrame, CRT_PREVIEW_RENDER_INTERVAL_MS);
1274
1280
  }
1275
1281
  }
1276
1282
 
1277
- function appendCrtPreviewAfterimage(agentId, snapshot, fallbackText) {
1278
- if (typeof document === 'undefined') return;
1279
- const block = Array.from(document.querySelectorAll('[data-agent-id]'))
1280
- .find((candidate) => candidate.dataset.agentId === agentId);
1281
- const output = block && block.querySelector('.agent-output');
1282
- if (!output || (!snapshot && !fallbackText)) return;
1283
-
1284
- const afterimage = document.createElement('div');
1285
- afterimage.className = 'agent-output-afterimage';
1286
- const tail = document.createElement('div');
1287
- tail.className = 'agent-output-tail';
1288
- if (!renderCrtTerminalSnapshot(tail, snapshot)) tail.textContent = fallbackText;
1289
- afterimage.appendChild(tail);
1290
- afterimage.addEventListener('animationend', () => afterimage.remove(), { once: true });
1291
- output.appendChild(afterimage);
1292
- }
1293
-
1294
1283
  function crtCommandProgram(command) {
1295
1284
  const tokens = String(command || '').trim().split(/\s+/).filter(Boolean);
1296
1285
  let index = tokens[0] === 'env' ? 1 : 0;
@@ -1400,8 +1389,8 @@ function isCrtAgentWorking(agent) {
1400
1389
  if (agent.terminalStatus && agent.terminalStatus.busy === true) return true;
1401
1390
  const runtime = agent.runtimeBinding || { kind: 'terminal' };
1402
1391
  if (
1403
- runtime.kind === 'app-server' &&
1404
- ['working', 'waiting-for-input', 'interrupting'].includes(runtime.state || '')
1392
+ runtime.kind === 'acp' &&
1393
+ ['working', 'waiting-for-permission', 'waiting-for-input', 'interrupting'].includes(runtime.state || '')
1405
1394
  ) {
1406
1395
  return true;
1407
1396
  }
@@ -2273,10 +2262,25 @@ function installCrtTerminalTestApi() {
2273
2262
  checkpointInFlight: replication.checkpointInFlight,
2274
2263
  checkpointInstallInProgress: replication.installInProgress,
2275
2264
  initialFocusPending: replication.initialFocusPending,
2265
+ queuedTransitionCount: replication.replayState.queuedTransitions.length,
2276
2266
  pendingFitResize: replication.pendingFitResize,
2277
2267
  fitResizeTimerPending: replication.fitResizeTimer !== null
2278
2268
  };
2279
2269
  },
2270
+ getGeometry() {
2271
+ const buffer = terminal?.buffer?.active;
2272
+ const proposed = fitAddon && typeof fitAddon.proposeDimensions === 'function'
2273
+ ? fitAddon.proposeDimensions()
2274
+ : null;
2275
+ return {
2276
+ cols: terminal?.cols || 0,
2277
+ rows: terminal?.rows || 0,
2278
+ proposedCols: proposed?.cols || 0,
2279
+ proposedRows: proposed?.rows || 0,
2280
+ baseY: buffer?.baseY || 0,
2281
+ viewportY: buffer?.viewportY || 0
2282
+ };
2283
+ },
2280
2284
  getRows() {
2281
2285
  const buffer = terminal && terminal.buffer && terminal.buffer.active;
2282
2286
  if (!buffer || typeof buffer.getLine !== 'function') return [];
@@ -2414,7 +2418,7 @@ function finishCrtTerminalReplay(replication = crtTerminalReplication) {
2414
2418
  requestAnimationFrame(() => {
2415
2419
  if (!crtTerminalReplication || crtTerminalReplication !== replication || replication.disposed) return;
2416
2420
  document.getElementById('terminal-output')?.classList.remove('crt-terminal-checkpoint-installing');
2417
- sendSessionResize(replication.agentId);
2421
+ sendSessionResize(replication.agentId, null, { forceDuringRecovery: true });
2418
2422
  settleInitialCrtTerminalFocus(replication);
2419
2423
  });
2420
2424
  }
@@ -2546,6 +2550,10 @@ function performCrtTerminalCheckpointInstall(replication, sessionView) {
2546
2550
  runtime.markHydrated(sessionView.renderOutput.length);
2547
2551
  syncSessionRuntimeState();
2548
2552
  }
2553
+ // The checkpoint geometry belongs to the previous viewer. Reconcile it with
2554
+ // this CRT container as soon as the checkpoint is committed; a continuous
2555
+ // live stream must not postpone the fit until the replay queue becomes idle.
2556
+ sendSessionResize(replication.agentId, null, { forceDuringRecovery: true });
2549
2557
  if (drainCrtTerminalCheckpointInstall(replication)) return;
2550
2558
  flushCrtTerminalTransitions();
2551
2559
  finishCrtTerminalReplay(replication);
@@ -2600,6 +2608,7 @@ function installCrtTerminalCheckpoint(sessionView) {
2600
2608
  terminal.rows === sessionView.previewRows
2601
2609
  ) {
2602
2610
  TERMINAL_REPLAY.commitCheckpoint(replication.replayState, checkpoint);
2611
+ sendSessionResize(replication.agentId);
2603
2612
  flushCrtTerminalTransitions();
2604
2613
  finishCrtTerminalReplay(replication);
2605
2614
  return true;
@@ -2786,6 +2795,9 @@ async function loadGlobalSettings() {
2786
2795
  ...globalSettings,
2787
2796
  ...(data.settings || {})
2788
2797
  };
2798
+ if (globalSettings.instanceName) {
2799
+ document.title = `${globalSettings.instanceName} · Farming CRT`;
2800
+ }
2789
2801
  applyCRTEffects(globalSettings.crtSkinEffectsEnabled !== false);
2790
2802
  if (state) renderCrtDashboardIfNeeded();
2791
2803
  if (agents.length > 0) renderAgentList();
@@ -2813,6 +2825,9 @@ async function saveGlobalSettings() {
2813
2825
  ...globalSettings,
2814
2826
  ...(data.settings || {})
2815
2827
  };
2828
+ if (globalSettings.instanceName) {
2829
+ document.title = `${globalSettings.instanceName} · Farming CRT`;
2830
+ }
2816
2831
  syncWorkspaceSettings();
2817
2832
  }
2818
2833
  } catch (error) {
@@ -3841,11 +3856,17 @@ function renderCrtBillingSelectedDay({ preferSummary = false } = {}) {
3841
3856
  updateCrtBillingExactMetric('billing-day-cache-read', displayPoint && displayPoint.cacheReadTokens, { date: selectedDate, live: isToday });
3842
3857
  updateCrtBillingExactMetric('billing-day-cache-write', displayPoint && displayPoint.cacheWriteTokens, { date: selectedDate, live: isToday });
3843
3858
  if (providers) {
3844
- const providerTotals = displayProviders ? Object.entries(displayProviders) : [];
3845
- providers.textContent = providerTotals
3859
+ const providerTotals = displayProviders
3860
+ ? Object.entries(displayProviders).filter(([, usage]) => (
3861
+ Math.max(0, Number(usage && usage.totalTokens) || 0) > 0
3862
+ ))
3863
+ : [];
3864
+ providers.textContent = providerTotals.length > 0
3865
+ ? `${providerTotals.length} ${providerTotals.length === 1 ? 'SOURCE' : 'SOURCES'}`
3866
+ : '--';
3867
+ providers.title = providerTotals
3846
3868
  .map(([provider, usage]) => `${provider.toUpperCase()} ${formatCrtExactUsageValue(usage && usage.totalTokens)}`)
3847
3869
  .join(' · ') || '--';
3848
- providers.title = providers.textContent;
3849
3870
  }
3850
3871
  if (stateLabel) {
3851
3872
  const notes = isToday ? ['LIVE 5S', 'PARTIAL DAY', 'INCL CACHE'] : ['COMPLETE DAY', 'INCL CACHE'];
@@ -5297,6 +5318,7 @@ function connect() {
5297
5318
  socket.onmessage = (event) => {
5298
5319
  if (ws !== socket) return;
5299
5320
  const data = JSON.parse(event.data);
5321
+ if (getSessionClient()?.handleServerMessage(data)) return;
5300
5322
  if (data.type === 'protocol-hello') {
5301
5323
  if (data.protocolVersion !== CRT_PROTOCOL_VERSION) {
5302
5324
  socket.close(4002, `Unsupported Farming protocol version ${data.protocolVersion}`);
@@ -5315,6 +5337,18 @@ function connect() {
5315
5337
  terminalPreviewSnapshots.forEach((_snapshot, agentId) => {
5316
5338
  if (!activeAgentIds.has(agentId)) terminalPreviewSnapshots.delete(agentId);
5317
5339
  });
5340
+ if (
5341
+ focusedAgentId
5342
+ && !activeAgentIds.has(focusedAgentId)
5343
+ && !runtimeSwitchPending
5344
+ && !pendingRuntimeSwitchAgentId
5345
+ ) {
5346
+ // Another viewer may kill or archive the Agent while this CRT session
5347
+ // is open. Runtime replacement has its own bounded handoff and may
5348
+ // briefly remove the old id, so do not mistake that legal transition
5349
+ // for a dead session.
5350
+ closeSession();
5351
+ }
5318
5352
  state.agents.forEach((agent) => {
5319
5353
  if (terminalPreviewSnapshots.has(agent.id)) {
5320
5354
  agent.previewSnapshot = terminalPreviewSnapshots.get(agent.id);
@@ -5359,21 +5393,23 @@ function connect() {
5359
5393
  } else if (data.type === 'session-preview') {
5360
5394
  const preview = data.preview;
5361
5395
  if (preview && preview.agentId) {
5362
- terminalPreviewSnapshots.set(preview.agentId, preview.previewSnapshot || null);
5396
+ if (preview.previewSnapshot && typeof preview.previewSnapshot === 'object') {
5397
+ terminalPreviewSnapshots.set(preview.agentId, preview.previewSnapshot);
5398
+ }
5363
5399
  const agent = state && state.agents.find((candidate) => candidate.id === preview.agentId);
5364
5400
  if (agent) {
5365
- const previousSnapshot = agent.previewSnapshot;
5366
- const previousText = getAgentDisplayText(agent);
5367
5401
  const previewChanged = typeof preview.previewText === 'string'
5368
5402
  && preview.previewText !== agent.previewText;
5369
5403
  agent.previewText = preview.previewText || agent.previewText;
5370
5404
  agent.previewCols = preview.cols || agent.previewCols;
5371
5405
  agent.previewRows = preview.rows || agent.previewRows;
5372
- agent.previewSnapshot = preview.previewSnapshot || null;
5406
+ // A null preview is not a completed new frame. Retain the last
5407
+ // authoritative snapshot until a replacement is ready.
5408
+ agent.previewSnapshot = preview.previewSnapshot || agent.previewSnapshot || null;
5373
5409
  if (preview.terminalStatus) agent.terminalStatus = preview.terminalStatus;
5374
5410
  if (preview.runtimeObservation) agent.runtimeObservation = preview.runtimeObservation;
5375
5411
  if (isCrtSessionOpen()) dashboardRenderDeferred = true;
5376
- else scheduleCrtPreviewCardRender(agent, previousSnapshot, previousText, previewChanged);
5412
+ else scheduleCrtPreviewCardRender(agent, previewChanged);
5377
5413
  }
5378
5414
  }
5379
5415
  } else if (data.type === 'session-output') {
@@ -5404,7 +5440,8 @@ function connect() {
5404
5440
  const agent = activity && state && state.agents.find((candidate) => candidate.id === activity.agentId);
5405
5441
  if (agent) {
5406
5442
  Object.assign(agent, activity);
5407
- renderCrtDashboardIfNeeded();
5443
+ if (isCrtSessionOpen()) dashboardRenderDeferred = true;
5444
+ else scheduleCrtPreviewCardRender(agent);
5408
5445
  }
5409
5446
  } else if (data.type === 'system-stats') {
5410
5447
  updateSystemStats(data.stats, data.uptime, data.usageRate);
@@ -5420,6 +5457,7 @@ function connect() {
5420
5457
  socket.onclose = () => {
5421
5458
  if (ws !== socket) return;
5422
5459
  ws = null;
5460
+ getSessionClient()?.rejectPendingComposerMessages();
5423
5461
  console.log('Disconnected from server');
5424
5462
  if (crtTerminalReplication) {
5425
5463
  clearPendingCrtTerminalFitResize(crtTerminalReplication);
@@ -5842,7 +5880,6 @@ function structuredRuntimeKind(agent) {
5842
5880
  const kind = agent && agent.runtimeBinding && agent.runtimeBinding.kind;
5843
5881
  if (kind === 'acp') return 'ACP';
5844
5882
  if (kind === 'json') return 'JSON';
5845
- if (kind === 'app-server') return 'APP SERVER';
5846
5883
  return '';
5847
5884
  }
5848
5885
 
@@ -5868,6 +5905,19 @@ function canSwitchCrtAgentRuntime(agent) {
5868
5905
  );
5869
5906
  }
5870
5907
 
5908
+ function crtRuntimeSwitchUnavailableReason(agent) {
5909
+ if (!agent || !agent.providerCapabilities?.runtimeSwitch || !agent.providerCapabilities?.supportsChat) {
5910
+ return '';
5911
+ }
5912
+ if (agent.providerSessionTemporary === true && agent.terminalInputReceived === true) {
5913
+ return 'MSG unavailable: this temporary Terminal session already has input and cannot be resumed safely.';
5914
+ }
5915
+ if (!String(agent.providerSessionId || '').trim()) {
5916
+ return 'MSG unavailable: this Agent has no resumable provider session yet.';
5917
+ }
5918
+ return '';
5919
+ }
5920
+
5871
5921
  function isCrtRuntimeSwitchShortcut(event) {
5872
5922
  return Boolean(
5873
5923
  event
@@ -5931,11 +5981,27 @@ function updateCrtRuntimeSwitchControl(agent) {
5931
5981
  const terminalButton = document.getElementById('crt-runtime-terminal');
5932
5982
  if (!control || !chatButton || !terminalButton) return;
5933
5983
  const supported = canSwitchCrtAgentRuntime(agent);
5984
+ const unavailableReason = crtRuntimeSwitchUnavailableReason(agent);
5934
5985
  const view = crtRuntimeView(agent);
5935
- control.hidden = !supported;
5986
+ // Keep a disabled control visible for a provider that supports Chat when
5987
+ // this particular session cannot be resumed. Hiding it made a live
5988
+ // Terminal look like a product capability was missing, while silently
5989
+ // enabling it would risk replacing a temporary session and losing input.
5990
+ control.hidden = !supported && !unavailableReason;
5936
5991
  control.setAttribute('aria-busy', runtimeSwitchPending ? 'true' : 'false');
5937
5992
  chatButton.disabled = !supported || runtimeSwitchPending;
5938
5993
  terminalButton.disabled = !supported || runtimeSwitchPending;
5994
+ if (unavailableReason) {
5995
+ control.title = unavailableReason;
5996
+ control.setAttribute('aria-label', unavailableReason);
5997
+ chatButton.title = unavailableReason;
5998
+ terminalButton.title = unavailableReason;
5999
+ } else {
6000
+ control.removeAttribute('title');
6001
+ control.removeAttribute('aria-label');
6002
+ chatButton.title = 'Chat view [Alt+M]';
6003
+ terminalButton.title = 'Terminal view [Alt+M]';
6004
+ }
5939
6005
  chatButton.classList.toggle('active', view === 'chat');
5940
6006
  terminalButton.classList.toggle('active', view === 'terminal');
5941
6007
  chatButton.setAttribute('aria-pressed', view === 'chat' ? 'true' : 'false');
@@ -5973,7 +6039,7 @@ async function switchCrtSessionRuntimeMode(mode) {
5973
6039
  if (runtimeSwitchPending || !focusedAgentId || !state) return;
5974
6040
  const agent = state.agents.find((candidate) => candidate.id === focusedAgentId);
5975
6041
  if (!canSwitchCrtAgentRuntime(agent)) return;
5976
- const targetMode = mode === 'terminal' ? 'terminal' : 'acp';
6042
+ const targetMode = mode === 'terminal' ? 'terminal' : 'chat';
5977
6043
  if (crtRuntimeView(agent) === (targetMode === 'terminal' ? 'terminal' : 'chat')) return;
5978
6044
 
5979
6045
  const requestSequence = ++runtimeSwitchRequestSequence;
@@ -6006,14 +6072,17 @@ async function switchCrtSessionRuntimeMode(mode) {
6006
6072
  function toggleCrtSessionRuntimeMode() {
6007
6073
  if (!focusedAgentId || !state) return;
6008
6074
  const agent = state.agents.find((candidate) => candidate.id === focusedAgentId);
6009
- if (!canSwitchCrtAgentRuntime(agent)) return;
6010
- void switchCrtSessionRuntimeMode(crtRuntimeView(agent) === 'chat' ? 'terminal' : 'acp');
6075
+ if (!canSwitchCrtAgentRuntime(agent)) {
6076
+ const reason = crtRuntimeSwitchUnavailableReason(agent);
6077
+ if (reason) setCrtRuntimeSwitchStatus(reason, true);
6078
+ return;
6079
+ }
6080
+ void switchCrtSessionRuntimeMode(crtRuntimeView(agent) === 'chat' ? 'terminal' : 'chat');
6011
6081
  }
6012
6082
 
6013
6083
  function structuredTranscriptEndpoint(agent) {
6014
6084
  if (agent.runtimeBinding.kind === 'acp') return 'acp-transcript';
6015
- if (agent.runtimeBinding.kind === 'json') return 'json-cli-transcript';
6016
- return 'codex-app-server-transcript';
6085
+ return 'json-cli-transcript';
6017
6086
  }
6018
6087
 
6019
6088
  function structuredRuntimeStatus(agent) {
@@ -6033,7 +6102,6 @@ function structuredComposerAction(agent, draft = '') {
6033
6102
  const working = ['working', 'waiting-for-permission'].includes(status);
6034
6103
  if (working) {
6035
6104
  if (agent.runtimeBinding?.kind === 'acp' && String(draft || '').trim()) return 'send';
6036
- if (agent.runtimeBinding?.kind === 'app-server' && String(draft || '').trim()) return 'steer';
6037
6105
  return 'interrupt';
6038
6106
  }
6039
6107
  if (['starting', 'interrupting'].includes(status)) return 'disabled';
@@ -6042,9 +6110,9 @@ function structuredComposerAction(agent, draft = '') {
6042
6110
  : 'disabled';
6043
6111
  }
6044
6112
 
6045
- function queueStructuredComposerFollowUp(agentId, message) {
6113
+ function queueStructuredComposerFollowUp(agentId, message, attachments = []) {
6046
6114
  const queue = structuredComposerPendingFollowUps.get(agentId) || [];
6047
- queue.push(message);
6115
+ queue.push({ message, attachments });
6048
6116
  structuredComposerPendingFollowUps.set(agentId, queue);
6049
6117
  }
6050
6118
 
@@ -6052,10 +6120,10 @@ function flushStructuredComposerFollowUp(agent) {
6052
6120
  if (!agent || structuredRuntimeStatus(agent) !== 'idle') return;
6053
6121
  const queue = structuredComposerPendingFollowUps.get(agent.id);
6054
6122
  if (!queue || queue.length === 0) return;
6055
- const message = queue.shift();
6123
+ const next = queue.shift();
6056
6124
  if (queue.length === 0) structuredComposerPendingFollowUps.delete(agent.id);
6057
- if (!getSessionClient()?.sendComposerMessage(agent.id, message)) {
6058
- queue.unshift(message);
6125
+ if (!getSessionClient()?.sendComposerMessage(agent.id, next.message, next.attachments)) {
6126
+ queue.unshift(next);
6059
6127
  structuredComposerPendingFollowUps.set(agent.id, queue);
6060
6128
  return;
6061
6129
  }
@@ -6481,9 +6549,7 @@ function updateStructuredComposerState(agent) {
6481
6549
  }
6482
6550
  sendButton.disabled = action === 'disabled';
6483
6551
  sendButton.dataset.action = action;
6484
- sendButton.textContent = action === 'interrupt'
6485
- ? 'BREAK [ESC]'
6486
- : action === 'steer' ? 'STEER [ENTER]' : 'SEND [ENTER]';
6552
+ sendButton.textContent = action === 'interrupt' ? 'BREAK [ESC]' : 'SEND [ENTER]';
6487
6553
  input.placeholder = error
6488
6554
  ? 'Session unavailable'
6489
6555
  : (busy ? 'Agent is changing state...' : 'Type a message...');
@@ -6542,6 +6608,7 @@ function readStructuredTextFile(file) {
6542
6608
  async function prepareStructuredAttachment(file) {
6543
6609
  const attachment = {
6544
6610
  id: structuredAttachmentId(file),
6611
+ kind: String(file.type || '').startsWith('audio/') ? 'audio' : 'image',
6545
6612
  name: file.name || 'attachment',
6546
6613
  status: 'uploading',
6547
6614
  messageBlock: ''
@@ -6549,15 +6616,21 @@ async function prepareStructuredAttachment(file) {
6549
6616
  structuredComposerAttachments.push(attachment);
6550
6617
  renderStructuredComposerAttachments();
6551
6618
  try {
6552
- if (String(file.type || '').startsWith('image/')) {
6553
- const response = await fetch(farmingApiPath('/attachments/image'), {
6619
+ if (String(file.type || '').startsWith('image/') || String(file.type || '').startsWith('audio/')) {
6620
+ const image = attachment.kind === 'image';
6621
+ const response = await fetch(farmingApiPath(image ? '/attachments/image' : '/attachments/audio'), {
6554
6622
  method: 'POST',
6555
- headers: { 'Content-Type': file.type || 'image/png' },
6623
+ headers: { 'Content-Type': file.type || (image ? 'image/png' : 'audio/wav') },
6556
6624
  body: file
6557
6625
  });
6558
6626
  const body = await response.json().catch(() => null);
6559
- if (!response.ok || !body || !body.path) throw new Error(`Image upload failed (${response.status})`);
6560
- attachment.messageBlock = `Attached image: ${body.name || attachment.name}\n\nImage path: ${body.path}`;
6627
+ if (!response.ok || !body || !body.path) throw new Error(`${image ? 'Image' : 'Audio'} upload failed (${response.status})`);
6628
+ attachment.path = body.path;
6629
+ attachment.type = body.type || file.type || 'image/png';
6630
+ attachment.size = Number(body.size) || file.size || 0;
6631
+ attachment.messageBlock = image
6632
+ ? `Attached image: ${body.name || attachment.name}\n\nImage path: ${body.path}`
6633
+ : `Attached audio: ${body.name || attachment.name}\n\nAudio path: ${body.path}`;
6561
6634
  } else {
6562
6635
  const content = await readStructuredTextFile(file);
6563
6636
  const limit = 50000;
@@ -6581,11 +6654,25 @@ function addStructuredAttachmentFiles(files) {
6581
6654
 
6582
6655
  function structuredComposerMessage(draft) {
6583
6656
  return structuredComposerAttachments
6584
- .filter((attachment) => attachment.status === 'ready' && attachment.messageBlock)
6657
+ .filter((attachment) => attachment.status === 'ready' && attachment.messageBlock && !attachment.path)
6585
6658
  .reduce((message, attachment) => structuredAttachmentBlock(message, attachment.messageBlock), String(draft || ''))
6586
6659
  .trim();
6587
6660
  }
6588
6661
 
6662
+ function structuredComposerPromptAttachments() {
6663
+ return structuredComposerAttachments.flatMap((attachment) => (
6664
+ attachment.status === 'ready' && attachment.path
6665
+ ? [{
6666
+ kind: attachment.kind,
6667
+ path: attachment.path,
6668
+ name: attachment.name,
6669
+ type: attachment.type,
6670
+ size: attachment.size,
6671
+ }]
6672
+ : []
6673
+ ));
6674
+ }
6675
+
6589
6676
  function structuredComposerHistoryFor(agentId) {
6590
6677
  if (!agentId) return [];
6591
6678
  return structuredComposerHistory.get(agentId) || [];
@@ -7013,11 +7100,11 @@ function setupStructuredSessionComposer() {
7013
7100
  updateStructuredComposerState(agent);
7014
7101
  });
7015
7102
  input.addEventListener('paste', (event) => {
7016
- const imageFiles = Array.from(event.clipboardData && event.clipboardData.files || [])
7017
- .filter((file) => String(file.type || '').startsWith('image/'));
7018
- if (!imageFiles.length) return;
7103
+ const mediaFiles = Array.from(event.clipboardData && event.clipboardData.files || [])
7104
+ .filter((file) => /^(image|audio)\//.test(String(file.type || '')));
7105
+ if (!mediaFiles.length) return;
7019
7106
  event.preventDefault();
7020
- addStructuredAttachmentFiles(imageFiles);
7107
+ addStructuredAttachmentFiles(mediaFiles);
7021
7108
  });
7022
7109
  input.addEventListener('compositionend', () => {
7023
7110
  structuredComposerCompositionEndAt = Date.now();
@@ -7166,11 +7253,25 @@ function setupStructuredSessionComposer() {
7166
7253
  }
7167
7254
  const draft = input.value;
7168
7255
  const message = structuredComposerMessage(draft);
7169
- if (!message) return;
7170
- const sent = action === 'send' && structuredRuntimeStatus(agent) !== 'idle'
7171
- ? (queueStructuredComposerFollowUp(focusedAgentId, message), true)
7172
- : getSessionClient()?.sendComposerMessage(focusedAgentId, message);
7256
+ const promptAttachments = structuredComposerPromptAttachments();
7257
+ if (!message && promptAttachments.length === 0) return;
7173
7258
  const statusNode = document.getElementById('crt-structured-composer-status');
7259
+ const completeSubmission = () => {
7260
+ addStructuredComposerHistory(focusedAgentId, draft);
7261
+ input.value = '';
7262
+ structuredComposerAttachments = [];
7263
+ renderStructuredComposerAttachments();
7264
+ resizeStructuredComposerInput(input);
7265
+ if (statusNode) {
7266
+ statusNode.textContent = 'SENDING...';
7267
+ statusNode.classList.remove('error');
7268
+ }
7269
+ updateStructuredComposerState(agent);
7270
+ setTimeout(() => void refreshStructuredSession(focusedAgentId, true), 160);
7271
+ };
7272
+ const sent = action === 'send' && structuredRuntimeStatus(agent) !== 'idle'
7273
+ ? (queueStructuredComposerFollowUp(focusedAgentId, message, promptAttachments), true)
7274
+ : getSessionClient()?.sendComposerMessage(focusedAgentId, message, promptAttachments);
7174
7275
  if (!sent) {
7175
7276
  if (statusNode) {
7176
7277
  statusNode.textContent = 'Connection unavailable';
@@ -7178,17 +7279,7 @@ function setupStructuredSessionComposer() {
7178
7279
  }
7179
7280
  return;
7180
7281
  }
7181
- addStructuredComposerHistory(focusedAgentId, draft);
7182
- input.value = '';
7183
- structuredComposerAttachments = [];
7184
- renderStructuredComposerAttachments();
7185
- resizeStructuredComposerInput(input);
7186
- if (statusNode) {
7187
- statusNode.textContent = action === 'steer' ? 'STEERING...' : 'SENDING...';
7188
- statusNode.classList.remove('error');
7189
- }
7190
- updateStructuredComposerState(agent);
7191
- setTimeout(() => void refreshStructuredSession(focusedAgentId, true), 160);
7282
+ completeSubmission();
7192
7283
  });
7193
7284
  }
7194
7285
 
@@ -7536,7 +7627,11 @@ function clearPendingCrtTerminalFitResize(replication) {
7536
7627
  replication.pendingFitResize = null;
7537
7628
  }
7538
7629
 
7539
- function commitCrtTerminalResize(agentId, normalizedDimensions) {
7630
+ function commitCrtTerminalResize(
7631
+ agentId,
7632
+ normalizedDimensions,
7633
+ { allowDuringRecovery = false } = {}
7634
+ ) {
7540
7635
  const replication = crtTerminalReplication;
7541
7636
  if (
7542
7637
  !agentId ||
@@ -7544,7 +7639,7 @@ function commitCrtTerminalResize(agentId, normalizedDimensions) {
7544
7639
  !replication ||
7545
7640
  replication.disposed ||
7546
7641
  replication.agentId !== agentId ||
7547
- replication.replayState.recovering ||
7642
+ (!allowDuringRecovery && replication.replayState.recovering) ||
7548
7643
  (
7549
7644
  normalizedDimensions.cols === replication.lastResizeCols &&
7550
7645
  normalizedDimensions.rows === replication.lastResizeRows
@@ -7586,7 +7681,11 @@ function scheduleCrtTerminalFitResize(replication, agentId, normalizedDimensions
7586
7681
  }, CRT_TERMINAL_RESIZE_SETTLE_MS);
7587
7682
  }
7588
7683
 
7589
- function sendSessionResize(agentId = focusedAgentId, requestedDimensions = null) {
7684
+ function sendSessionResize(
7685
+ agentId = focusedAgentId,
7686
+ requestedDimensions = null,
7687
+ { forceDuringRecovery = false } = {}
7688
+ ) {
7590
7689
  const replication = crtTerminalReplication;
7591
7690
  if (
7592
7691
  !agentId ||
@@ -7594,7 +7693,7 @@ function sendSessionResize(agentId = focusedAgentId, requestedDimensions = null)
7594
7693
  !fitAddon ||
7595
7694
  !replication ||
7596
7695
  replication.agentId !== agentId ||
7597
- replication.replayState.recovering
7696
+ (!forceDuringRecovery && replication.replayState.recovering)
7598
7697
  ) return;
7599
7698
  const dimensions = requestedDimensions || (
7600
7699
  typeof fitAddon.proposeDimensions === 'function'
@@ -7618,6 +7717,25 @@ function sendSessionResize(agentId = focusedAgentId, requestedDimensions = null)
7618
7717
  normalizedDimensions.rows === replication.lastResizeRows
7619
7718
  ) {
7620
7719
  clearPendingCrtTerminalFitResize(replication);
7720
+ // A checkpoint can temporarily restore the previous viewer's geometry
7721
+ // after this surface's resize was already delivered. Reapply the known
7722
+ // local geometry without sending a duplicate resize to the backend.
7723
+ if (
7724
+ terminal.cols !== normalizedDimensions.cols ||
7725
+ terminal.rows !== normalizedDimensions.rows
7726
+ ) {
7727
+ replication.applyingLocalResize = true;
7728
+ try {
7729
+ terminal.resize(normalizedDimensions.cols, normalizedDimensions.rows);
7730
+ } finally {
7731
+ replication.applyingLocalResize = false;
7732
+ }
7733
+ }
7734
+ return;
7735
+ }
7736
+ if (forceDuringRecovery) {
7737
+ clearPendingCrtTerminalFitResize(replication);
7738
+ commitCrtTerminalResize(agentId, normalizedDimensions, { allowDuringRecovery: true });
7621
7739
  return;
7622
7740
  }
7623
7741
  scheduleCrtTerminalFitResize(replication, agentId, normalizedDimensions);