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.
- package/README.ko.md +175 -0
- package/README.md +175 -0
- package/README.zh-CN.md +175 -0
- package/bin/loadtoagent.js +171 -0
- package/docs/ARCHITECTURE.md +30 -0
- package/docs/PROVIDER-CONTRACTS.md +58 -0
- package/docs/assets/loadtoagent-dashboard.png +0 -0
- package/docs/assets/loadtoagent-demo.gif +0 -0
- package/main.js +493 -0
- package/package.json +133 -0
- package/preload.js +75 -0
- package/renderer/app-agent-actions.js +285 -0
- package/renderer/app-bootstrap.js +95 -0
- package/renderer/app-dashboard.js +361 -0
- package/renderer/app-drawer-content.js +203 -0
- package/renderer/app-drawer-data.js +41 -0
- package/renderer/app-drawer.js +183 -0
- package/renderer/app-events-dialogs.js +169 -0
- package/renderer/app-events-filters.js +74 -0
- package/renderer/app-events-navigation.js +96 -0
- package/renderer/app-events-sessions.js +195 -0
- package/renderer/app-events.js +16 -0
- package/renderer/app-graph-layout.js +71 -0
- package/renderer/app-graph-model.js +107 -0
- package/renderer/app-graph-orchestration.js +76 -0
- package/renderer/app-graph-view.js +544 -0
- package/renderer/app-run-modal.js +221 -0
- package/renderer/app-session-render.js +243 -0
- package/renderer/app-tmux-render.js +247 -0
- package/renderer/app.js +608 -0
- package/renderer/fonts/geist-ext.woff2 +0 -0
- package/renderer/fonts/geist.woff2 +0 -0
- package/renderer/i18n-messages.js +355 -0
- package/renderer/i18n.js +122 -0
- package/renderer/index.html +386 -0
- package/renderer/shared.js +26 -0
- package/renderer/styles-agent-map.css +401 -0
- package/renderer/styles-cards.css +600 -0
- package/renderer/styles-collaboration.css +534 -0
- package/renderer/styles-components.css +1293 -0
- package/renderer/styles-onboarding.css +344 -0
- package/renderer/styles-overlays.css +284 -0
- package/renderer/styles-product.css +686 -0
- package/renderer/styles-responsive-product.css +689 -0
- package/renderer/styles-responsive-runtime.css +718 -0
- package/renderer/styles-responsive-shell.css +533 -0
- package/renderer/styles-responsive-workflows.css +778 -0
- package/renderer/styles-run-composer.css +695 -0
- package/renderer/styles-settings.css +606 -0
- package/renderer/styles-terminal.css +1241 -0
- package/renderer/styles-tmux.css +1162 -0
- package/renderer/styles-workflow-map.css +513 -0
- package/renderer/styles-workflows.css +1217 -0
- package/renderer/styles.css +486 -0
- package/renderer/terminal-agent.js +152 -0
- package/renderer/terminal-events.js +226 -0
- package/renderer/terminal-workbench.js +464 -0
- package/renderer/terminal.js +497 -0
- package/src/agentMonitor/claudeParser.js +197 -0
- package/src/agentMonitor/codexCollaboration.js +95 -0
- package/src/agentMonitor/codexParser.js +447 -0
- package/src/agentMonitor/genericParser.js +244 -0
- package/src/agentMonitor/hierarchy.js +248 -0
- package/src/agentMonitor/sessionFiles.js +86 -0
- package/src/agentMonitor.js +591 -0
- package/src/agentRunner.js +465 -0
- package/src/bridgeServer.js +246 -0
- package/src/contracts.js +71 -0
- package/src/diagnostics.js +23 -0
- package/src/ipc/registerAgentIpc.js +12 -0
- package/src/ipc/registerAppIpc.js +20 -0
- package/src/ipc/registerTerminalIpc.js +50 -0
- package/src/ipc/registerTmuxIpc.js +30 -0
- package/src/ipc/registerWorkspaceIpc.js +14 -0
- package/src/monitorWorker.js +273 -0
- package/src/processMonitor.js +394 -0
- package/src/providerRegistry.js +120 -0
- package/src/terminalManager.js +438 -0
- package/src/tmuxController.js +183 -0
- package/src/tmuxMonitor.js +432 -0
- package/src/updateManager.js +339 -0
- package/src/workspaceStore.js +77 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
|
+
|
|
5
|
+
window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(context = {}) {
|
|
6
|
+
const { $, esc, state, compact, providerInfo, providerStyle } = context;
|
|
7
|
+
|
|
8
|
+
function tmuxEntities(tmux) {
|
|
9
|
+
const distros = new Map();
|
|
10
|
+
const sessions = new Map();
|
|
11
|
+
const windows = new Map();
|
|
12
|
+
const panes = new Map();
|
|
13
|
+
for (const distro of (tmux && tmux.distros) || []) {
|
|
14
|
+
distros.set(distro.id, distro);
|
|
15
|
+
for (const tmuxSession of distro.sessions || []) {
|
|
16
|
+
sessions.set(tmuxSession.id, { item: tmuxSession, distro });
|
|
17
|
+
for (const window of tmuxSession.windows || []) {
|
|
18
|
+
windows.set(window.id, { item: window, session: tmuxSession, distro });
|
|
19
|
+
for (const pane of window.panes || []) panes.set(pane.id, { item: pane, window, session: tmuxSession, distro });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { distros, sessions, windows, panes };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function tmuxFocusPath(index) {
|
|
27
|
+
const focus = state.tmuxFocus;
|
|
28
|
+
if (!focus) return [];
|
|
29
|
+
if (focus.type === "distro") {
|
|
30
|
+
const distro = index.distros.get(focus.id);
|
|
31
|
+
return distro ? [{ type: "distro", id: distro.id, label: distro.name }] : [];
|
|
32
|
+
}
|
|
33
|
+
if (focus.type === "session") {
|
|
34
|
+
const found = index.sessions.get(focus.id);
|
|
35
|
+
return found
|
|
36
|
+
? [
|
|
37
|
+
{ type: "distro", id: found.distro.id, label: found.distro.name },
|
|
38
|
+
{ type: "session", id: found.item.id, label: found.item.name },
|
|
39
|
+
]
|
|
40
|
+
: [];
|
|
41
|
+
}
|
|
42
|
+
if (focus.type === "window") {
|
|
43
|
+
const found = index.windows.get(focus.id);
|
|
44
|
+
return found
|
|
45
|
+
? [
|
|
46
|
+
{ type: "distro", id: found.distro.id, label: found.distro.name },
|
|
47
|
+
{ type: "session", id: found.session.id, label: found.session.name },
|
|
48
|
+
{ type: "window", id: found.item.id, label: `${found.item.index}:${found.item.name}` },
|
|
49
|
+
]
|
|
50
|
+
: [];
|
|
51
|
+
}
|
|
52
|
+
const found = index.panes.get(focus.id);
|
|
53
|
+
return found
|
|
54
|
+
? [
|
|
55
|
+
{ type: "distro", id: found.distro.id, label: found.distro.name },
|
|
56
|
+
{ type: "session", id: found.session.id, label: found.session.name },
|
|
57
|
+
{ type: "window", id: found.window.id, label: `${found.window.index}:${found.window.name}` },
|
|
58
|
+
{ type: "pane", id: found.item.id, label: `pane ${found.item.index}` },
|
|
59
|
+
]
|
|
60
|
+
: [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function tmuxPaneCard(pane) {
|
|
64
|
+
const agent = pane.agent;
|
|
65
|
+
const provider = agent && providerInfo(agent.provider);
|
|
66
|
+
const context = (agent && agent.context) || {};
|
|
67
|
+
const usage = (agent && agent.usage) || {};
|
|
68
|
+
const percent = Math.max(0, Math.min(100, Number(context.percent || 0)));
|
|
69
|
+
return `<article class="tmux-pane-node ${pane.active ? "active" : ""} ${pane.dead ? "dead" : ""} ${agent ? "has-agent" : ""}"
|
|
70
|
+
${agent ? `style="${providerStyle(agent.provider)}"` : ""}>
|
|
71
|
+
<button type="button" class="tmux-pane-main" data-tmux-type="pane" data-tmux-id="${esc(pane.id)}">
|
|
72
|
+
<span class="tmux-pane-head">
|
|
73
|
+
<b>나눠진 칸 ${pane.index + 1}</b><span>프로그램 ${pane.pid || "--"}</span>
|
|
74
|
+
<i>${pane.dead ? "끝남" : pane.active ? "사용 중" : "뒤에서 실행"}</i>
|
|
75
|
+
</span>
|
|
76
|
+
<strong class="tmux-pane-command">${esc(pane.command || "shell")}</strong>
|
|
77
|
+
<span class="tmux-pane-cwd" title="${esc(pane.cwd)}">${esc(pane.cwd || "경로 미보고")}</span>
|
|
78
|
+
${
|
|
79
|
+
agent
|
|
80
|
+
? `<span class="tmux-agent-block">
|
|
81
|
+
<span class="provider-mark">${esc(provider.mark)}</span>
|
|
82
|
+
<span>
|
|
83
|
+
<small>${esc(provider.label)} · 실행 번호 ${agent.pid}</small>
|
|
84
|
+
<strong>${esc(agent.title)}</strong>
|
|
85
|
+
<em>${esc(agent.statusDetail)}</em>
|
|
86
|
+
</span>
|
|
87
|
+
</span>
|
|
88
|
+
<span class="tmux-agent-metrics">
|
|
89
|
+
<span>
|
|
90
|
+
<small>기억 사용</small>
|
|
91
|
+
<b>${context.window ? `${percent.toFixed(1)}%` : "--"}</b>
|
|
92
|
+
</span>
|
|
93
|
+
<span>
|
|
94
|
+
<small>${window.LoadToAgentI18n.t("ui.tokens_used_2")}</small>
|
|
95
|
+
<b>${compact(usage.total)}</b>
|
|
96
|
+
</span>
|
|
97
|
+
<span>
|
|
98
|
+
<small>도움 AI</small>
|
|
99
|
+
<b>${(agent.childIds || []).length}</b>
|
|
100
|
+
</span>
|
|
101
|
+
</span>
|
|
102
|
+
<span class="tmux-context-track"><i style="width:${percent}%"></i></span>`
|
|
103
|
+
: '<span class="tmux-shell-note">AI가 아닌 일반 명령창입니다.</span>'
|
|
104
|
+
}
|
|
105
|
+
</button>
|
|
106
|
+
<footer>
|
|
107
|
+
<span>${agent ? (agent.linkedSessionId ? "대화 기록과 연결됨" : "AI가 실행 중인 것을 확인함") : pane.title || "명령창"}</span>
|
|
108
|
+
<span class="tmux-pane-actions">
|
|
109
|
+
<button type="button" data-control-tmux="${esc(pane.id)}">이 칸 조작하기 ↓</button>
|
|
110
|
+
${agent && agent.linkedSessionId ? `<button type="button" data-open-session="${esc(agent.linkedSessionId)}">대화 내용 보기 ↗</button>` : ""}
|
|
111
|
+
</span>
|
|
112
|
+
</footer>
|
|
113
|
+
</article>`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function tmuxWindowTree(window) {
|
|
117
|
+
return `<div class="tmux-window-tree">
|
|
118
|
+
<button type="button" class="tmux-window-node ${window.active ? "active" : ""}" data-tmux-type="window" data-tmux-id="${esc(window.id)}">
|
|
119
|
+
<small>열린 창</small>
|
|
120
|
+
<strong>${window.index + 1}. ${esc(window.name)}</strong>
|
|
121
|
+
<span>${window.panes.length}개 칸으로 나눔</span>
|
|
122
|
+
</button>
|
|
123
|
+
<div class="tmux-link-line" aria-hidden="true">
|
|
124
|
+
<i>
|
|
125
|
+
</i>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="tmux-pane-stack">${window.panes.map(tmuxPaneCard).join("")}</div>
|
|
128
|
+
</div>`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function tmuxSessionTree(tmuxSession) {
|
|
132
|
+
return `<div class="tmux-session-tree">
|
|
133
|
+
<button type="button" class="tmux-session-node ${tmuxSession.attached ? "attached" : ""}" data-tmux-type="session" data-tmux-id="${esc(tmuxSession.id)}">
|
|
134
|
+
<small>작업 묶음</small>
|
|
135
|
+
<strong>${esc(tmuxSession.name)}</strong>
|
|
136
|
+
<span>${tmuxSession.attached ? "화면에 연결됨" : "뒤에서 실행 중"} · 열린 창 ${tmuxSession.windows.length}개</span>
|
|
137
|
+
</button>
|
|
138
|
+
<div class="tmux-link-line session-link" aria-hidden="true">
|
|
139
|
+
<i>
|
|
140
|
+
</i>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="tmux-window-stack">${tmuxSession.windows.map(tmuxWindowTree).join("")}</div>
|
|
143
|
+
</div>`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function filteredTmuxDistros(tmux, index) {
|
|
147
|
+
if (!state.tmuxFocus) return tmux.distros || [];
|
|
148
|
+
const path = tmuxFocusPath(index);
|
|
149
|
+
if (!path.length) {
|
|
150
|
+
state.tmuxFocus = null;
|
|
151
|
+
return tmux.distros || [];
|
|
152
|
+
}
|
|
153
|
+
const distroId = path[0].id;
|
|
154
|
+
return (tmux.distros || [])
|
|
155
|
+
.filter((distro) => distro.id === distroId)
|
|
156
|
+
.map((distro) => ({
|
|
157
|
+
...distro,
|
|
158
|
+
sessions: (distro.sessions || [])
|
|
159
|
+
.filter((tmuxSession) => {
|
|
160
|
+
const target = path.find((item) => item.type === "session");
|
|
161
|
+
return !target || tmuxSession.id === target.id;
|
|
162
|
+
})
|
|
163
|
+
.map((tmuxSession) => ({
|
|
164
|
+
...tmuxSession,
|
|
165
|
+
windows: (tmuxSession.windows || [])
|
|
166
|
+
.filter((window) => {
|
|
167
|
+
const target = path.find((item) => item.type === "window");
|
|
168
|
+
return !target || window.id === target.id;
|
|
169
|
+
})
|
|
170
|
+
.map((window) => ({
|
|
171
|
+
...window,
|
|
172
|
+
panes: (window.panes || []).filter((pane) => {
|
|
173
|
+
const target = path.find((item) => item.type === "pane");
|
|
174
|
+
return !target || pane.id === target.id;
|
|
175
|
+
}),
|
|
176
|
+
})),
|
|
177
|
+
})),
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function renderTmuxMap() {
|
|
182
|
+
const tmux = (state.snapshot && state.snapshot.tmux) || { available: false, status: "확인 중", distros: [], summary: {} };
|
|
183
|
+
const summary = tmux.summary || {};
|
|
184
|
+
$("#tmuxStats").innerHTML = [
|
|
185
|
+
["Linux 환경", summary.distros || 0, window.LoadToAgentI18n.t("ui.items")],
|
|
186
|
+
["작업 묶음", summary.sessions || 0, window.LoadToAgentI18n.t("ui.items")],
|
|
187
|
+
["열린 창", summary.windows || 0, window.LoadToAgentI18n.t("ui.items")],
|
|
188
|
+
["나눠진 칸", summary.panes || 0, window.LoadToAgentI18n.t("ui.items")],
|
|
189
|
+
["AI가 일하는 칸", summary.aiPanes || 0, window.LoadToAgentI18n.t("ui.items")],
|
|
190
|
+
["대화 기록 연결", summary.linked || 0, window.LoadToAgentI18n.t("ui.items")],
|
|
191
|
+
]
|
|
192
|
+
.map(
|
|
193
|
+
([label, value, unit], index) => `<div class="${index >= 4 ? "accent" : ""}">
|
|
194
|
+
<span>${label}</span>
|
|
195
|
+
<strong>${value}</strong>
|
|
196
|
+
<small>${unit}</small>
|
|
197
|
+
</div>`,
|
|
198
|
+
)
|
|
199
|
+
.join("");
|
|
200
|
+
const index = tmuxEntities(tmux);
|
|
201
|
+
const path = tmuxFocusPath(index);
|
|
202
|
+
$("#tmuxBreadcrumbs").innerHTML = path.length
|
|
203
|
+
? `<button type="button" data-tmux-reset>전체 목록</button>${path
|
|
204
|
+
.map(
|
|
205
|
+
(item) => `<i>›</i>
|
|
206
|
+
<button type="button"
|
|
207
|
+
class="${item.type === state.tmuxFocus.type && item.id === state.tmuxFocus.id ? "current" : ""}"
|
|
208
|
+
data-tmux-type="${item.type}" data-tmux-id="${esc(item.id)}">
|
|
209
|
+
${esc(item.label)}
|
|
210
|
+
</button>`,
|
|
211
|
+
)
|
|
212
|
+
.join("")}`
|
|
213
|
+
: `<span class="map-hint">
|
|
214
|
+
<b>${summary.sessions || 0}</b>개 작업 묶음 · <b>${summary.aiPanes || 0}</b>개 칸에서 AI가 일하는 중</span>`;
|
|
215
|
+
$("#tmuxResetBtn").classList.toggle("hidden", !path.length);
|
|
216
|
+
const distros = filteredTmuxDistros(tmux, index);
|
|
217
|
+
if (!distros.length || !Number(summary.sessions || 0)) {
|
|
218
|
+
$("#tmuxMap").innerHTML = `<div class="tmux-empty">
|
|
219
|
+
<span>▦</span>
|
|
220
|
+
<h3>나눠서 실행 중인 명령창이 없습니다</h3>
|
|
221
|
+
<p>${esc(tmux.status || "Linux 명령창 상태를 확인하는 중입니다.")}</p>
|
|
222
|
+
<small>이 화면은 tmux를 사용하는 고급 작업이 있을 때 자동으로 채워집니다.</small>
|
|
223
|
+
</div>`;
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
$("#tmuxMap").innerHTML = distros
|
|
227
|
+
.map(
|
|
228
|
+
(distro) => `<section class="tmux-distro-group">
|
|
229
|
+
<button type="button" class="tmux-distro-node" data-tmux-type="distro" data-tmux-id="${esc(distro.id)}">
|
|
230
|
+
<span>Linux</span>
|
|
231
|
+
<div>
|
|
232
|
+
<small>실행 환경</small>
|
|
233
|
+
<strong>${esc(distro.name)}</strong>
|
|
234
|
+
<em>${esc(distro.tmuxVersion || "tmux")}</em>
|
|
235
|
+
</div>
|
|
236
|
+
<b>작업 묶음 ${distro.sessions.length}개</b>
|
|
237
|
+
</button>
|
|
238
|
+
<div class="tmux-distro-line" aria-hidden="true">
|
|
239
|
+
</div>
|
|
240
|
+
<div class="tmux-session-stack">${distro.sessions.map(tmuxSessionTree).join("")}</div>
|
|
241
|
+
</section>`,
|
|
242
|
+
)
|
|
243
|
+
.join("");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return { tmuxEntities, tmuxFocusPath, tmuxPaneCard, tmuxWindowTree, tmuxSessionTree, filteredTmuxDistros, renderTmuxMap };
|
|
247
|
+
};
|