loadtoagent 1.1.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 +135 -38
- package/package.json +13 -4
- package/preload.js +6 -0
- package/renderer/app-agent-actions.js +125 -53
- package/renderer/app-bootstrap.js +54 -19
- package/renderer/app-dashboard.js +190 -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 +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 +52 -44
- package/renderer/app.js +153 -75
- package/renderer/i18n-messages.js +833 -16
- package/renderer/i18n.js +104 -0
- package/renderer/index.html +145 -59
- package/renderer/shared.js +17 -0
- package/renderer/styles-agent-map.css +6 -0
- package/renderer/styles-cards.css +26 -0
- package/renderer/styles-components.css +86 -9
- package/renderer/styles-management.css +355 -0
- package/renderer/styles-overlays.css +8 -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 +22 -14
- package/renderer/styles-responsive-shell.css +44 -48
- package/renderer/styles-responsive-workflows.css +37 -3
- 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-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 +165 -77
- package/renderer/terminal.js +341 -34
- 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 -6
- 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 +247 -0
- package/src/terminalHost.js +381 -0
- package/src/terminalHostDaemon.js +60 -0
- package/src/terminalManager.js +158 -3
- 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
|
+
};
|