farming-code 2.2.13 → 2.2.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +81 -105
  2. package/README.zh_cn.md +82 -106
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/backend/acp/packaged-codex-acp.js +13 -0
  5. package/backend/acp-runtime.js +192 -14
  6. package/backend/agent-manager.js +176 -510
  7. package/backend/agent-runtime-binding.js +9 -55
  8. package/backend/chat-runtime.js +28 -0
  9. package/backend/codex-transcript.js +2 -1
  10. package/backend/config-manager.js +22 -14
  11. package/backend/control-api.js +3 -1
  12. package/backend/farming-app-cli.js +13 -0
  13. package/backend/farming-session-store.js +2 -1
  14. package/backend/npm-update-helper.js +65 -7
  15. package/backend/provider-adapters.js +6 -2
  16. package/backend/server.js +89 -104
  17. package/backend/storage-layout.js +0 -16
  18. package/backend/update-service.js +94 -5
  19. package/dist/acp/LICENSE.codex-acp +190 -0
  20. package/dist/acp/codex-acp-1.1.4.js +30718 -0
  21. package/dist/assets/App-y6p2yRar.js +206 -0
  22. package/dist/assets/{FileEditorMarkdownPreview-UlVd0O4D.js → FileEditorMarkdownPreview-R0jwwLME.js} +4 -4
  23. package/dist/assets/FileEditorPane-DNy3LAqS.js +2 -0
  24. package/dist/assets/{IconGlyphs-AU22gPDY.js → IconGlyphs-BsUfqpKN.js} +1 -1
  25. package/dist/assets/ProjectFilesSection-BhDEUVHA.js +12 -0
  26. package/dist/assets/{ReviewPage-CeDKDyZh.js → ReviewPage-ChtByBe3.js} +1 -1
  27. package/dist/assets/{abnfDiagram-VRR7QNED-BgawzDQT.js → abnfDiagram-VRR7QNED-nnglrXFi.js} +1 -1
  28. package/dist/assets/{architectureDiagram-ZJ3FMSHR-DUShwPg0.js → architectureDiagram-ZJ3FMSHR-BEXMaFfY.js} +1 -1
  29. package/dist/assets/{blockDiagram-677ZJIJ3-CeGZviXR.js → blockDiagram-677ZJIJ3-1mCezSBk.js} +1 -1
  30. package/dist/assets/{c4Diagram-LMCZKHZV-CLhYht6S.js → c4Diagram-LMCZKHZV-DhTiI6Uv.js} +1 -1
  31. package/dist/assets/channel-BVWNatUB.js +1 -0
  32. package/dist/assets/{chunk-32BRIVSS-LFYpT-Cu.js → chunk-32BRIVSS-CCdsKd73.js} +1 -1
  33. package/dist/assets/{chunk-52WLFC77-BH6jDw9J.js → chunk-52WLFC77-Gncjk4LF.js} +1 -1
  34. package/dist/assets/{chunk-C7G6YPKG-B_mIUlb7.js → chunk-C7G6YPKG-CiDVUP82.js} +1 -1
  35. package/dist/assets/{chunk-EX3LRPZG-C63hVuAJ.js → chunk-EX3LRPZG-z6gXP2_N.js} +1 -1
  36. package/dist/assets/{chunk-FWX5IMBZ-CboX-WF_.js → chunk-FWX5IMBZ-DMZBccQa.js} +2 -2
  37. package/dist/assets/{chunk-HOUHSVGY-BOgSd6Un.js → chunk-HOUHSVGY-D8tIeoPk.js} +1 -1
  38. package/dist/assets/{chunk-ICXQ74PX-C0K-pgZt.js → chunk-ICXQ74PX-b6oQ2Q17.js} +1 -1
  39. package/dist/assets/{chunk-MOJQB5TN-CVMfwN_7.js → chunk-MOJQB5TN-DAVH_p2a.js} +1 -1
  40. package/dist/assets/{chunk-OGEWGWER-DLT3pxyw.js → chunk-OGEWGWER-DxoiMCI-.js} +1 -1
  41. package/dist/assets/{chunk-PUDLZKDR-Cd079yIQ.js → chunk-PUDLZKDR-NsOWh7HM.js} +1 -1
  42. package/dist/assets/{chunk-Q4XR5HBZ-D0jHa6SO.js → chunk-Q4XR5HBZ-Di4H7L21.js} +1 -1
  43. package/dist/assets/{chunk-V7JOEXUC-CVDtR75H.js → chunk-V7JOEXUC-DttpZg9Y.js} +1 -1
  44. package/dist/assets/{chunk-VAUOI2AC-B-W2_9Ot.js → chunk-VAUOI2AC-UHNr_yhX.js} +1 -1
  45. package/dist/assets/{chunk-VR4S4FIN-FD3kWh32.js → chunk-VR4S4FIN-D-Fmtd4Z.js} +1 -1
  46. package/dist/assets/{chunk-WYO6CB5R-C2bGaS5r.js → chunk-WYO6CB5R-CUxMtfK2.js} +4 -4
  47. package/dist/assets/{chunk-ZGVPDNZ5-ymgts0zo.js → chunk-ZGVPDNZ5-CoV2QEl0.js} +1 -1
  48. package/dist/assets/classDiagram-OUVF2IWQ-Db0ib5iE.js +1 -0
  49. package/dist/assets/classDiagram-v2-EOCWNBFH-Bkf69XF9.js +1 -0
  50. package/dist/assets/code-dark-DKBGxoPX.css +1 -0
  51. package/dist/assets/{cynefinDiagram-TSTJHNR4-CwpobXIl.js → cynefinDiagram-TSTJHNR4-0xPI92Lg.js} +1 -1
  52. package/dist/assets/{dagre-VKFMJZFB-Dkhlfjna.js → dagre-VKFMJZFB-DfuMG4gU.js} +1 -1
  53. package/dist/assets/{diagram-FQU43EPY-hA8dicp6.js → diagram-FQU43EPY-CT00bLZ6.js} +1 -1
  54. package/dist/assets/{diagram-G47NLZAW-BTY6JTzO.js → diagram-G47NLZAW-Xbh7zXUx.js} +1 -1
  55. package/dist/assets/{diagram-NH7WQ7WH-xkaHHX9M.js → diagram-NH7WQ7WH-SRDVPfn7.js} +1 -1
  56. package/dist/assets/{diagram-OA4YK3LP-gk_fg9UU.js → diagram-OA4YK3LP-CQRli4Np.js} +1 -1
  57. package/dist/assets/{diagram-WEI45ONY-BvC8xd6T.js → diagram-WEI45ONY-DMf17WQi.js} +1 -1
  58. package/dist/assets/{ebnfDiagram-CCIWWBDH-CR7RaNat.js → ebnfDiagram-CCIWWBDH-DhpFkgJ5.js} +1 -1
  59. package/dist/assets/{erDiagram-Q63AITRT-_M-JFPbj.js → erDiagram-Q63AITRT-1IwxWmJo.js} +1 -1
  60. package/dist/assets/file-icons-DpbrD5ZP.js +1 -0
  61. package/dist/assets/flowDiagram-23GEKE2U-DYFo2xmB.js +1 -0
  62. package/dist/assets/{ganttDiagram-NO4QXBWP-Bl8P1yf_.js → ganttDiagram-NO4QXBWP-BNCdfyr1.js} +1 -1
  63. package/dist/assets/{gitGraphDiagram-IHSO6WYX-DoHa5ysz.js → gitGraphDiagram-IHSO6WYX-pbyWZuiX.js} +1 -1
  64. package/dist/assets/{index-CYZKdoqi.js → index-iKBvPjWS.js} +2 -2
  65. package/dist/assets/{infoDiagram-FWYZ7A6U-DSFcSuUs.js → infoDiagram-FWYZ7A6U-BcTOgDbe.js} +1 -1
  66. package/dist/assets/{ishikawaDiagram-FXEZZL3T-DdkqullI.js → ishikawaDiagram-FXEZZL3T-DCdvGKrn.js} +1 -1
  67. package/dist/assets/{journeyDiagram-5HDEW3XC-B4hRnQJr.js → journeyDiagram-5HDEW3XC-BZ-1q_IT.js} +1 -1
  68. package/dist/assets/{kanban-definition-HUTT4EX6-tm2FZB2E.js → kanban-definition-HUTT4EX6-BIgejCjj.js} +1 -1
  69. package/dist/assets/{line-CmsjK4kn.js → line-CfptFMmX.js} +1 -1
  70. package/dist/assets/main-CdkLOXYl.css +1 -0
  71. package/dist/assets/{mermaid.core-C7UT_EEL.js → mermaid.core-gSpX30pV.js} +3 -3
  72. package/dist/assets/{mindmap-definition-LN4V7U3C-DplfjGms.js → mindmap-definition-LN4V7U3C-BKBaI4LW.js} +1 -1
  73. package/dist/assets/{pegDiagram-2B236MQR-lu3SdkQs.js → pegDiagram-2B236MQR-sBvDY3VX.js} +1 -1
  74. package/dist/assets/{pieDiagram-ENE6RG2P-3fHbmDf9.js → pieDiagram-ENE6RG2P-DWlWcu08.js} +1 -1
  75. package/dist/assets/{quadrantDiagram-ABIIQ3AL-a5qoWz-X.js → quadrantDiagram-ABIIQ3AL-DZutUd3J.js} +1 -1
  76. package/dist/assets/{railroadDiagram-RFXS5EU6-D2TOuXW9.js → railroadDiagram-RFXS5EU6-DQb3lXGM.js} +1 -1
  77. package/dist/assets/{requirementDiagram-TGXJPOKE-LQBWpUvd.js → requirementDiagram-TGXJPOKE-DqFGXu2e.js} +1 -1
  78. package/dist/assets/{sankeyDiagram-HTMAVEWB-B1ohYoyK.js → sankeyDiagram-HTMAVEWB-d7ECahnO.js} +1 -1
  79. package/dist/assets/{sequenceDiagram-DBY2YBRQ-C3vM-hMq.js → sequenceDiagram-DBY2YBRQ-DdsbKqct.js} +1 -1
  80. package/dist/assets/{stateDiagram-2N3HPSRC-BvFhOryI.js → stateDiagram-2N3HPSRC-P6iSrgwR.js} +1 -1
  81. package/dist/assets/stateDiagram-v2-6OUMAXLB-DIYSf_qh.js +1 -0
  82. package/dist/assets/{swimlanes-5IMT3BWC-DKF_b-2s.js → swimlanes-5IMT3BWC-BGnZJx1M.js} +1 -1
  83. package/dist/assets/swimlanesDiagram-G3AALYLV-BIkrlKrZ.js +8 -0
  84. package/dist/assets/{timeline-definition-FHXFAJF6-vAs11ux_.js → timeline-definition-FHXFAJF6-rA-r3aUR.js} +1 -1
  85. package/dist/assets/{vennDiagram-L72KCM5P-BybTX_9B.js → vennDiagram-L72KCM5P-DiVKUjA9.js} +1 -1
  86. package/dist/assets/{wardleyDiagram-EHGQE667-CicUmMBR.js → wardleyDiagram-EHGQE667-Cfxh7UOn.js} +1 -1
  87. package/dist/assets/workspace-editor-model-D0Im34S5.js +1 -0
  88. package/dist/assets/{workspace-editor-monaco-BICJESG0.js → workspace-editor-monaco-BIpqcWf1.js} +1 -1
  89. package/dist/assets/workspace-editor-monaco-DLlws5Ig.js +1 -0
  90. package/dist/assets/{workspace-view-state-DvYG_9PH.js → workspace-view-state-BpPINu84.js} +1 -1
  91. package/dist/assets/{xychartDiagram-FW5EYKEG-BhWDyCX-.js → xychartDiagram-FW5EYKEG--hmryQeL.js} +1 -1
  92. package/dist/crt-mermaid-renderer.js +313 -313
  93. package/dist/index.html +1 -1
  94. package/frontend/session-bridge.js +24 -2
  95. package/frontend/skins/crt/app.js +194 -82
  96. package/frontend/skins/crt/index.html +2 -11
  97. package/frontend/skins/crt/styles/effects.css +2 -34
  98. package/package.json +23 -9
  99. package/shared/browser-protocol.js +3 -3
  100. package/backend/app-server-api.js +0 -917
  101. package/backend/codex-app-server-home.js +0 -39
  102. package/backend/codex-app-server-runtime.js +0 -901
  103. package/dist/assets/App-xASd_Y0F.js +0 -206
  104. package/dist/assets/FileEditorPane-Dxx4rKOg.js +0 -2
  105. package/dist/assets/ProjectFilesSection-DE7Nb9Jk.js +0 -12
  106. package/dist/assets/channel-Uw90AO0b.js +0 -1
  107. package/dist/assets/classDiagram-OUVF2IWQ-DKW_dtku.js +0 -1
  108. package/dist/assets/classDiagram-v2-EOCWNBFH-C8Fi7-yv.js +0 -1
  109. package/dist/assets/code-dark-DPiuyeSp.css +0 -1
  110. package/dist/assets/code-mobile-3C2Rr-CN.css +0 -1
  111. package/dist/assets/file-icons-Bw2qd5iT.js +0 -1
  112. package/dist/assets/flowDiagram-23GEKE2U-DAiS5Y7G.js +0 -1
  113. package/dist/assets/main-Bqtb5kuz.css +0 -1
  114. package/dist/assets/stateDiagram-v2-6OUMAXLB-WMrz4PNo.js +0 -1
  115. package/dist/assets/swimlanesDiagram-G3AALYLV-BGswC9oC.js +0 -8
  116. package/dist/assets/workspace-editor-model-BQol4qbA.js +0 -1
  117. package/dist/assets/workspace-editor-monaco-DRJ7IaXk.js +0 -1
  118. package/scripts/sync-ghostty-vendor.js +0 -66
  119. /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-CYZKdoqi.js"></script>
18
+ <script type="module" crossorigin src="/farming/assets/index-iKBvPjWS.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) {
@@ -5297,6 +5312,7 @@ function connect() {
5297
5312
  socket.onmessage = (event) => {
5298
5313
  if (ws !== socket) return;
5299
5314
  const data = JSON.parse(event.data);
5315
+ if (getSessionClient()?.handleServerMessage(data)) return;
5300
5316
  if (data.type === 'protocol-hello') {
5301
5317
  if (data.protocolVersion !== CRT_PROTOCOL_VERSION) {
5302
5318
  socket.close(4002, `Unsupported Farming protocol version ${data.protocolVersion}`);
@@ -5315,6 +5331,18 @@ function connect() {
5315
5331
  terminalPreviewSnapshots.forEach((_snapshot, agentId) => {
5316
5332
  if (!activeAgentIds.has(agentId)) terminalPreviewSnapshots.delete(agentId);
5317
5333
  });
5334
+ if (
5335
+ focusedAgentId
5336
+ && !activeAgentIds.has(focusedAgentId)
5337
+ && !runtimeSwitchPending
5338
+ && !pendingRuntimeSwitchAgentId
5339
+ ) {
5340
+ // Another viewer may kill or archive the Agent while this CRT session
5341
+ // is open. Runtime replacement has its own bounded handoff and may
5342
+ // briefly remove the old id, so do not mistake that legal transition
5343
+ // for a dead session.
5344
+ closeSession();
5345
+ }
5318
5346
  state.agents.forEach((agent) => {
5319
5347
  if (terminalPreviewSnapshots.has(agent.id)) {
5320
5348
  agent.previewSnapshot = terminalPreviewSnapshots.get(agent.id);
@@ -5359,21 +5387,23 @@ function connect() {
5359
5387
  } else if (data.type === 'session-preview') {
5360
5388
  const preview = data.preview;
5361
5389
  if (preview && preview.agentId) {
5362
- terminalPreviewSnapshots.set(preview.agentId, preview.previewSnapshot || null);
5390
+ if (preview.previewSnapshot && typeof preview.previewSnapshot === 'object') {
5391
+ terminalPreviewSnapshots.set(preview.agentId, preview.previewSnapshot);
5392
+ }
5363
5393
  const agent = state && state.agents.find((candidate) => candidate.id === preview.agentId);
5364
5394
  if (agent) {
5365
- const previousSnapshot = agent.previewSnapshot;
5366
- const previousText = getAgentDisplayText(agent);
5367
5395
  const previewChanged = typeof preview.previewText === 'string'
5368
5396
  && preview.previewText !== agent.previewText;
5369
5397
  agent.previewText = preview.previewText || agent.previewText;
5370
5398
  agent.previewCols = preview.cols || agent.previewCols;
5371
5399
  agent.previewRows = preview.rows || agent.previewRows;
5372
- agent.previewSnapshot = preview.previewSnapshot || null;
5400
+ // A null preview is not a completed new frame. Retain the last
5401
+ // authoritative snapshot until a replacement is ready.
5402
+ agent.previewSnapshot = preview.previewSnapshot || agent.previewSnapshot || null;
5373
5403
  if (preview.terminalStatus) agent.terminalStatus = preview.terminalStatus;
5374
5404
  if (preview.runtimeObservation) agent.runtimeObservation = preview.runtimeObservation;
5375
5405
  if (isCrtSessionOpen()) dashboardRenderDeferred = true;
5376
- else scheduleCrtPreviewCardRender(agent, previousSnapshot, previousText, previewChanged);
5406
+ else scheduleCrtPreviewCardRender(agent, previewChanged);
5377
5407
  }
5378
5408
  }
5379
5409
  } else if (data.type === 'session-output') {
@@ -5404,7 +5434,8 @@ function connect() {
5404
5434
  const agent = activity && state && state.agents.find((candidate) => candidate.id === activity.agentId);
5405
5435
  if (agent) {
5406
5436
  Object.assign(agent, activity);
5407
- renderCrtDashboardIfNeeded();
5437
+ if (isCrtSessionOpen()) dashboardRenderDeferred = true;
5438
+ else scheduleCrtPreviewCardRender(agent);
5408
5439
  }
5409
5440
  } else if (data.type === 'system-stats') {
5410
5441
  updateSystemStats(data.stats, data.uptime, data.usageRate);
@@ -5420,6 +5451,7 @@ function connect() {
5420
5451
  socket.onclose = () => {
5421
5452
  if (ws !== socket) return;
5422
5453
  ws = null;
5454
+ getSessionClient()?.rejectPendingComposerMessages();
5423
5455
  console.log('Disconnected from server');
5424
5456
  if (crtTerminalReplication) {
5425
5457
  clearPendingCrtTerminalFitResize(crtTerminalReplication);
@@ -5842,7 +5874,6 @@ function structuredRuntimeKind(agent) {
5842
5874
  const kind = agent && agent.runtimeBinding && agent.runtimeBinding.kind;
5843
5875
  if (kind === 'acp') return 'ACP';
5844
5876
  if (kind === 'json') return 'JSON';
5845
- if (kind === 'app-server') return 'APP SERVER';
5846
5877
  return '';
5847
5878
  }
5848
5879
 
@@ -5868,6 +5899,19 @@ function canSwitchCrtAgentRuntime(agent) {
5868
5899
  );
5869
5900
  }
5870
5901
 
5902
+ function crtRuntimeSwitchUnavailableReason(agent) {
5903
+ if (!agent || !agent.providerCapabilities?.runtimeSwitch || !agent.providerCapabilities?.supportsChat) {
5904
+ return '';
5905
+ }
5906
+ if (agent.providerSessionTemporary === true && agent.terminalInputReceived === true) {
5907
+ return 'MSG unavailable: this temporary Terminal session already has input and cannot be resumed safely.';
5908
+ }
5909
+ if (!String(agent.providerSessionId || '').trim()) {
5910
+ return 'MSG unavailable: this Agent has no resumable provider session yet.';
5911
+ }
5912
+ return '';
5913
+ }
5914
+
5871
5915
  function isCrtRuntimeSwitchShortcut(event) {
5872
5916
  return Boolean(
5873
5917
  event
@@ -5931,11 +5975,27 @@ function updateCrtRuntimeSwitchControl(agent) {
5931
5975
  const terminalButton = document.getElementById('crt-runtime-terminal');
5932
5976
  if (!control || !chatButton || !terminalButton) return;
5933
5977
  const supported = canSwitchCrtAgentRuntime(agent);
5978
+ const unavailableReason = crtRuntimeSwitchUnavailableReason(agent);
5934
5979
  const view = crtRuntimeView(agent);
5935
- control.hidden = !supported;
5980
+ // Keep a disabled control visible for a provider that supports Chat when
5981
+ // this particular session cannot be resumed. Hiding it made a live
5982
+ // Terminal look like a product capability was missing, while silently
5983
+ // enabling it would risk replacing a temporary session and losing input.
5984
+ control.hidden = !supported && !unavailableReason;
5936
5985
  control.setAttribute('aria-busy', runtimeSwitchPending ? 'true' : 'false');
5937
5986
  chatButton.disabled = !supported || runtimeSwitchPending;
5938
5987
  terminalButton.disabled = !supported || runtimeSwitchPending;
5988
+ if (unavailableReason) {
5989
+ control.title = unavailableReason;
5990
+ control.setAttribute('aria-label', unavailableReason);
5991
+ chatButton.title = unavailableReason;
5992
+ terminalButton.title = unavailableReason;
5993
+ } else {
5994
+ control.removeAttribute('title');
5995
+ control.removeAttribute('aria-label');
5996
+ chatButton.title = 'Chat view [Alt+M]';
5997
+ terminalButton.title = 'Terminal view [Alt+M]';
5998
+ }
5939
5999
  chatButton.classList.toggle('active', view === 'chat');
5940
6000
  terminalButton.classList.toggle('active', view === 'terminal');
5941
6001
  chatButton.setAttribute('aria-pressed', view === 'chat' ? 'true' : 'false');
@@ -5973,7 +6033,7 @@ async function switchCrtSessionRuntimeMode(mode) {
5973
6033
  if (runtimeSwitchPending || !focusedAgentId || !state) return;
5974
6034
  const agent = state.agents.find((candidate) => candidate.id === focusedAgentId);
5975
6035
  if (!canSwitchCrtAgentRuntime(agent)) return;
5976
- const targetMode = mode === 'terminal' ? 'terminal' : 'acp';
6036
+ const targetMode = mode === 'terminal' ? 'terminal' : 'chat';
5977
6037
  if (crtRuntimeView(agent) === (targetMode === 'terminal' ? 'terminal' : 'chat')) return;
5978
6038
 
5979
6039
  const requestSequence = ++runtimeSwitchRequestSequence;
@@ -6006,14 +6066,17 @@ async function switchCrtSessionRuntimeMode(mode) {
6006
6066
  function toggleCrtSessionRuntimeMode() {
6007
6067
  if (!focusedAgentId || !state) return;
6008
6068
  const agent = state.agents.find((candidate) => candidate.id === focusedAgentId);
6009
- if (!canSwitchCrtAgentRuntime(agent)) return;
6010
- void switchCrtSessionRuntimeMode(crtRuntimeView(agent) === 'chat' ? 'terminal' : 'acp');
6069
+ if (!canSwitchCrtAgentRuntime(agent)) {
6070
+ const reason = crtRuntimeSwitchUnavailableReason(agent);
6071
+ if (reason) setCrtRuntimeSwitchStatus(reason, true);
6072
+ return;
6073
+ }
6074
+ void switchCrtSessionRuntimeMode(crtRuntimeView(agent) === 'chat' ? 'terminal' : 'chat');
6011
6075
  }
6012
6076
 
6013
6077
  function structuredTranscriptEndpoint(agent) {
6014
6078
  if (agent.runtimeBinding.kind === 'acp') return 'acp-transcript';
6015
- if (agent.runtimeBinding.kind === 'json') return 'json-cli-transcript';
6016
- return 'codex-app-server-transcript';
6079
+ return 'json-cli-transcript';
6017
6080
  }
6018
6081
 
6019
6082
  function structuredRuntimeStatus(agent) {
@@ -6033,7 +6096,6 @@ function structuredComposerAction(agent, draft = '') {
6033
6096
  const working = ['working', 'waiting-for-permission'].includes(status);
6034
6097
  if (working) {
6035
6098
  if (agent.runtimeBinding?.kind === 'acp' && String(draft || '').trim()) return 'send';
6036
- if (agent.runtimeBinding?.kind === 'app-server' && String(draft || '').trim()) return 'steer';
6037
6099
  return 'interrupt';
6038
6100
  }
6039
6101
  if (['starting', 'interrupting'].includes(status)) return 'disabled';
@@ -6042,9 +6104,9 @@ function structuredComposerAction(agent, draft = '') {
6042
6104
  : 'disabled';
6043
6105
  }
6044
6106
 
6045
- function queueStructuredComposerFollowUp(agentId, message) {
6107
+ function queueStructuredComposerFollowUp(agentId, message, attachments = []) {
6046
6108
  const queue = structuredComposerPendingFollowUps.get(agentId) || [];
6047
- queue.push(message);
6109
+ queue.push({ message, attachments });
6048
6110
  structuredComposerPendingFollowUps.set(agentId, queue);
6049
6111
  }
6050
6112
 
@@ -6052,10 +6114,10 @@ function flushStructuredComposerFollowUp(agent) {
6052
6114
  if (!agent || structuredRuntimeStatus(agent) !== 'idle') return;
6053
6115
  const queue = structuredComposerPendingFollowUps.get(agent.id);
6054
6116
  if (!queue || queue.length === 0) return;
6055
- const message = queue.shift();
6117
+ const next = queue.shift();
6056
6118
  if (queue.length === 0) structuredComposerPendingFollowUps.delete(agent.id);
6057
- if (!getSessionClient()?.sendComposerMessage(agent.id, message)) {
6058
- queue.unshift(message);
6119
+ if (!getSessionClient()?.sendComposerMessage(agent.id, next.message, next.attachments)) {
6120
+ queue.unshift(next);
6059
6121
  structuredComposerPendingFollowUps.set(agent.id, queue);
6060
6122
  return;
6061
6123
  }
@@ -6481,9 +6543,7 @@ function updateStructuredComposerState(agent) {
6481
6543
  }
6482
6544
  sendButton.disabled = action === 'disabled';
6483
6545
  sendButton.dataset.action = action;
6484
- sendButton.textContent = action === 'interrupt'
6485
- ? 'BREAK [ESC]'
6486
- : action === 'steer' ? 'STEER [ENTER]' : 'SEND [ENTER]';
6546
+ sendButton.textContent = action === 'interrupt' ? 'BREAK [ESC]' : 'SEND [ENTER]';
6487
6547
  input.placeholder = error
6488
6548
  ? 'Session unavailable'
6489
6549
  : (busy ? 'Agent is changing state...' : 'Type a message...');
@@ -6542,6 +6602,7 @@ function readStructuredTextFile(file) {
6542
6602
  async function prepareStructuredAttachment(file) {
6543
6603
  const attachment = {
6544
6604
  id: structuredAttachmentId(file),
6605
+ kind: String(file.type || '').startsWith('audio/') ? 'audio' : 'image',
6545
6606
  name: file.name || 'attachment',
6546
6607
  status: 'uploading',
6547
6608
  messageBlock: ''
@@ -6549,15 +6610,21 @@ async function prepareStructuredAttachment(file) {
6549
6610
  structuredComposerAttachments.push(attachment);
6550
6611
  renderStructuredComposerAttachments();
6551
6612
  try {
6552
- if (String(file.type || '').startsWith('image/')) {
6553
- const response = await fetch(farmingApiPath('/attachments/image'), {
6613
+ if (String(file.type || '').startsWith('image/') || String(file.type || '').startsWith('audio/')) {
6614
+ const image = attachment.kind === 'image';
6615
+ const response = await fetch(farmingApiPath(image ? '/attachments/image' : '/attachments/audio'), {
6554
6616
  method: 'POST',
6555
- headers: { 'Content-Type': file.type || 'image/png' },
6617
+ headers: { 'Content-Type': file.type || (image ? 'image/png' : 'audio/wav') },
6556
6618
  body: file
6557
6619
  });
6558
6620
  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}`;
6621
+ if (!response.ok || !body || !body.path) throw new Error(`${image ? 'Image' : 'Audio'} upload failed (${response.status})`);
6622
+ attachment.path = body.path;
6623
+ attachment.type = body.type || file.type || 'image/png';
6624
+ attachment.size = Number(body.size) || file.size || 0;
6625
+ attachment.messageBlock = image
6626
+ ? `Attached image: ${body.name || attachment.name}\n\nImage path: ${body.path}`
6627
+ : `Attached audio: ${body.name || attachment.name}\n\nAudio path: ${body.path}`;
6561
6628
  } else {
6562
6629
  const content = await readStructuredTextFile(file);
6563
6630
  const limit = 50000;
@@ -6581,11 +6648,25 @@ function addStructuredAttachmentFiles(files) {
6581
6648
 
6582
6649
  function structuredComposerMessage(draft) {
6583
6650
  return structuredComposerAttachments
6584
- .filter((attachment) => attachment.status === 'ready' && attachment.messageBlock)
6651
+ .filter((attachment) => attachment.status === 'ready' && attachment.messageBlock && !attachment.path)
6585
6652
  .reduce((message, attachment) => structuredAttachmentBlock(message, attachment.messageBlock), String(draft || ''))
6586
6653
  .trim();
6587
6654
  }
6588
6655
 
6656
+ function structuredComposerPromptAttachments() {
6657
+ return structuredComposerAttachments.flatMap((attachment) => (
6658
+ attachment.status === 'ready' && attachment.path
6659
+ ? [{
6660
+ kind: attachment.kind,
6661
+ path: attachment.path,
6662
+ name: attachment.name,
6663
+ type: attachment.type,
6664
+ size: attachment.size,
6665
+ }]
6666
+ : []
6667
+ ));
6668
+ }
6669
+
6589
6670
  function structuredComposerHistoryFor(agentId) {
6590
6671
  if (!agentId) return [];
6591
6672
  return structuredComposerHistory.get(agentId) || [];
@@ -7013,11 +7094,11 @@ function setupStructuredSessionComposer() {
7013
7094
  updateStructuredComposerState(agent);
7014
7095
  });
7015
7096
  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;
7097
+ const mediaFiles = Array.from(event.clipboardData && event.clipboardData.files || [])
7098
+ .filter((file) => /^(image|audio)\//.test(String(file.type || '')));
7099
+ if (!mediaFiles.length) return;
7019
7100
  event.preventDefault();
7020
- addStructuredAttachmentFiles(imageFiles);
7101
+ addStructuredAttachmentFiles(mediaFiles);
7021
7102
  });
7022
7103
  input.addEventListener('compositionend', () => {
7023
7104
  structuredComposerCompositionEndAt = Date.now();
@@ -7166,11 +7247,25 @@ function setupStructuredSessionComposer() {
7166
7247
  }
7167
7248
  const draft = input.value;
7168
7249
  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);
7250
+ const promptAttachments = structuredComposerPromptAttachments();
7251
+ if (!message && promptAttachments.length === 0) return;
7173
7252
  const statusNode = document.getElementById('crt-structured-composer-status');
7253
+ const completeSubmission = () => {
7254
+ addStructuredComposerHistory(focusedAgentId, draft);
7255
+ input.value = '';
7256
+ structuredComposerAttachments = [];
7257
+ renderStructuredComposerAttachments();
7258
+ resizeStructuredComposerInput(input);
7259
+ if (statusNode) {
7260
+ statusNode.textContent = 'SENDING...';
7261
+ statusNode.classList.remove('error');
7262
+ }
7263
+ updateStructuredComposerState(agent);
7264
+ setTimeout(() => void refreshStructuredSession(focusedAgentId, true), 160);
7265
+ };
7266
+ const sent = action === 'send' && structuredRuntimeStatus(agent) !== 'idle'
7267
+ ? (queueStructuredComposerFollowUp(focusedAgentId, message, promptAttachments), true)
7268
+ : getSessionClient()?.sendComposerMessage(focusedAgentId, message, promptAttachments);
7174
7269
  if (!sent) {
7175
7270
  if (statusNode) {
7176
7271
  statusNode.textContent = 'Connection unavailable';
@@ -7178,17 +7273,7 @@ function setupStructuredSessionComposer() {
7178
7273
  }
7179
7274
  return;
7180
7275
  }
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);
7276
+ completeSubmission();
7192
7277
  });
7193
7278
  }
7194
7279
 
@@ -7536,7 +7621,11 @@ function clearPendingCrtTerminalFitResize(replication) {
7536
7621
  replication.pendingFitResize = null;
7537
7622
  }
7538
7623
 
7539
- function commitCrtTerminalResize(agentId, normalizedDimensions) {
7624
+ function commitCrtTerminalResize(
7625
+ agentId,
7626
+ normalizedDimensions,
7627
+ { allowDuringRecovery = false } = {}
7628
+ ) {
7540
7629
  const replication = crtTerminalReplication;
7541
7630
  if (
7542
7631
  !agentId ||
@@ -7544,7 +7633,7 @@ function commitCrtTerminalResize(agentId, normalizedDimensions) {
7544
7633
  !replication ||
7545
7634
  replication.disposed ||
7546
7635
  replication.agentId !== agentId ||
7547
- replication.replayState.recovering ||
7636
+ (!allowDuringRecovery && replication.replayState.recovering) ||
7548
7637
  (
7549
7638
  normalizedDimensions.cols === replication.lastResizeCols &&
7550
7639
  normalizedDimensions.rows === replication.lastResizeRows
@@ -7586,7 +7675,11 @@ function scheduleCrtTerminalFitResize(replication, agentId, normalizedDimensions
7586
7675
  }, CRT_TERMINAL_RESIZE_SETTLE_MS);
7587
7676
  }
7588
7677
 
7589
- function sendSessionResize(agentId = focusedAgentId, requestedDimensions = null) {
7678
+ function sendSessionResize(
7679
+ agentId = focusedAgentId,
7680
+ requestedDimensions = null,
7681
+ { forceDuringRecovery = false } = {}
7682
+ ) {
7590
7683
  const replication = crtTerminalReplication;
7591
7684
  if (
7592
7685
  !agentId ||
@@ -7594,7 +7687,7 @@ function sendSessionResize(agentId = focusedAgentId, requestedDimensions = null)
7594
7687
  !fitAddon ||
7595
7688
  !replication ||
7596
7689
  replication.agentId !== agentId ||
7597
- replication.replayState.recovering
7690
+ (!forceDuringRecovery && replication.replayState.recovering)
7598
7691
  ) return;
7599
7692
  const dimensions = requestedDimensions || (
7600
7693
  typeof fitAddon.proposeDimensions === 'function'
@@ -7618,6 +7711,25 @@ function sendSessionResize(agentId = focusedAgentId, requestedDimensions = null)
7618
7711
  normalizedDimensions.rows === replication.lastResizeRows
7619
7712
  ) {
7620
7713
  clearPendingCrtTerminalFitResize(replication);
7714
+ // A checkpoint can temporarily restore the previous viewer's geometry
7715
+ // after this surface's resize was already delivered. Reapply the known
7716
+ // local geometry without sending a duplicate resize to the backend.
7717
+ if (
7718
+ terminal.cols !== normalizedDimensions.cols ||
7719
+ terminal.rows !== normalizedDimensions.rows
7720
+ ) {
7721
+ replication.applyingLocalResize = true;
7722
+ try {
7723
+ terminal.resize(normalizedDimensions.cols, normalizedDimensions.rows);
7724
+ } finally {
7725
+ replication.applyingLocalResize = false;
7726
+ }
7727
+ }
7728
+ return;
7729
+ }
7730
+ if (forceDuringRecovery) {
7731
+ clearPendingCrtTerminalFitResize(replication);
7732
+ commitCrtTerminalResize(agentId, normalizedDimensions, { allowDuringRecovery: true });
7621
7733
  return;
7622
7734
  }
7623
7735
  scheduleCrtTerminalFitResize(replication, agentId, normalizedDimensions);
@@ -174,7 +174,7 @@
174
174
  inset: 3px 0 0;
175
175
  display: flex;
176
176
  flex-direction: column;
177
- justify-content: flex-end;
177
+ justify-content: flex-start;
178
178
  gap: 5px;
179
179
  min-height: 0;
180
180
  padding: 0 4px 2px 12px;
@@ -209,7 +209,7 @@
209
209
  display: flex;
210
210
  flex: 0 1 auto;
211
211
  flex-direction: column;
212
- justify-content: flex-end;
212
+ justify-content: flex-start;
213
213
  gap: 5px;
214
214
  min-height: 0;
215
215
  overflow: hidden;
@@ -300,20 +300,11 @@
300
300
  opacity: 0.58;
301
301
  }
302
302
 
303
- .agent-block.working .agent-output:not(.structured-preview) {
304
- animation: blink 0.5s infinite;
305
- }
306
-
307
303
  @keyframes crt-chat-signal {
308
304
  0%, 100% { opacity: 0.45; transform: scale(0.82); }
309
305
  50% { opacity: 1; transform: scale(1); }
310
306
  }
311
307
 
312
- @keyframes blink {
313
- 0%, 100% { opacity: 1; }
314
- 50% { opacity: 0.8; }
315
- }
316
-
317
308
  .sidebar {
318
309
  width: 200px;
319
310
  background: #2a2a2a;