loadtoagent 1.0.0 → 1.3.1
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 +7 -0
- package/README.md +7 -0
- package/README.zh-CN.md +7 -0
- package/docs/PROVIDER-CONTRACTS.md +23 -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 +183 -37
- package/package.json +15 -4
- package/preload.js +11 -0
- package/renderer/app-agent-actions.js +125 -53
- package/renderer/app-bootstrap.js +54 -9
- package/renderer/app-dashboard.js +197 -58
- package/renderer/app-drawer-content.js +103 -86
- package/renderer/app-drawer-data.js +26 -13
- package/renderer/app-drawer.js +79 -55
- package/renderer/app-events-dialogs.js +146 -37
- package/renderer/app-events-filters.js +172 -9
- package/renderer/app-events-navigation.js +78 -31
- package/renderer/app-events-sessions.js +171 -4
- package/renderer/app-events.js +2 -1
- package/renderer/app-graph-model.js +32 -4
- package/renderer/app-graph-orchestration.js +16 -13
- package/renderer/app-graph-view.js +228 -112
- package/renderer/app-management.js +211 -0
- package/renderer/app-provider-visibility.js +124 -0
- package/renderer/app-quality.js +568 -0
- package/renderer/app-run-modal.js +103 -33
- package/renderer/app-runtime-overview.js +312 -0
- package/renderer/app-session-render.js +94 -37
- package/renderer/app-tmux-render.js +119 -38
- package/renderer/app.js +155 -76
- package/renderer/i18n-messages.js +833 -15
- package/renderer/i18n.js +104 -0
- package/renderer/index.html +162 -75
- package/renderer/shared.js +17 -0
- package/renderer/styles-agent-map.css +50 -1
- package/renderer/styles-cards.css +26 -0
- package/renderer/styles-collaboration.css +18 -88
- package/renderer/styles-components.css +126 -14
- package/renderer/styles-management.css +355 -0
- package/renderer/styles-overlays.css +13 -2
- package/renderer/styles-product.css +24 -16
- package/renderer/styles-quality.css +312 -0
- package/renderer/styles-readability.css +862 -0
- package/renderer/styles-responsive-product.css +84 -12
- package/renderer/styles-responsive-runtime.css +31 -14
- package/renderer/styles-responsive-shell.css +44 -48
- package/renderer/styles-responsive-workflows.css +37 -17
- package/renderer/styles-run-composer.css +5 -0
- package/renderer/styles-runtime-overview.css +285 -0
- package/renderer/styles-settings.css +160 -0
- package/renderer/styles-terminal.css +339 -2
- package/renderer/styles-tmux.css +154 -0
- package/renderer/styles-workflow-map.css +362 -15
- package/renderer/styles-workflows.css +69 -2
- package/renderer/styles.css +27 -12
- package/renderer/terminal-agent.js +17 -13
- package/renderer/terminal-events.js +233 -40
- package/renderer/terminal-workbench.js +176 -78
- package/renderer/terminal.js +350 -37
- 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 +71 -0
- 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 -6
- package/src/macUpdateHelper.js +210 -0
- package/src/monitorWorker.js +65 -6
- package/src/platformPath.js +80 -0
- package/src/processMonitor.js +81 -23
- package/src/providerVisibilityStore.js +45 -0
- package/src/sessionIntelligence.js +247 -0
- package/src/terminalHost.js +381 -0
- package/src/terminalHostDaemon.js +60 -0
- package/src/terminalManager.js +158 -3
- package/src/tmuxMonitor.js +2 -2
- package/src/updateInstaller.js +175 -0
package/main.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { app, BrowserWindow, ipcMain, dialog, shell, clipboard, Tray, Menu, net } = require('electron');
|
|
3
|
+
const { app, BrowserWindow, ipcMain, dialog, shell, clipboard, Tray, Menu, net, Notification } = require('electron');
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const os = require('os');
|
|
@@ -10,10 +10,11 @@ const { execFileSync } = require('child_process');
|
|
|
10
10
|
const { AgentRunner, probeProviders } = require('./src/agentRunner');
|
|
11
11
|
const { providerList, blankUsage } = require('./src/providerRegistry');
|
|
12
12
|
const { TerminalManager } = require('./src/terminalManager');
|
|
13
|
+
const { TerminalHostClient, launchTerminalHost } = require('./src/terminalHost');
|
|
13
14
|
const { TmuxController } = require('./src/tmuxController');
|
|
14
15
|
const { normalizeWslList } = require('./src/tmuxMonitor');
|
|
15
|
-
const { BridgeServer } = require('./src/bridgeServer');
|
|
16
16
|
const { UpdateManager } = require('./src/updateManager');
|
|
17
|
+
const { launchDownloadedUpdate } = require('./src/updateInstaller');
|
|
17
18
|
const { readWorkspaces, removeWorkspace, writeWorkspaces } = require('./src/workspaceStore');
|
|
18
19
|
const { registerAppIpc } = require('./src/ipc/registerAppIpc');
|
|
19
20
|
const { registerAgentIpc } = require('./src/ipc/registerAgentIpc');
|
|
@@ -21,51 +22,69 @@ const { registerTerminalIpc } = require('./src/ipc/registerTerminalIpc');
|
|
|
21
22
|
const { registerTmuxIpc } = require('./src/ipc/registerTmuxIpc');
|
|
22
23
|
const { registerWorkspaceIpc } = require('./src/ipc/registerWorkspaceIpc');
|
|
23
24
|
const { reportRecoverableError } = require('./src/diagnostics');
|
|
25
|
+
const { AttentionNotifier } = require('./src/attentionNotifier');
|
|
26
|
+
const { ProviderVisibilityStore } = require('./src/providerVisibilityStore');
|
|
27
|
+
const { macPathEntries } = require('./src/platformPath');
|
|
28
|
+
|
|
29
|
+
const PRODUCT_NAME = 'LoadToAgent';
|
|
30
|
+
app.setName(PRODUCT_NAME);
|
|
31
|
+
process.title = PRODUCT_NAME;
|
|
32
|
+
if (process.platform === 'win32') app.setAppUserModelId('com.wincube.loadtoagent');
|
|
24
33
|
|
|
25
34
|
const demoCapture = process.env.LOADTOAGENT_DEMO_CAPTURE === '1';
|
|
26
35
|
let mainWindow = null;
|
|
27
36
|
let monitorWorker = null;
|
|
28
37
|
let runner = null;
|
|
29
38
|
let terminalManager = null;
|
|
30
|
-
let bridgeServer = null;
|
|
31
39
|
let bridgeLauncher = null;
|
|
32
40
|
let backgroundTray = null;
|
|
33
41
|
let updateManager = null;
|
|
42
|
+
let attentionNotifier = null;
|
|
34
43
|
let isQuitting = false;
|
|
35
44
|
let appLocale = 'ko';
|
|
45
|
+
let providerVisibilityStore = null;
|
|
46
|
+
let pendingAttentionSessionId = '';
|
|
47
|
+
let rendererBootstrapped = false;
|
|
36
48
|
const tmuxController = new TmuxController({ platform: process.platform });
|
|
37
49
|
let availability = {};
|
|
38
50
|
let detailRequestId = 0;
|
|
39
51
|
const pendingDetails = new Map();
|
|
40
52
|
const MAIN_COPY = {
|
|
41
53
|
ko: {
|
|
42
|
-
trayTooltip: 'LoadToAgent · 백그라운드
|
|
54
|
+
trayTooltip: 'LoadToAgent · 백그라운드 터미널 {count}개',
|
|
43
55
|
trayOpen: 'LoadToAgent 열기',
|
|
44
|
-
traySessions: '백그라운드
|
|
45
|
-
trayQuit: '프로그램 끝내기 ·
|
|
56
|
+
traySessions: '백그라운드 터미널 {count}개 유지 중',
|
|
57
|
+
trayQuit: '프로그램 끝내기 · 터미널 세션 유지',
|
|
46
58
|
addWorkspaces: 'AI 작업 폴더 선택',
|
|
47
59
|
pickWorkspace: '작업 폴더 선택',
|
|
60
|
+
attentionTitle: '내 확인이 필요합니다',
|
|
61
|
+
attentionBody: '{provider} · {title}',
|
|
48
62
|
},
|
|
49
63
|
en: {
|
|
50
|
-
trayTooltip: 'LoadToAgent · {count} background
|
|
64
|
+
trayTooltip: 'LoadToAgent · {count} background terminals',
|
|
51
65
|
trayOpen: 'Open LoadToAgent',
|
|
52
|
-
traySessions: '{count} background
|
|
53
|
-
trayQuit: 'Quit app ·
|
|
66
|
+
traySessions: '{count} background terminals active',
|
|
67
|
+
trayQuit: 'Quit app · Keep terminal sessions',
|
|
54
68
|
addWorkspaces: 'Choose AI workspaces',
|
|
55
69
|
pickWorkspace: 'Choose workspace',
|
|
70
|
+
attentionTitle: 'Your review is needed',
|
|
71
|
+
attentionBody: '{provider} · {title}',
|
|
56
72
|
},
|
|
57
73
|
'zh-CN': {
|
|
58
|
-
trayTooltip: 'LoadToAgent · {count}
|
|
74
|
+
trayTooltip: 'LoadToAgent · {count} 个后台终端',
|
|
59
75
|
trayOpen: '打开 LoadToAgent',
|
|
60
|
-
traySessions: '正在保持 {count}
|
|
61
|
-
trayQuit: '退出应用 ·
|
|
76
|
+
traySessions: '正在保持 {count} 个后台终端',
|
|
77
|
+
trayQuit: '退出应用 · 保留终端会话',
|
|
62
78
|
addWorkspaces: '选择 AI 工作文件夹',
|
|
63
79
|
pickWorkspace: '选择工作文件夹',
|
|
80
|
+
attentionTitle: '需要你的确认',
|
|
81
|
+
attentionBody: '{provider} · {title}',
|
|
64
82
|
},
|
|
65
83
|
};
|
|
66
84
|
let lastSnapshot = {
|
|
67
85
|
generatedAt: new Date().toISOString(),
|
|
68
86
|
sessions: [],
|
|
87
|
+
automations: [],
|
|
69
88
|
tmux: { generatedAt: new Date().toISOString(), available: false, status: '확인 중', distros: [], summary: { distros: 0, sessions: 0, windows: 0, panes: 0, aiPanes: 0, linked: 0 } },
|
|
70
89
|
summary: {
|
|
71
90
|
providers: providerList().map(provider => ({ ...provider, installed: false, sessions: 0, active: 0, waiting: 0, subagents: 0, usage: blankUsage() })),
|
|
@@ -120,6 +139,30 @@ function listWorkspaces() {
|
|
|
120
139
|
return readWorkspaces(userFile('workspaces.json'));
|
|
121
140
|
}
|
|
122
141
|
|
|
142
|
+
function isProviderVisible(providerId) {
|
|
143
|
+
return providerVisibilityStore ? providerVisibilityStore.isVisible(providerId) : true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function loadProviderVisibility() {
|
|
147
|
+
providerVisibilityStore = new ProviderVisibilityStore(
|
|
148
|
+
userFile('provider-visibility.json'),
|
|
149
|
+
providerList().map(provider => provider.id),
|
|
150
|
+
error => reportRecoverableError('provider-visibility-load', error),
|
|
151
|
+
);
|
|
152
|
+
return providerVisibilityStore.load();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function saveProviderVisibility(value = {}) {
|
|
156
|
+
if (!providerVisibilityStore) loadProviderVisibility();
|
|
157
|
+
const saved = providerVisibilityStore.save(value);
|
|
158
|
+
updateBackgroundTrayMenu();
|
|
159
|
+
return saved;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function visibleSnapshotSessions(snapshot = lastSnapshot) {
|
|
163
|
+
return { ...snapshot, sessions: (snapshot.sessions || []).filter(session => isProviderVisible(session.provider)) };
|
|
164
|
+
}
|
|
165
|
+
|
|
123
166
|
function saveWorkspaces(items) {
|
|
124
167
|
return writeWorkspaces(userFile('workspaces.json'), items);
|
|
125
168
|
}
|
|
@@ -141,18 +184,11 @@ function listWslDistros() {
|
|
|
141
184
|
|
|
142
185
|
function hydratePlatformPath() {
|
|
143
186
|
if (process.platform !== 'darwin') return;
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
const shellPath = process.env.SHELL || '/bin/zsh';
|
|
147
|
-
const loginPath = execFileSync(shellPath, ['-lic', 'printf %s "$PATH"'], { encoding: 'utf8', timeout: 5_000 }).trim();
|
|
148
|
-
additions.unshift(...loginPath.split(path.delimiter));
|
|
149
|
-
} catch (error) {
|
|
150
|
-
reportRecoverableError('platform-path', error);
|
|
151
|
-
}
|
|
152
|
-
process.env.PATH = [...new Set([...additions, ...String(process.env.PATH || '').split(path.delimiter)].filter(Boolean))].join(path.delimiter);
|
|
187
|
+
process.env.PATH = macPathEntries(os.homedir(), process.env.PATH).join(path.delimiter);
|
|
153
188
|
}
|
|
154
189
|
|
|
155
190
|
function createWindow() {
|
|
191
|
+
rendererBootstrapped = false;
|
|
156
192
|
mainWindow = new BrowserWindow({
|
|
157
193
|
width: 1600,
|
|
158
194
|
height: 980,
|
|
@@ -169,13 +205,14 @@ function createWindow() {
|
|
|
169
205
|
},
|
|
170
206
|
});
|
|
171
207
|
mainWindow.setMenuBarVisibility(false);
|
|
208
|
+
mainWindow.webContents.on('did-start-loading', () => { rendererBootstrapped = false; });
|
|
172
209
|
mainWindow.loadFile(path.join(__dirname, 'renderer', 'index.html'));
|
|
173
210
|
mainWindow.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));
|
|
174
211
|
const allowedUrl = pathToFileURL(path.join(__dirname, 'renderer', 'index.html')).href;
|
|
175
212
|
mainWindow.webContents.on('will-navigate', (event, url) => { if (url !== allowedUrl) event.preventDefault(); });
|
|
176
213
|
mainWindow.once('ready-to-show', () => mainWindow && mainWindow.show());
|
|
177
214
|
mainWindow.on('close', event => {
|
|
178
|
-
if (isQuitting || !
|
|
215
|
+
if (isQuitting || !backgroundTerminalSessions().length) return;
|
|
179
216
|
event.preventDefault();
|
|
180
217
|
mainWindow.hide();
|
|
181
218
|
ensureBackgroundTray();
|
|
@@ -183,9 +220,13 @@ function createWindow() {
|
|
|
183
220
|
mainWindow.on('closed', () => { mainWindow = null; });
|
|
184
221
|
}
|
|
185
222
|
|
|
186
|
-
function
|
|
223
|
+
function backgroundTerminalSessions() {
|
|
187
224
|
if (!terminalManager) return [];
|
|
188
|
-
return terminalManager.list().filter(session => session.
|
|
225
|
+
return terminalManager.list().filter(session => session.status === 'running' || session.status === 'starting');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function visibleTerminalSessions(sessions) {
|
|
229
|
+
return (sessions || []).filter(session => session.type !== 'agent' || isProviderVisible(session.provider));
|
|
189
230
|
}
|
|
190
231
|
|
|
191
232
|
function showMainWindow() {
|
|
@@ -197,7 +238,7 @@ function showMainWindow() {
|
|
|
197
238
|
|
|
198
239
|
function updateBackgroundTrayMenu() {
|
|
199
240
|
if (!backgroundTray) return;
|
|
200
|
-
const count =
|
|
241
|
+
const count = backgroundTerminalSessions().length;
|
|
201
242
|
backgroundTray.setToolTip(mainText('trayTooltip', { count }));
|
|
202
243
|
backgroundTray.setContextMenu(Menu.buildFromTemplate([
|
|
203
244
|
{ label: mainText('trayOpen'), click: showMainWindow },
|
|
@@ -254,6 +295,56 @@ function sendSnapshot(snapshot) {
|
|
|
254
295
|
try { mainWindow.webContents.send('agents:snapshot', snapshot); } catch (error) { reportRecoverableError('ipc-send:agents:snapshot', error); }
|
|
255
296
|
}
|
|
256
297
|
|
|
298
|
+
function openAttentionSession(session) {
|
|
299
|
+
if (!isProviderVisible(session && session.provider)) return;
|
|
300
|
+
pendingAttentionSessionId = String(session && session.id || '');
|
|
301
|
+
showMainWindow();
|
|
302
|
+
if (!mainWindow || mainWindow.isDestroyed()) return;
|
|
303
|
+
mainWindow.flashFrame(false);
|
|
304
|
+
if (!rendererBootstrapped || mainWindow.webContents.isLoadingMainFrame()) return;
|
|
305
|
+
try {
|
|
306
|
+
mainWindow.webContents.send('agents:attention-requested', { sessionId: pendingAttentionSessionId });
|
|
307
|
+
pendingAttentionSessionId = '';
|
|
308
|
+
} catch (error) {
|
|
309
|
+
reportRecoverableError('ipc-send:agents:attention-requested', error);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function markRendererReady() {
|
|
314
|
+
rendererBootstrapped = true;
|
|
315
|
+
if (!pendingAttentionSessionId || !mainWindow || mainWindow.isDestroyed()) return { ok: true };
|
|
316
|
+
const sessionId = pendingAttentionSessionId;
|
|
317
|
+
try {
|
|
318
|
+
mainWindow.webContents.send('agents:attention-requested', { sessionId });
|
|
319
|
+
pendingAttentionSessionId = '';
|
|
320
|
+
} catch (error) {
|
|
321
|
+
reportRecoverableError('ipc-send:agents:attention-requested', error);
|
|
322
|
+
}
|
|
323
|
+
return { ok: true };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function createAttentionNotifier() {
|
|
327
|
+
return new AttentionNotifier({
|
|
328
|
+
Notification,
|
|
329
|
+
isSupported: () => Notification.isSupported(),
|
|
330
|
+
copy: session => {
|
|
331
|
+
const provider = providerList().find(item => item.id === session.provider);
|
|
332
|
+
return {
|
|
333
|
+
title: mainText('attentionTitle'),
|
|
334
|
+
body: mainText('attentionBody', {
|
|
335
|
+
provider: provider && provider.label || session.provider || 'AI',
|
|
336
|
+
title: session.title || '이름 없는 세션',
|
|
337
|
+
}),
|
|
338
|
+
};
|
|
339
|
+
},
|
|
340
|
+
onOpen: openAttentionSession,
|
|
341
|
+
onFallback: session => {
|
|
342
|
+
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.flashFrame(true);
|
|
343
|
+
openAttentionSession(session);
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
257
348
|
function sendUpdateState(update) {
|
|
258
349
|
if (!mainWindow || mainWindow.isDestroyed()) return;
|
|
259
350
|
try { mainWindow.webContents.send('app:update-state', update); } catch (error) { reportRecoverableError('ipc-send:app:update-state', error); }
|
|
@@ -264,10 +355,46 @@ function installationType() {
|
|
|
264
355
|
return fs.existsSync(path.join(__dirname, '.git')) ? 'source' : 'npm';
|
|
265
356
|
}
|
|
266
357
|
|
|
267
|
-
function
|
|
358
|
+
async function installDownloadedUpdate() {
|
|
359
|
+
if (!updateManager) throw new Error('업데이트 관리자가 준비되지 않았습니다.');
|
|
360
|
+
const downloaded = await updateManager.download();
|
|
361
|
+
const outcome = await launchDownloadedUpdate({
|
|
362
|
+
platform: process.platform,
|
|
363
|
+
installType: process.env.PORTABLE_EXECUTABLE_FILE ? 'portable' : installationType(),
|
|
364
|
+
installerPath: downloaded.downloadedPath,
|
|
365
|
+
downloadsDir: path.join(app.getPath('userData'), 'updates'),
|
|
366
|
+
appPath: process.execPath,
|
|
367
|
+
parentPid: process.pid,
|
|
368
|
+
shell,
|
|
369
|
+
});
|
|
370
|
+
if (outcome.mode === 'automatic') {
|
|
371
|
+
isQuitting = true;
|
|
372
|
+
setImmediate(() => app.quit());
|
|
373
|
+
}
|
|
374
|
+
return { ...updateManager.getState(), installMode: outcome.mode };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async function setupRuntime() {
|
|
378
|
+
loadProviderVisibility();
|
|
268
379
|
const runsDir = userFile('agent-runs');
|
|
269
380
|
runner = new AgentRunner({ runsDir });
|
|
270
|
-
|
|
381
|
+
const terminalStoreFile = userFile('terminal-sessions.json');
|
|
382
|
+
const terminalHostFile = userFile('terminal-host.json');
|
|
383
|
+
terminalManager = demoCapture
|
|
384
|
+
? new TerminalManager({
|
|
385
|
+
storeFile: terminalStoreFile,
|
|
386
|
+
onPersistenceError: (operation, error) => reportRecoverableError(`terminal-sessions:${operation}`, error),
|
|
387
|
+
})
|
|
388
|
+
: new TerminalHostClient({
|
|
389
|
+
discoveryFile: terminalHostFile,
|
|
390
|
+
spawnHost: () => launchTerminalHost({
|
|
391
|
+
executable: process.execPath,
|
|
392
|
+
script: path.join(__dirname, 'src', 'terminalHostDaemon.js'),
|
|
393
|
+
storeFile: terminalStoreFile,
|
|
394
|
+
discoveryFile: terminalHostFile,
|
|
395
|
+
bridgeHome,
|
|
396
|
+
}),
|
|
397
|
+
});
|
|
271
398
|
updateManager = new UpdateManager({
|
|
272
399
|
currentVersion: app.getVersion(),
|
|
273
400
|
platform: process.platform,
|
|
@@ -278,6 +405,7 @@ function setupRuntime() {
|
|
|
278
405
|
downloadsDir: path.join(app.getPath('userData'), 'updates'),
|
|
279
406
|
});
|
|
280
407
|
updateManager.on('state', sendUpdateState);
|
|
408
|
+
attentionNotifier = createAttentionNotifier();
|
|
281
409
|
updateManager.check().catch(error => reportRecoverableError('startup-update-check', error));
|
|
282
410
|
if (demoCapture) {
|
|
283
411
|
availability = Object.fromEntries(providerList().map(provider => [provider.id, true]));
|
|
@@ -291,12 +419,16 @@ function setupRuntime() {
|
|
|
291
419
|
}
|
|
292
420
|
terminalManager.on('data', payload => sendTerminal('terminals:data', payload));
|
|
293
421
|
terminalManager.on('state', payload => {
|
|
294
|
-
|
|
422
|
+
if (!payload.session || payload.session.type !== 'agent' || isProviderVisible(payload.session.provider)) {
|
|
423
|
+
sendTerminal('terminals:state', { ...payload, sessions: visibleTerminalSessions(payload.sessions) });
|
|
424
|
+
}
|
|
295
425
|
updateBackgroundTrayMenu();
|
|
296
426
|
if (monitorWorker) monitorWorker.postMessage({ type: 'bridge-presence', bridges: bridgePresence() });
|
|
297
427
|
});
|
|
298
|
-
|
|
299
|
-
|
|
428
|
+
terminalManager.on('disconnect', () => {
|
|
429
|
+
sendTerminal('terminals:error', { id: '', message: '터미널 호스트 연결이 끊어졌습니다. 프로그램을 다시 시작하면 실행 중인 세션에 다시 연결합니다.' });
|
|
430
|
+
});
|
|
431
|
+
await terminalManager.connect();
|
|
300
432
|
availability = probeProviders();
|
|
301
433
|
monitorWorker = new Worker(path.join(__dirname, 'src', 'monitorWorker.js'), {
|
|
302
434
|
workerData: { runsDir, home: os.homedir(), intervalMs: 1200, availability },
|
|
@@ -305,6 +437,7 @@ function setupRuntime() {
|
|
|
305
437
|
monitorWorker.on('message', message => {
|
|
306
438
|
if (message && message.type === 'snapshot') {
|
|
307
439
|
lastSnapshot = message.snapshot;
|
|
440
|
+
attentionNotifier.sync(visibleSnapshotSessions(lastSnapshot));
|
|
308
441
|
sendSnapshot(lastSnapshot);
|
|
309
442
|
}
|
|
310
443
|
if (message && message.type === 'detail-result') {
|
|
@@ -325,10 +458,11 @@ function bridgePresence() {
|
|
|
325
458
|
if (!terminalManager) return [];
|
|
326
459
|
const environment = process.platform === 'win32' ? 'windows' : (process.platform === 'darwin' ? 'macos' : 'linux');
|
|
327
460
|
return terminalManager.list()
|
|
328
|
-
.filter(session => session.type === 'agent' && session.status === 'running')
|
|
461
|
+
.filter(session => session.type === 'agent' && (session.status === 'running' || session.status === 'starting'))
|
|
329
462
|
.map(session => ({
|
|
330
463
|
id: session.bridgeId || session.id,
|
|
331
464
|
bridgeId: session.bridgeId || '',
|
|
465
|
+
linkedSessionId: session.bridgeId || '',
|
|
332
466
|
terminalId: session.id,
|
|
333
467
|
provider: session.provider,
|
|
334
468
|
pid: session.pid,
|
|
@@ -353,17 +487,20 @@ function bootstrapState() {
|
|
|
353
487
|
id: process.platform,
|
|
354
488
|
label: process.platform === 'darwin' ? 'macOS' : (process.platform === 'win32' ? 'Windows' : 'Linux'),
|
|
355
489
|
localShell: process.platform === 'win32' ? 'powershell' : 'shell',
|
|
356
|
-
localShellLabel: process.platform === 'darwin' ? '
|
|
490
|
+
localShellLabel: process.platform === 'darwin' ? 'macOS shell' : (process.platform === 'win32' ? 'Windows PowerShell' : 'Linux shell'),
|
|
357
491
|
nativeTmux: process.platform !== 'win32',
|
|
358
492
|
},
|
|
359
493
|
bridgeCli: bridgeLauncher,
|
|
360
494
|
update: updateManager ? updateManager.getState() : null,
|
|
495
|
+
providerVisibility: providerVisibilityStore ? providerVisibilityStore.snapshot() : { hidden: [] },
|
|
361
496
|
};
|
|
362
497
|
}
|
|
363
498
|
|
|
364
499
|
function requestAgentDetail(sessionId) {
|
|
365
500
|
return new Promise(resolve => {
|
|
366
501
|
if (!monitorWorker || String(sessionId || '').length > 500) return resolve(null);
|
|
502
|
+
const card = (lastSnapshot.sessions || []).find(session => session.id === String(sessionId || ''));
|
|
503
|
+
if (card && !isProviderVisible(card.provider)) return resolve(null);
|
|
367
504
|
const requestId = ++detailRequestId;
|
|
368
505
|
const timer = setTimeout(() => {
|
|
369
506
|
if (!pendingDetails.has(requestId)) return;
|
|
@@ -384,9 +521,10 @@ function registerIpcHandlers() {
|
|
|
384
521
|
registerAppIpc({
|
|
385
522
|
handleTrusted,
|
|
386
523
|
bootstrap: bootstrapState,
|
|
524
|
+
rendererReady: markRendererReady,
|
|
387
525
|
backgroundState: () => ({
|
|
388
526
|
visible: Boolean(mainWindow && !mainWindow.isDestroyed() && mainWindow.isVisible()),
|
|
389
|
-
backgroundSessions:
|
|
527
|
+
backgroundSessions: backgroundTerminalSessions().length,
|
|
390
528
|
trayReady: Boolean(backgroundTray),
|
|
391
529
|
}),
|
|
392
530
|
show: () => { showMainWindow(); return { ok: true }; },
|
|
@@ -395,13 +533,16 @@ function registerIpcHandlers() {
|
|
|
395
533
|
updateBackgroundTrayMenu();
|
|
396
534
|
return { locale: appLocale };
|
|
397
535
|
},
|
|
536
|
+
setProviderVisibility: saveProviderVisibility,
|
|
398
537
|
updateManager: () => updateManager,
|
|
538
|
+
installUpdate: installDownloadedUpdate,
|
|
399
539
|
});
|
|
400
540
|
registerAgentIpc({
|
|
401
541
|
handleTrusted,
|
|
402
542
|
snapshot: () => { refreshMonitor(); return lastSnapshot; },
|
|
403
543
|
requestDetail: requestAgentDetail,
|
|
404
544
|
runner: () => runner,
|
|
545
|
+
isProviderVisible,
|
|
405
546
|
probeProviders: () => {
|
|
406
547
|
availability = probeProviders();
|
|
407
548
|
if (monitorWorker) monitorWorker.postMessage({ type: 'availability', availability });
|
|
@@ -414,6 +555,7 @@ function registerIpcHandlers() {
|
|
|
414
555
|
requireTrustedSender,
|
|
415
556
|
trustedSender,
|
|
416
557
|
manager: () => terminalManager,
|
|
558
|
+
isProviderVisible,
|
|
417
559
|
listWslDistros,
|
|
418
560
|
sendError: payload => sendTerminal('terminals:error', payload),
|
|
419
561
|
});
|
|
@@ -466,11 +608,14 @@ function registerIpcHandlers() {
|
|
|
466
608
|
|
|
467
609
|
registerIpcHandlers();
|
|
468
610
|
|
|
469
|
-
app.whenReady().then(() => {
|
|
611
|
+
app.whenReady().then(async () => {
|
|
470
612
|
hydratePlatformPath();
|
|
471
|
-
setupRuntime();
|
|
613
|
+
await setupRuntime();
|
|
472
614
|
createWindow();
|
|
473
615
|
app.on('activate', showMainWindow);
|
|
616
|
+
}).catch(error => {
|
|
617
|
+
dialog.showErrorBox('LoadToAgent 시작 실패', error.stack || error.message || String(error));
|
|
618
|
+
app.quit();
|
|
474
619
|
});
|
|
475
620
|
|
|
476
621
|
app.on('window-all-closed', () => {
|
|
@@ -479,8 +624,9 @@ app.on('window-all-closed', () => {
|
|
|
479
624
|
|
|
480
625
|
app.on('before-quit', () => {
|
|
481
626
|
isQuitting = true;
|
|
482
|
-
if (
|
|
483
|
-
if (terminalManager) terminalManager.dispose();
|
|
627
|
+
if (attentionNotifier) attentionNotifier.dispose();
|
|
628
|
+
if (terminalManager instanceof TerminalHostClient) terminalManager.dispose({ shutdownIfIdle: true });
|
|
629
|
+
else if (terminalManager) terminalManager.dispose({ preserveSessions: true });
|
|
484
630
|
if (monitorWorker) {
|
|
485
631
|
monitorWorker.postMessage({ type: 'stop' });
|
|
486
632
|
monitorWorker.terminate();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loadtoagent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "A local desktop dashboard for Claude, Codex, Gemini, and Grok sessions.",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"author": "wincube AX",
|
|
@@ -44,14 +44,20 @@
|
|
|
44
44
|
"start": "electron .",
|
|
45
45
|
"test": "node scripts/regression-test.js",
|
|
46
46
|
"test:terminal": "electron scripts/terminal-integration-test.js",
|
|
47
|
+
"test:terminal-restart": "node scripts/terminal-restart-persistence-check.js",
|
|
47
48
|
"test:bridge": "electron scripts/bridge-integration-test.js",
|
|
48
49
|
"test:tmux": "node scripts/tmux-integration-test.js",
|
|
49
50
|
"test:visual": "electron scripts/visual-check.js",
|
|
50
51
|
"test:responsive": "electron scripts/responsive-check.js",
|
|
51
52
|
"test:interaction": "electron scripts/interaction-check.js",
|
|
53
|
+
"test:scroll": "electron scripts/scroll-retention-check.js",
|
|
54
|
+
"audit:ui": "electron scripts/browser-audit-host.js",
|
|
55
|
+
"test:runtime-overview": "electron scripts/runtime-overview-visual.js",
|
|
56
|
+
"test:readability": "electron scripts/readability-visual-check.js",
|
|
57
|
+
"test:update:mac": "node scripts/mac-update-integration-test.js",
|
|
52
58
|
"demo:readme": "electron scripts/readme-demo.js",
|
|
53
59
|
"dist": "electron-builder",
|
|
54
|
-
"dist:win": "electron-builder --win
|
|
60
|
+
"dist:win": "electron-builder --win --publish never",
|
|
55
61
|
"dist:mac": "electron-builder --mac --publish never",
|
|
56
62
|
"pack": "electron-builder --dir",
|
|
57
63
|
"prepublishOnly": "npm test"
|
|
@@ -90,7 +96,9 @@
|
|
|
90
96
|
"bin/**/*",
|
|
91
97
|
"node_modules/node-pty/**/*"
|
|
92
98
|
],
|
|
99
|
+
"afterPack": "scripts/after-pack.js",
|
|
93
100
|
"win": {
|
|
101
|
+
"icon": "build/icon.ico",
|
|
94
102
|
"target": [
|
|
95
103
|
{
|
|
96
104
|
"target": "portable",
|
|
@@ -107,6 +115,7 @@
|
|
|
107
115
|
]
|
|
108
116
|
},
|
|
109
117
|
"mac": {
|
|
118
|
+
"icon": "build/icon.png",
|
|
110
119
|
"category": "public.app-category.developer-tools",
|
|
111
120
|
"target": [
|
|
112
121
|
{
|
|
@@ -121,13 +130,15 @@
|
|
|
121
130
|
"artifactName": "LoadToAgent-${version}-${arch}.${ext}"
|
|
122
131
|
},
|
|
123
132
|
"portable": {
|
|
124
|
-
"artifactName": "LoadToAgent-${version}-portable.exe"
|
|
133
|
+
"artifactName": "LoadToAgent-${version}-portable.exe",
|
|
134
|
+
"unpackDirName": false
|
|
125
135
|
},
|
|
126
136
|
"nsis": {
|
|
127
137
|
"artifactName": "LoadToAgent-Setup-${version}.exe",
|
|
128
138
|
"oneClick": false,
|
|
129
139
|
"perMachine": false,
|
|
130
|
-
"allowToChangeInstallationDirectory": true
|
|
140
|
+
"allowToChangeInstallationDirectory": true,
|
|
141
|
+
"runAfterFinish": false
|
|
131
142
|
}
|
|
132
143
|
}
|
|
133
144
|
}
|
package/preload.js
CHANGED
|
@@ -4,17 +4,23 @@ const { contextBridge, ipcRenderer } = require('electron');
|
|
|
4
4
|
|
|
5
5
|
contextBridge.exposeInMainWorld('loadtoagent', {
|
|
6
6
|
bootstrap: () => ipcRenderer.invoke('app:bootstrap'),
|
|
7
|
+
rendererReady: () => ipcRenderer.invoke('app:renderer-ready'),
|
|
7
8
|
backgroundState: () => ipcRenderer.invoke('app:background-state'),
|
|
8
9
|
showApp: () => ipcRenderer.invoke('app:show'),
|
|
9
10
|
setLocale: locale => ipcRenderer.invoke('app:set-locale', locale),
|
|
11
|
+
setProviderVisibility: preference => ipcRenderer.invoke('app:set-provider-visibility', preference),
|
|
10
12
|
checkForUpdate: () => ipcRenderer.invoke('app:update-check'),
|
|
11
13
|
downloadUpdate: () => ipcRenderer.invoke('app:update-download'),
|
|
12
14
|
openDownloadedUpdate: () => ipcRenderer.invoke('app:update-open'),
|
|
15
|
+
installDownloadedUpdate: () => ipcRenderer.invoke('app:update-install'),
|
|
13
16
|
openUpdateRelease: () => ipcRenderer.invoke('app:update-open-release'),
|
|
14
17
|
snapshot: () => ipcRenderer.invoke('agents:snapshot'),
|
|
15
18
|
sessionDetail: sessionId => ipcRenderer.invoke('agents:detail', sessionId),
|
|
16
19
|
runAgent: options => ipcRenderer.invoke('agents:run', options),
|
|
17
20
|
stopAgent: runId => ipcRenderer.invoke('agents:stop', runId),
|
|
21
|
+
pauseAgent: runId => ipcRenderer.invoke('agents:pause', runId),
|
|
22
|
+
resumeAgentRun: runId => ipcRenderer.invoke('agents:resume-run', runId),
|
|
23
|
+
retryAgent: runId => ipcRenderer.invoke('agents:retry', runId),
|
|
18
24
|
activeRuns: () => ipcRenderer.invoke('agents:active-runs'),
|
|
19
25
|
probeProviders: () => ipcRenderer.invoke('providers:probe'),
|
|
20
26
|
listWorkspaces: () => ipcRenderer.invoke('workspaces:list'),
|
|
@@ -67,6 +73,11 @@ contextBridge.exposeInMainWorld('loadtoagent', {
|
|
|
67
73
|
ipcRenderer.on('agents:snapshot', handler);
|
|
68
74
|
return () => ipcRenderer.removeListener('agents:snapshot', handler);
|
|
69
75
|
},
|
|
76
|
+
onAttentionRequested: callback => {
|
|
77
|
+
const handler = (_event, payload) => callback(payload);
|
|
78
|
+
ipcRenderer.on('agents:attention-requested', handler);
|
|
79
|
+
return () => ipcRenderer.removeListener('agents:attention-requested', handler);
|
|
80
|
+
},
|
|
70
81
|
onUpdateState: callback => {
|
|
71
82
|
const handler = (_event, update) => callback(update);
|
|
72
83
|
ipcRenderer.on('app:update-state', handler);
|