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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
4
|
|
|
5
5
|
window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRenderer(context = {}) {
|
|
6
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
6
7
|
const {
|
|
7
8
|
$,
|
|
8
9
|
esc,
|
|
@@ -27,17 +28,25 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
|
|
|
27
28
|
statusIcon,
|
|
28
29
|
renderProviderRail,
|
|
29
30
|
isProjectlessSession,
|
|
31
|
+
sessionOriginPath,
|
|
30
32
|
sessionWorkspaceLabel,
|
|
31
33
|
renderWorkspaces,
|
|
32
34
|
renderGlobalStats,
|
|
33
35
|
renderUpdateSettings,
|
|
34
36
|
renderProviderOverview,
|
|
35
37
|
renderProviderFilter,
|
|
38
|
+
renderRuntimeOverview,
|
|
39
|
+
renderProviderVisibilitySettings = () => {},
|
|
40
|
+
visibleSnapshot = () => state.snapshot,
|
|
36
41
|
filteredSessions,
|
|
37
42
|
graphFilteredSessions,
|
|
38
43
|
executionModeBadge,
|
|
39
44
|
renderAgentMap,
|
|
40
45
|
renderTmuxMap,
|
|
46
|
+
renderAttentionInbox,
|
|
47
|
+
renderOperationsOverview,
|
|
48
|
+
progressHtml,
|
|
49
|
+
healthHtml,
|
|
41
50
|
} = context;
|
|
42
51
|
|
|
43
52
|
function recentConversation(session) {
|
|
@@ -46,10 +55,10 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
|
|
|
46
55
|
const assistant = [...messages].reverse().find((message) => message.role === "assistant");
|
|
47
56
|
const tool = [...messages].reverse().find((message) => message.role === "tool");
|
|
48
57
|
const rows = [];
|
|
49
|
-
if (user) rows.push({ label: "
|
|
58
|
+
if (user) rows.push({ label: t("session.me"), text: readablePreview(user.text, 140).text, tone: "user" });
|
|
50
59
|
if (assistant) rows.push({ label: providerInfo(session.provider).label, text: readablePreview(assistant.text, 140).text, tone: "assistant" });
|
|
51
|
-
else if (tool) rows.push({ label: tool.title || "
|
|
52
|
-
if (!rows.length) rows.push({ label: "
|
|
60
|
+
else if (tool) rows.push({ label: tool.title || t("session.tool"), text: readablePreview(tool.text, 140).text, tone: "tool" });
|
|
61
|
+
if (!rows.length) rows.push({ label: t("session.status"), text: window.LoadToAgentI18n.observedText(session.statusDetail || t("session.waiting_for_event")), tone: "system" });
|
|
53
62
|
return rows.slice(-2);
|
|
54
63
|
}
|
|
55
64
|
|
|
@@ -60,16 +69,18 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
|
|
|
60
69
|
const activity = currentActivity(session);
|
|
61
70
|
const running = session.status === "running" || session.status === "starting";
|
|
62
71
|
const children = session.childIds || [];
|
|
63
|
-
const model = session.model || "
|
|
72
|
+
const model = session.model || t("session.model_unknown");
|
|
64
73
|
const contextPercent = Math.max(0, Math.min(100, Number(context.percent || 0)));
|
|
65
74
|
const remaining = context.window ? Math.max(0, Number(context.window) - Number(context.used || 0)) : 0;
|
|
66
75
|
const gaugeTone = contextPercent >= 90 ? "critical" : contextPercent >= 75 ? "warning" : "safe";
|
|
67
76
|
const conversation = recentConversation(session);
|
|
68
77
|
const runtime = session.runtimePresence || [];
|
|
69
78
|
const titlePreview = readablePreview(session.title, 96);
|
|
70
|
-
const activityCopy = latestWorkCopy(session) || session.statusDetail || "
|
|
79
|
+
const activityCopy = latestWorkCopy(session) || window.LoadToAgentI18n.observedText(session.statusDetail) || t("session.waiting_for_new_event");
|
|
71
80
|
const activityPreview = readablePreview(activityCopy, 116);
|
|
72
81
|
const accessibleId = `session-${String(session.id || "").replace(/[^a-zA-Z0-9_-]/g, "-")}`;
|
|
82
|
+
const originPath = sessionOriginPath(session);
|
|
83
|
+
const originLabel = sessionWorkspaceLabel(session);
|
|
73
84
|
return `<article class="session-card ${opts.live ? "live-card" : ""} ${statusClass(session.status)} ${session.parentId ? "subagent" : ""}"
|
|
74
85
|
data-session-id="${esc(session.id)}"
|
|
75
86
|
data-motion-key="session:${esc(session.id)}"
|
|
@@ -88,8 +99,9 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
|
|
|
88
99
|
<span>${esc(model)}</span>
|
|
89
100
|
<i>
|
|
90
101
|
</i>
|
|
91
|
-
<span title="${esc(isProjectlessSession(session) ? window.LoadToAgentI18n.t("ui.session_not_linked_to_a_specific_project") :
|
|
92
|
-
${esc(
|
|
102
|
+
<span class="origin-project" title="${esc(isProjectlessSession(session) ? window.LoadToAgentI18n.t("ui.session_not_linked_to_a_specific_project") : originPath)}"
|
|
103
|
+
aria-label="${esc(t("project.origin_named", { name: originLabel }))}">
|
|
104
|
+
<small>${esc(t("project.origin"))}</small><b>${esc(originLabel)}</b>
|
|
93
105
|
</span>${
|
|
94
106
|
session.agentName
|
|
95
107
|
? `<i>
|
|
@@ -99,16 +111,18 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
|
|
|
99
111
|
}</div>
|
|
100
112
|
<div id="${accessibleId}-summary" class="now-strip ${running ? "is-live" : ""}">
|
|
101
113
|
<span class="now-strip-icon">${statusIcon(activity.type)}</span>
|
|
102
|
-
<div><b>${running ? "
|
|
114
|
+
<div><b>${running ? `${esc(t("session.now"))}: ` : ""}${esc(activity.title)}</b><span title="${esc(activityPreview.full)}">${esc(activityPreview.text)}</span></div>
|
|
103
115
|
${running ? '<span class="activity-wave"><i></i><i></i><i></i><i></i><i></i></span>' : ""}
|
|
104
116
|
</div>
|
|
117
|
+
${progressHtml(session, true)}
|
|
118
|
+
${healthHtml(session, true)}
|
|
105
119
|
${
|
|
106
120
|
runtime.length
|
|
107
121
|
? `<div class="runtime-strip">
|
|
108
122
|
<span class="runtime-pulse">
|
|
109
123
|
</span>
|
|
110
|
-
<b
|
|
111
|
-
<span>${esc(runtime.map((item) => item.label ||
|
|
124
|
+
<b>${esc(t("session.running_programs", { count: runtime.length }))}</b>
|
|
125
|
+
<span>${esc(runtime.map((item) => item.label || t("session.program_pid", { pid: item.pid })).join(" · "))}</span>
|
|
112
126
|
</div>`
|
|
113
127
|
: ""
|
|
114
128
|
}
|
|
@@ -118,28 +132,28 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
|
|
|
118
132
|
<div class="context-meter ${gaugeTone}">
|
|
119
133
|
<div class="context-meter-head">
|
|
120
134
|
<div>
|
|
121
|
-
<span
|
|
135
|
+
<span>${esc(t("session.context_usage"))}</span>
|
|
122
136
|
<strong>${context.window ? `${fullNumber(context.used)} / ${fullNumber(context.window)}` : `${fullNumber(context.used)} / --`}</strong>
|
|
123
137
|
</div>
|
|
124
138
|
<b>${context.window ? `${contextPercent.toFixed(1)}%` : "--"}</b>
|
|
125
139
|
</div>
|
|
126
140
|
<div class="context-meter-track"><span style="width:${contextPercent}%"></span><i style="left:75%"></i><i style="left:90%"></i></div>
|
|
127
141
|
<div class="context-meter-foot">
|
|
128
|
-
<span>${context.window ?
|
|
129
|
-
<span
|
|
142
|
+
<span>${esc(context.window ? t("session.context_remaining", { count: compact(remaining) }) : t("session.context_size_unknown"))}</span>
|
|
143
|
+
<span>${esc(t("session.tokens_used_so_far", { count: compact(usage.total) }))}</span>
|
|
130
144
|
</div>
|
|
131
145
|
</div>
|
|
132
146
|
<div class="token-row">
|
|
133
|
-
<div><span
|
|
134
|
-
<div><span
|
|
135
|
-
<div><span
|
|
136
|
-
<div class="total"><span
|
|
147
|
+
<div><span>${esc(t("session.input_tokens"))}</span><b>${compact(usage.input)}</b></div>
|
|
148
|
+
<div><span>${esc(t("session.output_tokens"))}</span><b>${compact(usage.output)}</b></div>
|
|
149
|
+
<div><span>${esc(t("session.cached_tokens"))}</span><b>${compact(usage.cachedInput)}</b></div>
|
|
150
|
+
<div class="total"><span>${esc(t("session.total_tokens"))}</span><b>${compact(usage.total)}</b></div>
|
|
137
151
|
</div>
|
|
138
152
|
${
|
|
139
153
|
children.length
|
|
140
154
|
? `<div class="child-row">
|
|
141
155
|
<b>⑂</b>
|
|
142
|
-
<span
|
|
156
|
+
<span>${esc(t("session.subagents_created", { count: children.length }))}</span>
|
|
143
157
|
<span class="child-dots">${children
|
|
144
158
|
.slice(0, 4)
|
|
145
159
|
.map(() => "<i></i>")
|
|
@@ -148,31 +162,47 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
|
|
|
148
162
|
: ""
|
|
149
163
|
}
|
|
150
164
|
<footer class="card-footer">
|
|
151
|
-
<span class="source-tag">${esc(session.sourceLabel || "
|
|
165
|
+
<span class="source-tag">${esc(window.LoadToAgentI18n.observedText(session.sourceLabel || t("session.local_history")))}</span>
|
|
152
166
|
<span>${esc(timeAgo(session.updatedAt))}</span>
|
|
153
167
|
</footer>
|
|
154
168
|
</article>`;
|
|
155
169
|
}
|
|
156
170
|
|
|
157
|
-
function
|
|
171
|
+
function renderSessionsContent(motionKind = "refresh", deferMotion = false) {
|
|
158
172
|
const previousLayout = deferMotion ? null : captureMotionLayout();
|
|
159
173
|
syncViewChrome();
|
|
160
174
|
renderGuide();
|
|
161
175
|
const tmuxView = state.view === "tmux";
|
|
162
176
|
const terminalView = state.view === "terminal";
|
|
163
177
|
const settingsView = state.view === "settings";
|
|
178
|
+
const runtimeView = state.view === "runtime";
|
|
179
|
+
const attentionView = state.view === "waiting";
|
|
180
|
+
const operationsView = state.view === "all" || state.view === "active";
|
|
181
|
+
const focusedToolView = tmuxView || terminalView || settingsView || runtimeView;
|
|
164
182
|
$("#terminalSection").classList.toggle("hidden", !terminalView);
|
|
165
183
|
$("#tmuxSection").classList.toggle("hidden", !tmuxView);
|
|
166
184
|
$("#settingsSection").classList.toggle("hidden", !settingsView);
|
|
167
|
-
$("#globalStats").classList.toggle("hidden",
|
|
168
|
-
$("#providerOverview").classList.toggle("hidden",
|
|
169
|
-
$("#sessionSection").classList.toggle("hidden",
|
|
185
|
+
$("#globalStats").classList.toggle("hidden", focusedToolView);
|
|
186
|
+
$("#providerOverview").classList.toggle("hidden", focusedToolView || state.view !== "all");
|
|
187
|
+
$("#sessionSection").classList.toggle("hidden", focusedToolView || state.view === "active" || attentionView);
|
|
188
|
+
$("#operationsOverview").classList.toggle("hidden", !operationsView);
|
|
189
|
+
$("#attentionInbox").classList.toggle("hidden", !attentionView);
|
|
190
|
+
if (runtimeView) renderRuntimeOverview();
|
|
191
|
+
$("#automationOverview").classList.toggle("hidden", !runtimeView);
|
|
170
192
|
const guideVisible = state.view === "all" && state.guideExpanded && !state.graphFocusId;
|
|
171
193
|
$("#beginnerGuide").classList.toggle("hidden", !guideVisible);
|
|
172
194
|
$("#guideBtn").setAttribute("aria-expanded", guideVisible ? "true" : "false");
|
|
173
195
|
renderUpdateSettings();
|
|
196
|
+
if (runtimeView) {
|
|
197
|
+
$("#liveSection").classList.add("hidden");
|
|
198
|
+
if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.deactivate();
|
|
199
|
+
if (!deferMotion) playMotionLayout(previousLayout, motionKind);
|
|
200
|
+
if (motionKind === "view") animateVisibleSections();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
174
203
|
if (settingsView) {
|
|
175
204
|
$("#liveSection").classList.add("hidden");
|
|
205
|
+
renderProviderVisibilitySettings();
|
|
176
206
|
if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.deactivate();
|
|
177
207
|
if (!deferMotion) playMotionLayout(previousLayout, motionKind);
|
|
178
208
|
if (motionKind === "view") animateVisibleSections();
|
|
@@ -180,7 +210,7 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
|
|
|
180
210
|
}
|
|
181
211
|
if (terminalView) {
|
|
182
212
|
$("#liveSection").classList.add("hidden");
|
|
183
|
-
if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.activate(
|
|
213
|
+
if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.activate(visibleSnapshot(), state.workspaces, "general");
|
|
184
214
|
if (!deferMotion) playMotionLayout(previousLayout, motionKind);
|
|
185
215
|
if (motionKind === "view") animateVisibleSections();
|
|
186
216
|
return;
|
|
@@ -188,24 +218,32 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
|
|
|
188
218
|
if (tmuxView) {
|
|
189
219
|
$("#liveSection").classList.add("hidden");
|
|
190
220
|
renderTmuxMap();
|
|
191
|
-
if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.activate(
|
|
221
|
+
if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.activate(visibleSnapshot(), state.workspaces, "tmux");
|
|
192
222
|
if (!deferMotion) playMotionLayout(previousLayout, motionKind);
|
|
193
223
|
if (motionKind === "view") animateVisibleSections();
|
|
194
224
|
return;
|
|
195
225
|
}
|
|
196
226
|
if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.deactivate();
|
|
197
227
|
const sessions = filteredSessions();
|
|
228
|
+
if (operationsView) renderOperationsOverview();
|
|
229
|
+
const attentionCount = attentionView ? renderAttentionInbox() : 0;
|
|
198
230
|
const showMap = ["all", "active"].includes(state.view);
|
|
199
231
|
const graphLiveCount = showMap ? renderAgentMap(graphFilteredSessions(), motionKind) : 0;
|
|
200
232
|
const regular = state.view === "all" ? sessions.filter((session) => !isLiveSession(session)) : state.view === "active" ? [] : sessions;
|
|
201
233
|
const visible = regular.slice(0, state.visibleLimit);
|
|
202
|
-
|
|
234
|
+
const resultCount = attentionView ? attentionCount : graphLiveCount + regular.length;
|
|
235
|
+
$("#sessionResultSummary").textContent = window.LoadToAgentI18n.t("quality.results_summary", { count: resultCount });
|
|
236
|
+
const activeEmpty = state.view === "active" && graphLiveCount === 0;
|
|
237
|
+
$("#activeEmptyState").classList.toggle("hidden", !activeEmpty);
|
|
238
|
+
$("#liveSection").classList.toggle("hidden", attentionView || (graphLiveCount === 0 && state.view !== "active"));
|
|
203
239
|
$("#viewTitle").textContent = VIEW_TITLES[state.view] || window.LoadToAgentI18n.t("ui.recent_conversations_and_tasks");
|
|
204
240
|
$("#sessionGrid").innerHTML = visible.map((session) => sessionCard(session)).join("");
|
|
205
241
|
$("#sessionGrid").classList.toggle("hidden", visible.length === 0);
|
|
206
242
|
$("#loadMoreBtn").classList.toggle("hidden", regular.length <= state.visibleLimit);
|
|
207
243
|
$("#loadMoreBtn").textContent = window.LoadToAgentI18n.t("common.remaining", { count: regular.length - state.visibleLimit });
|
|
208
|
-
$("#emptyState").classList.toggle("hidden", graphLiveCount + regular.length !== 0);
|
|
244
|
+
$("#emptyState").classList.toggle("hidden", attentionView || graphLiveCount + regular.length !== 0);
|
|
245
|
+
const hasConditions = Boolean(state.search || state.providerFilters.size || state.workspace !== "all" || state.sort !== "recent");
|
|
246
|
+
$("#emptyClearFiltersBtn").classList.toggle("hidden", resultCount !== 0 || !hasConditions);
|
|
209
247
|
if (graphLiveCount + regular.length === 0) {
|
|
210
248
|
const emptyCopy = state.search
|
|
211
249
|
? [window.LoadToAgentI18n.t("ui.no_search_results"), window.LoadToAgentI18n.t("ui.clear_the_search_or_change_the_ai_and_workspace_filters")]
|
|
@@ -221,17 +259,36 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
|
|
|
221
259
|
if (motionKind === "view") animateVisibleSections();
|
|
222
260
|
}
|
|
223
261
|
|
|
262
|
+
function renderSessions(motionKind = "refresh", deferMotion = false) {
|
|
263
|
+
const restoreScroll = window.LoadToAgentRendererUtils.preserveScrollPositions([".main-stage", ".sidebar"]);
|
|
264
|
+
context.rememberDisclosureStates?.(document);
|
|
265
|
+
try {
|
|
266
|
+
return renderSessionsContent(motionKind, deferMotion);
|
|
267
|
+
} finally {
|
|
268
|
+
context.restoreDisclosureStates?.(document);
|
|
269
|
+
restoreScroll();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
224
273
|
function render(motionKind = "refresh") {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
274
|
+
const restoreScroll = window.LoadToAgentRendererUtils.preserveScrollPositions([".main-stage", ".sidebar"]);
|
|
275
|
+
context.rememberDisclosureStates?.(document);
|
|
276
|
+
try {
|
|
277
|
+
const previousLayout = captureMotionLayout();
|
|
278
|
+
renderProviderRail();
|
|
279
|
+
renderWorkspaces();
|
|
280
|
+
renderGlobalStats();
|
|
281
|
+
renderProviderOverview();
|
|
282
|
+
renderProviderFilter();
|
|
283
|
+
renderProviderVisibilitySettings();
|
|
284
|
+
renderSessions(motionKind, true);
|
|
285
|
+
if (state.selectedId && $("#detailDrawer").classList.contains("open")) context.renderDrawer();
|
|
286
|
+
playMotionLayout(previousLayout, motionKind);
|
|
287
|
+
if (motionKind === "view") animateVisibleSections();
|
|
288
|
+
} finally {
|
|
289
|
+
context.restoreDisclosureStates?.(document);
|
|
290
|
+
restoreScroll();
|
|
291
|
+
}
|
|
235
292
|
}
|
|
236
293
|
|
|
237
294
|
return {
|
|
@@ -3,7 +3,23 @@
|
|
|
3
3
|
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
4
|
|
|
5
5
|
window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(context = {}) {
|
|
6
|
-
const
|
|
6
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
7
|
+
const {
|
|
8
|
+
$,
|
|
9
|
+
esc,
|
|
10
|
+
state,
|
|
11
|
+
compact,
|
|
12
|
+
providerInfo,
|
|
13
|
+
providerStyle,
|
|
14
|
+
agentRoleLabel,
|
|
15
|
+
subagentWorkState,
|
|
16
|
+
subagentWorkLabel,
|
|
17
|
+
latestWorkCopy,
|
|
18
|
+
readablePreview,
|
|
19
|
+
timeAgo,
|
|
20
|
+
visibleTmux = () => state.snapshot && state.snapshot.tmux,
|
|
21
|
+
visibleSessions = () => ((state.snapshot && state.snapshot.sessions) || []),
|
|
22
|
+
} = context;
|
|
7
23
|
|
|
8
24
|
function tmuxEntities(tmux) {
|
|
9
25
|
const distros = new Map();
|
|
@@ -55,11 +71,70 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
55
71
|
{ type: "distro", id: found.distro.id, label: found.distro.name },
|
|
56
72
|
{ type: "session", id: found.session.id, label: found.session.name },
|
|
57
73
|
{ type: "window", id: found.window.id, label: `${found.window.index}:${found.window.name}` },
|
|
58
|
-
{ type: "pane", id: found.item.id, label:
|
|
74
|
+
{ type: "pane", id: found.item.id, label: t('tmux.pane_label', { index: found.item.index }) },
|
|
59
75
|
]
|
|
60
76
|
: [];
|
|
61
77
|
}
|
|
62
78
|
|
|
79
|
+
function linkedTmuxSubagents(agent) {
|
|
80
|
+
if (!agent || !agent.linkedSessionId) return [];
|
|
81
|
+
const sessions = visibleSessions();
|
|
82
|
+
const byId = new Map(sessions.map((session) => [session.id, session]));
|
|
83
|
+
const root = byId.get(agent.linkedSessionId);
|
|
84
|
+
const queue = (root && root.childIds || agent.childIds || []).map((id) => ({ id, depth: 1 }));
|
|
85
|
+
const seen = new Set(root ? [root.id] : []);
|
|
86
|
+
const children = [];
|
|
87
|
+
for (let cursor = 0; cursor < queue.length; cursor += 1) {
|
|
88
|
+
const item = queue[cursor];
|
|
89
|
+
if (!item.id || seen.has(item.id)) continue;
|
|
90
|
+
seen.add(item.id);
|
|
91
|
+
const child = byId.get(item.id);
|
|
92
|
+
if (!child) continue;
|
|
93
|
+
children.push({ session: child, depth: item.depth });
|
|
94
|
+
for (const childId of child.childIds || []) queue.push({ id: childId, depth: item.depth + 1 });
|
|
95
|
+
}
|
|
96
|
+
return children;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function tmuxSubagentPanel(pane, agent) {
|
|
100
|
+
const children = linkedTmuxSubagents(agent);
|
|
101
|
+
if (!children.length) return "";
|
|
102
|
+
const expanded = state.expandedTmuxSubagents.has(pane.id);
|
|
103
|
+
const listId = `tmux-subagents-list-${encodeURIComponent(pane.id)}`;
|
|
104
|
+
const working = children.filter(({ session }) => subagentWorkState(session) === "working").length;
|
|
105
|
+
const attention = children.filter(({ session }) => subagentWorkState(session) === "attention").length;
|
|
106
|
+
const statusSummary = [
|
|
107
|
+
working ? t('tmux.subagents.working_count', { count: working }) : "",
|
|
108
|
+
attention ? t('tmux.subagents.attention_count', { count: attention }) : "",
|
|
109
|
+
].filter(Boolean).join(" · ") || t('tmux.subagents.all_idle');
|
|
110
|
+
const rows = children
|
|
111
|
+
.map(({ session, depth }) => {
|
|
112
|
+
const provider = providerInfo(session.provider);
|
|
113
|
+
const role = session.agentName || agentRoleLabel(session.agentRole);
|
|
114
|
+
const assigned = session.delegation && session.delegation.assignment || session.taskName || session.title || t('tmux.subagents.checking_assignment');
|
|
115
|
+
const work = readablePreview(latestWorkCopy(session) || window.LoadToAgentI18n.observedText(session.statusDetail) || t('tmux.subagents.checking_status'), 96);
|
|
116
|
+
const workState = subagentWorkState(session);
|
|
117
|
+
return `<article class="tmux-subagent-row work-${workState}" data-tmux-subagent-id="${esc(session.id)}"
|
|
118
|
+
style="${providerStyle(session.provider)};--tmux-subagent-depth:${Math.min(2, Math.max(0, depth - 1))}">
|
|
119
|
+
<span class="provider-mark" aria-hidden="true">${esc(provider.mark)}</span>
|
|
120
|
+
<span class="tmux-subagent-copy">
|
|
121
|
+
<span><b>${esc(role)}</b><i>${esc(subagentWorkLabel(session))}</i><small>${esc(timeAgo(session.updatedAt))}</small></span>
|
|
122
|
+
<strong>${esc(assigned)}</strong>
|
|
123
|
+
<em title="${esc(work.full)}">${esc(work.text)}</em>
|
|
124
|
+
</span>
|
|
125
|
+
<button type="button" data-open-subagent-chat="${esc(session.id)}" aria-label="${esc(t('tmux.subagents.view_conversation_aria', { role, assignment: assigned }))}">${t('tmux.subagents.view_conversation')}</button>
|
|
126
|
+
</article>`;
|
|
127
|
+
})
|
|
128
|
+
.join("");
|
|
129
|
+
return `<section class="tmux-subagents ${expanded ? "expanded" : ""}" data-tmux-subagents="${esc(pane.id)}">
|
|
130
|
+
<button type="button" class="tmux-subagents-toggle" data-tmux-subagents-toggle="${esc(pane.id)}" aria-expanded="${expanded}" aria-controls="${esc(listId)}">
|
|
131
|
+
<span><b>${t('tmux.subagents.connected_count', { count: children.length })}</b><small>${statusSummary} · ${t('tmux.subagents.main_session_basis')}</small></span>
|
|
132
|
+
<i aria-hidden="true">↓</i>
|
|
133
|
+
</button>
|
|
134
|
+
<div id="${esc(listId)}" class="tmux-subagent-list ${expanded ? "" : "hidden"}">${rows}</div>
|
|
135
|
+
</section>`;
|
|
136
|
+
}
|
|
137
|
+
|
|
63
138
|
function tmuxPaneCard(pane) {
|
|
64
139
|
const agent = pane.agent;
|
|
65
140
|
const provider = agent && providerInfo(agent.provider);
|
|
@@ -68,26 +143,26 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
68
143
|
const percent = Math.max(0, Math.min(100, Number(context.percent || 0)));
|
|
69
144
|
return `<article class="tmux-pane-node ${pane.active ? "active" : ""} ${pane.dead ? "dead" : ""} ${agent ? "has-agent" : ""}"
|
|
70
145
|
${agent ? `style="${providerStyle(agent.provider)}"` : ""}>
|
|
71
|
-
<button type="button" class="tmux-pane-main" data-tmux-type="pane" data-tmux-id="${esc(pane.id)}">
|
|
146
|
+
<button type="button" class="tmux-pane-main" data-tmux-type="pane" data-tmux-id="${esc(pane.id)}" aria-pressed="${state.tmuxFocus?.type === "pane" && state.tmuxFocus?.id === pane.id ? "true" : "false"}">
|
|
72
147
|
<span class="tmux-pane-head">
|
|
73
|
-
<b
|
|
74
|
-
<i>${pane.dead ?
|
|
148
|
+
<b>${t('tmux.split_pane_number', { number: pane.index + 1 })}</b><span>${t('tmux.process_number', { pid: pane.pid || "--" })}</span>
|
|
149
|
+
<i>${pane.dead ? t('tmux.state.ended') : pane.active ? t('tmux.state.active') : t('tmux.state.background')}</i>
|
|
75
150
|
</span>
|
|
76
151
|
<strong class="tmux-pane-command">${esc(pane.command || "shell")}</strong>
|
|
77
|
-
<span class="tmux-pane-cwd" title="${esc(pane.cwd)}">${esc(pane.cwd ||
|
|
152
|
+
<span class="tmux-pane-cwd" title="${esc(pane.cwd)}">${esc(pane.cwd || t('terminal.path_unreported'))}</span>
|
|
78
153
|
${
|
|
79
154
|
agent
|
|
80
155
|
? `<span class="tmux-agent-block">
|
|
81
156
|
<span class="provider-mark">${esc(provider.mark)}</span>
|
|
82
157
|
<span>
|
|
83
|
-
<small>${esc(provider.label)} ·
|
|
158
|
+
<small>${esc(provider.label)} · ${t('tmux.process_number', { pid: agent.pid })}</small>
|
|
84
159
|
<strong>${esc(agent.title)}</strong>
|
|
85
|
-
<em>${esc(agent.statusDetail)}</em>
|
|
160
|
+
<em>${esc(window.LoadToAgentI18n.observedText(agent.statusDetail))}</em>
|
|
86
161
|
</span>
|
|
87
162
|
</span>
|
|
88
163
|
<span class="tmux-agent-metrics">
|
|
89
164
|
<span>
|
|
90
|
-
<small
|
|
165
|
+
<small>${t('tmux.context_usage')}</small>
|
|
91
166
|
<b>${context.window ? `${percent.toFixed(1)}%` : "--"}</b>
|
|
92
167
|
</span>
|
|
93
168
|
<span>
|
|
@@ -95,19 +170,20 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
95
170
|
<b>${compact(usage.total)}</b>
|
|
96
171
|
</span>
|
|
97
172
|
<span>
|
|
98
|
-
<small
|
|
173
|
+
<small>${t('tmux.helper_ai')}</small>
|
|
99
174
|
<b>${(agent.childIds || []).length}</b>
|
|
100
175
|
</span>
|
|
101
176
|
</span>
|
|
102
177
|
<span class="tmux-context-track"><i style="width:${percent}%"></i></span>`
|
|
103
|
-
:
|
|
178
|
+
: `<span class="tmux-shell-note">${t('tmux.regular_terminal_note')}</span>`
|
|
104
179
|
}
|
|
105
180
|
</button>
|
|
181
|
+
${tmuxSubagentPanel(pane, agent)}
|
|
106
182
|
<footer>
|
|
107
|
-
<span>${agent ? (agent.linkedSessionId ?
|
|
183
|
+
<span>${agent ? (agent.linkedSessionId ? t('tmux.linked_to_history') : t('tmux.ai_detected')) : pane.title || t('terminal.type.terminal')}</span>
|
|
108
184
|
<span class="tmux-pane-actions">
|
|
109
|
-
<button type="button" data-control-tmux="${esc(pane.id)}"
|
|
110
|
-
${agent && agent.linkedSessionId ? `<button type="button" data-open-session="${esc(agent.linkedSessionId)}"
|
|
185
|
+
<button type="button" data-control-tmux="${esc(pane.id)}">${t('tmux.control_pane')}</button>
|
|
186
|
+
${agent && agent.linkedSessionId ? `<button type="button" data-open-session="${esc(agent.linkedSessionId)}">${t('tmux.view_conversation')}</button>` : ""}
|
|
111
187
|
</span>
|
|
112
188
|
</footer>
|
|
113
189
|
</article>`;
|
|
@@ -115,10 +191,10 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
115
191
|
|
|
116
192
|
function tmuxWindowTree(window) {
|
|
117
193
|
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
|
|
194
|
+
<button type="button" class="tmux-window-node ${window.active ? "active" : ""}" data-tmux-type="window" data-tmux-id="${esc(window.id)}" aria-pressed="${state.tmuxFocus?.type === "window" && state.tmuxFocus?.id === window.id ? "true" : "false"}">
|
|
195
|
+
<small>${t('tmux.open_window')}</small>
|
|
120
196
|
<strong>${window.index + 1}. ${esc(window.name)}</strong>
|
|
121
|
-
<span>${window.panes.length}
|
|
197
|
+
<span>${t('tmux.split_count', { count: window.panes.length })}</span>
|
|
122
198
|
</button>
|
|
123
199
|
<div class="tmux-link-line" aria-hidden="true">
|
|
124
200
|
<i>
|
|
@@ -130,10 +206,10 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
130
206
|
|
|
131
207
|
function tmuxSessionTree(tmuxSession) {
|
|
132
208
|
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
|
|
209
|
+
<button type="button" class="tmux-session-node ${tmuxSession.attached ? "attached" : ""}" data-tmux-type="session" data-tmux-id="${esc(tmuxSession.id)}" aria-pressed="${state.tmuxFocus?.type === "session" && state.tmuxFocus?.id === tmuxSession.id ? "true" : "false"}">
|
|
210
|
+
<small>${t('tmux.workspace')}</small>
|
|
135
211
|
<strong>${esc(tmuxSession.name)}</strong>
|
|
136
|
-
<span>${tmuxSession.attached ?
|
|
212
|
+
<span>${tmuxSession.attached ? t('terminal.tmux.attached') : t('terminal.tmux.running_background')} · ${t('tmux.open_window_count', { count: tmuxSession.windows.length })}</span>
|
|
137
213
|
</button>
|
|
138
214
|
<div class="tmux-link-line session-link" aria-hidden="true">
|
|
139
215
|
<i>
|
|
@@ -179,15 +255,16 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
179
255
|
}
|
|
180
256
|
|
|
181
257
|
function renderTmuxMap() {
|
|
182
|
-
const tmux = (
|
|
258
|
+
const tmux = visibleTmux() || { available: false, status: t('tmux.status.checking'), distros: [], summary: {} };
|
|
183
259
|
const summary = tmux.summary || {};
|
|
260
|
+
const environmentLabel = state.platform?.nativeTmux ? (state.platform.label || t('tmux.local_environment')) : t('tmux.stats.linux_environments');
|
|
184
261
|
$("#tmuxStats").innerHTML = [
|
|
185
|
-
[
|
|
186
|
-
[
|
|
187
|
-
[
|
|
188
|
-
[
|
|
189
|
-
[
|
|
190
|
-
[
|
|
262
|
+
[t('tmux.stats.environments'), summary.distros || 0, t('ui.items')],
|
|
263
|
+
[t('tmux.workspace'), summary.sessions || 0, t('ui.items')],
|
|
264
|
+
[t('tmux.open_window'), summary.windows || 0, t('ui.items')],
|
|
265
|
+
[t('tmux.stats.split_panes'), summary.panes || 0, t('ui.items')],
|
|
266
|
+
[t('tmux.stats.ai_panes'), summary.aiPanes || 0, t('ui.items')],
|
|
267
|
+
[t('tmux.stats.linked_history'), summary.linked || 0, t('ui.items')],
|
|
191
268
|
]
|
|
192
269
|
.map(
|
|
193
270
|
([label, value, unit], index) => `<div class="${index >= 4 ? "accent" : ""}">
|
|
@@ -200,40 +277,41 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
200
277
|
const index = tmuxEntities(tmux);
|
|
201
278
|
const path = tmuxFocusPath(index);
|
|
202
279
|
$("#tmuxBreadcrumbs").innerHTML = path.length
|
|
203
|
-
? `<button type="button" data-tmux-reset
|
|
280
|
+
? `<button type="button" data-tmux-reset tabindex="-1">${t('tmux.full_list')}</button>${path
|
|
204
281
|
.map(
|
|
205
|
-
(item) => `<i>›</i>
|
|
282
|
+
(item) => `<i aria-hidden="true">›</i>
|
|
206
283
|
<button type="button"
|
|
207
284
|
class="${item.type === state.tmuxFocus.type && item.id === state.tmuxFocus.id ? "current" : ""}"
|
|
285
|
+
${item.type === state.tmuxFocus.type && item.id === state.tmuxFocus.id ? 'aria-current="location" tabindex="0"' : 'tabindex="-1"'}
|
|
208
286
|
data-tmux-type="${item.type}" data-tmux-id="${esc(item.id)}">
|
|
209
287
|
${esc(item.label)}
|
|
210
288
|
</button>`,
|
|
211
289
|
)
|
|
212
290
|
.join("")}`
|
|
213
291
|
: `<span class="map-hint">
|
|
214
|
-
|
|
292
|
+
${t('tmux.summary', { sessions: `<b>${summary.sessions || 0}</b>`, panes: `<b>${summary.aiPanes || 0}</b>` })}</span>`;
|
|
215
293
|
$("#tmuxResetBtn").classList.toggle("hidden", !path.length);
|
|
216
294
|
const distros = filteredTmuxDistros(tmux, index);
|
|
217
295
|
if (!distros.length || !Number(summary.sessions || 0)) {
|
|
218
296
|
$("#tmuxMap").innerHTML = `<div class="tmux-empty">
|
|
219
297
|
<span>▦</span>
|
|
220
|
-
<h3
|
|
221
|
-
<p>${esc(tmux.status ||
|
|
222
|
-
<small
|
|
298
|
+
<h3>${t('tmux.empty.title')}</h3>
|
|
299
|
+
<p>${esc(window.LoadToAgentI18n.observedText(tmux.status || t('tmux.empty.checking_linux')))}</p>
|
|
300
|
+
<small>${t('tmux.empty.description')}</small>
|
|
223
301
|
</div>`;
|
|
224
302
|
return;
|
|
225
303
|
}
|
|
226
304
|
$("#tmuxMap").innerHTML = distros
|
|
227
305
|
.map(
|
|
228
306
|
(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
|
|
307
|
+
<button type="button" class="tmux-distro-node" data-tmux-type="distro" data-tmux-id="${esc(distro.id)}" aria-pressed="${state.tmuxFocus?.type === "distro" && state.tmuxFocus?.id === distro.id ? "true" : "false"}">
|
|
308
|
+
<span>${esc(environmentLabel)}</span>
|
|
231
309
|
<div>
|
|
232
|
-
<small
|
|
310
|
+
<small>${t('tmux.runtime_environment')}</small>
|
|
233
311
|
<strong>${esc(distro.name)}</strong>
|
|
234
312
|
<em>${esc(distro.tmuxVersion || "tmux")}</em>
|
|
235
313
|
</div>
|
|
236
|
-
<b
|
|
314
|
+
<b>${t('terminal.tmux.workspace_count', { count: distro.sessions.length })}</b>
|
|
237
315
|
</button>
|
|
238
316
|
<div class="tmux-distro-line" aria-hidden="true">
|
|
239
317
|
</div>
|
|
@@ -241,7 +319,10 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
241
319
|
</section>`,
|
|
242
320
|
)
|
|
243
321
|
.join("");
|
|
322
|
+
const mapNodes = Array.from($("#tmuxMap").querySelectorAll("[data-tmux-type][data-tmux-id]"));
|
|
323
|
+
const focusedNode = mapNodes.find((node) => node.dataset.tmuxType === state.tmuxFocus?.type && node.dataset.tmuxId === state.tmuxFocus?.id) || mapNodes[0];
|
|
324
|
+
mapNodes.forEach((node) => { node.tabIndex = node === focusedNode ? 0 : -1; });
|
|
244
325
|
}
|
|
245
326
|
|
|
246
|
-
return { tmuxEntities, tmuxFocusPath, tmuxPaneCard, tmuxWindowTree, tmuxSessionTree, filteredTmuxDistros, renderTmuxMap };
|
|
327
|
+
return { tmuxEntities, tmuxFocusPath, linkedTmuxSubagents, tmuxPaneCard, tmuxWindowTree, tmuxSessionTree, filteredTmuxDistros, renderTmuxMap };
|
|
247
328
|
};
|