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.
Files changed (86) hide show
  1. package/README.ko.md +7 -0
  2. package/README.md +7 -0
  3. package/README.zh-CN.md +7 -0
  4. package/docs/PROVIDER-CONTRACTS.md +23 -1
  5. package/docs/UI-AUDIT-100.md +118 -0
  6. package/docs/UI-AUDIT-101-200.md +120 -0
  7. package/docs/UI-AUDIT-201-300.md +120 -0
  8. package/main.js +183 -37
  9. package/package.json +15 -4
  10. package/preload.js +11 -0
  11. package/renderer/app-agent-actions.js +125 -53
  12. package/renderer/app-bootstrap.js +54 -9
  13. package/renderer/app-dashboard.js +197 -58
  14. package/renderer/app-drawer-content.js +103 -86
  15. package/renderer/app-drawer-data.js +26 -13
  16. package/renderer/app-drawer.js +79 -55
  17. package/renderer/app-events-dialogs.js +146 -37
  18. package/renderer/app-events-filters.js +172 -9
  19. package/renderer/app-events-navigation.js +78 -31
  20. package/renderer/app-events-sessions.js +171 -4
  21. package/renderer/app-events.js +2 -1
  22. package/renderer/app-graph-model.js +32 -4
  23. package/renderer/app-graph-orchestration.js +16 -13
  24. package/renderer/app-graph-view.js +228 -112
  25. package/renderer/app-management.js +211 -0
  26. package/renderer/app-provider-visibility.js +124 -0
  27. package/renderer/app-quality.js +568 -0
  28. package/renderer/app-run-modal.js +103 -33
  29. package/renderer/app-runtime-overview.js +312 -0
  30. package/renderer/app-session-render.js +94 -37
  31. package/renderer/app-tmux-render.js +119 -38
  32. package/renderer/app.js +155 -76
  33. package/renderer/i18n-messages.js +833 -15
  34. package/renderer/i18n.js +104 -0
  35. package/renderer/index.html +162 -75
  36. package/renderer/shared.js +17 -0
  37. package/renderer/styles-agent-map.css +50 -1
  38. package/renderer/styles-cards.css +26 -0
  39. package/renderer/styles-collaboration.css +18 -88
  40. package/renderer/styles-components.css +126 -14
  41. package/renderer/styles-management.css +355 -0
  42. package/renderer/styles-overlays.css +13 -2
  43. package/renderer/styles-product.css +24 -16
  44. package/renderer/styles-quality.css +312 -0
  45. package/renderer/styles-readability.css +862 -0
  46. package/renderer/styles-responsive-product.css +84 -12
  47. package/renderer/styles-responsive-runtime.css +31 -14
  48. package/renderer/styles-responsive-shell.css +44 -48
  49. package/renderer/styles-responsive-workflows.css +37 -17
  50. package/renderer/styles-run-composer.css +5 -0
  51. package/renderer/styles-runtime-overview.css +285 -0
  52. package/renderer/styles-settings.css +160 -0
  53. package/renderer/styles-terminal.css +339 -2
  54. package/renderer/styles-tmux.css +154 -0
  55. package/renderer/styles-workflow-map.css +362 -15
  56. package/renderer/styles-workflows.css +69 -2
  57. package/renderer/styles.css +27 -12
  58. package/renderer/terminal-agent.js +17 -13
  59. package/renderer/terminal-events.js +233 -40
  60. package/renderer/terminal-workbench.js +176 -78
  61. package/renderer/terminal.js +350 -37
  62. package/src/agentMonitor/claudeParser.js +96 -7
  63. package/src/agentMonitor/codexParser.js +59 -4
  64. package/src/agentMonitor/executionActivity.js +282 -0
  65. package/src/agentMonitor/genericParser.js +56 -13
  66. package/src/agentMonitor/hierarchy.js +17 -0
  67. package/src/agentMonitor/responseIntent.js +51 -0
  68. package/src/agentMonitor.js +21 -3
  69. package/src/agentRunner.js +66 -1
  70. package/src/attentionNotifier.js +71 -0
  71. package/src/automationMonitor.js +258 -0
  72. package/src/contracts.js +10 -1
  73. package/src/ipc/registerAgentIpc.js +9 -3
  74. package/src/ipc/registerAppIpc.js +4 -1
  75. package/src/ipc/registerTerminalIpc.js +12 -6
  76. package/src/macUpdateHelper.js +210 -0
  77. package/src/monitorWorker.js +65 -6
  78. package/src/platformPath.js +80 -0
  79. package/src/processMonitor.js +81 -23
  80. package/src/providerVisibilityStore.js +45 -0
  81. package/src/sessionIntelligence.js +247 -0
  82. package/src/terminalHost.js +381 -0
  83. package/src/terminalHostDaemon.js +60 -0
  84. package/src/terminalManager.js +158 -3
  85. package/src/tmuxMonitor.js +2 -2
  86. package/src/updateInstaller.js +175 -0
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+
3
+ window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
4
+
5
+ window.LoadToAgentAppFactories.createManagement = function createManagement(context = {}) {
6
+ const t = (key, params) => window.LoadToAgentI18n.t(key, params);
7
+ const { $, esc, state, providerInfo, timeAgo, readablePreview = value => ({ text: String(value || "") }) } = context;
8
+
9
+ const attentionLabel = kind => t(`management.attention.${kind || "response"}`);
10
+ const healthLabel = level => t(`management.health.${level || "unknown"}`);
11
+ const signalLabel = code => t(`management.signal.${code || "low-confidence"}`);
12
+ const evidenceLabel = value => t(`management.evidence.${value || "unverified"}`);
13
+ const prioritySummary = value => {
14
+ const lines = String(value || "")
15
+ .replace(/```[\s\S]*?```/g, " ")
16
+ .split(/\r?\n/)
17
+ .map(line => {
18
+ let heading = /^\s{0,3}#{1,6}\s+/.test(line);
19
+ let text = line
20
+ .replace(/`([^`]*)`/g, "$1")
21
+ .replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1")
22
+ .replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
23
+ .replace(/^\s{0,3}(?:#{1,6}|>|[-*+]|\d+[.)])\s+/, "")
24
+ .replace(/\*\*|__|~~/g, "")
25
+ .replace(/\s+/g, " ")
26
+ .trim();
27
+ const inlineOrderedItem = heading ? text.match(/(?:^|\s)\d+[.)]\s+(.+)$/) : null;
28
+ if (inlineOrderedItem) {
29
+ text = inlineOrderedItem[1].trim();
30
+ heading = false;
31
+ }
32
+ return { heading, text };
33
+ })
34
+ .filter(line => line.text);
35
+ const plain = (lines.find(line => !line.heading) || lines[0])?.text || "";
36
+ const firstSentence = plain.match(/^.*?(?:[.!?。!?](?=\s|$)|$)/)?.[0]?.trim() || plain;
37
+ return readablePreview(firstSentence || t("management.signal_unavailable"), 104).text;
38
+ };
39
+
40
+ function managementBucket(session) {
41
+ if (session.health?.level === "critical") return "critical";
42
+ if (session.health?.level === "warning") return "warning";
43
+ if (session.attention?.required) return "attention";
44
+ if (!session.health || session.health.level === "unknown") return "unknown";
45
+ return session.health.level === "healthy" ? "healthy" : "unknown";
46
+ }
47
+
48
+ function progressHtml(session, compactView = false) {
49
+ const progress = session.progress || { percent: 0, checkpoints: [] };
50
+ const checkpoints = compactView ? [] : (progress.checkpoints || []).slice(-8);
51
+ return `<section class="management-progress" data-progress-stage="${esc(progress.stage || "idle")}">
52
+ <header><span>${esc(t("management.progress"))}</span><b>${esc(t("management.progress_steps", {
53
+ completed: progress.completedSteps || 0, total: progress.totalSteps || 0,
54
+ }))}</b><strong>${Number(progress.percent || 0)}%</strong></header>
55
+ <div class="management-progress-track" role="progressbar" aria-label="${esc(t("management.progress"))}" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${Number(progress.percent || 0)}"><i style="width:${Math.max(0, Math.min(100, Number(progress.percent || 0)))}%"></i></div>
56
+ <p><b>${esc(progress.currentStep || t("management.no_current_step"))}</b><span>${esc(progress.lastActivityAt ? t("management.last_signal", { time: timeAgo(progress.lastActivityAt) }) : t("management.signal_unavailable"))}</span></p>
57
+ ${progress.blocker ? `<aside><span>${esc(t("management.blocker"))}</span><b>${esc(progress.blocker)}</b></aside>` : ""}
58
+ ${checkpoints.length ? `<ol class="management-checkpoints">${checkpoints.map(row => `<li class="${esc(row.status)}"><i></i><div><b>${esc(row.label)}</b>${row.detail ? `<small>${esc(row.detail)}</small>` : ""}</div><span>${esc(row.timestamp ? timeAgo(row.timestamp) : "")}</span></li>`).join("")}</ol>` : ""}
59
+ </section>`;
60
+ }
61
+
62
+ function healthHtml(session, compactView = false) {
63
+ const health = session.health || { level: "unknown", signals: [] };
64
+ const signals = health.signals || [];
65
+ return `<section class="management-health ${esc(health.level || "unknown")}">
66
+ <header><span>${esc(t("management.health_title"))}</span><b>${esc(healthLabel(health.level))}</b><strong>${Number(health.score == null ? 0 : health.score)}</strong></header>
67
+ ${signals.length
68
+ ? `<ul>${signals.slice(0, compactView ? 2 : 8).map(signal => `<li class="${esc(signal.severity)}"><i></i><b>${esc(signalLabel(signal.code))}</b>${signal.detail ? `<span title="${esc(signal.detail)}">${esc(signal.detail)}</span>` : ""}</li>`).join("")}</ul>`
69
+ : `<p>${esc(t("management.no_health_signals"))}</p>`}
70
+ </section>`;
71
+ }
72
+
73
+ function controlButtonsHtml(session) {
74
+ const controls = session.controlCapabilities || {};
75
+ const canReassign = controls.reassign && (context.visibleProviders?.() || state.providers)
76
+ .some(provider => provider.id !== session.provider && state.availability[provider.id]);
77
+ const busy = action => state.runControlRequests.has(`${session.runId || session.id}:${action}`);
78
+ const button = (action, label, tone = "") => controls[action]
79
+ ? `<button type="button" class="${tone}" data-managed-run-action="${action}" data-management-session-id="${esc(session.id)}" ${busy(action) ? 'disabled aria-busy="true"' : ""}>${esc(t(label))}</button>`
80
+ : "";
81
+ return `<div class="management-control-buttons" aria-label="${esc(t("management.controls"))}">
82
+ ${button("pause", "management.pause")}
83
+ ${session.status === "paused" ? button("resume", "management.resume") : ""}
84
+ ${button("retry", "management.retry")}
85
+ ${canReassign ? `<button type="button" data-reassign-session="${esc(session.id)}">${esc(t("management.reassign"))}</button>` : ""}
86
+ ${button("stop", "management.stop", "danger")}
87
+ </div>`;
88
+ }
89
+
90
+ function quickActionsHtml(session) {
91
+ const attention = session.attention || {};
92
+ if (!attention.required) return "";
93
+ const controls = session.controlCapabilities || {};
94
+ const buttons = [];
95
+ if (controls.sendInstruction && ["approval", "decision"].includes(attention.kind)) {
96
+ buttons.push(`<button type="button" class="approve" data-attention-session-id="${esc(session.id)}" data-attention-quick="${esc(t("management.quick.approve_text"))}">${esc(t("management.approve"))}</button>`);
97
+ buttons.push(`<button type="button" data-attention-session-id="${esc(session.id)}" data-attention-quick="${esc(t("management.quick.deny_text"))}">${esc(t("management.deny"))}</button>`);
98
+ }
99
+ buttons.push(`<button type="button" data-open-session="${esc(session.id)}">${esc(t("management.review_detail"))}</button>`);
100
+ return `<div class="management-quick-actions">${buttons.join("")}</div>`;
101
+ }
102
+
103
+ function attentionCardHtml(session) {
104
+ const provider = providerInfo(session.provider);
105
+ const attention = session.attention || {};
106
+ const evidence = session.evidence || {};
107
+ return `<article class="attention-card ${esc(attention.kind || "response")}" data-management-session="${esc(session.id)}" style="--management-provider:${provider.accent}">
108
+ <header><span class="provider-mark">${esc(provider.mark)}</span><div><small>${esc(provider.label)} · ${esc(attentionLabel(attention.kind))}</small><h3>${esc(session.title)}</h3></div><em class="confidence ${esc(evidence.confidence || "low")}">${esc(evidenceLabel(evidence.confidence))}</em></header>
109
+ <div class="attention-reason"><span>${esc(t("management.requested_action"))}</span><p>${esc(attention.summary || session.statusDetail || t("management.response_needed"))}</p><small>${esc(attention.requestedAt ? timeAgo(attention.requestedAt) : t("management.signal_unavailable"))}</small></div>
110
+ ${progressHtml(session, true)}
111
+ ${healthHtml(session, true)}
112
+ ${quickActionsHtml(session)}
113
+ ${session.controlCapabilities?.sendInstruction ? context.agentCommandComposer(session) : ""}
114
+ ${controlButtonsHtml(session)}
115
+ </article>`;
116
+ }
117
+
118
+ function renderAttentionInbox() {
119
+ const section = $("#attentionInbox");
120
+ if (!section) return 0;
121
+ const reviewSessions = context.filteredSessions().filter(session => managementBucket(session) !== "healthy");
122
+ const filter = ["critical", "warning", "attention", "unknown"].includes(state.managementFilter) ? state.managementFilter : "all";
123
+ const sessions = filter === "all" ? reviewSessions : reviewSessions.filter(session => managementBucket(session) === filter);
124
+ const counts = reviewSessions.reduce((output, session) => {
125
+ const key = managementBucket(session);
126
+ output[key] = (output[key] || 0) + 1;
127
+ return output;
128
+ }, { critical: 0, warning: 0, attention: 0, unknown: 0 });
129
+ const filterButton = (value, label, count) => `<button type="button" data-management-inbox-filter="${value}" aria-pressed="${filter === value ? "true" : "false"}"><i></i><span>${esc(label)}</span><b>${count}</b></button>`;
130
+ section.innerHTML = `<header class="attention-inbox-head"><div><p>${esc(t("management.inbox_eyebrow"))}</p><h2>${esc(t("management.inbox_title"))}</h2><span>${esc(t("management.inbox_description"))}</span></div><strong>${sessions.length}</strong></header>
131
+ <div class="attention-inbox-summary" role="toolbar" aria-label="${esc(t("management.operations_severity_buckets"))}">
132
+ ${filterButton("all", t("management.filter_all"), reviewSessions.length)}
133
+ ${filterButton("critical", t("management.health.critical"), counts.critical)}
134
+ ${filterButton("warning", t("management.health.warning"), counts.warning)}
135
+ ${filterButton("attention", t("management.health.attention"), counts.attention)}
136
+ ${filterButton("unknown", t("management.health.unknown"), counts.unknown)}
137
+ </div>
138
+ <div class="attention-card-list">${sessions.length ? sessions.map(attentionCardHtml).join("") : `<div class="management-empty"><b>${esc(t("management.inbox_empty"))}</b><span>${esc(t("management.inbox_empty_detail"))}</span></div>`}</div>`;
139
+ return sessions.length;
140
+ }
141
+
142
+ function renderOperationsOverview() {
143
+ const section = $("#operationsOverview");
144
+ if (!section) return;
145
+ const sessions = typeof context.graphFilteredSessions === "function"
146
+ ? context.graphFilteredSessions()
147
+ : (state.snapshot?.sessions || []);
148
+ const critical = sessions.filter(session => session.health?.level === "critical");
149
+ const warning = sessions.filter(session => session.health?.level === "warning");
150
+ const attention = sessions.filter(session => session.attention?.required);
151
+ const flaggedIds = new Set([...critical, ...warning].map(session => session.id));
152
+ const attentionOnly = attention.filter(session => !flaggedIds.has(session.id));
153
+ attentionOnly.forEach(session => flaggedIds.add(session.id));
154
+ const unknown = sessions.filter(session => !flaggedIds.has(session.id) && (!session.health || session.health.level === "unknown" || !["healthy", "critical", "warning"].includes(session.health.level)));
155
+ unknown.forEach(session => flaggedIds.add(session.id));
156
+ const healthy = sessions.filter(session => !flaggedIds.has(session.id) && session.health?.level === "healthy");
157
+ const priority = [...new Map([...critical, ...warning, ...attentionOnly, ...unknown].map(session => [session.id, session])).values()].slice(0, 4);
158
+ const reviewCount = critical.length + warning.length + attentionOnly.length + unknown.length;
159
+ section.innerHTML = `<header>
160
+ <div class="operations-heading">
161
+ <span class="operations-signal" aria-hidden="true">!</span>
162
+ <div class="operations-heading-copy"><p>${esc(t("management.operations_eyebrow"))}</p><h2>${esc(t("management.operations_title"))}</h2><span>${esc(t("management.operations_description"))}</span></div>
163
+ </div>
164
+ <div class="operations-review-total"><strong>${reviewCount}</strong><span>${esc(t("management.operations_review_count"))}</span></div>
165
+ </header>
166
+ <div class="operations-metrics" aria-label="${esc(t("management.operations_severity_buckets"))}">
167
+ <button type="button" data-management-filter="critical"><span>${esc(t("management.health.critical"))}</span><b>${critical.length}</b></button>
168
+ <button type="button" data-management-filter="warning"><span>${esc(t("management.health.warning"))}</span><b>${warning.length}</b></button>
169
+ <button type="button" data-management-filter="attention"><span>${esc(t("management.health.attention"))}</span><b>${attentionOnly.length}</b></button>
170
+ <button type="button" data-management-filter="unknown"><span>${esc(t("management.health.unknown"))}</span><b>${unknown.length}</b></button>
171
+ <div><span>${esc(t("management.health.healthy"))}</span><b>${healthy.length}</b></div>
172
+ </div>
173
+ ${priority.length ? `<div class="operations-priority">${priority.map(session => {
174
+ const health = session.health || {};
175
+ const summary = prioritySummary((health.signals || []).map(signal => signalLabel(signal.code)).join(" · ")
176
+ || session.attention?.summary
177
+ || t("management.signal_unavailable"));
178
+ return `<button type="button" data-open-session="${esc(session.id)}"><i class="${esc(managementBucket(session))}"></i><span><b>${esc(session.title)}</b><small>${esc(summary)}</small></span><em>${esc(timeAgo(health.lastActivityAt || session.updatedAt))}</em></button>`;
179
+ }).join("")}</div>${reviewCount > priority.length ? `<div class="operations-more"><button type="button" data-management-filter="all"><span>${esc(t("management.operations_view_all"))}</span><b>${reviewCount}</b><i aria-hidden="true">→</i></button></div>` : ""}` : `<p class="operations-clear">${esc(t("management.operations_clear"))}</p>`}`;
180
+ }
181
+
182
+ function outcomeHtml(session) {
183
+ const outcome = session.outcome || { artifacts: [], checks: [] };
184
+ const evidence = session.evidence || { sources: [] };
185
+ const controls = session.controlCapabilities || {};
186
+ return `<div class="management-detail">
187
+ <section class="management-outcome ${esc(outcome.status || "in-progress")}">
188
+ <header><div><span>${esc(t("management.outcome"))}</span><h3>${esc(t(`management.outcome.${outcome.status || "in-progress"}`))}</h3></div><em class="${outcome.verified ? "verified" : "unverified"}">${esc(outcome.verified ? t("management.verified") : t("management.unverified"))}</em></header>
189
+ <p>${esc(outcome.summary || t("management.outcome_pending"))}</p>
190
+ </section>
191
+ ${session.attention?.required ? `<section class="management-attention-detail"><header><span>${esc(attentionLabel(session.attention.kind))}</span><b>${esc(session.attention.summary)}</b></header>${quickActionsHtml(session)}${controls.sendInstruction ? context.agentCommandComposer(session) : ""}</section>` : ""}
192
+ ${progressHtml(session)}
193
+ ${healthHtml(session)}
194
+ <section class="management-artifacts"><header><span>${esc(t("management.artifacts"))}</span><b>${esc(t("common.items", { count: (outcome.artifacts || []).length }))}</b></header>${outcome.artifacts?.length ? `<ul>${outcome.artifacts.map(item => `<li><i>${esc(item.kind)}</i><b title="${esc(item.value)}">${esc(item.value)}</b><span>${esc(item.verified ? t("management.verified") : t("management.detected"))}</span></li>`).join("")}</ul>` : `<p>${esc(t("management.no_artifacts"))}</p>`}</section>
195
+ <section class="management-checks"><header><span>${esc(t("management.verification_checks"))}</span><b>${esc(t("common.items", { count: (outcome.checks || []).length }))}</b></header>${outcome.checks?.length ? `<ul>${outcome.checks.map(check => `<li class="${esc(check.status)}"><i></i><b>${esc(check.label)}</b><span>${esc(t(`management.check.${check.status}`))}</span></li>`).join("")}</ul>` : `<p>${esc(t("management.no_checks"))}</p>`}</section>
196
+ <section class="management-evidence"><header><span>${esc(t("management.evidence_title"))}</span><b>${esc(evidenceLabel(evidence.confidence))}</b></header><dl><div><dt>${esc(t("management.evidence_status"))}</dt><dd>${esc(evidenceLabel(evidence.status))}</dd></div><div><dt>${esc(t("management.evidence_hierarchy"))}</dt><dd>${esc(evidenceLabel(evidence.hierarchy))}</dd></div><div><dt>${esc(t("management.evidence_completion"))}</dt><dd>${esc(evidenceLabel(evidence.completion))}</dd></div></dl><p>${esc((evidence.sources || []).join(" · ") || t("management.signal_unavailable"))}</p></section>
197
+ <section class="management-controls"><header><span>${esc(t("management.controls"))}</span><b>${esc(t("management.controls_description"))}</b></header>${controlButtonsHtml(session)}</section>
198
+ </div>`;
199
+ }
200
+
201
+ return {
202
+ managementBucket,
203
+ attentionCardHtml,
204
+ controlButtonsHtml,
205
+ healthHtml,
206
+ outcomeHtml,
207
+ progressHtml,
208
+ renderAttentionInbox,
209
+ renderOperationsOverview,
210
+ };
211
+ };
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+
3
+ window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
4
+
5
+ window.LoadToAgentAppFactories.createProviderVisibility = function createProviderVisibility(context = {}) {
6
+ const { state, reportRecoverableError } = context;
7
+ const STORAGE_KEY = "loadtoagent:provider-visibility:v1";
8
+ const USAGE_KEYS = ["input", "cachedInput", "cacheWrite", "output", "reasoning", "total"];
9
+
10
+ function loadProviderVisibility(preference = null) {
11
+ try {
12
+ const saved = preference && typeof preference === "object"
13
+ ? preference
14
+ : JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
15
+ const known = new Set(state.providers.map((provider) => provider.id));
16
+ state.hiddenProviders = new Set((saved.hidden || []).filter((id) => known.has(id)));
17
+ } catch (error) {
18
+ reportRecoverableError("provider-visibility-load", error);
19
+ state.hiddenProviders = new Set();
20
+ }
21
+ }
22
+
23
+ function saveProviderVisibility() {
24
+ try {
25
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({ hidden: [...state.hiddenProviders] }));
26
+ } catch (error) {
27
+ reportRecoverableError("provider-visibility-save", error);
28
+ }
29
+ }
30
+
31
+ function isProviderVisible(providerId) {
32
+ return !state.hiddenProviders.has(String(providerId || ""));
33
+ }
34
+
35
+ function visibleProviders() {
36
+ return state.providers.filter((provider) => isProviderVisible(provider.id));
37
+ }
38
+
39
+ function visibleSessions() {
40
+ return (state.snapshot?.sessions || []).filter((session) => isProviderVisible(session.provider));
41
+ }
42
+
43
+ function visibleTmux(tmux = (state.rawSnapshot || state.snapshot)?.tmux) {
44
+ if (!tmux) return tmux;
45
+ const projected = {
46
+ ...tmux,
47
+ distros: (tmux.distros || []).map((distro) => ({
48
+ ...distro,
49
+ sessions: (distro.sessions || []).map((tmuxSession) => ({
50
+ ...tmuxSession,
51
+ windows: (tmuxSession.windows || []).map((window) => ({
52
+ ...window,
53
+ panes: (window.panes || []).filter((pane) => (!pane.dead || !pane.agent) && (!pane.agent || isProviderVisible(pane.agent.provider))),
54
+ })).filter((window) => window.panes.length),
55
+ })).filter((tmuxSession) => tmuxSession.windows.length),
56
+ })).filter((distro) => distro.sessions.length),
57
+ };
58
+ const panes = projected.distros.flatMap((distro) =>
59
+ distro.sessions.flatMap((session) => session.windows.flatMap((window) => window.panes)));
60
+ projected.summary = {
61
+ distros: projected.distros.length,
62
+ sessions: projected.distros.reduce((sum, distro) => sum + distro.sessions.length, 0),
63
+ windows: projected.distros.reduce((sum, distro) =>
64
+ sum + distro.sessions.reduce((count, session) => count + session.windows.length, 0), 0),
65
+ panes: panes.length,
66
+ aiPanes: panes.filter((pane) => pane.agent && !pane.dead).length,
67
+ linked: panes.filter((pane) => pane.agent?.linkedSessionId && !pane.dead).length,
68
+ };
69
+ return projected;
70
+ }
71
+
72
+ function projectVisibleSnapshot(snapshot = state.rawSnapshot || state.snapshot) {
73
+ if (!snapshot) return snapshot;
74
+ const sessions = (snapshot.sessions || []).filter((session) => isProviderVisible(session.provider));
75
+ const usage = Object.fromEntries(USAGE_KEYS.map((key) => [
76
+ key,
77
+ sessions.reduce((sum, session) => sum + Number(session.usage?.[key] || 0), 0),
78
+ ]));
79
+ return {
80
+ ...snapshot,
81
+ sessions,
82
+ tmux: visibleTmux(snapshot.tmux),
83
+ summary: {
84
+ ...(snapshot.summary || {}),
85
+ providers: (snapshot.summary?.providers || []).filter((provider) => isProviderVisible(provider.id)),
86
+ totals: {
87
+ sessions: sessions.length,
88
+ active: sessions.filter((session) => session.status === "running" || session.status === "starting").length,
89
+ waiting: sessions.filter((session) => session.status === "waiting").length,
90
+ subagents: sessions.filter((session) => session.parentId).length,
91
+ usage,
92
+ },
93
+ },
94
+ };
95
+ }
96
+
97
+ function setProviderVisible(providerId, visible) {
98
+ const id = String(providerId || "");
99
+ if (!state.providers.some((provider) => provider.id === id)) return;
100
+ if (visible) state.hiddenProviders.delete(id);
101
+ else state.hiddenProviders.add(id);
102
+ state.providerFilters.delete(id);
103
+ if (!isProviderVisible(state.runProvider)) {
104
+ state.runProvider = visibleProviders().find((provider) => state.availability[provider.id])?.id
105
+ || visibleProviders()[0]?.id
106
+ || "";
107
+ }
108
+ saveProviderVisibility();
109
+ if (state.rawSnapshot) state.snapshot = projectVisibleSnapshot(state.rawSnapshot);
110
+ }
111
+
112
+ return {
113
+ PROVIDER_VISIBILITY_STORAGE_KEY: STORAGE_KEY,
114
+ loadProviderVisibility,
115
+ saveProviderVisibility,
116
+ isProviderVisible,
117
+ visibleProviders,
118
+ visibleSessions,
119
+ visibleTmux,
120
+ projectVisibleSnapshot,
121
+ visibleSnapshot: () => projectVisibleSnapshot(),
122
+ setProviderVisible,
123
+ };
124
+ };