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
package/main.js
CHANGED
|
@@ -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, resolveTerminalHostExecutable } = 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');
|
|
@@ -22,6 +23,8 @@ const { registerTmuxIpc } = require('./src/ipc/registerTmuxIpc');
|
|
|
22
23
|
const { registerWorkspaceIpc } = require('./src/ipc/registerWorkspaceIpc');
|
|
23
24
|
const { reportRecoverableError } = require('./src/diagnostics');
|
|
24
25
|
const { AttentionNotifier } = require('./src/attentionNotifier');
|
|
26
|
+
const { ProviderVisibilityStore } = require('./src/providerVisibilityStore');
|
|
27
|
+
const { macPathEntries } = require('./src/platformPath');
|
|
25
28
|
|
|
26
29
|
const PRODUCT_NAME = 'LoadToAgent';
|
|
27
30
|
app.setName(PRODUCT_NAME);
|
|
@@ -33,52 +36,64 @@ let mainWindow = null;
|
|
|
33
36
|
let monitorWorker = null;
|
|
34
37
|
let runner = null;
|
|
35
38
|
let terminalManager = null;
|
|
36
|
-
let bridgeServer = null;
|
|
37
39
|
let bridgeLauncher = null;
|
|
38
40
|
let backgroundTray = null;
|
|
39
41
|
let updateManager = null;
|
|
40
42
|
let attentionNotifier = null;
|
|
41
43
|
let isQuitting = false;
|
|
42
44
|
let appLocale = 'ko';
|
|
45
|
+
let providerVisibilityStore = null;
|
|
46
|
+
let pendingAttentionSessionId = '';
|
|
47
|
+
let rendererBootstrapped = false;
|
|
43
48
|
const tmuxController = new TmuxController({ platform: process.platform });
|
|
44
49
|
let availability = {};
|
|
45
50
|
let detailRequestId = 0;
|
|
46
51
|
const pendingDetails = new Map();
|
|
47
52
|
const MAIN_COPY = {
|
|
48
53
|
ko: {
|
|
49
|
-
trayTooltip: 'LoadToAgent · 백그라운드
|
|
54
|
+
trayTooltip: 'LoadToAgent · 백그라운드 터미널 {count}개',
|
|
50
55
|
trayOpen: 'LoadToAgent 열기',
|
|
51
|
-
traySessions: '백그라운드
|
|
52
|
-
trayQuit: '프로그램 끝내기 ·
|
|
56
|
+
traySessions: '백그라운드 터미널 {count}개 유지 중',
|
|
57
|
+
trayQuit: '프로그램 끝내기 · 터미널 세션 유지',
|
|
53
58
|
addWorkspaces: 'AI 작업 폴더 선택',
|
|
54
59
|
pickWorkspace: '작업 폴더 선택',
|
|
55
60
|
attentionTitle: '내 확인이 필요합니다',
|
|
56
61
|
attentionBody: '{provider} · {title}',
|
|
62
|
+
terminalHostReconnecting: '터미널 연결을 자동으로 복구하는 중입니다.',
|
|
63
|
+
terminalHostReconnected: '터미널 연결을 복구했습니다.',
|
|
64
|
+
terminalHostReconnectFailed: '터미널 연결을 복구하지 못했습니다: {reason}',
|
|
57
65
|
},
|
|
58
66
|
en: {
|
|
59
|
-
trayTooltip: 'LoadToAgent · {count} background
|
|
67
|
+
trayTooltip: 'LoadToAgent · {count} background terminals',
|
|
60
68
|
trayOpen: 'Open LoadToAgent',
|
|
61
|
-
traySessions: '{count} background
|
|
62
|
-
trayQuit: 'Quit app ·
|
|
69
|
+
traySessions: '{count} background terminals active',
|
|
70
|
+
trayQuit: 'Quit app · Keep terminal sessions',
|
|
63
71
|
addWorkspaces: 'Choose AI workspaces',
|
|
64
72
|
pickWorkspace: 'Choose workspace',
|
|
65
73
|
attentionTitle: 'Your review is needed',
|
|
66
74
|
attentionBody: '{provider} · {title}',
|
|
75
|
+
terminalHostReconnecting: 'Restoring the terminal connection automatically.',
|
|
76
|
+
terminalHostReconnected: 'Terminal connection restored.',
|
|
77
|
+
terminalHostReconnectFailed: 'Could not restore the terminal connection: {reason}',
|
|
67
78
|
},
|
|
68
79
|
'zh-CN': {
|
|
69
|
-
trayTooltip: 'LoadToAgent · {count}
|
|
80
|
+
trayTooltip: 'LoadToAgent · {count} 个后台终端',
|
|
70
81
|
trayOpen: '打开 LoadToAgent',
|
|
71
|
-
traySessions: '正在保持 {count}
|
|
72
|
-
trayQuit: '退出应用 ·
|
|
82
|
+
traySessions: '正在保持 {count} 个后台终端',
|
|
83
|
+
trayQuit: '退出应用 · 保留终端会话',
|
|
73
84
|
addWorkspaces: '选择 AI 工作文件夹',
|
|
74
85
|
pickWorkspace: '选择工作文件夹',
|
|
75
86
|
attentionTitle: '需要你的确认',
|
|
76
87
|
attentionBody: '{provider} · {title}',
|
|
88
|
+
terminalHostReconnecting: '正在自动恢复终端连接。',
|
|
89
|
+
terminalHostReconnected: '终端连接已恢复。',
|
|
90
|
+
terminalHostReconnectFailed: '无法恢复终端连接:{reason}',
|
|
77
91
|
},
|
|
78
92
|
};
|
|
79
93
|
let lastSnapshot = {
|
|
80
94
|
generatedAt: new Date().toISOString(),
|
|
81
95
|
sessions: [],
|
|
96
|
+
automations: [],
|
|
82
97
|
tmux: { generatedAt: new Date().toISOString(), available: false, status: '확인 중', distros: [], summary: { distros: 0, sessions: 0, windows: 0, panes: 0, aiPanes: 0, linked: 0 } },
|
|
83
98
|
summary: {
|
|
84
99
|
providers: providerList().map(provider => ({ ...provider, installed: false, sessions: 0, active: 0, waiting: 0, subagents: 0, usage: blankUsage() })),
|
|
@@ -133,6 +148,30 @@ function listWorkspaces() {
|
|
|
133
148
|
return readWorkspaces(userFile('workspaces.json'));
|
|
134
149
|
}
|
|
135
150
|
|
|
151
|
+
function isProviderVisible(providerId) {
|
|
152
|
+
return providerVisibilityStore ? providerVisibilityStore.isVisible(providerId) : true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function loadProviderVisibility() {
|
|
156
|
+
providerVisibilityStore = new ProviderVisibilityStore(
|
|
157
|
+
userFile('provider-visibility.json'),
|
|
158
|
+
providerList().map(provider => provider.id),
|
|
159
|
+
error => reportRecoverableError('provider-visibility-load', error),
|
|
160
|
+
);
|
|
161
|
+
return providerVisibilityStore.load();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function saveProviderVisibility(value = {}) {
|
|
165
|
+
if (!providerVisibilityStore) loadProviderVisibility();
|
|
166
|
+
const saved = providerVisibilityStore.save(value);
|
|
167
|
+
updateBackgroundTrayMenu();
|
|
168
|
+
return saved;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function visibleSnapshotSessions(snapshot = lastSnapshot) {
|
|
172
|
+
return { ...snapshot, sessions: (snapshot.sessions || []).filter(session => isProviderVisible(session.provider)) };
|
|
173
|
+
}
|
|
174
|
+
|
|
136
175
|
function saveWorkspaces(items) {
|
|
137
176
|
return writeWorkspaces(userFile('workspaces.json'), items);
|
|
138
177
|
}
|
|
@@ -154,18 +193,11 @@ function listWslDistros() {
|
|
|
154
193
|
|
|
155
194
|
function hydratePlatformPath() {
|
|
156
195
|
if (process.platform !== 'darwin') return;
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
const shellPath = process.env.SHELL || '/bin/zsh';
|
|
160
|
-
const loginPath = execFileSync(shellPath, ['-lic', 'printf %s "$PATH"'], { encoding: 'utf8', timeout: 5_000 }).trim();
|
|
161
|
-
additions.unshift(...loginPath.split(path.delimiter));
|
|
162
|
-
} catch (error) {
|
|
163
|
-
reportRecoverableError('platform-path', error);
|
|
164
|
-
}
|
|
165
|
-
process.env.PATH = [...new Set([...additions, ...String(process.env.PATH || '').split(path.delimiter)].filter(Boolean))].join(path.delimiter);
|
|
196
|
+
process.env.PATH = macPathEntries(os.homedir(), process.env.PATH).join(path.delimiter);
|
|
166
197
|
}
|
|
167
198
|
|
|
168
199
|
function createWindow() {
|
|
200
|
+
rendererBootstrapped = false;
|
|
169
201
|
mainWindow = new BrowserWindow({
|
|
170
202
|
width: 1600,
|
|
171
203
|
height: 980,
|
|
@@ -182,13 +214,14 @@ function createWindow() {
|
|
|
182
214
|
},
|
|
183
215
|
});
|
|
184
216
|
mainWindow.setMenuBarVisibility(false);
|
|
217
|
+
mainWindow.webContents.on('did-start-loading', () => { rendererBootstrapped = false; });
|
|
185
218
|
mainWindow.loadFile(path.join(__dirname, 'renderer', 'index.html'));
|
|
186
219
|
mainWindow.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));
|
|
187
220
|
const allowedUrl = pathToFileURL(path.join(__dirname, 'renderer', 'index.html')).href;
|
|
188
221
|
mainWindow.webContents.on('will-navigate', (event, url) => { if (url !== allowedUrl) event.preventDefault(); });
|
|
189
222
|
mainWindow.once('ready-to-show', () => mainWindow && mainWindow.show());
|
|
190
223
|
mainWindow.on('close', event => {
|
|
191
|
-
if (isQuitting || !
|
|
224
|
+
if (isQuitting || !backgroundTerminalSessions().length) return;
|
|
192
225
|
event.preventDefault();
|
|
193
226
|
mainWindow.hide();
|
|
194
227
|
ensureBackgroundTray();
|
|
@@ -196,9 +229,13 @@ function createWindow() {
|
|
|
196
229
|
mainWindow.on('closed', () => { mainWindow = null; });
|
|
197
230
|
}
|
|
198
231
|
|
|
199
|
-
function
|
|
232
|
+
function backgroundTerminalSessions() {
|
|
200
233
|
if (!terminalManager) return [];
|
|
201
|
-
return terminalManager.list().filter(session => session.
|
|
234
|
+
return terminalManager.list().filter(session => session.status === 'running' || session.status === 'starting');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function visibleTerminalSessions(sessions) {
|
|
238
|
+
return (sessions || []).filter(session => session.type !== 'agent' || isProviderVisible(session.provider));
|
|
202
239
|
}
|
|
203
240
|
|
|
204
241
|
function showMainWindow() {
|
|
@@ -210,7 +247,7 @@ function showMainWindow() {
|
|
|
210
247
|
|
|
211
248
|
function updateBackgroundTrayMenu() {
|
|
212
249
|
if (!backgroundTray) return;
|
|
213
|
-
const count =
|
|
250
|
+
const count = backgroundTerminalSessions().length;
|
|
214
251
|
backgroundTray.setToolTip(mainText('trayTooltip', { count }));
|
|
215
252
|
backgroundTray.setContextMenu(Menu.buildFromTemplate([
|
|
216
253
|
{ label: mainText('trayOpen'), click: showMainWindow },
|
|
@@ -268,14 +305,31 @@ function sendSnapshot(snapshot) {
|
|
|
268
305
|
}
|
|
269
306
|
|
|
270
307
|
function openAttentionSession(session) {
|
|
308
|
+
if (!isProviderVisible(session && session.provider)) return;
|
|
309
|
+
pendingAttentionSessionId = String(session && session.id || '');
|
|
271
310
|
showMainWindow();
|
|
272
311
|
if (!mainWindow || mainWindow.isDestroyed()) return;
|
|
273
312
|
mainWindow.flashFrame(false);
|
|
313
|
+
if (!rendererBootstrapped || mainWindow.webContents.isLoadingMainFrame()) return;
|
|
314
|
+
try {
|
|
315
|
+
mainWindow.webContents.send('agents:attention-requested', { sessionId: pendingAttentionSessionId });
|
|
316
|
+
pendingAttentionSessionId = '';
|
|
317
|
+
} catch (error) {
|
|
318
|
+
reportRecoverableError('ipc-send:agents:attention-requested', error);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function markRendererReady() {
|
|
323
|
+
rendererBootstrapped = true;
|
|
324
|
+
if (!pendingAttentionSessionId || !mainWindow || mainWindow.isDestroyed()) return { ok: true };
|
|
325
|
+
const sessionId = pendingAttentionSessionId;
|
|
274
326
|
try {
|
|
275
|
-
mainWindow.webContents.send('agents:attention-requested', { sessionId
|
|
327
|
+
mainWindow.webContents.send('agents:attention-requested', { sessionId });
|
|
328
|
+
pendingAttentionSessionId = '';
|
|
276
329
|
} catch (error) {
|
|
277
330
|
reportRecoverableError('ipc-send:agents:attention-requested', error);
|
|
278
331
|
}
|
|
332
|
+
return { ok: true };
|
|
279
333
|
}
|
|
280
334
|
|
|
281
335
|
function createAttentionNotifier() {
|
|
@@ -310,10 +364,46 @@ function installationType() {
|
|
|
310
364
|
return fs.existsSync(path.join(__dirname, '.git')) ? 'source' : 'npm';
|
|
311
365
|
}
|
|
312
366
|
|
|
313
|
-
function
|
|
367
|
+
async function installDownloadedUpdate() {
|
|
368
|
+
if (!updateManager) throw new Error('업데이트 관리자가 준비되지 않았습니다.');
|
|
369
|
+
const downloaded = await updateManager.download();
|
|
370
|
+
const outcome = await launchDownloadedUpdate({
|
|
371
|
+
platform: process.platform,
|
|
372
|
+
installType: process.env.PORTABLE_EXECUTABLE_FILE ? 'portable' : installationType(),
|
|
373
|
+
installerPath: downloaded.downloadedPath,
|
|
374
|
+
downloadsDir: path.join(app.getPath('userData'), 'updates'),
|
|
375
|
+
appPath: process.execPath,
|
|
376
|
+
parentPid: process.pid,
|
|
377
|
+
shell,
|
|
378
|
+
});
|
|
379
|
+
if (outcome.mode === 'automatic') {
|
|
380
|
+
isQuitting = true;
|
|
381
|
+
setImmediate(() => app.quit());
|
|
382
|
+
}
|
|
383
|
+
return { ...updateManager.getState(), installMode: outcome.mode };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async function setupRuntime() {
|
|
387
|
+
loadProviderVisibility();
|
|
314
388
|
const runsDir = userFile('agent-runs');
|
|
315
389
|
runner = new AgentRunner({ runsDir });
|
|
316
|
-
|
|
390
|
+
const terminalStoreFile = userFile('terminal-sessions.json');
|
|
391
|
+
const terminalHostFile = userFile('terminal-host.json');
|
|
392
|
+
terminalManager = demoCapture
|
|
393
|
+
? new TerminalManager({
|
|
394
|
+
storeFile: terminalStoreFile,
|
|
395
|
+
onPersistenceError: (operation, error) => reportRecoverableError(`terminal-sessions:${operation}`, error),
|
|
396
|
+
})
|
|
397
|
+
: new TerminalHostClient({
|
|
398
|
+
discoveryFile: terminalHostFile,
|
|
399
|
+
spawnHost: () => launchTerminalHost({
|
|
400
|
+
executable: resolveTerminalHostExecutable({ isPackaged: app.isPackaged }),
|
|
401
|
+
script: path.join(__dirname, 'src', 'terminalHostDaemon.js'),
|
|
402
|
+
storeFile: terminalStoreFile,
|
|
403
|
+
discoveryFile: terminalHostFile,
|
|
404
|
+
bridgeHome,
|
|
405
|
+
}),
|
|
406
|
+
});
|
|
317
407
|
updateManager = new UpdateManager({
|
|
318
408
|
currentVersion: app.getVersion(),
|
|
319
409
|
platform: process.platform,
|
|
@@ -338,12 +428,29 @@ function setupRuntime() {
|
|
|
338
428
|
}
|
|
339
429
|
terminalManager.on('data', payload => sendTerminal('terminals:data', payload));
|
|
340
430
|
terminalManager.on('state', payload => {
|
|
341
|
-
|
|
431
|
+
if (!payload.session || payload.session.type !== 'agent' || isProviderVisible(payload.session.provider)) {
|
|
432
|
+
sendTerminal('terminals:state', { ...payload, sessions: visibleTerminalSessions(payload.sessions) });
|
|
433
|
+
}
|
|
434
|
+
updateBackgroundTrayMenu();
|
|
435
|
+
if (monitorWorker) monitorWorker.postMessage({ type: 'bridge-presence', bridges: bridgePresence() });
|
|
436
|
+
});
|
|
437
|
+
terminalManager.on('disconnect', () => {
|
|
438
|
+
sendTerminal('terminals:connection', { state: 'reconnecting', message: mainText('terminalHostReconnecting') });
|
|
439
|
+
});
|
|
440
|
+
terminalManager.on('reconnect', payload => {
|
|
441
|
+
const sessions = visibleTerminalSessions(payload?.sessions || terminalManager.list());
|
|
442
|
+
sendTerminal('terminals:state', { change: 'reconnected', session: null, sessions });
|
|
443
|
+
sendTerminal('terminals:connection', { state: 'connected', message: mainText('terminalHostReconnected') });
|
|
342
444
|
updateBackgroundTrayMenu();
|
|
343
445
|
if (monitorWorker) monitorWorker.postMessage({ type: 'bridge-presence', bridges: bridgePresence() });
|
|
344
446
|
});
|
|
345
|
-
|
|
346
|
-
|
|
447
|
+
terminalManager.on('reconnect-error', error => {
|
|
448
|
+
sendTerminal('terminals:connection', {
|
|
449
|
+
state: 'failed',
|
|
450
|
+
message: mainText('terminalHostReconnectFailed', { reason: error?.message || String(error) }),
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
await terminalManager.connect();
|
|
347
454
|
availability = probeProviders();
|
|
348
455
|
monitorWorker = new Worker(path.join(__dirname, 'src', 'monitorWorker.js'), {
|
|
349
456
|
workerData: { runsDir, home: os.homedir(), intervalMs: 1200, availability },
|
|
@@ -352,7 +459,7 @@ function setupRuntime() {
|
|
|
352
459
|
monitorWorker.on('message', message => {
|
|
353
460
|
if (message && message.type === 'snapshot') {
|
|
354
461
|
lastSnapshot = message.snapshot;
|
|
355
|
-
attentionNotifier.sync(lastSnapshot);
|
|
462
|
+
attentionNotifier.sync(visibleSnapshotSessions(lastSnapshot));
|
|
356
463
|
sendSnapshot(lastSnapshot);
|
|
357
464
|
}
|
|
358
465
|
if (message && message.type === 'detail-result') {
|
|
@@ -373,10 +480,11 @@ function bridgePresence() {
|
|
|
373
480
|
if (!terminalManager) return [];
|
|
374
481
|
const environment = process.platform === 'win32' ? 'windows' : (process.platform === 'darwin' ? 'macos' : 'linux');
|
|
375
482
|
return terminalManager.list()
|
|
376
|
-
.filter(session => session.type === 'agent' && session.status === 'running')
|
|
483
|
+
.filter(session => session.type === 'agent' && (session.status === 'running' || session.status === 'starting'))
|
|
377
484
|
.map(session => ({
|
|
378
485
|
id: session.bridgeId || session.id,
|
|
379
486
|
bridgeId: session.bridgeId || '',
|
|
487
|
+
linkedSessionId: session.bridgeId || '',
|
|
380
488
|
terminalId: session.id,
|
|
381
489
|
provider: session.provider,
|
|
382
490
|
pid: session.pid,
|
|
@@ -401,17 +509,20 @@ function bootstrapState() {
|
|
|
401
509
|
id: process.platform,
|
|
402
510
|
label: process.platform === 'darwin' ? 'macOS' : (process.platform === 'win32' ? 'Windows' : 'Linux'),
|
|
403
511
|
localShell: process.platform === 'win32' ? 'powershell' : 'shell',
|
|
404
|
-
localShellLabel: process.platform === 'darwin' ? '
|
|
512
|
+
localShellLabel: process.platform === 'darwin' ? 'macOS shell' : (process.platform === 'win32' ? 'Windows PowerShell' : 'Linux shell'),
|
|
405
513
|
nativeTmux: process.platform !== 'win32',
|
|
406
514
|
},
|
|
407
515
|
bridgeCli: bridgeLauncher,
|
|
408
516
|
update: updateManager ? updateManager.getState() : null,
|
|
517
|
+
providerVisibility: providerVisibilityStore ? providerVisibilityStore.snapshot() : { hidden: [] },
|
|
409
518
|
};
|
|
410
519
|
}
|
|
411
520
|
|
|
412
521
|
function requestAgentDetail(sessionId) {
|
|
413
522
|
return new Promise(resolve => {
|
|
414
523
|
if (!monitorWorker || String(sessionId || '').length > 500) return resolve(null);
|
|
524
|
+
const card = (lastSnapshot.sessions || []).find(session => session.id === String(sessionId || ''));
|
|
525
|
+
if (card && !isProviderVisible(card.provider)) return resolve(null);
|
|
415
526
|
const requestId = ++detailRequestId;
|
|
416
527
|
const timer = setTimeout(() => {
|
|
417
528
|
if (!pendingDetails.has(requestId)) return;
|
|
@@ -432,9 +543,10 @@ function registerIpcHandlers() {
|
|
|
432
543
|
registerAppIpc({
|
|
433
544
|
handleTrusted,
|
|
434
545
|
bootstrap: bootstrapState,
|
|
546
|
+
rendererReady: markRendererReady,
|
|
435
547
|
backgroundState: () => ({
|
|
436
548
|
visible: Boolean(mainWindow && !mainWindow.isDestroyed() && mainWindow.isVisible()),
|
|
437
|
-
backgroundSessions:
|
|
549
|
+
backgroundSessions: backgroundTerminalSessions().length,
|
|
438
550
|
trayReady: Boolean(backgroundTray),
|
|
439
551
|
}),
|
|
440
552
|
show: () => { showMainWindow(); return { ok: true }; },
|
|
@@ -443,13 +555,16 @@ function registerIpcHandlers() {
|
|
|
443
555
|
updateBackgroundTrayMenu();
|
|
444
556
|
return { locale: appLocale };
|
|
445
557
|
},
|
|
558
|
+
setProviderVisibility: saveProviderVisibility,
|
|
446
559
|
updateManager: () => updateManager,
|
|
560
|
+
installUpdate: installDownloadedUpdate,
|
|
447
561
|
});
|
|
448
562
|
registerAgentIpc({
|
|
449
563
|
handleTrusted,
|
|
450
564
|
snapshot: () => { refreshMonitor(); return lastSnapshot; },
|
|
451
565
|
requestDetail: requestAgentDetail,
|
|
452
566
|
runner: () => runner,
|
|
567
|
+
isProviderVisible,
|
|
453
568
|
probeProviders: () => {
|
|
454
569
|
availability = probeProviders();
|
|
455
570
|
if (monitorWorker) monitorWorker.postMessage({ type: 'availability', availability });
|
|
@@ -462,6 +577,7 @@ function registerIpcHandlers() {
|
|
|
462
577
|
requireTrustedSender,
|
|
463
578
|
trustedSender,
|
|
464
579
|
manager: () => terminalManager,
|
|
580
|
+
isProviderVisible,
|
|
465
581
|
listWslDistros,
|
|
466
582
|
sendError: payload => sendTerminal('terminals:error', payload),
|
|
467
583
|
});
|
|
@@ -514,11 +630,14 @@ function registerIpcHandlers() {
|
|
|
514
630
|
|
|
515
631
|
registerIpcHandlers();
|
|
516
632
|
|
|
517
|
-
app.whenReady().then(() => {
|
|
633
|
+
app.whenReady().then(async () => {
|
|
518
634
|
hydratePlatformPath();
|
|
519
|
-
setupRuntime();
|
|
635
|
+
await setupRuntime();
|
|
520
636
|
createWindow();
|
|
521
637
|
app.on('activate', showMainWindow);
|
|
638
|
+
}).catch(error => {
|
|
639
|
+
dialog.showErrorBox('LoadToAgent 시작 실패', error.stack || error.message || String(error));
|
|
640
|
+
app.quit();
|
|
522
641
|
});
|
|
523
642
|
|
|
524
643
|
app.on('window-all-closed', () => {
|
|
@@ -528,8 +647,8 @@ app.on('window-all-closed', () => {
|
|
|
528
647
|
app.on('before-quit', () => {
|
|
529
648
|
isQuitting = true;
|
|
530
649
|
if (attentionNotifier) attentionNotifier.dispose();
|
|
531
|
-
if (
|
|
532
|
-
if (terminalManager) terminalManager.dispose();
|
|
650
|
+
if (terminalManager instanceof TerminalHostClient) terminalManager.dispose({ shutdownIfIdle: true });
|
|
651
|
+
else if (terminalManager) terminalManager.dispose({ preserveSessions: true });
|
|
533
652
|
if (monitorWorker) {
|
|
534
653
|
monitorWorker.postMessage({ type: 'stop' });
|
|
535
654
|
monitorWorker.terminate();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loadtoagent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.2",
|
|
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,6 +96,7 @@
|
|
|
90
96
|
"bin/**/*",
|
|
91
97
|
"node_modules/node-pty/**/*"
|
|
92
98
|
],
|
|
99
|
+
"afterPack": "scripts/after-pack.js",
|
|
93
100
|
"win": {
|
|
94
101
|
"icon": "build/icon.ico",
|
|
95
102
|
"target": [
|
|
@@ -123,13 +130,15 @@
|
|
|
123
130
|
"artifactName": "LoadToAgent-${version}-${arch}.${ext}"
|
|
124
131
|
},
|
|
125
132
|
"portable": {
|
|
126
|
-
"artifactName": "LoadToAgent-${version}-portable.exe"
|
|
133
|
+
"artifactName": "LoadToAgent-${version}-portable.exe",
|
|
134
|
+
"unpackDirName": false
|
|
127
135
|
},
|
|
128
136
|
"nsis": {
|
|
129
137
|
"artifactName": "LoadToAgent-Setup-${version}.exe",
|
|
130
138
|
"oneClick": false,
|
|
131
139
|
"perMachine": false,
|
|
132
|
-
"allowToChangeInstallationDirectory": true
|
|
140
|
+
"allowToChangeInstallationDirectory": true,
|
|
141
|
+
"runAfterFinish": false
|
|
133
142
|
}
|
|
134
143
|
}
|
|
135
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'),
|
|
@@ -29,9 +35,9 @@ contextBridge.exposeInMainWorld('loadtoagent', {
|
|
|
29
35
|
wslDistros: () => ipcRenderer.invoke('wsl:list-distros'),
|
|
30
36
|
terminalGet: id => ipcRenderer.invoke('terminals:get', id),
|
|
31
37
|
terminalCreate: options => ipcRenderer.invoke('terminals:create', options),
|
|
32
|
-
terminalWrite: (id, data) => ipcRenderer.
|
|
38
|
+
terminalWrite: (id, data) => ipcRenderer.invoke('terminals:write', id, data),
|
|
33
39
|
terminalCommand: (id, command) => ipcRenderer.invoke('terminals:command', id, command),
|
|
34
|
-
terminalResize: (id, cols, rows) => ipcRenderer.
|
|
40
|
+
terminalResize: (id, cols, rows) => ipcRenderer.invoke('terminals:resize', id, cols, rows),
|
|
35
41
|
terminalSignal: (id, signal) => ipcRenderer.invoke('terminals:signal', id, signal),
|
|
36
42
|
terminalRestart: id => ipcRenderer.invoke('terminals:restart', id),
|
|
37
43
|
terminalClose: id => ipcRenderer.invoke('terminals:close', id),
|
|
@@ -62,6 +68,11 @@ contextBridge.exposeInMainWorld('loadtoagent', {
|
|
|
62
68
|
ipcRenderer.on('terminals:error', handler);
|
|
63
69
|
return () => ipcRenderer.removeListener('terminals:error', handler);
|
|
64
70
|
},
|
|
71
|
+
onTerminalConnection: callback => {
|
|
72
|
+
const handler = (_event, payload) => callback(payload);
|
|
73
|
+
ipcRenderer.on('terminals:connection', handler);
|
|
74
|
+
return () => ipcRenderer.removeListener('terminals:connection', handler);
|
|
75
|
+
},
|
|
65
76
|
onSnapshot: callback => {
|
|
66
77
|
const handler = (_event, snapshot) => callback(snapshot);
|
|
67
78
|
ipcRenderer.on('agents:snapshot', handler);
|