loadtoagent 1.3.1 → 1.3.3

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 (40) hide show
  1. package/README.ko.md +11 -9
  2. package/docs/PROVIDER-CONTRACTS.md +8 -6
  3. package/main.js +25 -3
  4. package/package.json +1 -1
  5. package/preload.js +7 -2
  6. package/renderer/app-dashboard.js +18 -12
  7. package/renderer/app-graph-view.js +1 -1
  8. package/renderer/app-management.js +91 -45
  9. package/renderer/app-run-modal.js +1 -1
  10. package/renderer/app.js +1 -1
  11. package/renderer/i18n-messages.js +212 -199
  12. package/renderer/index.html +28 -27
  13. package/renderer/styles-agent-map.css +10 -10
  14. package/renderer/styles-cards.css +19 -19
  15. package/renderer/styles-collaboration.css +23 -23
  16. package/renderer/styles-components.css +36 -36
  17. package/renderer/styles-management.css +23 -14
  18. package/renderer/styles-onboarding.css +8 -8
  19. package/renderer/styles-overlays.css +5 -5
  20. package/renderer/styles-product.css +24 -24
  21. package/renderer/styles-quality.css +4 -4
  22. package/renderer/styles-readability.css +179 -1
  23. package/renderer/styles-responsive-runtime.css +5 -5
  24. package/renderer/styles-responsive-workflows.css +2 -2
  25. package/renderer/styles-run-composer.css +23 -23
  26. package/renderer/styles-runtime-overview.css +22 -22
  27. package/renderer/styles-settings.css +21 -21
  28. package/renderer/styles-terminal.css +66 -66
  29. package/renderer/styles-tmux.css +49 -49
  30. package/renderer/styles-workflows.css +48 -48
  31. package/renderer/styles.css +7 -7
  32. package/renderer/terminal-events.js +34 -11
  33. package/renderer/terminal-workbench.js +52 -21
  34. package/renderer/terminal.js +9 -1
  35. package/src/contracts.js +5 -5
  36. package/src/ipc/registerTerminalIpc.js +6 -14
  37. package/src/sessionIntelligence.js +18 -4
  38. package/src/terminalHost.js +100 -26
  39. package/src/terminalHostDaemon.js +1 -0
  40. package/src/terminalManager.js +61 -4
@@ -109,7 +109,7 @@
109
109
  border-radius: 999px;
110
110
  color: #09140f;
111
111
  background: #63dba0;
112
- font-size: 11px;
112
+ font-size: 12px;
113
113
  font-variant-numeric: tabular-nums;
114
114
  }
115
115
 
@@ -213,7 +213,7 @@
213
213
  .agent-node-top .provider-mark {
214
214
  width: 40px;
215
215
  height: 40px;
216
- font-size: 11px;
216
+ font-size: 12px;
217
217
  }
218
218
 
219
219
  .agent-identity {
@@ -237,11 +237,11 @@
237
237
  white-space: nowrap;
238
238
  margin-top: 4px;
239
239
  color: #7c899c;
240
- font-size: 10px;
240
+ font-size: 12px;
241
241
  }
242
242
 
243
243
  .agent-node .status-pill {
244
- font-size: 9px;
244
+ font-size: 12px;
245
245
  padding: 5px 8px;
246
246
  }
247
247
 
@@ -249,7 +249,7 @@
249
249
  display: block;
250
250
  margin-top: 17px;
251
251
  color: var(--provider);
252
- font-size: 10px;
252
+ font-size: 12px;
253
253
  font-weight: 750;
254
254
  letter-spacing: .13em;
255
255
  }
@@ -290,13 +290,13 @@
290
290
  border-radius: 6px;
291
291
  background: rgba(76,227,154,.1);
292
292
  color: #67e9a6;
293
- font-size: 10px;
293
+ font-size: 12px;
294
294
  font-style: normal;
295
295
  }
296
296
 
297
297
  .agent-current b {
298
298
  color: #76e8ac;
299
- font-size: 11px;
299
+ font-size: 12px;
300
300
  }
301
301
 
302
302
  .agent-current>strong {
@@ -325,7 +325,7 @@
325
325
  .agent-node-metrics small {
326
326
  display: block;
327
327
  color: #6f7c8f;
328
- font-size: 9px;
328
+ font-size: 12px;
329
329
  }
330
330
 
331
331
  .agent-node-metrics b {
@@ -359,7 +359,7 @@
359
359
  border-top: 1px solid #192230;
360
360
  background: rgba(5,8,13,.5);
361
361
  color: #748195;
362
- font-size: 10px;
362
+ font-size: 12px;
363
363
  }
364
364
 
365
365
  .agent-node-footer button {
@@ -369,7 +369,7 @@
369
369
  border-radius: 7px;
370
370
  background: transparent;
371
371
  color: #aab7c7;
372
- font-size: 10px;
372
+ font-size: 12px;
373
373
  cursor: pointer;
374
374
  }
375
375
 
@@ -428,7 +428,7 @@
428
428
  .agent-flow-lane-head .provider-mark {
429
429
  width: 32px;
430
430
  height: 32px;
431
- font-size: 8px;
431
+ font-size: 12px;
432
432
  }
433
433
 
434
434
  .agent-flow-lane-head>span:nth-child(2) {
@@ -540,7 +540,7 @@
540
540
  white-space: nowrap;
541
541
  margin-top: 3px;
542
542
  color: #667587;
543
- font-size: 11px;
543
+ font-size: 12px;
544
544
  }
545
545
 
546
546
  .agent-flow-lane-head em {
@@ -550,7 +550,7 @@
550
550
  color: #65dba9;
551
551
  font-style: normal;
552
552
  font-weight: 750;
553
- font-size: 11px;
553
+ font-size: 12px;
554
554
  }
555
555
 
556
556
  .agent-flow-copy small {
@@ -562,7 +562,7 @@
562
562
  color: var(--provider);
563
563
  font-weight: 700;
564
564
  letter-spacing: .05em;
565
- font-size: 10px;
565
+ font-size: 12px;
566
566
  }
567
567
 
568
568
  .agent-flow-copy b {
@@ -583,7 +583,7 @@
583
583
  margin-top: 4px;
584
584
  color: #5f6f82;
585
585
  font-style: normal;
586
- font-size: 11px;
586
+ font-size: 12px;
587
587
  }
588
588
 
589
589
  .agent-flow-provider small {
@@ -592,7 +592,7 @@
592
592
  color: #718092;
593
593
  text-overflow: ellipsis;
594
594
  white-space: nowrap;
595
- font-size: 11px;
595
+ font-size: 12px;
596
596
  }
597
597
 
598
598
  .agent-flow-provider i {
@@ -605,7 +605,7 @@
605
605
  font-weight: 800;
606
606
  width: 27px;
607
607
  height: 23px;
608
- font-size: 9px;
608
+ font-size: 12px;
609
609
  }
610
610
 
611
611
  .agent-flow-more {
@@ -616,7 +616,7 @@
616
616
  color: #7894a1;
617
617
  cursor: pointer;
618
618
  height: 40px;
619
- font-size: 11px;
619
+ font-size: 12px;
620
620
  }
621
621
 
622
622
  .agent-relation-block>header b {
@@ -626,7 +626,7 @@
626
626
 
627
627
  .agent-relation-block>header span {
628
628
  color: #657587;
629
- font-size: 11px;
629
+ font-size: 12px;
630
630
  }
631
631
 
632
632
  .agent-relation-empty {
@@ -636,7 +636,7 @@
636
636
  padding: 14px;
637
637
  color: #596a7d;
638
638
  text-align: center;
639
- font-size: 11px;
639
+ font-size: 12px;
640
640
  }
641
641
 
642
642
  .agent-relation-block .agent-flow-copy b {
@@ -644,7 +644,7 @@
644
644
  }
645
645
 
646
646
  .agent-relation-block .agent-flow-copy em {
647
- font-size: 11px;
647
+ font-size: 12px;
648
648
  }
649
649
 
650
650
  .workflow-origin-icon {
@@ -739,7 +739,7 @@
739
739
  background: color-mix(in srgb,var(--command-accent) 10%,#111c22);
740
740
  color: var(--command-accent);
741
741
  font-family: "Cascadia Code",Consolas,monospace;
742
- font-size: 11px;
742
+ font-size: 12px;
743
743
  font-weight: 800;
744
744
  }
745
745
 
@@ -758,7 +758,7 @@
758
758
  white-space: nowrap;
759
759
  margin-top: 4px;
760
760
  color: #7f918f;
761
- font-size: 10px;
761
+ font-size: 12px;
762
762
  }
763
763
 
764
764
  .agent-command-panel header>i {
@@ -822,7 +822,7 @@
822
822
 
823
823
  .agent-command-target>span {
824
824
  color: #849493;
825
- font-size: 10px;
825
+ font-size: 12px;
826
826
  }
827
827
 
828
828
  .agent-command-target select {
@@ -833,7 +833,7 @@
833
833
  border-radius: 8px;
834
834
  background: #0b1518;
835
835
  color: #b8ccc8;
836
- font-size: 10px;
836
+ font-size: 12px;
837
837
  outline: none;
838
838
  }
839
839
 
@@ -853,7 +853,7 @@
853
853
  border-radius: 10px;
854
854
  background: #071013;
855
855
  color: #dbe9e6;
856
- font-size: 11px;
856
+ font-size: 12px;
857
857
  line-height: 1.5;
858
858
  outline: none;
859
859
  transition: border-color var(--motion-fast) ease,box-shadow var(--motion-medium) ease;
@@ -884,7 +884,7 @@
884
884
  min-width: 0;
885
885
  margin-right: auto;
886
886
  color: #647672;
887
- font-size: 9px;
887
+ font-size: 12px;
888
888
  line-height: 1.4;
889
889
  }
890
890
 
@@ -895,7 +895,7 @@
895
895
  border-radius: 8px;
896
896
  background: #111d1f;
897
897
  color: #9cc9bd;
898
- font-size: 10px;
898
+ font-size: 12px;
899
899
  font-weight: 650;
900
900
  cursor: pointer;
901
901
  transition: transform var(--motion-fast) var(--motion-ease),border-color var(--motion-fast) ease,background-color var(--motion-fast) ease,color var(--motion-fast) ease;
@@ -931,7 +931,7 @@
931
931
  display: block;
932
932
  margin-top: 5px;
933
933
  color: #6f9f91;
934
- font-size: 8px;
934
+ font-size: 12px;
935
935
  letter-spacing: .01em;
936
936
  }
937
937
 
@@ -942,7 +942,7 @@
942
942
  text-overflow: ellipsis;
943
943
  white-space: nowrap;
944
944
  color: #71879a;
945
- font-size: 8px;
945
+ font-size: 12px;
946
946
  }
947
947
 
948
948
  .agent-flow-copy .agent-flow-outcome {
@@ -954,14 +954,14 @@
954
954
  padding-top: 5px;
955
955
  border-top: 1px solid rgba(92,132,145,.12);
956
956
  color: #9fb1bf;
957
- font-size: 9px;
957
+ font-size: 12px;
958
958
  }
959
959
 
960
960
  .agent-flow-copy .agent-flow-outcome b {
961
961
  display: inline;
962
962
  margin-right: 6px;
963
963
  color: #67dfb0;
964
- font-size: 8px;
964
+ font-size: 12px;
965
965
  }
966
966
 
967
967
  .agent-flow-copy .agent-flow-outcome.done b {
@@ -990,7 +990,7 @@
990
990
  .child-session .agent-flow-kicker small {
991
991
  margin: 0;
992
992
  color: var(--provider);
993
- font-size: 9px;
993
+ font-size: 12px;
994
994
  font-weight: 800;
995
995
  letter-spacing: .08em;
996
996
  }
@@ -998,7 +998,7 @@
998
998
  .child-session .agent-flow-kicker time {
999
999
  flex: 0 0 auto;
1000
1000
  color: #60727e;
1001
- font-size: 8px;
1001
+ font-size: 12px;
1002
1002
  }
1003
1003
 
1004
1004
  .child-session .agent-flow-agent {
@@ -1018,7 +1018,7 @@
1018
1018
  border-radius: 5px;
1019
1019
  background: color-mix(in srgb,var(--provider) 13%,#17212b);
1020
1020
  color: var(--provider);
1021
- font-size: 6px;
1021
+ font-size: 12px;
1022
1022
  font-style: normal;
1023
1023
  font-weight: 850;
1024
1024
  }
@@ -1026,7 +1026,7 @@
1026
1026
  .child-session .agent-flow-agent strong {
1027
1027
  flex: 0 0 auto;
1028
1028
  color: #b9c7cf;
1029
- font-size: 9px;
1029
+ font-size: 12px;
1030
1030
  }
1031
1031
 
1032
1032
  .child-session .agent-flow-agent small {
@@ -1034,7 +1034,7 @@
1034
1034
  margin: 0;
1035
1035
  overflow: hidden;
1036
1036
  color: #627581;
1037
- font-size: 8px;
1037
+ font-size: 12px;
1038
1038
  font-weight: 500;
1039
1039
  letter-spacing: 0;
1040
1040
  text-overflow: ellipsis;
@@ -1052,7 +1052,7 @@
1052
1052
  .child-session .agent-flow-assignment small {
1053
1053
  margin: 0 0 4px;
1054
1054
  color: #6e8791;
1055
- font-size: 8px;
1055
+ font-size: 12px;
1056
1056
  letter-spacing: .04em;
1057
1057
  }
1058
1058
 
@@ -1060,7 +1060,7 @@
1060
1060
  display: -webkit-box;
1061
1061
  overflow: hidden;
1062
1062
  color: #aebdc5;
1063
- font-size: 9px;
1063
+ font-size: 12px;
1064
1064
  font-weight: 520;
1065
1065
  line-height: 1.45;
1066
1066
  white-space: normal;
@@ -1079,7 +1079,7 @@
1079
1079
  border-radius: 8px;
1080
1080
  background: rgba(109,174,204,.055);
1081
1081
  color: #c1d0d8;
1082
- font-size: 10px;
1082
+ font-size: 12px;
1083
1083
  white-space: normal;
1084
1084
  }
1085
1085
 
@@ -1088,7 +1088,7 @@
1088
1088
  flex: 0 0 auto;
1089
1089
  margin: 0;
1090
1090
  color: #79dfb6;
1091
- font-size: 8px;
1091
+ font-size: 12px;
1092
1092
  }
1093
1093
 
1094
1094
  .child-session .agent-flow-outcome.done b {
@@ -1102,7 +1102,7 @@
1102
1102
  .child-session .agent-flow-provider .status-pill {
1103
1103
  margin: 0;
1104
1104
  padding: 4px 6px;
1105
- font-size: 8px;
1105
+ font-size: 12px;
1106
1106
  letter-spacing: 0;
1107
1107
  text-transform: none;
1108
1108
  }
@@ -1130,7 +1130,7 @@
1130
1130
  display: block;
1131
1131
  overflow: hidden;
1132
1132
  color: #7b9099;
1133
- font-size: 8px;
1133
+ font-size: 12px;
1134
1134
  text-overflow: ellipsis;
1135
1135
  white-space: nowrap;
1136
1136
  }
@@ -1146,7 +1146,7 @@
1146
1146
  .workflow-metric-grid em {
1147
1147
  margin-left: 3px;
1148
1148
  color: #73918d;
1149
- font-size: 8px;
1149
+ font-size: 12px;
1150
1150
  font-style: normal;
1151
1151
  }
1152
1152
 
@@ -1181,7 +1181,7 @@
1181
1181
  .child-session .agent-flow-provider em {
1182
1182
  max-width: 76px;
1183
1183
  color: #607884;
1184
- font-size: 7px;
1184
+ font-size: 12px;
1185
1185
  font-style: normal;
1186
1186
  line-height: 1.3;
1187
1187
  text-align: right;
@@ -1191,7 +1191,7 @@
1191
1191
  display: block;
1192
1192
  margin-top: 9px;
1193
1193
  color: #6f9f9a;
1194
- font-size: 8px;
1194
+ font-size: 12px;
1195
1195
  font-weight: 750;
1196
1196
  text-align: right;
1197
1197
  }
@@ -1224,7 +1224,7 @@
1224
1224
  .agent-flow-provider .execution-mode-badge {
1225
1225
  margin-bottom: 2px;
1226
1226
  padding: 3px 5px;
1227
- font-size: 7px;
1227
+ font-size: 12px;
1228
1228
  }
1229
1229
 
1230
1230
  .agent-node-top .execution-mode-badge {
@@ -1241,7 +1241,7 @@
1241
1241
  gap: 6px;
1242
1242
  margin-top: 7px;
1243
1243
  color: #728695;
1244
- font-size: 9px;
1244
+ font-size: 12px;
1245
1245
  line-height: 1.4;
1246
1246
  }
1247
1247
 
@@ -162,13 +162,13 @@ body[data-current-view="settings"] .sidebar>.workspace-section {
162
162
  color: #657187;
163
163
  font-weight: 700;
164
164
  letter-spacing: .18em;
165
- font-size: 9px;
165
+ font-size: 12px;
166
166
  }
167
167
 
168
168
  .nav-group-label {
169
169
  margin: 18px 10px 6px;
170
170
  color: #718096;
171
- font-size: 11px;
171
+ font-size: 12px;
172
172
  font-weight: 700;
173
173
  letter-spacing: .08em;
174
174
  }
@@ -195,7 +195,7 @@ body {
195
195
  background: #101923;
196
196
  color: #7f929d;
197
197
  font-family: "Geist Mono","Cascadia Code",Consolas,monospace;
198
- font-size: 8px;
198
+ font-size: 12px;
199
199
  font-weight: 700;
200
200
  letter-spacing: .02em;
201
201
  white-space: nowrap;
@@ -242,7 +242,7 @@ html {
242
242
  font-weight: 760;
243
243
  letter-spacing: .17em;
244
244
  color: #8d9bad;
245
- font-size: 11px;
245
+ font-size: 12px;
246
246
  }
247
247
 
248
248
  .nav-item b {
@@ -252,20 +252,20 @@ html {
252
252
  border-radius: 10px;
253
253
  background: #1c2431;
254
254
  color: #aeb9c7;
255
- font-size: 11px;
255
+ font-size: 12px;
256
256
  }
257
257
 
258
258
  .sidebar-footer strong {
259
259
  display: block;
260
260
  color: #aeb9c7;
261
- font-size: 11px;
261
+ font-size: 12px;
262
262
  }
263
263
 
264
264
  .sidebar-footer small {
265
265
  display: block;
266
266
  margin-top: 3px;
267
267
  color: #8795a6;
268
- font-size: 10px;
268
+ font-size: 12px;
269
269
  }
270
270
 
271
271
  .app-shell {
@@ -9,6 +9,7 @@ window.LoadToAgentTerminalEvents = function bindTerminalEvents(context) {
9
9
  refreshSessions, renderHistoryPanel, fitEntry, attachTmux, currentTmux, manageTmux,
10
10
  closeTmuxModal, errorMessage, notice, reorderSession, moveSessionByOffset,
11
11
  setTerminalFontSize, toggleTerminalFocusMode,
12
+ isAiTerminalSession,
12
13
  } = context;
13
14
 
14
15
  const runBusy = async (button, action) => {
@@ -283,17 +284,13 @@ window.LoadToAgentTerminalEvents = function bindTerminalEvents(context) {
283
284
  });
284
285
  renderAll();
285
286
  });
286
- $('#terminalCloseBtn').addEventListener('click', async event => {
287
- const session = currentSession();
288
- if (!session) {
289
- state.selectedTmux = null;
290
- renderAll();
291
- showSelection();
292
- return;
293
- }
294
- if (session.status === 'running' && !window.confirm(t('terminal.session.confirm_end', { title: session.title }))) return;
295
- await runBusy(event.currentTarget, async () => {
296
- const closed = await guarded(() => window.loadtoagent.terminalClose(session.id), t('terminal.session.ended'), `terminal-close:${session.id}`);
287
+ const endTerminalSession = async (button, session) => {
288
+ if (!session) return;
289
+ const confirmation = isAiTerminalSession(session) ? 'terminal.session.confirm_end_ai' : 'terminal.session.confirm_end';
290
+ if (session.type !== 'tmux' && session.status === 'running' && !window.confirm(t(confirmation, { title: session.title }))) return;
291
+ await runBusy(button, async () => {
292
+ const message = session.type === 'tmux' ? t('terminal.tmux.detached_input') : t('terminal.session.ended');
293
+ const closed = await guarded(() => window.loadtoagent.terminalClose(session.id), message, `terminal-close:${session.id}`);
297
294
  if (!closed) return;
298
295
  const entry = state.terminals.get(session.id);
299
296
  if (entry) {
@@ -310,7 +307,29 @@ window.LoadToAgentTerminalEvents = function bindTerminalEvents(context) {
310
307
  await refreshSessions();
311
308
  });
312
309
  renderAll();
310
+ };
311
+ $('#terminalCloseBtn').addEventListener('click', async event => {
312
+ const session = currentSession();
313
+ if (!session) {
314
+ state.selectedTmux = null;
315
+ renderAll();
316
+ await showSelection();
317
+ return;
318
+ }
319
+ if (isAiTerminalSession(session)) {
320
+ state.captureGeneration += 1;
321
+ state.selectedId = null;
322
+ state.boundAgent = null;
323
+ state.boundTargetId = '';
324
+ renderAll();
325
+ renderHistoryPanel();
326
+ await showSelection();
327
+ notice(t('terminal.view_closed_ai_kept'), 'success');
328
+ return;
329
+ }
330
+ await endTerminalSession(event.currentTarget, session);
313
331
  });
332
+ $('#terminalEndSessionBtn').addEventListener('click', event => endTerminalSession(event.currentTarget, currentSession()));
314
333
  $('#terminalHistoryToggle').addEventListener('click', () => {
315
334
  state.historyCollapsed = !state.historyCollapsed;
316
335
  renderHistoryPanel();
@@ -415,5 +434,9 @@ window.LoadToAgentTerminalEvents = function bindTerminalEvents(context) {
415
434
  });
416
435
  window.loadtoagent.onTerminalState(payload => refreshSessions(payload));
417
436
  window.loadtoagent.onTerminalError(payload => notice(payload && payload.message || t('terminal.error.input_failed'), 'error'));
437
+ window.loadtoagent.onTerminalConnection?.(payload => {
438
+ const tone = payload?.state === 'failed' ? 'error' : payload?.state === 'connected' ? 'success' : 'info';
439
+ notice(payload?.message || t('terminal.error.input_failed'), tone);
440
+ });
418
441
  }
419
442
  };
@@ -19,7 +19,10 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
19
19
  const fit = new window.FitAddon.FitAddon();
20
20
  terminal.loadAddon(fit);
21
21
  terminal.open(host);
22
- const entry = { terminal, fit, host, readOnly, userScrollRevision: 0, outputWritePending: 0 };
22
+ const entry = {
23
+ terminal, fit, host, readOnly, userScrollRevision: 0, outputWritePending: 0,
24
+ writeQueue: Promise.resolve(), pendingResize: null, resizePromise: null,
25
+ };
23
26
  const syncScrollState = viewportY => {
24
27
  const normalizedViewport = Number(viewportY) || 0;
25
28
  const baseY = Number(terminal.buffer.active.baseY) || 0;
@@ -43,27 +46,33 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
43
46
  if (['PageUp', 'PageDown', 'Home', 'End', 'ArrowUp', 'ArrowDown'].includes(event.key)) rememberUserScroll();
44
47
  }, true);
45
48
  terminal.onData(data => {
46
- if (state.selectedId === key) Promise.resolve(window.loadtoagent.terminalWrite(key, data)).catch(error => notice(errorMessage(error), 'error'));
49
+ if (state.selectedId !== key) return;
50
+ entry.writeQueue = entry.writeQueue
51
+ .then(() => window.loadtoagent.terminalWrite(key, data))
52
+ .catch(error => notice(errorMessage(error), 'error'));
47
53
  });
48
54
  terminal.onResize(size => {
49
- Promise.resolve(window.loadtoagent.terminalResize(key, size.cols, size.rows)).catch(error => {
55
+ entry.pendingResize = { cols: size.cols, rows: size.rows };
56
+ if (entry.resizePromise) return;
57
+ entry.resizePromise = (async () => {
58
+ while (entry.pendingResize) {
59
+ const pending = entry.pendingResize;
60
+ entry.pendingResize = null;
61
+ await window.loadtoagent.terminalResize(key, pending.cols, pending.rows);
62
+ }
63
+ })().catch(error => {
50
64
  window.LoadToAgentRendererUtils.reportRecoverableError('terminal-resize', error);
51
- });
65
+ }).finally(() => { entry.resizePromise = null; });
52
66
  });
53
67
  }
54
68
  return entry;
55
69
  }
56
70
 
57
- function fitEntry(entry, sessionId = '') {
71
+ function fitEntry(entry, _sessionId = '') {
58
72
  if (!entry || entry.host.classList.contains('hidden')) return;
59
73
  requestAnimationFrame(() => {
60
74
  try {
61
75
  entry.fit.fit();
62
- if (sessionId) {
63
- Promise.resolve(window.loadtoagent.terminalResize(sessionId, entry.terminal.cols, entry.terminal.rows)).catch(error => {
64
- window.LoadToAgentRendererUtils.reportRecoverableError('terminal-fit-resize', error);
65
- });
66
- }
67
76
  } catch (error) {
68
77
  window.LoadToAgentRendererUtils.reportRecoverableError('terminal-fit', error);
69
78
  }
@@ -95,15 +104,25 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
95
104
  function linkedAgentSession(session) {
96
105
  if (!session) return null;
97
106
  if (state.boundTargetId === session.id && state.boundAgent) return state.boundAgent;
107
+ const agents = Array.isArray(state.snapshot?.sessions) ? state.snapshot.sessions : [];
98
108
  const bridgeId = String(session.bridgeId || '');
99
- return bridgeId && Array.isArray(state.snapshot?.sessions)
100
- ? state.snapshot.sessions.find(item => item.id === bridgeId) || null
101
- : null;
109
+ const bridged = bridgeId ? agents.find(item => item.id === bridgeId) : null;
110
+ if (bridged) return bridged;
111
+ const terminalPid = Number(session.pid || 0);
112
+ return agents.find(agent => (Array.isArray(agent.runtimePresence) ? agent.runtimePresence : []).some(item => (
113
+ item.terminalId === session.id
114
+ || (terminalPid > 0 && Number(item.pid || 0) === terminalPid)
115
+ || (terminalPid > 0 && Number(item.parentPid || 0) === terminalPid)
116
+ ))) || null;
117
+ }
118
+
119
+ function isAiTerminalSession(session) {
120
+ return Boolean(session && (session.type === 'agent' || linkedAgentSession(session)));
102
121
  }
103
122
 
104
123
  function terminalPresentation(session) {
105
124
  const agent = linkedAgentSession(session);
106
- if (agent?.attention?.required || agent?.status === 'waiting') return { tone: 'attention', label: t('app.nav.needs_review') };
125
+ if (agent?.attention?.required || agent?.status === 'waiting') return { tone: 'attention', label: t('ui.waiting_for_review') };
107
126
  if (agent?.status === 'failed' || session?.status === 'failed') return { tone: 'failed', label: t('terminal.status.failed') };
108
127
  if (agent?.status === 'completed') return { tone: 'completed', label: t('ui.completed') };
109
128
  if (agent && ['running', 'starting'].includes(agent.status)) return { tone: 'running', label: t('ui.working') };
@@ -136,7 +155,10 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
136
155
  state.selectedId,
137
156
  general.map(session => {
138
157
  const presentation = terminalPresentation(session);
139
- return [session.id, session.title, session.type, session.status, session.cwd, session.background, presentation.tone, presentation.label];
158
+ return [
159
+ session.id, session.title, session.type, session.status, session.pid, session.cwd, session.background,
160
+ session.recoveredAfterHostRestart, session.recoverySkippedReason, presentation.tone, presentation.label,
161
+ ];
140
162
  }),
141
163
  ]);
142
164
  if (renderKey === state.sessionRenderKey) return;
@@ -157,7 +179,7 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
157
179
  aria-describedby="terminalReorderHelp"
158
180
  title="${esc(session.cwd || window.LoadToAgentI18n.t('terminal.reorder_hint'))}">
159
181
  <span class="terminal-session-icon">${esc(terminalTypeMark(session))}</span>
160
- <span><b>${esc(session.title)}</b><small>${esc(terminalTypeLabel(session))}${session.background ? ` · ${t('terminal.background_kept')}` : ''}</small><em>${esc(session.cwd || session.distro || `PID ${session.pid || '--'}`)}</em><span class="sr-only">${index + 1}/${general.length}</span></span>
182
+ <span><b>${esc(session.title)}</b><small>${esc(terminalTypeLabel(session))}${session.background ? ` · ${t('terminal.background_kept')}` : ''}${session.recoveredAfterHostRestart ? ` · ${t('terminal.recovered_after_host_restart')}` : ''}</small><em>${esc(session.cwd || session.distro || `PID ${session.pid || '--'}`)}</em><span class="sr-only">${index + 1}/${general.length}</span></span>
161
183
  <span class="terminal-session-status" data-status="${esc(presentation.tone)}"><i></i>${esc(presentation.label)}</span>
162
184
  </button>
163
185
  <span class="terminal-reorder-actions" aria-label="${esc(window.LoadToAgentI18n.t('terminal.reorder_group', { title: session.title }))}">
@@ -192,12 +214,20 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
192
214
  const session = currentSession();
193
215
  const remote = currentTmux();
194
216
  const bound = visibleBoundAgent();
217
+ const aiTerminal = isAiTerminalSession(session);
195
218
  const hasTarget = Boolean(session || remote);
196
219
  const canInput = Boolean((remote && !remote.pane.dead) || (session && session.status === 'running'));
197
220
  const closeButton = $('#terminalCloseBtn');
198
221
  closeButton.disabled = !hasTarget;
199
- closeButton.textContent = remote && !session ? t('terminal.clear_selection') : t('ui.end_session');
200
- closeButton.classList.toggle('terminal-danger-button', Boolean(session));
222
+ closeButton.textContent = remote && !session
223
+ ? t('terminal.clear_selection')
224
+ : session?.type === 'tmux'
225
+ ? t('terminal.detach_tmux_input')
226
+ : aiTerminal ? t('terminal.close_view') : t('ui.end_session');
227
+ closeButton.classList.toggle('terminal-danger-button', Boolean(session && !aiTerminal && session.type !== 'tmux'));
228
+ const endSessionButton = $('#terminalEndSessionBtn');
229
+ endSessionButton.classList.toggle('hidden', !aiTerminal);
230
+ endSessionButton.disabled = !aiTerminal;
201
231
  $('#terminalRestartBtn').classList.toggle('hidden', !session || session.status === 'running' || session.type === 'agent');
202
232
  $('#terminalRestartBtn').disabled = !session || session.status === 'running';
203
233
  $('#terminalCommandInput').disabled = !canInput;
@@ -215,7 +245,7 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
215
245
  const presentation = terminalPresentation(session);
216
246
  setConnectionState(presentation.label, presentation.tone);
217
247
  $('#terminalTargetIcon').textContent = terminalTypeMark(session);
218
- $('#terminalTargetMeta').innerHTML = `<b>${esc(session.title)}</b><span>${bound ? `● ${t('terminal.bound_ai_session')} · ` : ''}${esc(session.type.toUpperCase())} · PID ${session.pid || '--'} · ${esc(session.cwd || session.distro || '')}</span>`;
248
+ $('#terminalTargetMeta').innerHTML = `<b>${esc(session.title)}</b><span>${bound ? `● ${t('terminal.bound_ai_session')} · ` : ''}${session.recoveredAfterHostRestart ? `${t('terminal.recovered_after_host_restart')} · ` : ''}${esc(session.type.toUpperCase())} · PID ${session.pid || '--'} · ${esc(session.cwd || session.distro || '')}</span>`;
219
249
  $('#terminalConsoleCaption').textContent = `${terminalTypeLabel(session)} · PID ${session.pid || '--'}`;
220
250
  $('#terminalConsoleState').textContent = presentation.tone === 'attention' || presentation.tone === 'completed'
221
251
  ? presentation.label
@@ -334,8 +364,9 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
334
364
  const nextSessions = payload && Array.isArray(payload.sessions) ? payload.sessions : await window.loadtoagent.terminalList();
335
365
  state.sessions = Array.isArray(nextSessions) ? nextSessions : [];
336
366
  const activeIds = new Set(state.sessions.map(session => session.id));
367
+ const rehydratedIds = new Set(payload?.change === 'reconnected' ? activeIds : []);
337
368
  for (const [id, entry] of state.terminals) {
338
- if (activeIds.has(id)) continue;
369
+ if (activeIds.has(id) && !rehydratedIds.has(id)) continue;
339
370
  entry.terminal.dispose();
340
371
  entry.host.remove();
341
372
  state.terminals.delete(id);
@@ -558,5 +589,5 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
558
589
  }
559
590
  }
560
591
 
561
- return { createXtermHost, fitEntry, ensureSessionTerminal, ensureRemoteTerminal, hideScreens, renderSessions, renderTmuxResources, renderTarget, showSelection, selectSession, selectTmux, selectTmuxById, renderAll, refreshSessions, createTerminal, captureRemote, startCapture, stopCapture, sendCommand, sendSignal, openTmuxModal, closeTmuxModal, refreshSnapshot, attachTmux, manageTmux };
592
+ return { createXtermHost, fitEntry, ensureSessionTerminal, ensureRemoteTerminal, hideScreens, linkedAgentSession, isAiTerminalSession, renderSessions, renderTmuxResources, renderTarget, showSelection, selectSession, selectTmux, selectTmuxById, renderAll, refreshSessions, createTerminal, captureRemote, startCapture, stopCapture, sendCommand, sendSignal, openTmuxModal, closeTmuxModal, refreshSnapshot, attachTmux, manageTmux };
562
593
  };