loadtoagent 1.0.0

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 (82) hide show
  1. package/README.ko.md +175 -0
  2. package/README.md +175 -0
  3. package/README.zh-CN.md +175 -0
  4. package/bin/loadtoagent.js +171 -0
  5. package/docs/ARCHITECTURE.md +30 -0
  6. package/docs/PROVIDER-CONTRACTS.md +58 -0
  7. package/docs/assets/loadtoagent-dashboard.png +0 -0
  8. package/docs/assets/loadtoagent-demo.gif +0 -0
  9. package/main.js +493 -0
  10. package/package.json +133 -0
  11. package/preload.js +75 -0
  12. package/renderer/app-agent-actions.js +285 -0
  13. package/renderer/app-bootstrap.js +95 -0
  14. package/renderer/app-dashboard.js +361 -0
  15. package/renderer/app-drawer-content.js +203 -0
  16. package/renderer/app-drawer-data.js +41 -0
  17. package/renderer/app-drawer.js +183 -0
  18. package/renderer/app-events-dialogs.js +169 -0
  19. package/renderer/app-events-filters.js +74 -0
  20. package/renderer/app-events-navigation.js +96 -0
  21. package/renderer/app-events-sessions.js +195 -0
  22. package/renderer/app-events.js +16 -0
  23. package/renderer/app-graph-layout.js +71 -0
  24. package/renderer/app-graph-model.js +107 -0
  25. package/renderer/app-graph-orchestration.js +76 -0
  26. package/renderer/app-graph-view.js +544 -0
  27. package/renderer/app-run-modal.js +221 -0
  28. package/renderer/app-session-render.js +243 -0
  29. package/renderer/app-tmux-render.js +247 -0
  30. package/renderer/app.js +608 -0
  31. package/renderer/fonts/geist-ext.woff2 +0 -0
  32. package/renderer/fonts/geist.woff2 +0 -0
  33. package/renderer/i18n-messages.js +355 -0
  34. package/renderer/i18n.js +122 -0
  35. package/renderer/index.html +386 -0
  36. package/renderer/shared.js +26 -0
  37. package/renderer/styles-agent-map.css +401 -0
  38. package/renderer/styles-cards.css +600 -0
  39. package/renderer/styles-collaboration.css +534 -0
  40. package/renderer/styles-components.css +1293 -0
  41. package/renderer/styles-onboarding.css +344 -0
  42. package/renderer/styles-overlays.css +284 -0
  43. package/renderer/styles-product.css +686 -0
  44. package/renderer/styles-responsive-product.css +689 -0
  45. package/renderer/styles-responsive-runtime.css +718 -0
  46. package/renderer/styles-responsive-shell.css +533 -0
  47. package/renderer/styles-responsive-workflows.css +778 -0
  48. package/renderer/styles-run-composer.css +695 -0
  49. package/renderer/styles-settings.css +606 -0
  50. package/renderer/styles-terminal.css +1241 -0
  51. package/renderer/styles-tmux.css +1162 -0
  52. package/renderer/styles-workflow-map.css +513 -0
  53. package/renderer/styles-workflows.css +1217 -0
  54. package/renderer/styles.css +486 -0
  55. package/renderer/terminal-agent.js +152 -0
  56. package/renderer/terminal-events.js +226 -0
  57. package/renderer/terminal-workbench.js +464 -0
  58. package/renderer/terminal.js +497 -0
  59. package/src/agentMonitor/claudeParser.js +197 -0
  60. package/src/agentMonitor/codexCollaboration.js +95 -0
  61. package/src/agentMonitor/codexParser.js +447 -0
  62. package/src/agentMonitor/genericParser.js +244 -0
  63. package/src/agentMonitor/hierarchy.js +248 -0
  64. package/src/agentMonitor/sessionFiles.js +86 -0
  65. package/src/agentMonitor.js +591 -0
  66. package/src/agentRunner.js +465 -0
  67. package/src/bridgeServer.js +246 -0
  68. package/src/contracts.js +71 -0
  69. package/src/diagnostics.js +23 -0
  70. package/src/ipc/registerAgentIpc.js +12 -0
  71. package/src/ipc/registerAppIpc.js +20 -0
  72. package/src/ipc/registerTerminalIpc.js +50 -0
  73. package/src/ipc/registerTmuxIpc.js +30 -0
  74. package/src/ipc/registerWorkspaceIpc.js +14 -0
  75. package/src/monitorWorker.js +273 -0
  76. package/src/processMonitor.js +394 -0
  77. package/src/providerRegistry.js +120 -0
  78. package/src/terminalManager.js +438 -0
  79. package/src/tmuxController.js +183 -0
  80. package/src/tmuxMonitor.js +432 -0
  81. package/src/updateManager.js +339 -0
  82. package/src/workspaceStore.js +77 -0
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const path = require('path');
7
+ const net = require('net');
8
+ const { spawn } = require('child_process');
9
+
10
+ const PROVIDERS = new Set(['claude', 'codex', 'gemini', 'grok']);
11
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
12
+
13
+ function usage() {
14
+ return [
15
+ 'LoadToAgent · AI 작업 도우미',
16
+ '',
17
+ '사용법:',
18
+ ' loadtoagent 데스크톱 앱 열기',
19
+ ' loadtoagent open 데스크톱 앱 열기',
20
+ ' loadtoagent run <claude|codex|gemini|grok> [-- 옵션] 앱 브리지에서 AI 실행',
21
+ ' loadtoagent --version 버전 확인',
22
+ '',
23
+ '예시:',
24
+ ' loadtoagent',
25
+ ' loadtoagent run codex',
26
+ ' loadtoagent run claude -- --model claude-sonnet-4-6',
27
+ '',
28
+ '`run` 명령을 사용하려면 LoadToAgent 데스크톱 앱이 열려 있어야 합니다.',
29
+ ].join('\n');
30
+ }
31
+
32
+ function parseCliArguments(argv) {
33
+ const args = [...argv];
34
+ const command = String(args[0] || '').toLowerCase();
35
+ if (!command || command === 'open') return { action: 'open' };
36
+ if (command === '--help' || command === '-h' || command === 'help') return { action: 'help' };
37
+ if (command === '--version' || command === '-v' || command === 'version') return { action: 'version' };
38
+ if (command === 'run') return { action: 'run', ...parseArguments(args) };
39
+ throw new Error(usage());
40
+ }
41
+
42
+ function parseArguments(argv) {
43
+ const args = [...argv];
44
+ if (args[0] !== 'run') throw new Error(usage());
45
+ const provider = String(args[1] || '').toLowerCase();
46
+ if (!PROVIDERS.has(provider)) throw new Error(usage());
47
+ const passthrough = args.slice(2);
48
+ if (passthrough[0] === '--') passthrough.shift();
49
+ return { provider, args: passthrough };
50
+ }
51
+
52
+ function terminalSize() {
53
+ return {
54
+ cols: Math.max(20, Number(process.stdout.columns || 120)),
55
+ rows: Math.max(5, Number(process.stdout.rows || 32)),
56
+ };
57
+ }
58
+
59
+ function desktopLaunchSpec(options = {}) {
60
+ const sourceEnv = options.env || process.env;
61
+ const env = { ...sourceEnv };
62
+ const packagedLauncher = sourceEnv.ELECTRON_RUN_AS_NODE === '1';
63
+ delete env.ELECTRON_RUN_AS_NODE;
64
+ if (packagedLauncher) {
65
+ return { executable: options.execPath || process.execPath, args: [], env };
66
+ }
67
+ const executable = options.electronPath || require('electron');
68
+ return { executable, args: [options.packageRoot || PACKAGE_ROOT], env };
69
+ }
70
+
71
+ function launchDesktop(options = {}) {
72
+ const spec = desktopLaunchSpec(options);
73
+ const spawnProcess = options.spawnProcess || spawn;
74
+ const child = spawnProcess(spec.executable, spec.args, {
75
+ detached: true,
76
+ stdio: 'ignore',
77
+ windowsHide: false,
78
+ env: spec.env,
79
+ });
80
+ child.unref();
81
+ return spec;
82
+ }
83
+
84
+ function readDiscovery(home = os.homedir()) {
85
+ const file = process.env.LOADTOAGENT_BRIDGE_FILE || path.join(home, '.loadtoagent', 'bridge.json');
86
+ let value;
87
+ try { value = JSON.parse(fs.readFileSync(file, 'utf8')); } catch { throw new Error('실행 중인 LoadToAgent 브리지를 찾지 못했습니다. LoadToAgent 프로그램을 먼저 여세요.'); }
88
+ if (!value || value.protocol !== 1 || !value.endpoint || !value.token) throw new Error('LoadToAgent 브리지 정보가 올바르지 않습니다. 프로그램을 다시 시작하세요.');
89
+ return value;
90
+ }
91
+
92
+ function writeFrame(socket, value) {
93
+ socket.write(`${JSON.stringify(value)}\n`, 'utf8');
94
+ }
95
+
96
+ function run(argv = process.argv.slice(2)) {
97
+ const command = parseArguments(argv);
98
+ const discovery = readDiscovery();
99
+ const socket = net.createConnection(discovery.endpoint);
100
+ let buffer = '';
101
+ let raw = false;
102
+ let exitCode = 0;
103
+
104
+ const restore = () => {
105
+ if (raw && process.stdin.isTTY && typeof process.stdin.setRawMode === 'function') {
106
+ try { process.stdin.setRawMode(false); } catch {}
107
+ }
108
+ process.stdin.pause();
109
+ };
110
+ const finish = code => {
111
+ restore();
112
+ process.exitCode = Number.isFinite(code) ? code : exitCode;
113
+ };
114
+ const sendResize = () => writeFrame(socket, { type: 'resize', ...terminalSize() });
115
+
116
+ socket.on('connect', () => writeFrame(socket, {
117
+ type: 'run',
118
+ token: discovery.token,
119
+ provider: command.provider,
120
+ args: command.args,
121
+ cwd: process.cwd(),
122
+ ...terminalSize(),
123
+ }));
124
+ socket.on('data', chunk => {
125
+ buffer += chunk.toString('utf8');
126
+ let newline;
127
+ while ((newline = buffer.indexOf('\n')) >= 0) {
128
+ const line = buffer.slice(0, newline).trim();
129
+ buffer = buffer.slice(newline + 1);
130
+ if (!line) continue;
131
+ let message;
132
+ try { message = JSON.parse(line); } catch { continue; }
133
+ if (message.type === 'started') {
134
+ if (message.replay) process.stdout.write(Buffer.from(message.replay, 'base64'));
135
+ if (process.stdin.isTTY && typeof process.stdin.setRawMode === 'function') {
136
+ process.stdin.setRawMode(true);
137
+ raw = true;
138
+ }
139
+ process.stdin.resume();
140
+ process.stdin.on('data', data => writeFrame(socket, { type: 'input', data: Buffer.from(data).toString('base64') }));
141
+ process.stdout.on('resize', sendResize);
142
+ } else if (message.type === 'output') process.stdout.write(Buffer.from(String(message.data || ''), 'base64'));
143
+ else if (message.type === 'state' && (message.status === 'exited' || message.status === 'failed')) exitCode = Number(message.exitCode || 0);
144
+ else if (message.type === 'error') {
145
+ process.stderr.write(`\nLoadToAgent: ${message.message}\n`);
146
+ exitCode = 1;
147
+ }
148
+ }
149
+ });
150
+ socket.on('error', error => {
151
+ process.stderr.write(`LoadToAgent 연결 실패: ${error.message}\n`);
152
+ exitCode = 1;
153
+ });
154
+ socket.on('close', () => finish(exitCode));
155
+ process.on('SIGTERM', () => { writeFrame(socket, { type: 'signal', signal: 'terminate' }); socket.end(); });
156
+ }
157
+
158
+ if (require.main === module) {
159
+ try {
160
+ const command = parseCliArguments(process.argv.slice(2));
161
+ if (command.action === 'open') launchDesktop();
162
+ else if (command.action === 'help') process.stdout.write(`${usage()}\n`);
163
+ else if (command.action === 'version') process.stdout.write(`${require('../package.json').version}\n`);
164
+ else run(process.argv.slice(2));
165
+ } catch (error) {
166
+ process.stderr.write(`${error.message}\n`);
167
+ process.exitCode = 1;
168
+ }
169
+ }
170
+
171
+ module.exports = { parseArguments, parseCliArguments, desktopLaunchSpec, launchDesktop, readDiscovery, terminalSize, run, usage };
@@ -0,0 +1,30 @@
1
+ # LoadToAgent architecture
2
+
3
+ LoadToAgent keeps Electron process boundaries explicit. `main.js` is the
4
+ composition root: it creates long-lived services, owns the application window,
5
+ and installs small IPC registration modules from `src/ipc/`. Each registration
6
+ module validates the sender through the injected `handleTrusted` boundary.
7
+
8
+ The monitoring pipeline converts provider-specific logs into the shared
9
+ `AgentSession` contract documented in `src/contracts.js`. Provider parsers live
10
+ under `src/agentMonitor/`; `src/agentMonitor.js` coordinates scanning and cache
11
+ state without owning the provider grammars.
12
+
13
+ Renderer code is assembled from explicit factories. `app.js` owns core state
14
+ and shared view helpers, feature factories receive that public context, and
15
+ `app-bootstrap.js` is the only module that installs them. Terminal factories use
16
+ the same pattern. Script order in `renderer/index.html` is therefore a bootstrap
17
+ manifest, not an implicit variable dependency.
18
+
19
+ CSS is loaded in ordered responsibility layers: foundations, shared components,
20
+ workflows, terminal surfaces, product-specific components, then responsive
21
+ overrides. A selector has one authoritative non-responsive definition; only
22
+ state variants and breakpoint adaptations may repeat it.
23
+
24
+ Recoverable main-process failures go through `src/diagnostics.js`. Expected
25
+ best-effort cleanup is logged with an operation name, while user-visible IPC
26
+ failures are returned to the renderer and shown near the initiating action.
27
+
28
+ Regression tests are registered by feature suites in `scripts/tests/` and run
29
+ through a shared harness. Electron integration scripts cover renderer events,
30
+ responsive layouts, the terminal bridge, and real BrowserWindow interaction.
@@ -0,0 +1,58 @@
1
+ # 제공사 이벤트 계약
2
+
3
+ LoadToAgent는 제공사별 이벤트를 아래 공통 단계로 정규화합니다.
4
+
5
+ `queued → session-start → turn-start → reasoning/tool/message → turn-complete → session-end`
6
+
7
+ 상태는 `starting`, `running`, `waiting`, `idle`, `completed`, `failed`, `cancelled` 중 하나입니다. 구조화 완료 이벤트가 없는 외부 세션은 파일 갱신 시각과 마지막 메시지 역할을 이용해 `running`, `waiting`, `idle`을 구분합니다.
8
+
9
+ ## Claude
10
+
11
+ - `--output-format stream-json --verbose --include-partial-messages`의 `system/init`, `assistant`, `stream_event`, `result`를 사용합니다.
12
+ - `SubagentStart`/`SubagentStop` 공식 훅 계약에서 서브에이전트의 별도 transcript와 `agent_id`가 정의되어 있습니다. 로컬 저장소의 `subagents/agent-*.jsonl` 경로를 같은 구조로 해석합니다.
13
+ - assistant message의 `usage`를 request ID별로 한 번만 합산합니다. 최근 request usage는 컨텍스트 사용량, 전체 request usage 합계는 누적 토큰으로 사용합니다.
14
+ - Claude Opus 4.6 이상, Opus 4.7/4.8, Sonnet 4.6/5는 공식 모델 문서 기준 1M 컨텍스트를 사용하고 그 밖의 모델은 200K로 표시합니다. 세션이 한도를 직접 보고하면 관측값이 우선합니다.
15
+
16
+ ## GPT · Codex
17
+
18
+ - `codex exec --json`의 `thread.started`, `turn.started`, `item.*`, `turn.completed`, `turn.failed`, `error`를 사용합니다.
19
+ - 로컬 rollout의 `session_meta`, `turn_context`, `event_msg`, `response_item`을 읽습니다.
20
+ - `event_msg.token_count.info.total_token_usage`는 누적 토큰, `last_token_usage`는 최근 턴, `model_context_window`는 컨텍스트 한도로 사용합니다.
21
+ - `session_meta.source.subagent.thread_spawn.parent_thread_id`를 부모 카드 ID로 사용합니다.
22
+
23
+ ## Gemini
24
+
25
+ - headless `stream-json`의 `init`, `message`, `tool_use`, `tool_result`, `error`, `result` 이벤트를 사용합니다.
26
+ - 공식 세션 관리 문서가 지정한 `~/.gemini/tmp/<project_hash>/chats/`에서 대화, 도구, token stats를 읽습니다.
27
+ - 모델별 한도를 세션이 보고하지 않으면 Gemini 장문 컨텍스트 기본값 1,048,576을 카탈로그 기준값으로 표시합니다. 정확한 값은 Google SDK의 model info 조회 결과가 기록된 경우 그 값을 우선합니다.
28
+
29
+ ## Grok
30
+
31
+ - headless `streaming-json` 이벤트를 기록하고, 세션은 공식 경로인 `~/.grok/sessions`에서 읽습니다.
32
+ - Grok Build 0.1은 256K, Grok 4.5는 500K, Grok 4.3/4.20은 1M 컨텍스트를 공식 모델 카탈로그 기준값으로 사용합니다.
33
+ - Grok ACP의 `session/update`처럼 점진적 agent message가 제공되면 같은 message 스트림으로 합칩니다.
34
+
35
+ ## 정확성 우선순위
36
+
37
+ 1. 세션 이벤트가 직접 보고한 값
38
+ 2. LoadToAgent가 시작한 CLI의 구조화 결과
39
+ 3. 제공사 공식 모델 카탈로그의 모델별 한도
40
+ 4. 값 미보고(`0` 또는 `--`)
41
+
42
+ 누적 토큰과 컨텍스트 점유율은 서로 다른 지표입니다. 누적 토큰은 세션 전체의 사용량 합계이고, 컨텍스트 점유율은 마지막 모델 호출이 현재 모델 창에서 사용한 비율입니다.
43
+
44
+ ## WSL · tmux 연결 계약
45
+
46
+ - `wsl.exe --list --quiet`로 사용자 배포판을 찾고 Docker 전용 배포판은 제외합니다.
47
+ - 각 배포판의 기본 tmux 서버에서 `session → window → pane` 구조와 패널 PID·현재 명령·작업 폴더를 읽습니다.
48
+ - 패널 PID의 전체 자식 프로세스를 따라가 실제 Claude, Codex, Gemini, Grok 실행 파일을 식별합니다. 패널 표면 명령이 `node`, `bash`여도 자식 바이너리가 우선합니다.
49
+ - WSL 사용자 홈의 최신 제공사 기록을 UNC 경로로 읽고 `제공사 + 배포판 + 작업 폴더 + 세션 활동 시각` 점수로 패널에 연결합니다.
50
+ - 연결 점수가 충분하지 않으면 대화나 토큰을 임의 연결하지 않고 AI 프로세스 정보만 표시합니다.
51
+
52
+ ## Windows 실행 프로세스 계약
53
+
54
+ - `claude.exe`, `codex.exe`, Gemini/Grok CLI 및 공식 Node 래퍼만 후보로 조회합니다.
55
+ - Claude/Codex 데스크톱 앱의 Electron 렌더러, GPU, 네트워크 및 app-server 프로세스는 CLI 세션에서 제외합니다.
56
+ - 동일한 CLI의 Node 래퍼와 네이티브 자식 바이너리가 함께 있으면 가장 안쪽 실행 프로세스 하나만 세션으로 계산합니다.
57
+ - 프로세스와 대화 기록을 제공사·환경·시작 시각·최근 활동으로 일대일 연결하며, 연결되지 않은 프로세스도 별도 실행 카드로 표시합니다.
58
+ - Windows 또는 tmux 프로세스가 살아 있는 동안 transcript 갱신 시각과 무관하게 해당 세션을 `작업 중`으로 유지합니다.
Binary file