loadtoagent 1.1.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +14 -5
- package/README.md +7 -0
- package/README.zh-CN.md +7 -0
- package/docs/PROVIDER-CONTRACTS.md +25 -1
- package/docs/UI-AUDIT-100.md +118 -0
- package/docs/UI-AUDIT-101-200.md +120 -0
- package/docs/UI-AUDIT-201-300.md +120 -0
- package/main.js +157 -38
- package/package.json +13 -4
- package/preload.js +13 -2
- package/renderer/app-agent-actions.js +125 -53
- package/renderer/app-bootstrap.js +54 -19
- package/renderer/app-dashboard.js +196 -55
- package/renderer/app-drawer-content.js +48 -44
- package/renderer/app-drawer-data.js +26 -13
- package/renderer/app-drawer.js +72 -54
- package/renderer/app-events-dialogs.js +146 -37
- package/renderer/app-events-filters.js +172 -13
- package/renderer/app-events-navigation.js +77 -30
- package/renderer/app-events-sessions.js +156 -9
- package/renderer/app-events.js +2 -1
- package/renderer/app-graph-model.js +20 -38
- package/renderer/app-graph-orchestration.js +15 -13
- package/renderer/app-graph-view.js +228 -113
- package/renderer/app-management.js +257 -0
- package/renderer/app-provider-visibility.js +124 -0
- package/renderer/app-quality.js +568 -0
- package/renderer/app-run-modal.js +104 -34
- package/renderer/app-runtime-overview.js +312 -0
- package/renderer/app-session-render.js +94 -37
- package/renderer/app-tmux-render.js +52 -44
- package/renderer/app.js +153 -75
- package/renderer/i18n-messages.js +867 -37
- package/renderer/i18n.js +104 -0
- package/renderer/index.html +154 -67
- package/renderer/shared.js +17 -0
- package/renderer/styles-agent-map.css +16 -10
- package/renderer/styles-cards.css +44 -18
- package/renderer/styles-collaboration.css +23 -23
- package/renderer/styles-components.css +122 -45
- package/renderer/styles-management.css +364 -0
- package/renderer/styles-onboarding.css +8 -8
- package/renderer/styles-overlays.css +13 -7
- package/renderer/styles-product.css +48 -40
- package/renderer/styles-quality.css +312 -0
- package/renderer/styles-readability.css +1040 -0
- package/renderer/styles-responsive-product.css +84 -12
- package/renderer/styles-responsive-runtime.css +25 -17
- package/renderer/styles-responsive-shell.css +44 -48
- package/renderer/styles-responsive-workflows.css +39 -5
- package/renderer/styles-run-composer.css +28 -23
- package/renderer/styles-runtime-overview.css +285 -0
- package/renderer/styles-settings.css +176 -16
- package/renderer/styles-terminal.css +374 -37
- package/renderer/styles-tmux.css +49 -49
- package/renderer/styles-workflow-map.css +362 -15
- package/renderer/styles-workflows.css +116 -49
- package/renderer/styles.css +34 -19
- package/renderer/terminal-agent.js +17 -13
- package/renderer/terminal-events.js +253 -37
- package/renderer/terminal-workbench.js +208 -89
- package/renderer/terminal.js +350 -35
- package/src/agentMonitor/claudeParser.js +96 -7
- package/src/agentMonitor/codexParser.js +59 -4
- package/src/agentMonitor/executionActivity.js +282 -0
- package/src/agentMonitor/genericParser.js +56 -13
- package/src/agentMonitor/hierarchy.js +17 -0
- package/src/agentMonitor/responseIntent.js +51 -0
- package/src/agentMonitor.js +21 -3
- package/src/agentRunner.js +66 -1
- package/src/attentionNotifier.js +14 -10
- package/src/automationMonitor.js +258 -0
- package/src/contracts.js +10 -1
- package/src/ipc/registerAgentIpc.js +9 -3
- package/src/ipc/registerAppIpc.js +4 -1
- package/src/ipc/registerTerminalIpc.js +12 -14
- package/src/macUpdateHelper.js +210 -0
- package/src/monitorWorker.js +65 -6
- package/src/platformPath.js +80 -0
- package/src/processMonitor.js +80 -22
- package/src/providerVisibilityStore.js +45 -0
- package/src/sessionIntelligence.js +261 -0
- package/src/terminalHost.js +453 -0
- package/src/terminalHostDaemon.js +61 -0
- package/src/terminalManager.js +218 -6
- package/src/updateInstaller.js +175 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/** Own xterm views, terminal/tmux selection, capture, and management actions. */
|
|
4
4
|
window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
5
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
5
6
|
const {
|
|
6
7
|
$, state, notice, setConnectionState, currentSession, currentTmux, saveCurrentDraft, restoreCurrentDraft,
|
|
7
8
|
renderHistoryPanel, terminalTypeMark, terminalTypeLabel, xtermOptions, preferredWorkspace, firstDistro, guarded,
|
|
@@ -9,7 +10,7 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
9
10
|
} = context;
|
|
10
11
|
|
|
11
12
|
function createXtermHost(key, readOnly = false) {
|
|
12
|
-
if (!window.Terminal || !window.FitAddon || !window.FitAddon.FitAddon) throw new Error('
|
|
13
|
+
if (!window.Terminal || !window.FitAddon || !window.FitAddon.FitAddon) throw new Error(t('terminal.error.screen_unavailable'));
|
|
13
14
|
const host = document.createElement('div');
|
|
14
15
|
host.className = 'terminal-screen hidden';
|
|
15
16
|
host.dataset.terminalScreen = key;
|
|
@@ -18,48 +19,60 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
18
19
|
const fit = new window.FitAddon.FitAddon();
|
|
19
20
|
terminal.loadAddon(fit);
|
|
20
21
|
terminal.open(host);
|
|
22
|
+
const entry = {
|
|
23
|
+
terminal, fit, host, readOnly, userScrollRevision: 0, outputWritePending: 0,
|
|
24
|
+
writeQueue: Promise.resolve(), pendingResize: null, resizePromise: null,
|
|
25
|
+
};
|
|
21
26
|
const syncScrollState = viewportY => {
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
const normalizedViewport = Number(viewportY) || 0;
|
|
28
|
+
const baseY = Number(terminal.buffer.active.baseY) || 0;
|
|
29
|
+
host.dataset.viewportY = String(normalizedViewport);
|
|
30
|
+
host.dataset.baseY = String(baseY);
|
|
31
|
+
// Xterm may consume wheel events before they bubble to the host. Its
|
|
32
|
+
// scroll event is the reliable source for mouse, keyboard and scrollbar
|
|
33
|
+
// viewport changes.
|
|
34
|
+
if (readOnly && !state.remoteCaptureApplying) {
|
|
35
|
+
state.remoteViewportAnchor = normalizedViewport;
|
|
36
|
+
state.remoteViewportAtBottom = normalizedViewport >= baseY;
|
|
37
|
+
}
|
|
24
38
|
};
|
|
25
39
|
terminal.onScroll(syncScrollState);
|
|
26
40
|
syncScrollState(0);
|
|
27
|
-
const entry = { terminal, fit, host, readOnly };
|
|
28
|
-
if (readOnly) {
|
|
29
|
-
const rememberUserViewport = () => requestAnimationFrame(() => {
|
|
30
|
-
const buffer = terminal.buffer.active;
|
|
31
|
-
state.remoteViewportAnchor = Number(buffer.viewportY) || 0;
|
|
32
|
-
state.remoteViewportAtBottom = state.remoteViewportAnchor >= Number(buffer.baseY || 0);
|
|
33
|
-
});
|
|
34
|
-
host.addEventListener('wheel', rememberUserViewport, { passive: true });
|
|
35
|
-
host.addEventListener('pointerup', rememberUserViewport);
|
|
36
|
-
host.addEventListener('keyup', event => {
|
|
37
|
-
if (['PageUp', 'PageDown', 'Home', 'End', 'ArrowUp', 'ArrowDown'].includes(event.key)) rememberUserViewport();
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
41
|
if (!readOnly) {
|
|
42
|
+
const rememberUserScroll = () => { entry.userScrollRevision += 1; };
|
|
43
|
+
host.addEventListener('wheel', rememberUserScroll, { capture: true, passive: true });
|
|
44
|
+
host.addEventListener('pointerup', rememberUserScroll, true);
|
|
45
|
+
host.addEventListener('keyup', event => {
|
|
46
|
+
if (['PageUp', 'PageDown', 'Home', 'End', 'ArrowUp', 'ArrowDown'].includes(event.key)) rememberUserScroll();
|
|
47
|
+
}, true);
|
|
41
48
|
terminal.onData(data => {
|
|
42
|
-
if (state.selectedId
|
|
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'));
|
|
43
53
|
});
|
|
44
54
|
terminal.onResize(size => {
|
|
45
|
-
|
|
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 => {
|
|
46
64
|
window.LoadToAgentRendererUtils.reportRecoverableError('terminal-resize', error);
|
|
47
|
-
});
|
|
65
|
+
}).finally(() => { entry.resizePromise = null; });
|
|
48
66
|
});
|
|
49
67
|
}
|
|
50
68
|
return entry;
|
|
51
69
|
}
|
|
52
70
|
|
|
53
|
-
function fitEntry(entry,
|
|
71
|
+
function fitEntry(entry, _sessionId = '') {
|
|
54
72
|
if (!entry || entry.host.classList.contains('hidden')) return;
|
|
55
73
|
requestAnimationFrame(() => {
|
|
56
74
|
try {
|
|
57
75
|
entry.fit.fit();
|
|
58
|
-
if (sessionId) {
|
|
59
|
-
Promise.resolve(window.loadtoagent.terminalResize(sessionId, entry.terminal.cols, entry.terminal.rows)).catch(error => {
|
|
60
|
-
window.LoadToAgentRendererUtils.reportRecoverableError('terminal-fit-resize', error);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
76
|
} catch (error) {
|
|
64
77
|
window.LoadToAgentRendererUtils.reportRecoverableError('terminal-fit', error);
|
|
65
78
|
}
|
|
@@ -88,49 +101,110 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
88
101
|
$('#terminalEmpty').classList.add('hidden');
|
|
89
102
|
}
|
|
90
103
|
|
|
104
|
+
function linkedAgentSession(session) {
|
|
105
|
+
if (!session) return null;
|
|
106
|
+
if (state.boundTargetId === session.id && state.boundAgent) return state.boundAgent;
|
|
107
|
+
const agents = Array.isArray(state.snapshot?.sessions) ? state.snapshot.sessions : [];
|
|
108
|
+
const bridgeId = String(session.bridgeId || '');
|
|
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)));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function terminalPresentation(session) {
|
|
124
|
+
const agent = linkedAgentSession(session);
|
|
125
|
+
if (agent?.attention?.required || agent?.status === 'waiting') return { tone: 'attention', label: t('ui.waiting_for_review') };
|
|
126
|
+
if (agent?.status === 'failed' || session?.status === 'failed') return { tone: 'failed', label: t('terminal.status.failed') };
|
|
127
|
+
if (agent?.status === 'completed') return { tone: 'completed', label: t('ui.completed') };
|
|
128
|
+
if (agent && ['running', 'starting'].includes(agent.status)) return { tone: 'running', label: t('ui.working') };
|
|
129
|
+
if (session?.status === 'running' || session?.status === 'starting') return { tone: 'running', label: STATUS_LABELS[session.status] || session.status };
|
|
130
|
+
return { tone: session?.status || 'idle', label: STATUS_LABELS[session?.status] || session?.status || t('ui.idle') };
|
|
131
|
+
}
|
|
132
|
+
|
|
91
133
|
function renderSessions() {
|
|
92
134
|
const general = modeSessions('general');
|
|
93
135
|
const running = general.filter(item => item.status === 'running').length;
|
|
94
136
|
const background = general.filter(item => item.background && item.status === 'running').length;
|
|
137
|
+
const attention = general.filter(item => terminalPresentation(item).tone === 'attention').length;
|
|
95
138
|
$('#navTerminalCount').textContent = running;
|
|
139
|
+
const terminalNav = document.querySelector('.nav-item[data-view="terminal"]');
|
|
140
|
+
if (terminalNav) terminalNav.setAttribute('aria-label', t('quality.nav_count_detailed', { label: t('app.nav.session_terminal'), count: running, unit: t('quality.unit.sessions') }));
|
|
141
|
+
const advancedCount = ['navRuntimeCount', 'navTerminalCount', 'navTmuxCount']
|
|
142
|
+
.reduce((total, id) => total + Number(document.getElementById(id)?.textContent || 0), 0);
|
|
143
|
+
const advancedCounter = document.getElementById('advancedToolsCount');
|
|
144
|
+
if (advancedCounter) advancedCounter.textContent = String(advancedCount);
|
|
145
|
+
document.querySelector('#advancedToolsNav > summary')?.setAttribute('aria-label', t('quality.nav_count_detailed', {
|
|
146
|
+
label: t('management.advanced_tools'), count: advancedCount, unit: t('quality.unit.items'),
|
|
147
|
+
}));
|
|
96
148
|
$('#terminalSessionSummary').textContent = [
|
|
97
149
|
window.LoadToAgentI18n.t('common.active', { count: running }),
|
|
150
|
+
attention ? t('terminal.monitor.attention_count', { count: attention }) : '',
|
|
98
151
|
background ? window.LoadToAgentI18n.t('session.background_count', { count: background }) : '',
|
|
99
152
|
window.LoadToAgentI18n.t('session.total_count', { count: general.length }),
|
|
100
153
|
].filter(Boolean).join(' · ');
|
|
101
|
-
|
|
154
|
+
const renderKey = JSON.stringify([
|
|
155
|
+
state.selectedId,
|
|
156
|
+
general.map(session => {
|
|
157
|
+
const presentation = terminalPresentation(session);
|
|
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
|
+
];
|
|
162
|
+
}),
|
|
163
|
+
]);
|
|
164
|
+
if (renderKey === state.sessionRenderKey) return;
|
|
165
|
+
state.sessionRenderKey = renderKey;
|
|
166
|
+
$('#terminalSessionList').innerHTML = general.length ? general.map((session, index) => {
|
|
167
|
+
const presentation = terminalPresentation(session);
|
|
168
|
+
return `
|
|
102
169
|
<div class="terminal-session-row">
|
|
103
170
|
<button type="button" draggable="true"
|
|
104
171
|
class="terminal-session-item ${state.selectedId === session.id ? 'active' : ''}"
|
|
172
|
+
data-status="${esc(presentation.tone)}"
|
|
105
173
|
data-terminal-id="${esc(session.id)}"
|
|
174
|
+
role="option"
|
|
175
|
+
aria-selected="${state.selectedId === session.id ? 'true' : 'false'}"
|
|
176
|
+
tabindex="${state.selectedId === session.id || (!state.selectedId && index === 0) ? '0' : '-1'}"
|
|
177
|
+
aria-pressed="${state.selectedId === session.id ? 'true' : 'false'}"
|
|
106
178
|
aria-grabbed="false"
|
|
107
179
|
aria-describedby="terminalReorderHelp"
|
|
108
|
-
title="${esc(window.LoadToAgentI18n.t('terminal.reorder_hint'))}">
|
|
180
|
+
title="${esc(session.cwd || window.LoadToAgentI18n.t('terminal.reorder_hint'))}">
|
|
109
181
|
<span class="terminal-session-icon">${esc(terminalTypeMark(session))}</span>
|
|
110
|
-
<span><b>${esc(session.title)}</b><small>${esc(terminalTypeLabel(session))}${session.background ?
|
|
111
|
-
<
|
|
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>
|
|
183
|
+
<span class="terminal-session-status" data-status="${esc(presentation.tone)}"><i></i>${esc(presentation.label)}</span>
|
|
112
184
|
</button>
|
|
113
185
|
<span class="terminal-reorder-actions" aria-label="${esc(window.LoadToAgentI18n.t('terminal.reorder_group', { title: session.title }))}">
|
|
114
186
|
<button type="button" data-session-move="-1" data-session-move-id="${esc(session.id)}" aria-label="${esc(window.LoadToAgentI18n.t('terminal.move_up', { title: session.title }))}" ${index === 0 ? 'disabled' : ''}>↑</button>
|
|
115
187
|
<button type="button" data-session-move="1" data-session-move-id="${esc(session.id)}" aria-label="${esc(window.LoadToAgentI18n.t('terminal.move_down', { title: session.title }))}" ${index === general.length - 1 ? 'disabled' : ''}>↓</button>
|
|
116
188
|
</span>
|
|
117
|
-
</div
|
|
189
|
+
</div>`;
|
|
190
|
+
}).join('') : `<div class="terminal-resource-empty">${t('terminal.empty.general')}</div>`;
|
|
118
191
|
}
|
|
119
192
|
|
|
120
193
|
function renderTmuxResources() {
|
|
121
194
|
const distros = state.snapshot && state.snapshot.tmux && state.snapshot.tmux.distros || [];
|
|
122
195
|
if (!distros.length) {
|
|
123
|
-
$('#terminalTmuxList').innerHTML =
|
|
196
|
+
$('#terminalTmuxList').innerHTML = `<div class="terminal-resource-empty">${t('terminal.empty.tmux')}</div>`;
|
|
124
197
|
return;
|
|
125
198
|
}
|
|
199
|
+
let paneIndex = 0;
|
|
126
200
|
$('#terminalTmuxList').innerHTML = distros.map(distro => `
|
|
127
201
|
<section class="terminal-tmux-group">
|
|
128
|
-
<header><b>${esc(distro.name)}</b><span
|
|
202
|
+
<header><b>${esc(distro.name)}</b><span>${t('terminal.tmux.workspace_count', { count: (distro.sessions || []).length })}</span></header>
|
|
129
203
|
${(distro.sessions || []).map(session => `
|
|
130
|
-
<div class="terminal-tmux-session"><strong>${esc(session.name)}</strong><small>${session.attached ? '
|
|
204
|
+
<div class="terminal-tmux-session"><strong>${esc(session.name)}</strong><small>${session.attached ? t('terminal.tmux.attached') : t('terminal.tmux.running_background')}</small></div>
|
|
131
205
|
${(session.windows || []).flatMap(windowItem => (windowItem.panes || []).map(pane => `
|
|
132
|
-
<button type="button" class="terminal-tmux-pane ${state.selectedTmux && state.selectedTmux.distro.name === distro.name && state.selectedTmux.pane.nativeId === pane.nativeId ? 'active' : ''}" data-tmux-distro="${esc(distro.name)}" data-tmux-pane="${esc(pane.nativeId)}">
|
|
133
|
-
<span><b>${esc(pane.nativeId)} · ${esc(windowItem.index)}:${esc(windowItem.name)}</b><small>${esc(pane.command || 'shell')} · ${esc(pane.cwd || '
|
|
206
|
+
<button type="button" role="option" class="terminal-tmux-pane ${state.selectedTmux && state.selectedTmux.distro.name === distro.name && state.selectedTmux.pane.nativeId === pane.nativeId ? 'active' : ''}" data-tmux-distro="${esc(distro.name)}" data-tmux-pane="${esc(pane.nativeId)}" aria-selected="${state.selectedTmux && state.selectedTmux.distro.name === distro.name && state.selectedTmux.pane.nativeId === pane.nativeId ? 'true' : 'false'}" aria-pressed="${state.selectedTmux && state.selectedTmux.distro.name === distro.name && state.selectedTmux.pane.nativeId === pane.nativeId ? 'true' : 'false'}" tabindex="${state.selectedTmux && state.selectedTmux.distro.name === distro.name && state.selectedTmux.pane.nativeId === pane.nativeId || (!state.selectedTmux && paneIndex === 0) ? '0' : '-1'}" data-pane-index="${paneIndex++}">
|
|
207
|
+
<span><b>${esc(pane.nativeId)} · ${esc(windowItem.index)}:${esc(windowItem.name)}</b><small>${esc(pane.command || 'shell')} · ${esc(pane.cwd || t('terminal.path_unreported'))}</small></span>
|
|
134
208
|
<i class="${pane.agent ? 'agent' : (pane.active ? 'live' : '')}">${pane.agent ? 'AI' : (pane.active ? 'ON' : '')}</i>
|
|
135
209
|
</button>`)).join('')}`).join('')}
|
|
136
210
|
</section>`).join('');
|
|
@@ -140,33 +214,45 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
140
214
|
const session = currentSession();
|
|
141
215
|
const remote = currentTmux();
|
|
142
216
|
const bound = visibleBoundAgent();
|
|
217
|
+
const aiTerminal = isAiTerminalSession(session);
|
|
143
218
|
const hasTarget = Boolean(session || remote);
|
|
144
219
|
const canInput = Boolean((remote && !remote.pane.dead) || (session && session.status === 'running'));
|
|
145
220
|
const closeButton = $('#terminalCloseBtn');
|
|
146
221
|
closeButton.disabled = !hasTarget;
|
|
147
|
-
closeButton.textContent = remote && !session
|
|
148
|
-
|
|
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;
|
|
149
231
|
$('#terminalRestartBtn').classList.toggle('hidden', !session || session.status === 'running' || session.type === 'agent');
|
|
150
232
|
$('#terminalRestartBtn').disabled = !session || session.status === 'running';
|
|
151
233
|
$('#terminalCommandInput').disabled = !canInput;
|
|
152
234
|
const commandForm = $('#terminalCommandForm');
|
|
153
|
-
const commandButton = commandForm.querySelector('button');
|
|
235
|
+
const commandButton = commandForm.querySelector('button[type="submit"]');
|
|
154
236
|
commandButton.disabled = !canInput || state.commandSending;
|
|
237
|
+
commandButton.toggleAttribute('aria-busy', state.commandSending);
|
|
155
238
|
commandForm.toggleAttribute('aria-busy', state.commandSending);
|
|
156
239
|
const commandButtonLabel = commandButton.querySelector('span');
|
|
157
|
-
if (commandButtonLabel) commandButtonLabel.textContent = state.commandSending ? '
|
|
240
|
+
if (commandButtonLabel) commandButtonLabel.textContent = state.commandSending ? t('terminal.sending') : t('common.send');
|
|
158
241
|
document.querySelectorAll('[data-terminal-signal]').forEach(button => { button.disabled = !canInput; });
|
|
159
242
|
$('#terminalAttachBtn').classList.toggle('hidden', !remote || Boolean(session));
|
|
160
243
|
$('#terminalTmuxTools').classList.toggle('hidden', !remote || Boolean(session));
|
|
161
244
|
if (session) {
|
|
162
|
-
|
|
245
|
+
const presentation = terminalPresentation(session);
|
|
246
|
+
setConnectionState(presentation.label, presentation.tone);
|
|
163
247
|
$('#terminalTargetIcon').textContent = terminalTypeMark(session);
|
|
164
|
-
$('#terminalTargetMeta').innerHTML = `<b>${esc(session.title)}</b><span>${bound ? '
|
|
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>`;
|
|
165
249
|
$('#terminalConsoleCaption').textContent = `${terminalTypeLabel(session)} · PID ${session.pid || '--'}`;
|
|
166
|
-
$('#terminalConsoleState').textContent =
|
|
167
|
-
|
|
250
|
+
$('#terminalConsoleState').textContent = presentation.tone === 'attention' || presentation.tone === 'completed'
|
|
251
|
+
? presentation.label
|
|
252
|
+
: canInput ? window.LoadToAgentI18n.t("ui.direct_input_available") : window.LoadToAgentI18n.t("ui.ended_session");
|
|
253
|
+
$('#terminalConsoleState').dataset.status = presentation.tone;
|
|
168
254
|
} else if (remote) {
|
|
169
|
-
setConnectionState(remote.pane.dead ? '
|
|
255
|
+
setConnectionState(remote.pane.dead ? t('terminal.tmux.ended_pane') : t('terminal.tmux.connected'), remote.pane.dead ? 'exited' : 'running');
|
|
170
256
|
$('#terminalTargetIcon').textContent = 'tm';
|
|
171
257
|
$('#terminalTargetMeta').innerHTML = `<b>${esc(remote.distro.name)} · ${esc(remote.session.name)} · ${esc(remote.pane.nativeId)}</b><span>${esc(remote.window.index)}:${esc(remote.window.name)} · ${esc(remote.pane.command || 'shell')} · ${esc(remote.pane.cwd || '')}</span>`;
|
|
172
258
|
$('#terminalConsoleCaption').textContent = `${remote.window.index}:${remote.window.name} · ${remote.pane.command || 'shell'}`;
|
|
@@ -176,7 +262,7 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
176
262
|
setConnectionState(window.LoadToAgentI18n.t("ui.waiting_for_selection"));
|
|
177
263
|
$('#terminalTargetIcon').textContent = '›_';
|
|
178
264
|
$('#terminalTargetMeta').innerHTML = state.mode === 'tmux'
|
|
179
|
-
? '
|
|
265
|
+
? `<b>${t('terminal.tmux.no_selection_title')}</b><span>${t('terminal.tmux.no_selection_description')}</span>`
|
|
180
266
|
: `<b>${window.LoadToAgentI18n.t("ui.select_a_session")}</b><span>${window.LoadToAgentI18n.t("ui.choose_a_session_on_the_left_or_create_a_new")}</span>`;
|
|
181
267
|
$('#terminalConsoleCaption').textContent = window.LoadToAgentI18n.t("ui.select_a_session_to_show_its_output_here");
|
|
182
268
|
$('#terminalConsoleState').textContent = window.LoadToAgentI18n.t("ui.waiting_for_selection");
|
|
@@ -187,25 +273,40 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
187
273
|
if (commandLabel) commandLabel.textContent = bound ? window.LoadToAgentI18n.t("ui.continue_instructing_ai") : (remote ? window.LoadToAgentI18n.t("ui.send_to_tmux_terminal") : window.LoadToAgentI18n.t("ui.send_command_to_terminal"));
|
|
188
274
|
if (commandInput) commandInput.placeholder = !hasTarget
|
|
189
275
|
? window.LoadToAgentI18n.t("ui.select_a_session_on_the_left_first")
|
|
190
|
-
: (bound ? '
|
|
276
|
+
: (bound ? t('terminal.command.continue_ai_placeholder') : t('ui.enter_a_command_to_run'));
|
|
277
|
+
if (commandInput) {
|
|
278
|
+
$('#terminalCommandCount').textContent = `${commandInput.value.length.toLocaleString()} / 8,000`;
|
|
279
|
+
$('#terminalCommandClearBtn')?.classList.toggle('hidden', !commandInput.value);
|
|
280
|
+
}
|
|
191
281
|
renderHistoryPanel();
|
|
192
282
|
}
|
|
193
283
|
|
|
194
284
|
async function showSelection() {
|
|
285
|
+
const generation = state.captureGeneration;
|
|
286
|
+
const expectedMode = state.mode;
|
|
287
|
+
const expectedSessionId = state.selectedId;
|
|
288
|
+
const expectedTmuxId = state.selectedTmux?.pane?.id || state.selectedTmux?.pane?.nativeId || '';
|
|
289
|
+
const selectionIsCurrent = () => generation === state.captureGeneration
|
|
290
|
+
&& expectedMode === state.mode
|
|
291
|
+
&& expectedSessionId === state.selectedId
|
|
292
|
+
&& expectedTmuxId === (state.selectedTmux?.pane?.id || state.selectedTmux?.pane?.nativeId || '');
|
|
195
293
|
hideScreens();
|
|
196
294
|
const session = currentSession();
|
|
197
295
|
const remote = currentTmux();
|
|
198
296
|
if (session) {
|
|
199
297
|
const entry = await ensureSessionTerminal(session);
|
|
298
|
+
if (!selectionIsCurrent()) return;
|
|
200
299
|
entry.host.classList.remove('hidden');
|
|
201
300
|
fitEntry(entry, session.id);
|
|
202
301
|
stopCapture();
|
|
203
302
|
} else if (remote) {
|
|
303
|
+
if (!selectionIsCurrent()) return;
|
|
204
304
|
const entry = ensureRemoteTerminal();
|
|
205
305
|
entry.host.classList.remove('hidden');
|
|
206
306
|
fitEntry(entry);
|
|
207
307
|
startCapture();
|
|
208
308
|
} else {
|
|
309
|
+
if (!selectionIsCurrent()) return;
|
|
209
310
|
$('#terminalEmpty').classList.remove('hidden');
|
|
210
311
|
stopCapture();
|
|
211
312
|
}
|
|
@@ -214,20 +315,22 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
214
315
|
|
|
215
316
|
async function selectSession(id) {
|
|
216
317
|
saveCurrentDraft();
|
|
217
|
-
state.captureGeneration
|
|
318
|
+
const generation = ++state.captureGeneration;
|
|
218
319
|
state.selectedId = id;
|
|
219
320
|
state.selectedTmux = null;
|
|
220
321
|
renderSessions();
|
|
221
322
|
renderTmuxResources();
|
|
222
323
|
await showSelection();
|
|
324
|
+
if (!state.active || state.captureGeneration !== generation || state.selectedId !== id || state.mode !== 'general') return;
|
|
223
325
|
restoreCurrentDraft();
|
|
326
|
+
if (!$('#terminalCommandInput')?.disabled) $('#terminalCommandInput').focus({ preventScroll: true });
|
|
224
327
|
}
|
|
225
328
|
|
|
226
329
|
async function selectTmux(distroName, paneId) {
|
|
227
330
|
const row = tmuxRows().find(item => item.distro.name === distroName && item.pane.nativeId === paneId);
|
|
228
|
-
if (!row) return notice('
|
|
331
|
+
if (!row) return notice(t('terminal.error.selected_split_missing'), 'error');
|
|
229
332
|
saveCurrentDraft();
|
|
230
|
-
state.captureGeneration
|
|
333
|
+
const generation = ++state.captureGeneration;
|
|
231
334
|
state.selectedId = null;
|
|
232
335
|
state.selectedTmux = row;
|
|
233
336
|
state.remoteCapture = '';
|
|
@@ -237,12 +340,15 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
237
340
|
renderSessions();
|
|
238
341
|
renderTmuxResources();
|
|
239
342
|
await showSelection();
|
|
343
|
+
if (!state.active || state.captureGeneration !== generation || state.selectedId || state.mode !== 'tmux'
|
|
344
|
+
|| state.selectedTmux?.distro?.name !== distroName || state.selectedTmux?.pane?.nativeId !== paneId) return;
|
|
240
345
|
restoreCurrentDraft();
|
|
346
|
+
if (!$('#terminalCommandInput')?.disabled) $('#terminalCommandInput').focus({ preventScroll: true });
|
|
241
347
|
}
|
|
242
348
|
|
|
243
349
|
async function selectTmuxById(paneId) {
|
|
244
350
|
const row = tmuxRows().find(item => item.pane.id === paneId || item.pane.nativeId === paneId);
|
|
245
|
-
if (!row) return notice('
|
|
351
|
+
if (!row) return notice(t('terminal.error.selected_tmux_missing'), 'error');
|
|
246
352
|
state.mode = 'tmux';
|
|
247
353
|
moveWorkbench('tmux');
|
|
248
354
|
return selectTmux(row.distro.name, row.pane.nativeId);
|
|
@@ -258,8 +364,9 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
258
364
|
const nextSessions = payload && Array.isArray(payload.sessions) ? payload.sessions : await window.loadtoagent.terminalList();
|
|
259
365
|
state.sessions = Array.isArray(nextSessions) ? nextSessions : [];
|
|
260
366
|
const activeIds = new Set(state.sessions.map(session => session.id));
|
|
367
|
+
const rehydratedIds = new Set(payload?.change === 'reconnected' ? activeIds : []);
|
|
261
368
|
for (const [id, entry] of state.terminals) {
|
|
262
|
-
if (activeIds.has(id)) continue;
|
|
369
|
+
if (activeIds.has(id) && !rehydratedIds.has(id)) continue;
|
|
263
370
|
entry.terminal.dispose();
|
|
264
371
|
entry.host.remove();
|
|
265
372
|
state.terminals.delete(id);
|
|
@@ -272,15 +379,15 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
272
379
|
|
|
273
380
|
async function createTerminal(type) {
|
|
274
381
|
const distro = type === 'wsl' ? firstDistro() : null;
|
|
275
|
-
if (type === 'wsl' && !distro) return notice('
|
|
382
|
+
if (type === 'wsl' && !distro) return notice(t('terminal.error.no_linux_environment'), 'error');
|
|
276
383
|
const created = await guarded(() => window.loadtoagent.terminalCreate({
|
|
277
384
|
type,
|
|
278
385
|
cwd: (type === 'powershell' || type === 'shell') ? (preferredWorkspace() || undefined) : undefined,
|
|
279
386
|
distro: distro && distro.name,
|
|
280
|
-
title: type === 'powershell' ? 'PowerShell' : (type === 'shell' ? state.platform.localShellLabel :
|
|
387
|
+
title: type === 'powershell' ? 'PowerShell' : (type === 'shell' ? state.platform.localShellLabel : t('terminal.linux_shell_title', { distro: distro.name })),
|
|
281
388
|
cols: 120,
|
|
282
389
|
rows: 32,
|
|
283
|
-
}),
|
|
390
|
+
}), t('terminal.opened', { platform: type === 'powershell' ? 'Windows' : (type === 'shell' ? state.platform.label : 'Linux') }), `terminal-create:${type}`);
|
|
284
391
|
if (!created) return;
|
|
285
392
|
await refreshSessions();
|
|
286
393
|
await selectSession(created.id);
|
|
@@ -308,6 +415,7 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
308
415
|
const wasAtBottom = state.remoteViewportAnchor == null
|
|
309
416
|
? Boolean(buffer && buffer.viewportY >= buffer.baseY)
|
|
310
417
|
: state.remoteViewportAtBottom;
|
|
418
|
+
state.remoteCaptureApplying = true;
|
|
311
419
|
entry.terminal.reset();
|
|
312
420
|
await new Promise(resolve => entry.terminal.write(result.output.replace(/\n/g, '\r\n'), resolve));
|
|
313
421
|
const selected = currentTmux();
|
|
@@ -317,19 +425,26 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
317
425
|
setTimeout(captureRemote, 0);
|
|
318
426
|
return;
|
|
319
427
|
}
|
|
320
|
-
requestAnimationFrame(() => {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
entry.terminal.scrollToTop();
|
|
325
|
-
state.remoteViewportAnchor
|
|
326
|
-
state.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
428
|
+
await new Promise(resolve => requestAnimationFrame(() => {
|
|
429
|
+
try {
|
|
430
|
+
const latest = currentTmux();
|
|
431
|
+
if (captureGeneration !== state.captureGeneration || !latest || `${latest.distro.name}:${latest.pane.nativeId}` !== captureKey) return;
|
|
432
|
+
if (firstCapture) entry.terminal.scrollToTop();
|
|
433
|
+
else if (state.remoteViewportAnchor == null ? wasAtBottom : state.remoteViewportAtBottom) entry.terminal.scrollToBottom();
|
|
434
|
+
else entry.terminal.scrollToLine(state.remoteViewportAnchor == null ? previousViewport : state.remoteViewportAnchor);
|
|
435
|
+
const restoredBuffer = entry.terminal.buffer.active;
|
|
436
|
+
state.remoteViewportAnchor = Number(restoredBuffer.viewportY) || 0;
|
|
437
|
+
state.remoteViewportAtBottom = !firstCapture && state.remoteViewportAnchor >= Number(restoredBuffer.baseY || 0);
|
|
438
|
+
state.captureRevision += 1;
|
|
439
|
+
entry.host.dataset.captureRevision = String(state.captureRevision);
|
|
440
|
+
} catch (error) {
|
|
441
|
+
window.LoadToAgentRendererUtils.reportRecoverableError('tmux-capture-render', error);
|
|
442
|
+
} finally {
|
|
443
|
+
resolve();
|
|
444
|
+
}
|
|
445
|
+
}));
|
|
332
446
|
} finally {
|
|
447
|
+
state.remoteCaptureApplying = false;
|
|
333
448
|
state.captureInFlight = false;
|
|
334
449
|
}
|
|
335
450
|
}
|
|
@@ -349,21 +464,21 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
349
464
|
const text = String(command || '');
|
|
350
465
|
if (state.commandSending) return false;
|
|
351
466
|
if (!text.trim()) {
|
|
352
|
-
notice('
|
|
467
|
+
notice(t('terminal.command.required'), 'error');
|
|
353
468
|
return false;
|
|
354
469
|
}
|
|
355
470
|
const session = currentSession();
|
|
356
471
|
const remote = currentTmux();
|
|
357
472
|
if (!session && !remote) {
|
|
358
|
-
notice('
|
|
473
|
+
notice(t('terminal.command.select_first'), 'error');
|
|
359
474
|
return false;
|
|
360
475
|
}
|
|
361
476
|
state.commandSending = true;
|
|
362
477
|
renderTarget();
|
|
363
478
|
try {
|
|
364
479
|
const result = session
|
|
365
|
-
? await guarded(() => window.loadtoagent.terminalCommand(session.id, text), '
|
|
366
|
-
: await guarded(() => window.loadtoagent.tmuxSendText({ distro: remote.distro.name, target: remote.pane.nativeId, text, enter: true }), '
|
|
480
|
+
? await guarded(() => window.loadtoagent.terminalCommand(session.id, text), t('terminal.command.sent'))
|
|
481
|
+
: await guarded(() => window.loadtoagent.tmuxSendText({ distro: remote.distro.name, target: remote.pane.nativeId, text, enter: true }), t('terminal.command.executed_in_split'));
|
|
367
482
|
if (result && remote) setTimeout(captureRemote, 160);
|
|
368
483
|
return Boolean(result);
|
|
369
484
|
} finally {
|
|
@@ -375,12 +490,12 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
375
490
|
async function sendSignal(signal) {
|
|
376
491
|
const session = currentSession();
|
|
377
492
|
const remote = currentTmux();
|
|
378
|
-
if (session) return guarded(() => window.loadtoagent.terminalSignal(session.id, signal), signal === 'interrupt' ? '
|
|
493
|
+
if (session) return guarded(() => window.loadtoagent.terminalSignal(session.id, signal), signal === 'interrupt' ? t('terminal.signal.interrupt_sent') : t('terminal.signal.cleared'));
|
|
379
494
|
if (remote) {
|
|
380
495
|
const key = signal === 'interrupt' ? 'C-c' : 'C-l';
|
|
381
|
-
return guarded(() => window.loadtoagent.tmuxSendKey({ distro: remote.distro.name, target: remote.pane.nativeId, key }),
|
|
496
|
+
return guarded(() => window.loadtoagent.tmuxSendKey({ distro: remote.distro.name, target: remote.pane.nativeId, key }), t('terminal.signal.key_sent', { key }));
|
|
382
497
|
}
|
|
383
|
-
notice('
|
|
498
|
+
notice(t('terminal.command.select_first'), 'error');
|
|
384
499
|
}
|
|
385
500
|
|
|
386
501
|
function openTmuxModal() {
|
|
@@ -388,18 +503,22 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
388
503
|
const distros = state.snapshot && state.snapshot.tmux && state.snapshot.tmux.distros || [];
|
|
389
504
|
$('#tmuxCreateDistro').innerHTML = distros.map(item => `<option value="${esc(item.name)}">${esc(item.name)}</option>`).join('');
|
|
390
505
|
$('#tmuxCreateError').classList.add('hidden');
|
|
506
|
+
window.LoadToAgentA11y?.setDialogOpenState($('#tmuxCreateModal'), true);
|
|
391
507
|
$('#tmuxCreateModal').classList.remove('hidden');
|
|
392
508
|
$('#tmuxCreateName').focus();
|
|
393
509
|
}
|
|
394
510
|
|
|
395
|
-
function closeTmuxModal() {
|
|
511
|
+
function closeTmuxModal(force = false) {
|
|
512
|
+
if (force !== true && $('#tmuxCreateForm [type="submit"]').dataset.busy === 'true') return;
|
|
396
513
|
$('#tmuxCreateModal').classList.add('hidden');
|
|
514
|
+
window.LoadToAgentA11y?.setDialogOpenState($('#tmuxCreateModal'), false);
|
|
397
515
|
$('#tmuxCreateForm').reset();
|
|
516
|
+
$('#tmuxCreateForm').querySelectorAll('[aria-invalid="true"]').forEach(element => element.removeAttribute('aria-invalid'));
|
|
398
517
|
window.LoadToAgentA11y?.restoreDialogTrigger();
|
|
399
518
|
}
|
|
400
519
|
|
|
401
520
|
async function refreshSnapshot() {
|
|
402
|
-
const snapshot = await guarded(() => window.loadtoagent.snapshot(), '
|
|
521
|
+
const snapshot = await guarded(() => window.loadtoagent.snapshot(), t('terminal.tmux.refreshed'), 'tmux-refresh');
|
|
403
522
|
if (snapshot) updateSnapshot(snapshot, state.workspaces);
|
|
404
523
|
}
|
|
405
524
|
|
|
@@ -414,7 +533,7 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
414
533
|
title: `tmux · ${remote.session.name} · ${remote.pane.nativeId}`,
|
|
415
534
|
cols: 120,
|
|
416
535
|
rows: 32,
|
|
417
|
-
}), '
|
|
536
|
+
}), t('terminal.tmux.attached_for_input'), `tmux-attach:${remote.distro.name}:${remote.pane.nativeId}`);
|
|
418
537
|
if (!created) return;
|
|
419
538
|
await refreshSessions();
|
|
420
539
|
await selectSession(created.id);
|
|
@@ -427,33 +546,33 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
427
546
|
let operation = null;
|
|
428
547
|
let message = '';
|
|
429
548
|
if (action === 'rename-session') {
|
|
430
|
-
const name = window.prompt('
|
|
549
|
+
const name = window.prompt(t('terminal.tmux.prompt_workspace_name'), remote.session.name);
|
|
431
550
|
if (!name || name === remote.session.name) return;
|
|
432
551
|
operation = () => window.loadtoagent.tmuxRenameSession({ ...base, target: remote.session.nativeId, name });
|
|
433
|
-
message = '
|
|
552
|
+
message = t('terminal.tmux.workspace_renamed');
|
|
434
553
|
} else if (action === 'new-window') {
|
|
435
|
-
const name = window.prompt('
|
|
554
|
+
const name = window.prompt(t('terminal.tmux.prompt_window_name'), 'window');
|
|
436
555
|
if (!name) return;
|
|
437
556
|
operation = () => window.loadtoagent.tmuxNewWindow({ ...base, target: remote.session.nativeId, name, cwd: remote.pane.cwd });
|
|
438
|
-
message = '
|
|
557
|
+
message = t('terminal.tmux.window_created');
|
|
439
558
|
} else if (action === 'split-horizontal' || action === 'split-vertical') {
|
|
440
559
|
operation = () => window.loadtoagent.tmuxSplitPane({ ...base, target: remote.pane.nativeId, direction: action === 'split-horizontal' ? 'horizontal' : 'vertical', cwd: remote.pane.cwd });
|
|
441
|
-
message = '
|
|
560
|
+
message = t('terminal.tmux.pane_split');
|
|
442
561
|
} else if (action === 'kill-pane') {
|
|
443
|
-
if (!window.confirm(
|
|
562
|
+
if (!window.confirm(t('terminal.tmux.confirm_close_pane', { pane: remote.pane.nativeId }))) return;
|
|
444
563
|
operation = () => window.loadtoagent.tmuxKillPane({ ...base, target: remote.pane.nativeId });
|
|
445
|
-
message = '
|
|
564
|
+
message = t('terminal.tmux.pane_closed');
|
|
446
565
|
} else if (action === 'kill-window') {
|
|
447
|
-
if (!window.confirm(`${remote.window.index}:${remote.window.name}
|
|
566
|
+
if (!window.confirm(t('terminal.tmux.confirm_close_window', { window: `${remote.window.index}:${remote.window.name}` }))) return;
|
|
448
567
|
operation = () => window.loadtoagent.tmuxKillWindow({ ...base, target: remote.window.nativeId });
|
|
449
|
-
message = '
|
|
568
|
+
message = t('terminal.tmux.window_closed');
|
|
450
569
|
} else if (action === 'kill-session') {
|
|
451
|
-
if (!window.confirm(
|
|
570
|
+
if (!window.confirm(t('terminal.tmux.confirm_end_workspace', { workspace: remote.session.name }))) return;
|
|
452
571
|
operation = () => window.loadtoagent.tmuxKillSession({ ...base, target: remote.session.nativeId });
|
|
453
|
-
message = '
|
|
572
|
+
message = t('terminal.tmux.workspace_ended');
|
|
454
573
|
}
|
|
455
574
|
if (!operation) return;
|
|
456
|
-
const result = await guarded(operation, message);
|
|
575
|
+
const result = await guarded(operation, message, `tmux-manage:${action}`);
|
|
457
576
|
if (result) {
|
|
458
577
|
if (action.startsWith('kill-')) {
|
|
459
578
|
stopCapture();
|
|
@@ -470,5 +589,5 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
|
|
|
470
589
|
}
|
|
471
590
|
}
|
|
472
591
|
|
|
473
|
-
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 };
|
|
474
593
|
};
|