loadtoagent 1.1.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +14 -5
- package/README.md +7 -0
- package/README.zh-CN.md +7 -0
- package/docs/PROVIDER-CONTRACTS.md +25 -1
- package/docs/UI-AUDIT-100.md +118 -0
- package/docs/UI-AUDIT-101-200.md +120 -0
- package/docs/UI-AUDIT-201-300.md +120 -0
- package/main.js +157 -38
- package/package.json +13 -4
- package/preload.js +13 -2
- package/renderer/app-agent-actions.js +125 -53
- package/renderer/app-bootstrap.js +54 -19
- package/renderer/app-dashboard.js +196 -55
- package/renderer/app-drawer-content.js +48 -44
- package/renderer/app-drawer-data.js +26 -13
- package/renderer/app-drawer.js +72 -54
- package/renderer/app-events-dialogs.js +146 -37
- package/renderer/app-events-filters.js +172 -13
- package/renderer/app-events-navigation.js +77 -30
- package/renderer/app-events-sessions.js +156 -9
- package/renderer/app-events.js +2 -1
- package/renderer/app-graph-model.js +20 -38
- package/renderer/app-graph-orchestration.js +15 -13
- package/renderer/app-graph-view.js +228 -113
- package/renderer/app-management.js +257 -0
- package/renderer/app-provider-visibility.js +124 -0
- package/renderer/app-quality.js +568 -0
- package/renderer/app-run-modal.js +104 -34
- package/renderer/app-runtime-overview.js +312 -0
- package/renderer/app-session-render.js +94 -37
- package/renderer/app-tmux-render.js +52 -44
- package/renderer/app.js +153 -75
- package/renderer/i18n-messages.js +867 -37
- package/renderer/i18n.js +104 -0
- package/renderer/index.html +154 -67
- package/renderer/shared.js +17 -0
- package/renderer/styles-agent-map.css +16 -10
- package/renderer/styles-cards.css +44 -18
- package/renderer/styles-collaboration.css +23 -23
- package/renderer/styles-components.css +122 -45
- package/renderer/styles-management.css +364 -0
- package/renderer/styles-onboarding.css +8 -8
- package/renderer/styles-overlays.css +13 -7
- package/renderer/styles-product.css +48 -40
- package/renderer/styles-quality.css +312 -0
- package/renderer/styles-readability.css +1040 -0
- package/renderer/styles-responsive-product.css +84 -12
- package/renderer/styles-responsive-runtime.css +25 -17
- package/renderer/styles-responsive-shell.css +44 -48
- package/renderer/styles-responsive-workflows.css +39 -5
- package/renderer/styles-run-composer.css +28 -23
- package/renderer/styles-runtime-overview.css +285 -0
- package/renderer/styles-settings.css +176 -16
- package/renderer/styles-terminal.css +374 -37
- package/renderer/styles-tmux.css +49 -49
- package/renderer/styles-workflow-map.css +362 -15
- package/renderer/styles-workflows.css +116 -49
- package/renderer/styles.css +34 -19
- package/renderer/terminal-agent.js +17 -13
- package/renderer/terminal-events.js +253 -37
- package/renderer/terminal-workbench.js +208 -89
- package/renderer/terminal.js +350 -35
- package/src/agentMonitor/claudeParser.js +96 -7
- package/src/agentMonitor/codexParser.js +59 -4
- package/src/agentMonitor/executionActivity.js +282 -0
- package/src/agentMonitor/genericParser.js +56 -13
- package/src/agentMonitor/hierarchy.js +17 -0
- package/src/agentMonitor/responseIntent.js +51 -0
- package/src/agentMonitor.js +21 -3
- package/src/agentRunner.js +66 -1
- package/src/attentionNotifier.js +14 -10
- package/src/automationMonitor.js +258 -0
- package/src/contracts.js +10 -1
- package/src/ipc/registerAgentIpc.js +9 -3
- package/src/ipc/registerAppIpc.js +4 -1
- package/src/ipc/registerTerminalIpc.js +12 -14
- package/src/macUpdateHelper.js +210 -0
- package/src/monitorWorker.js +65 -6
- package/src/platformPath.js +80 -0
- package/src/processMonitor.js +80 -22
- package/src/providerVisibilityStore.js +45 -0
- package/src/sessionIntelligence.js +261 -0
- package/src/terminalHost.js +453 -0
- package/src/terminalHostDaemon.js +61 -0
- package/src/terminalManager.js +218 -6
- package/src/updateInstaller.js +175 -0
|
@@ -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,6 +3,7 @@
|
|
|
3
3
|
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
4
|
|
|
5
5
|
window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(context = {}) {
|
|
6
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
6
7
|
const {
|
|
7
8
|
$,
|
|
8
9
|
esc,
|
|
@@ -16,6 +17,8 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
16
17
|
latestWorkCopy,
|
|
17
18
|
readablePreview,
|
|
18
19
|
timeAgo,
|
|
20
|
+
visibleTmux = () => state.snapshot && state.snapshot.tmux,
|
|
21
|
+
visibleSessions = () => ((state.snapshot && state.snapshot.sessions) || []),
|
|
19
22
|
} = context;
|
|
20
23
|
|
|
21
24
|
function tmuxEntities(tmux) {
|
|
@@ -68,14 +71,14 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
68
71
|
{ type: "distro", id: found.distro.id, label: found.distro.name },
|
|
69
72
|
{ type: "session", id: found.session.id, label: found.session.name },
|
|
70
73
|
{ type: "window", id: found.window.id, label: `${found.window.index}:${found.window.name}` },
|
|
71
|
-
{ type: "pane", id: found.item.id, label:
|
|
74
|
+
{ type: "pane", id: found.item.id, label: t('tmux.pane_label', { index: found.item.index }) },
|
|
72
75
|
]
|
|
73
76
|
: [];
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
function linkedTmuxSubagents(agent) {
|
|
77
80
|
if (!agent || !agent.linkedSessionId) return [];
|
|
78
|
-
const sessions = (
|
|
81
|
+
const sessions = visibleSessions();
|
|
79
82
|
const byId = new Map(sessions.map((session) => [session.id, session]));
|
|
80
83
|
const root = byId.get(agent.linkedSessionId);
|
|
81
84
|
const queue = (root && root.childIds || agent.childIds || []).map((id) => ({ id, depth: 1 }));
|
|
@@ -101,15 +104,15 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
101
104
|
const working = children.filter(({ session }) => subagentWorkState(session) === "working").length;
|
|
102
105
|
const attention = children.filter(({ session }) => subagentWorkState(session) === "attention").length;
|
|
103
106
|
const statusSummary = [
|
|
104
|
-
working ?
|
|
105
|
-
attention ?
|
|
106
|
-
].filter(Boolean).join(" · ") ||
|
|
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');
|
|
107
110
|
const rows = children
|
|
108
111
|
.map(({ session, depth }) => {
|
|
109
112
|
const provider = providerInfo(session.provider);
|
|
110
113
|
const role = session.agentName || agentRoleLabel(session.agentRole);
|
|
111
|
-
const assigned = session.delegation && session.delegation.assignment || session.taskName || session.title ||
|
|
112
|
-
const work = readablePreview(latestWorkCopy(session) || session.statusDetail ||
|
|
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);
|
|
113
116
|
const workState = subagentWorkState(session);
|
|
114
117
|
return `<article class="tmux-subagent-row work-${workState}" data-tmux-subagent-id="${esc(session.id)}"
|
|
115
118
|
style="${providerStyle(session.provider)};--tmux-subagent-depth:${Math.min(2, Math.max(0, depth - 1))}">
|
|
@@ -119,13 +122,13 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
119
122
|
<strong>${esc(assigned)}</strong>
|
|
120
123
|
<em title="${esc(work.full)}">${esc(work.text)}</em>
|
|
121
124
|
</span>
|
|
122
|
-
<button type="button" data-open-subagent-chat="${esc(session.id)}" aria-label="${esc(
|
|
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>
|
|
123
126
|
</article>`;
|
|
124
127
|
})
|
|
125
128
|
.join("");
|
|
126
129
|
return `<section class="tmux-subagents ${expanded ? "expanded" : ""}" data-tmux-subagents="${esc(pane.id)}">
|
|
127
130
|
<button type="button" class="tmux-subagents-toggle" data-tmux-subagents-toggle="${esc(pane.id)}" aria-expanded="${expanded}" aria-controls="${esc(listId)}">
|
|
128
|
-
<span><b
|
|
131
|
+
<span><b>${t('tmux.subagents.connected_count', { count: children.length })}</b><small>${statusSummary} · ${t('tmux.subagents.main_session_basis')}</small></span>
|
|
129
132
|
<i aria-hidden="true">↓</i>
|
|
130
133
|
</button>
|
|
131
134
|
<div id="${esc(listId)}" class="tmux-subagent-list ${expanded ? "" : "hidden"}">${rows}</div>
|
|
@@ -140,26 +143,26 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
140
143
|
const percent = Math.max(0, Math.min(100, Number(context.percent || 0)));
|
|
141
144
|
return `<article class="tmux-pane-node ${pane.active ? "active" : ""} ${pane.dead ? "dead" : ""} ${agent ? "has-agent" : ""}"
|
|
142
145
|
${agent ? `style="${providerStyle(agent.provider)}"` : ""}>
|
|
143
|
-
<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"}">
|
|
144
147
|
<span class="tmux-pane-head">
|
|
145
|
-
<b
|
|
146
|
-
<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>
|
|
147
150
|
</span>
|
|
148
151
|
<strong class="tmux-pane-command">${esc(pane.command || "shell")}</strong>
|
|
149
|
-
<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>
|
|
150
153
|
${
|
|
151
154
|
agent
|
|
152
155
|
? `<span class="tmux-agent-block">
|
|
153
156
|
<span class="provider-mark">${esc(provider.mark)}</span>
|
|
154
157
|
<span>
|
|
155
|
-
<small>${esc(provider.label)} ·
|
|
158
|
+
<small>${esc(provider.label)} · ${t('tmux.process_number', { pid: agent.pid })}</small>
|
|
156
159
|
<strong>${esc(agent.title)}</strong>
|
|
157
|
-
<em>${esc(agent.statusDetail)}</em>
|
|
160
|
+
<em>${esc(window.LoadToAgentI18n.observedText(agent.statusDetail))}</em>
|
|
158
161
|
</span>
|
|
159
162
|
</span>
|
|
160
163
|
<span class="tmux-agent-metrics">
|
|
161
164
|
<span>
|
|
162
|
-
<small
|
|
165
|
+
<small>${t('tmux.context_usage')}</small>
|
|
163
166
|
<b>${context.window ? `${percent.toFixed(1)}%` : "--"}</b>
|
|
164
167
|
</span>
|
|
165
168
|
<span>
|
|
@@ -167,20 +170,20 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
167
170
|
<b>${compact(usage.total)}</b>
|
|
168
171
|
</span>
|
|
169
172
|
<span>
|
|
170
|
-
<small
|
|
173
|
+
<small>${t('tmux.helper_ai')}</small>
|
|
171
174
|
<b>${(agent.childIds || []).length}</b>
|
|
172
175
|
</span>
|
|
173
176
|
</span>
|
|
174
177
|
<span class="tmux-context-track"><i style="width:${percent}%"></i></span>`
|
|
175
|
-
:
|
|
178
|
+
: `<span class="tmux-shell-note">${t('tmux.regular_terminal_note')}</span>`
|
|
176
179
|
}
|
|
177
180
|
</button>
|
|
178
181
|
${tmuxSubagentPanel(pane, agent)}
|
|
179
182
|
<footer>
|
|
180
|
-
<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>
|
|
181
184
|
<span class="tmux-pane-actions">
|
|
182
|
-
<button type="button" data-control-tmux="${esc(pane.id)}"
|
|
183
|
-
${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>` : ""}
|
|
184
187
|
</span>
|
|
185
188
|
</footer>
|
|
186
189
|
</article>`;
|
|
@@ -188,10 +191,10 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
188
191
|
|
|
189
192
|
function tmuxWindowTree(window) {
|
|
190
193
|
return `<div class="tmux-window-tree">
|
|
191
|
-
<button type="button" class="tmux-window-node ${window.active ? "active" : ""}" data-tmux-type="window" data-tmux-id="${esc(window.id)}">
|
|
192
|
-
<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>
|
|
193
196
|
<strong>${window.index + 1}. ${esc(window.name)}</strong>
|
|
194
|
-
<span>${window.panes.length}
|
|
197
|
+
<span>${t('tmux.split_count', { count: window.panes.length })}</span>
|
|
195
198
|
</button>
|
|
196
199
|
<div class="tmux-link-line" aria-hidden="true">
|
|
197
200
|
<i>
|
|
@@ -203,10 +206,10 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
203
206
|
|
|
204
207
|
function tmuxSessionTree(tmuxSession) {
|
|
205
208
|
return `<div class="tmux-session-tree">
|
|
206
|
-
<button type="button" class="tmux-session-node ${tmuxSession.attached ? "attached" : ""}" data-tmux-type="session" data-tmux-id="${esc(tmuxSession.id)}">
|
|
207
|
-
<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>
|
|
208
211
|
<strong>${esc(tmuxSession.name)}</strong>
|
|
209
|
-
<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>
|
|
210
213
|
</button>
|
|
211
214
|
<div class="tmux-link-line session-link" aria-hidden="true">
|
|
212
215
|
<i>
|
|
@@ -252,15 +255,16 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
252
255
|
}
|
|
253
256
|
|
|
254
257
|
function renderTmuxMap() {
|
|
255
|
-
const tmux = (
|
|
258
|
+
const tmux = visibleTmux() || { available: false, status: t('tmux.status.checking'), distros: [], summary: {} };
|
|
256
259
|
const summary = tmux.summary || {};
|
|
260
|
+
const environmentLabel = state.platform?.nativeTmux ? (state.platform.label || t('tmux.local_environment')) : t('tmux.stats.linux_environments');
|
|
257
261
|
$("#tmuxStats").innerHTML = [
|
|
258
|
-
[
|
|
259
|
-
[
|
|
260
|
-
[
|
|
261
|
-
[
|
|
262
|
-
[
|
|
263
|
-
[
|
|
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')],
|
|
264
268
|
]
|
|
265
269
|
.map(
|
|
266
270
|
([label, value, unit], index) => `<div class="${index >= 4 ? "accent" : ""}">
|
|
@@ -273,40 +277,41 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
273
277
|
const index = tmuxEntities(tmux);
|
|
274
278
|
const path = tmuxFocusPath(index);
|
|
275
279
|
$("#tmuxBreadcrumbs").innerHTML = path.length
|
|
276
|
-
? `<button type="button" data-tmux-reset
|
|
280
|
+
? `<button type="button" data-tmux-reset tabindex="-1">${t('tmux.full_list')}</button>${path
|
|
277
281
|
.map(
|
|
278
|
-
(item) => `<i>›</i>
|
|
282
|
+
(item) => `<i aria-hidden="true">›</i>
|
|
279
283
|
<button type="button"
|
|
280
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"'}
|
|
281
286
|
data-tmux-type="${item.type}" data-tmux-id="${esc(item.id)}">
|
|
282
287
|
${esc(item.label)}
|
|
283
288
|
</button>`,
|
|
284
289
|
)
|
|
285
290
|
.join("")}`
|
|
286
291
|
: `<span class="map-hint">
|
|
287
|
-
|
|
292
|
+
${t('tmux.summary', { sessions: `<b>${summary.sessions || 0}</b>`, panes: `<b>${summary.aiPanes || 0}</b>` })}</span>`;
|
|
288
293
|
$("#tmuxResetBtn").classList.toggle("hidden", !path.length);
|
|
289
294
|
const distros = filteredTmuxDistros(tmux, index);
|
|
290
295
|
if (!distros.length || !Number(summary.sessions || 0)) {
|
|
291
296
|
$("#tmuxMap").innerHTML = `<div class="tmux-empty">
|
|
292
297
|
<span>▦</span>
|
|
293
|
-
<h3
|
|
294
|
-
<p>${esc(tmux.status ||
|
|
295
|
-
<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>
|
|
296
301
|
</div>`;
|
|
297
302
|
return;
|
|
298
303
|
}
|
|
299
304
|
$("#tmuxMap").innerHTML = distros
|
|
300
305
|
.map(
|
|
301
306
|
(distro) => `<section class="tmux-distro-group">
|
|
302
|
-
<button type="button" class="tmux-distro-node" data-tmux-type="distro" data-tmux-id="${esc(distro.id)}">
|
|
303
|
-
<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>
|
|
304
309
|
<div>
|
|
305
|
-
<small
|
|
310
|
+
<small>${t('tmux.runtime_environment')}</small>
|
|
306
311
|
<strong>${esc(distro.name)}</strong>
|
|
307
312
|
<em>${esc(distro.tmuxVersion || "tmux")}</em>
|
|
308
313
|
</div>
|
|
309
|
-
<b
|
|
314
|
+
<b>${t('terminal.tmux.workspace_count', { count: distro.sessions.length })}</b>
|
|
310
315
|
</button>
|
|
311
316
|
<div class="tmux-distro-line" aria-hidden="true">
|
|
312
317
|
</div>
|
|
@@ -314,6 +319,9 @@ window.LoadToAgentAppFactories.createTmuxRenderer = function createTmuxRenderer(
|
|
|
314
319
|
</section>`,
|
|
315
320
|
)
|
|
316
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; });
|
|
317
325
|
}
|
|
318
326
|
|
|
319
327
|
return { tmuxEntities, tmuxFocusPath, linkedTmuxSubagents, tmuxPaneCard, tmuxWindowTree, tmuxSessionTree, filteredTmuxDistros, renderTmuxMap };
|