loadtoagent 1.1.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.
Files changed (83) hide show
  1. package/README.ko.md +7 -0
  2. package/README.md +7 -0
  3. package/README.zh-CN.md +7 -0
  4. package/docs/PROVIDER-CONTRACTS.md +23 -1
  5. package/docs/UI-AUDIT-100.md +118 -0
  6. package/docs/UI-AUDIT-101-200.md +120 -0
  7. package/docs/UI-AUDIT-201-300.md +120 -0
  8. package/main.js +135 -38
  9. package/package.json +13 -4
  10. package/preload.js +6 -0
  11. package/renderer/app-agent-actions.js +125 -53
  12. package/renderer/app-bootstrap.js +54 -19
  13. package/renderer/app-dashboard.js +190 -55
  14. package/renderer/app-drawer-content.js +48 -44
  15. package/renderer/app-drawer-data.js +26 -13
  16. package/renderer/app-drawer.js +72 -54
  17. package/renderer/app-events-dialogs.js +146 -37
  18. package/renderer/app-events-filters.js +172 -13
  19. package/renderer/app-events-navigation.js +77 -30
  20. package/renderer/app-events-sessions.js +156 -9
  21. package/renderer/app-events.js +2 -1
  22. package/renderer/app-graph-model.js +20 -38
  23. package/renderer/app-graph-orchestration.js +15 -13
  24. package/renderer/app-graph-view.js +228 -113
  25. package/renderer/app-management.js +211 -0
  26. package/renderer/app-provider-visibility.js +124 -0
  27. package/renderer/app-quality.js +568 -0
  28. package/renderer/app-run-modal.js +103 -33
  29. package/renderer/app-runtime-overview.js +312 -0
  30. package/renderer/app-session-render.js +94 -37
  31. package/renderer/app-tmux-render.js +52 -44
  32. package/renderer/app.js +153 -75
  33. package/renderer/i18n-messages.js +833 -16
  34. package/renderer/i18n.js +104 -0
  35. package/renderer/index.html +145 -59
  36. package/renderer/shared.js +17 -0
  37. package/renderer/styles-agent-map.css +6 -0
  38. package/renderer/styles-cards.css +26 -0
  39. package/renderer/styles-components.css +86 -9
  40. package/renderer/styles-management.css +355 -0
  41. package/renderer/styles-overlays.css +8 -2
  42. package/renderer/styles-product.css +24 -16
  43. package/renderer/styles-quality.css +312 -0
  44. package/renderer/styles-readability.css +862 -0
  45. package/renderer/styles-responsive-product.css +84 -12
  46. package/renderer/styles-responsive-runtime.css +22 -14
  47. package/renderer/styles-responsive-shell.css +44 -48
  48. package/renderer/styles-responsive-workflows.css +37 -3
  49. package/renderer/styles-run-composer.css +5 -0
  50. package/renderer/styles-runtime-overview.css +285 -0
  51. package/renderer/styles-settings.css +160 -0
  52. package/renderer/styles-terminal.css +339 -2
  53. package/renderer/styles-workflow-map.css +362 -15
  54. package/renderer/styles-workflows.css +69 -2
  55. package/renderer/styles.css +27 -12
  56. package/renderer/terminal-agent.js +17 -13
  57. package/renderer/terminal-events.js +233 -40
  58. package/renderer/terminal-workbench.js +165 -77
  59. package/renderer/terminal.js +341 -34
  60. package/src/agentMonitor/claudeParser.js +96 -7
  61. package/src/agentMonitor/codexParser.js +59 -4
  62. package/src/agentMonitor/executionActivity.js +282 -0
  63. package/src/agentMonitor/genericParser.js +56 -13
  64. package/src/agentMonitor/hierarchy.js +17 -0
  65. package/src/agentMonitor/responseIntent.js +51 -0
  66. package/src/agentMonitor.js +21 -3
  67. package/src/agentRunner.js +66 -1
  68. package/src/attentionNotifier.js +14 -10
  69. package/src/automationMonitor.js +258 -0
  70. package/src/contracts.js +10 -1
  71. package/src/ipc/registerAgentIpc.js +9 -3
  72. package/src/ipc/registerAppIpc.js +4 -1
  73. package/src/ipc/registerTerminalIpc.js +12 -6
  74. package/src/macUpdateHelper.js +210 -0
  75. package/src/monitorWorker.js +65 -6
  76. package/src/platformPath.js +80 -0
  77. package/src/processMonitor.js +80 -22
  78. package/src/providerVisibilityStore.js +45 -0
  79. package/src/sessionIntelligence.js +247 -0
  80. package/src/terminalHost.js +381 -0
  81. package/src/terminalHostDaemon.js +60 -0
  82. package/src/terminalManager.js +158 -3
  83. 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 } = 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,43 +36,45 @@ 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 · 백그라운드 AI 세션 {count}개',
54
+ trayTooltip: 'LoadToAgent · 백그라운드 터미널 {count}개',
50
55
  trayOpen: 'LoadToAgent 열기',
51
- traySessions: '백그라운드 AI 세션 {count}개 유지 중',
52
- trayQuit: '프로그램 끝내기 · AI 세션도 종료',
56
+ traySessions: '백그라운드 터미널 {count}개 유지 중',
57
+ trayQuit: '프로그램 끝내기 · 터미널 세션 유지',
53
58
  addWorkspaces: 'AI 작업 폴더 선택',
54
59
  pickWorkspace: '작업 폴더 선택',
55
60
  attentionTitle: '내 확인이 필요합니다',
56
61
  attentionBody: '{provider} · {title}',
57
62
  },
58
63
  en: {
59
- trayTooltip: 'LoadToAgent · {count} background AI sessions',
64
+ trayTooltip: 'LoadToAgent · {count} background terminals',
60
65
  trayOpen: 'Open LoadToAgent',
61
- traySessions: '{count} background AI sessions active',
62
- trayQuit: 'Quit app · End AI sessions too',
66
+ traySessions: '{count} background terminals active',
67
+ trayQuit: 'Quit app · Keep terminal sessions',
63
68
  addWorkspaces: 'Choose AI workspaces',
64
69
  pickWorkspace: 'Choose workspace',
65
70
  attentionTitle: 'Your review is needed',
66
71
  attentionBody: '{provider} · {title}',
67
72
  },
68
73
  'zh-CN': {
69
- trayTooltip: 'LoadToAgent · {count} 个后台 AI 会话',
74
+ trayTooltip: 'LoadToAgent · {count} 个后台终端',
70
75
  trayOpen: '打开 LoadToAgent',
71
- traySessions: '正在保持 {count} 个后台 AI 会话',
72
- trayQuit: '退出应用 · 同时结束 AI 会话',
76
+ traySessions: '正在保持 {count} 个后台终端',
77
+ trayQuit: '退出应用 · 保留终端会话',
73
78
  addWorkspaces: '选择 AI 工作文件夹',
74
79
  pickWorkspace: '选择工作文件夹',
75
80
  attentionTitle: '需要你的确认',
@@ -79,6 +84,7 @@ const MAIN_COPY = {
79
84
  let lastSnapshot = {
80
85
  generatedAt: new Date().toISOString(),
81
86
  sessions: [],
87
+ automations: [],
82
88
  tmux: { generatedAt: new Date().toISOString(), available: false, status: '확인 중', distros: [], summary: { distros: 0, sessions: 0, windows: 0, panes: 0, aiPanes: 0, linked: 0 } },
83
89
  summary: {
84
90
  providers: providerList().map(provider => ({ ...provider, installed: false, sessions: 0, active: 0, waiting: 0, subagents: 0, usage: blankUsage() })),
@@ -133,6 +139,30 @@ function listWorkspaces() {
133
139
  return readWorkspaces(userFile('workspaces.json'));
134
140
  }
135
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
+
136
166
  function saveWorkspaces(items) {
137
167
  return writeWorkspaces(userFile('workspaces.json'), items);
138
168
  }
@@ -154,18 +184,11 @@ function listWslDistros() {
154
184
 
155
185
  function hydratePlatformPath() {
156
186
  if (process.platform !== 'darwin') return;
157
- const additions = ['/opt/homebrew/bin', '/usr/local/bin', path.join(os.homedir(), '.local', 'bin')];
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);
187
+ process.env.PATH = macPathEntries(os.homedir(), process.env.PATH).join(path.delimiter);
166
188
  }
167
189
 
168
190
  function createWindow() {
191
+ rendererBootstrapped = false;
169
192
  mainWindow = new BrowserWindow({
170
193
  width: 1600,
171
194
  height: 980,
@@ -182,13 +205,14 @@ function createWindow() {
182
205
  },
183
206
  });
184
207
  mainWindow.setMenuBarVisibility(false);
208
+ mainWindow.webContents.on('did-start-loading', () => { rendererBootstrapped = false; });
185
209
  mainWindow.loadFile(path.join(__dirname, 'renderer', 'index.html'));
186
210
  mainWindow.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));
187
211
  const allowedUrl = pathToFileURL(path.join(__dirname, 'renderer', 'index.html')).href;
188
212
  mainWindow.webContents.on('will-navigate', (event, url) => { if (url !== allowedUrl) event.preventDefault(); });
189
213
  mainWindow.once('ready-to-show', () => mainWindow && mainWindow.show());
190
214
  mainWindow.on('close', event => {
191
- if (isQuitting || !backgroundAgentSessions().length) return;
215
+ if (isQuitting || !backgroundTerminalSessions().length) return;
192
216
  event.preventDefault();
193
217
  mainWindow.hide();
194
218
  ensureBackgroundTray();
@@ -196,9 +220,13 @@ function createWindow() {
196
220
  mainWindow.on('closed', () => { mainWindow = null; });
197
221
  }
198
222
 
199
- function backgroundAgentSessions() {
223
+ function backgroundTerminalSessions() {
200
224
  if (!terminalManager) return [];
201
- return terminalManager.list().filter(session => session.type === 'agent' && (session.status === 'running' || session.status === 'starting'));
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));
202
230
  }
203
231
 
204
232
  function showMainWindow() {
@@ -210,7 +238,7 @@ function showMainWindow() {
210
238
 
211
239
  function updateBackgroundTrayMenu() {
212
240
  if (!backgroundTray) return;
213
- const count = backgroundAgentSessions().length;
241
+ const count = backgroundTerminalSessions().length;
214
242
  backgroundTray.setToolTip(mainText('trayTooltip', { count }));
215
243
  backgroundTray.setContextMenu(Menu.buildFromTemplate([
216
244
  { label: mainText('trayOpen'), click: showMainWindow },
@@ -268,16 +296,33 @@ function sendSnapshot(snapshot) {
268
296
  }
269
297
 
270
298
  function openAttentionSession(session) {
299
+ if (!isProviderVisible(session && session.provider)) return;
300
+ pendingAttentionSessionId = String(session && session.id || '');
271
301
  showMainWindow();
272
302
  if (!mainWindow || mainWindow.isDestroyed()) return;
273
303
  mainWindow.flashFrame(false);
304
+ if (!rendererBootstrapped || mainWindow.webContents.isLoadingMainFrame()) return;
274
305
  try {
275
- mainWindow.webContents.send('agents:attention-requested', { sessionId: String(session && session.id || '') });
306
+ mainWindow.webContents.send('agents:attention-requested', { sessionId: pendingAttentionSessionId });
307
+ pendingAttentionSessionId = '';
276
308
  } catch (error) {
277
309
  reportRecoverableError('ipc-send:agents:attention-requested', error);
278
310
  }
279
311
  }
280
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
+
281
326
  function createAttentionNotifier() {
282
327
  return new AttentionNotifier({
283
328
  Notification,
@@ -310,10 +355,46 @@ function installationType() {
310
355
  return fs.existsSync(path.join(__dirname, '.git')) ? 'source' : 'npm';
311
356
  }
312
357
 
313
- function setupRuntime() {
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();
314
379
  const runsDir = userFile('agent-runs');
315
380
  runner = new AgentRunner({ runsDir });
316
- terminalManager = new TerminalManager();
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
+ });
317
398
  updateManager = new UpdateManager({
318
399
  currentVersion: app.getVersion(),
319
400
  platform: process.platform,
@@ -338,12 +419,16 @@ function setupRuntime() {
338
419
  }
339
420
  terminalManager.on('data', payload => sendTerminal('terminals:data', payload));
340
421
  terminalManager.on('state', payload => {
341
- sendTerminal('terminals:state', payload);
422
+ if (!payload.session || payload.session.type !== 'agent' || isProviderVisible(payload.session.provider)) {
423
+ sendTerminal('terminals:state', { ...payload, sessions: visibleTerminalSessions(payload.sessions) });
424
+ }
342
425
  updateBackgroundTrayMenu();
343
426
  if (monitorWorker) monitorWorker.postMessage({ type: 'bridge-presence', bridges: bridgePresence() });
344
427
  });
345
- bridgeServer = new BridgeServer({ terminalManager, home: bridgeHome, platform: process.platform });
346
- bridgeServer.start().catch(error => sendTerminal('terminals:error', { id: '', message: `외부 터미널 브리지를 열지 못했습니다: ${error.message}` }));
428
+ terminalManager.on('disconnect', () => {
429
+ sendTerminal('terminals:error', { id: '', message: '터미널 호스트 연결이 끊어졌습니다. 프로그램을 다시 시작하면 실행 중인 세션에 다시 연결합니다.' });
430
+ });
431
+ await terminalManager.connect();
347
432
  availability = probeProviders();
348
433
  monitorWorker = new Worker(path.join(__dirname, 'src', 'monitorWorker.js'), {
349
434
  workerData: { runsDir, home: os.homedir(), intervalMs: 1200, availability },
@@ -352,7 +437,7 @@ function setupRuntime() {
352
437
  monitorWorker.on('message', message => {
353
438
  if (message && message.type === 'snapshot') {
354
439
  lastSnapshot = message.snapshot;
355
- attentionNotifier.sync(lastSnapshot);
440
+ attentionNotifier.sync(visibleSnapshotSessions(lastSnapshot));
356
441
  sendSnapshot(lastSnapshot);
357
442
  }
358
443
  if (message && message.type === 'detail-result') {
@@ -373,10 +458,11 @@ function bridgePresence() {
373
458
  if (!terminalManager) return [];
374
459
  const environment = process.platform === 'win32' ? 'windows' : (process.platform === 'darwin' ? 'macos' : 'linux');
375
460
  return terminalManager.list()
376
- .filter(session => session.type === 'agent' && session.status === 'running')
461
+ .filter(session => session.type === 'agent' && (session.status === 'running' || session.status === 'starting'))
377
462
  .map(session => ({
378
463
  id: session.bridgeId || session.id,
379
464
  bridgeId: session.bridgeId || '',
465
+ linkedSessionId: session.bridgeId || '',
380
466
  terminalId: session.id,
381
467
  provider: session.provider,
382
468
  pid: session.pid,
@@ -401,17 +487,20 @@ function bootstrapState() {
401
487
  id: process.platform,
402
488
  label: process.platform === 'darwin' ? 'macOS' : (process.platform === 'win32' ? 'Windows' : 'Linux'),
403
489
  localShell: process.platform === 'win32' ? 'powershell' : 'shell',
404
- localShellLabel: process.platform === 'darwin' ? 'Mac 명령창' : (process.platform === 'win32' ? 'Windows 명령창' : 'Linux 명령창'),
490
+ localShellLabel: process.platform === 'darwin' ? 'macOS shell' : (process.platform === 'win32' ? 'Windows PowerShell' : 'Linux shell'),
405
491
  nativeTmux: process.platform !== 'win32',
406
492
  },
407
493
  bridgeCli: bridgeLauncher,
408
494
  update: updateManager ? updateManager.getState() : null,
495
+ providerVisibility: providerVisibilityStore ? providerVisibilityStore.snapshot() : { hidden: [] },
409
496
  };
410
497
  }
411
498
 
412
499
  function requestAgentDetail(sessionId) {
413
500
  return new Promise(resolve => {
414
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);
415
504
  const requestId = ++detailRequestId;
416
505
  const timer = setTimeout(() => {
417
506
  if (!pendingDetails.has(requestId)) return;
@@ -432,9 +521,10 @@ function registerIpcHandlers() {
432
521
  registerAppIpc({
433
522
  handleTrusted,
434
523
  bootstrap: bootstrapState,
524
+ rendererReady: markRendererReady,
435
525
  backgroundState: () => ({
436
526
  visible: Boolean(mainWindow && !mainWindow.isDestroyed() && mainWindow.isVisible()),
437
- backgroundSessions: backgroundAgentSessions().length,
527
+ backgroundSessions: backgroundTerminalSessions().length,
438
528
  trayReady: Boolean(backgroundTray),
439
529
  }),
440
530
  show: () => { showMainWindow(); return { ok: true }; },
@@ -443,13 +533,16 @@ function registerIpcHandlers() {
443
533
  updateBackgroundTrayMenu();
444
534
  return { locale: appLocale };
445
535
  },
536
+ setProviderVisibility: saveProviderVisibility,
446
537
  updateManager: () => updateManager,
538
+ installUpdate: installDownloadedUpdate,
447
539
  });
448
540
  registerAgentIpc({
449
541
  handleTrusted,
450
542
  snapshot: () => { refreshMonitor(); return lastSnapshot; },
451
543
  requestDetail: requestAgentDetail,
452
544
  runner: () => runner,
545
+ isProviderVisible,
453
546
  probeProviders: () => {
454
547
  availability = probeProviders();
455
548
  if (monitorWorker) monitorWorker.postMessage({ type: 'availability', availability });
@@ -462,6 +555,7 @@ function registerIpcHandlers() {
462
555
  requireTrustedSender,
463
556
  trustedSender,
464
557
  manager: () => terminalManager,
558
+ isProviderVisible,
465
559
  listWslDistros,
466
560
  sendError: payload => sendTerminal('terminals:error', payload),
467
561
  });
@@ -514,11 +608,14 @@ function registerIpcHandlers() {
514
608
 
515
609
  registerIpcHandlers();
516
610
 
517
- app.whenReady().then(() => {
611
+ app.whenReady().then(async () => {
518
612
  hydratePlatformPath();
519
- setupRuntime();
613
+ await setupRuntime();
520
614
  createWindow();
521
615
  app.on('activate', showMainWindow);
616
+ }).catch(error => {
617
+ dialog.showErrorBox('LoadToAgent 시작 실패', error.stack || error.message || String(error));
618
+ app.quit();
522
619
  });
523
620
 
524
621
  app.on('window-all-closed', () => {
@@ -528,8 +625,8 @@ app.on('window-all-closed', () => {
528
625
  app.on('before-quit', () => {
529
626
  isQuitting = true;
530
627
  if (attentionNotifier) attentionNotifier.dispose();
531
- if (bridgeServer) bridgeServer.dispose();
532
- if (terminalManager) terminalManager.dispose();
628
+ if (terminalManager instanceof TerminalHostClient) terminalManager.dispose({ shutdownIfIdle: true });
629
+ else if (terminalManager) terminalManager.dispose({ preserveSessions: true });
533
630
  if (monitorWorker) {
534
631
  monitorWorker.postMessage({ type: 'stop' });
535
632
  monitorWorker.terminate();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loadtoagent",
3
- "version": "1.1.0",
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,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'),