loadtoagent 1.3.9 → 1.3.10
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/package.json +9 -3
- package/renderer/app-agent-actions.js +1 -0
- package/renderer/app-dashboard.js +3 -1
- package/renderer/app-drawer.js +20 -2
- package/renderer/app-graph-orchestration.js +5 -3
- package/renderer/app-management.js +4 -1
- package/renderer/app-runtime-overview.js +7 -0
- package/renderer/styles-overlays.css +2 -1
- package/renderer/terminal-agent.js +5 -2
- package/renderer/terminal.js +16 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loadtoagent",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.10",
|
|
4
4
|
"description": "A local desktop dashboard for Claude, Codex, Gemini, and Grok sessions.",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"author": "wincube AX",
|
|
@@ -124,11 +124,17 @@
|
|
|
124
124
|
"target": [
|
|
125
125
|
{
|
|
126
126
|
"target": "dmg",
|
|
127
|
-
"arch": [
|
|
127
|
+
"arch": [
|
|
128
|
+
"arm64",
|
|
129
|
+
"x64"
|
|
130
|
+
]
|
|
128
131
|
},
|
|
129
132
|
{
|
|
130
133
|
"target": "zip",
|
|
131
|
-
"arch": [
|
|
134
|
+
"arch": [
|
|
135
|
+
"arm64",
|
|
136
|
+
"x64"
|
|
137
|
+
]
|
|
132
138
|
}
|
|
133
139
|
],
|
|
134
140
|
"artifactName": "LoadToAgent-${version}-${arch}.${ext}"
|
|
@@ -330,6 +330,7 @@ window.LoadToAgentAppFactories.createAgentActions = function createAgentActions(
|
|
|
330
330
|
try {
|
|
331
331
|
await window.LoadToAgentTerminal.resumeForAgent(targetSession, routedCommand, true, { focus: false });
|
|
332
332
|
state.agentCommandDrafts.delete(sessionId);
|
|
333
|
+
if (input) input.value = "";
|
|
333
334
|
updateConversationMessage(sessionId, pendingMessage, "awaiting");
|
|
334
335
|
context.toast(t(routingEnabled && routeContext.route === "parent" ? "agent.command_routed_via_parent" : "agent.command_sent_background"));
|
|
335
336
|
} catch (error) {
|
|
@@ -211,7 +211,9 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
211
211
|
if (desktopList.dataset.overflowBound !== "true") {
|
|
212
212
|
desktopList.dataset.overflowBound = "true";
|
|
213
213
|
desktopList.addEventListener("scroll", updateProjectOverflow, { passive: true });
|
|
214
|
-
|
|
214
|
+
// Mutating layout-related classes directly inside ResizeObserver can
|
|
215
|
+
// trigger Chromium's "undelivered notifications" loop warning.
|
|
216
|
+
desktopList._overflowObserver = new ResizeObserver(() => requestAnimationFrame(updateProjectOverflow));
|
|
215
217
|
desktopList._overflowObserver.observe(desktopList);
|
|
216
218
|
}
|
|
217
219
|
requestAnimationFrame(updateProjectOverflow);
|
package/renderer/app-drawer.js
CHANGED
|
@@ -197,7 +197,7 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
197
197
|
motionState.drawerRenderKey = renderKey;
|
|
198
198
|
motionState.drawerTab = state.drawerTab;
|
|
199
199
|
const detailError = state.detailErrors.get(state.selectedId);
|
|
200
|
-
|
|
200
|
+
const nextContentHtml = detailLoading
|
|
201
201
|
? `<div class="drawer-loading"><span></span><b>${esc(t("drawer.loading_history"))}</b><small>${esc(t("drawer.loading_history_help"))}</small></div>`
|
|
202
202
|
: detailError
|
|
203
203
|
? `<div class="drawer-error">
|
|
@@ -216,10 +216,28 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
216
216
|
: state.drawerTab === "lifecycle"
|
|
217
217
|
? lifecycleHtml(session)
|
|
218
218
|
: tokensHtml(session);
|
|
219
|
+
if (motionState.drawerContentHtml !== nextContentHtml) {
|
|
220
|
+
content.innerHTML = nextContentHtml;
|
|
221
|
+
motionState.drawerContentHtml = nextContentHtml;
|
|
222
|
+
}
|
|
219
223
|
const composer = $("#drawerComposer");
|
|
220
224
|
const showComposer = !executionMode && !detailLoading && !detailError && state.drawerTab === "chat" && typeof agentCommandComposer === "function";
|
|
221
225
|
composer.classList.toggle("hidden", !showComposer);
|
|
222
|
-
|
|
226
|
+
const nextComposerHtml = showComposer ? agentCommandComposer(session, { conversation: true }) : "";
|
|
227
|
+
const focusedDraft = document.activeElement?.matches?.("[data-agent-command-draft]")
|
|
228
|
+
&& composer.contains(document.activeElement)
|
|
229
|
+
&& document.activeElement.dataset.agentCommandDraft === session.id;
|
|
230
|
+
// Session snapshots arrive while the user is typing. Replacing the
|
|
231
|
+
// composer would destroy the focused textarea and its IME/caret state.
|
|
232
|
+
// Keep that exact node alive until focus leaves it; the delegated input
|
|
233
|
+
// handler already mirrors the current draft into state.
|
|
234
|
+
if (!focusedDraft && motionState.drawerComposerHtml !== nextComposerHtml) {
|
|
235
|
+
composer.innerHTML = nextComposerHtml;
|
|
236
|
+
motionState.drawerComposerHtml = nextComposerHtml;
|
|
237
|
+
} else if (!showComposer && composer.innerHTML) {
|
|
238
|
+
composer.innerHTML = "";
|
|
239
|
+
motionState.drawerComposerHtml = "";
|
|
240
|
+
}
|
|
223
241
|
restoreDisclosureStates(content);
|
|
224
242
|
content.classList.toggle("motion-content-in", shouldAnimateContent && !motionPreference.matches);
|
|
225
243
|
clearTimeout(motionState.drawerContentTimer);
|
|
@@ -35,6 +35,8 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
35
35
|
|
|
36
36
|
function renderAgentMap(sessions, motionKind = "refresh") {
|
|
37
37
|
const liveSessionGrid = $("#liveSessionGrid");
|
|
38
|
+
const preserveFocusedComposer = document.activeElement?.matches?.("[data-agent-command-draft]")
|
|
39
|
+
&& liveSessionGrid.contains(document.activeElement);
|
|
38
40
|
rememberDisclosureStates(liveSessionGrid);
|
|
39
41
|
const model = connectedGraphSessions(sessions);
|
|
40
42
|
const focus =
|
|
@@ -47,7 +49,7 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
47
49
|
? [...rootSessions].sort((a, b) => Number((b.context && b.context.percent) || 0) - Number((a.context && a.context.percent) || 0))
|
|
48
50
|
: stableSessionSort(rootSessions);
|
|
49
51
|
if (!model.nodes.length) {
|
|
50
|
-
liveSessionGrid.innerHTML = "";
|
|
52
|
+
if (!preserveFocusedComposer) liveSessionGrid.innerHTML = "";
|
|
51
53
|
$("#graphBreadcrumbs").innerHTML = "";
|
|
52
54
|
$("#graphResetBtn").classList.add("hidden");
|
|
53
55
|
$("#agentMapToolbar")?.classList.add("hidden");
|
|
@@ -61,7 +63,7 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
61
63
|
$("#agentMapToolbar")?.classList.remove("hidden");
|
|
62
64
|
$("#controlRoomProjectToolbar")?.classList.add("hidden");
|
|
63
65
|
$("#controlRoomListToolbar")?.classList.add("hidden");
|
|
64
|
-
liveSessionGrid.innerHTML = focusedGraph(focus, model, motionKind);
|
|
66
|
+
if (!preserveFocusedComposer) liveSessionGrid.innerHTML = focusedGraph(focus, model, motionKind);
|
|
65
67
|
const path = graphPath(focus, model.byId);
|
|
66
68
|
$("#graphBreadcrumbs").innerHTML = `<button type="button" data-graph-reset>${esc(t("graph.task_list"))}</button>${path
|
|
67
69
|
.map((item) => {
|
|
@@ -77,7 +79,7 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
77
79
|
scheduleAgentWorkflowConnections();
|
|
78
80
|
} else {
|
|
79
81
|
const runtime = runtimeAgentSummary(model, liveTmuxEntries(state.snapshot && state.snapshot.tmux));
|
|
80
|
-
liveSessionGrid.innerHTML = runtimeSeparatedOverview(roots, model, roots);
|
|
82
|
+
if (!preserveFocusedComposer) liveSessionGrid.innerHTML = runtimeSeparatedOverview(roots, model, roots);
|
|
81
83
|
restoreDisclosureStates(liveSessionGrid);
|
|
82
84
|
$("#graphBreadcrumbs").innerHTML = "";
|
|
83
85
|
$("#agentMapToolbar")?.classList.add("hidden");
|
|
@@ -246,6 +246,8 @@ window.LoadToAgentAppFactories.createManagement = function createManagement(cont
|
|
|
246
246
|
function renderAttentionInbox() {
|
|
247
247
|
const section = $("#attentionInbox");
|
|
248
248
|
if (!section) return 0;
|
|
249
|
+
const preserveFocusedComposer = document.activeElement?.matches?.("[data-agent-command-draft]")
|
|
250
|
+
&& section.contains(document.activeElement);
|
|
249
251
|
const reviewSessions = context.filteredSessions().filter(needsManagementReview);
|
|
250
252
|
const filter = ["critical", "warning", "attention"].includes(state.managementFilter) ? state.managementFilter : "all";
|
|
251
253
|
const sessions = reviewSessions.filter(session => filter === "all" || matchesManagementFilter(session, filter));
|
|
@@ -255,13 +257,14 @@ window.LoadToAgentAppFactories.createManagement = function createManagement(cont
|
|
|
255
257
|
attention: reviewSessions.filter(session => matchesManagementFilter(session, "attention")).length,
|
|
256
258
|
};
|
|
257
259
|
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>`;
|
|
258
|
-
|
|
260
|
+
const nextHtml = `<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>
|
|
259
261
|
<div class="attention-inbox-summary" role="toolbar" aria-label="${esc(t("management.operations_severity_buckets"))}">
|
|
260
262
|
<div class="management-filter-all">${filterButton("all", t("management.filter_all"), reviewSessions.length)}</div>
|
|
261
263
|
<div class="management-filter-group response" role="group" aria-label="${esc(t("management.filter_group_response"))}"><small>${esc(t("management.filter_group_response"))}</small>${filterButton("attention", t("management.health.attention"), counts.attention)}</div>
|
|
262
264
|
<div class="management-filter-group risk" role="group" aria-label="${esc(t("management.filter_group_risk"))}"><small>${esc(t("management.filter_group_risk"))}</small>${filterButton("critical", t("management.health.critical"), counts.critical)}${filterButton("warning", t("management.health.warning"), counts.warning)}</div>
|
|
263
265
|
</div>
|
|
264
266
|
<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>`;
|
|
267
|
+
if (!preserveFocusedComposer) section.innerHTML = nextHtml;
|
|
265
268
|
return sessions.length;
|
|
266
269
|
}
|
|
267
270
|
|
|
@@ -275,6 +275,13 @@ window.LoadToAgentAppFactories.createRuntimeOverview = function createRuntimeOve
|
|
|
275
275
|
${selected ? loopDetail(selected) : noActiveLoop()}
|
|
276
276
|
</section>
|
|
277
277
|
</div>`;
|
|
278
|
+
// Restore scroll synchronously before another snapshot render can replace
|
|
279
|
+
// this freshly-created DOM and accidentally capture zero as its position.
|
|
280
|
+
const immediateScheduleList = section.querySelector(".runtime-schedule-list");
|
|
281
|
+
const immediateSelectedTab = section.querySelector(".runtime-loop-tab.selected");
|
|
282
|
+
const immediateTabList = immediateSelectedTab?.closest(".runtime-loop-tabs");
|
|
283
|
+
if (immediateScheduleList) immediateScheduleList.scrollTop = scheduleScrollTop;
|
|
284
|
+
if (immediateTabList) immediateTabList.scrollLeft = loopScrollLeft;
|
|
278
285
|
requestAnimationFrame(() => {
|
|
279
286
|
if (renderVersion !== runtimeRenderVersion) return;
|
|
280
287
|
const scheduleList = section.querySelector(".runtime-schedule-list");
|
|
@@ -151,7 +151,8 @@
|
|
|
151
151
|
animation: motion-backdrop-in var(--motion-medium) ease both;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
.drawer-backdrop.closing
|
|
154
|
+
.drawer-backdrop.closing,
|
|
155
|
+
.drawer-backdrop.hidden {
|
|
155
156
|
animation: motion-backdrop-out 260ms ease both;
|
|
156
157
|
pointer-events: none;
|
|
157
158
|
}
|
|
@@ -153,12 +153,15 @@ window.LoadToAgentTerminalAgentActions = function createModule(context) {
|
|
|
153
153
|
const created = await window.loadtoagent.terminalCreate({
|
|
154
154
|
type: 'agent',
|
|
155
155
|
provider: support.provider,
|
|
156
|
-
args: resumeLaunchArgs(support, sendDraft ? prompt : ''
|
|
156
|
+
args: resumeLaunchArgs(support, sendDraft ? prompt : ''),
|
|
157
157
|
cwd,
|
|
158
158
|
distro,
|
|
159
159
|
bridgeId: agentSession.id,
|
|
160
160
|
title,
|
|
161
|
-
|
|
161
|
+
// Conversation sends must keep the resumed PTY alive. A transient
|
|
162
|
+
// one-shot process can exit after spawn (for example when the session is
|
|
163
|
+
// busy) while the composer incorrectly reports that the prompt was sent.
|
|
164
|
+
transient: false,
|
|
162
165
|
cols: 120,
|
|
163
166
|
rows: 32,
|
|
164
167
|
});
|
package/renderer/terminal.js
CHANGED
|
@@ -179,11 +179,9 @@
|
|
|
179
179
|
return { supported: true, provider, sessionId, args };
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
function resumeLaunchArgs(support, prompt = ''
|
|
182
|
+
function resumeLaunchArgs(support, prompt = '') {
|
|
183
183
|
const args = [...support.args];
|
|
184
184
|
const text = String(prompt || '').trim();
|
|
185
|
-
if (options.background && text && support.provider === 'codex') return ['exec', 'resume', support.sessionId, text];
|
|
186
|
-
if (options.background && text && support.provider === 'claude') return ['--resume', support.sessionId, '--print', text];
|
|
187
185
|
if (text) args.push(text);
|
|
188
186
|
return args;
|
|
189
187
|
}
|
|
@@ -698,13 +696,26 @@
|
|
|
698
696
|
state.active = false;
|
|
699
697
|
return;
|
|
700
698
|
}
|
|
699
|
+
// Snapshot renders call activate repeatedly while this view is already
|
|
700
|
+
// open. Refreshing and re-showing the same xterm screen would temporarily
|
|
701
|
+
// hide its helper textarea and steal direct keyboard focus.
|
|
702
|
+
if (!enteringMode) return;
|
|
701
703
|
await refreshSessions();
|
|
702
704
|
if (!state.active || state.mode !== nextMode) return;
|
|
705
|
+
let selectionChanged = false;
|
|
703
706
|
if (enteringMode && !state.selectedId && !state.selectedTmux) {
|
|
704
707
|
const visible = modeSessions();
|
|
705
|
-
if (visible.length)
|
|
706
|
-
|
|
708
|
+
if (visible.length) {
|
|
709
|
+
state.selectedId = visible[0].id;
|
|
710
|
+
selectionChanged = true;
|
|
711
|
+
} else if (state.mode === 'tmux') {
|
|
712
|
+
state.selectedTmux = tmuxRows()[0] || null;
|
|
713
|
+
selectionChanged = Boolean(state.selectedTmux);
|
|
714
|
+
}
|
|
707
715
|
}
|
|
716
|
+
// refreshSessions already rendered and showed the existing selection. Avoid
|
|
717
|
+
// hiding the same xterm host a second time after users can start typing.
|
|
718
|
+
if (!selectionChanged) return;
|
|
708
719
|
renderAll();
|
|
709
720
|
await showSelection();
|
|
710
721
|
}
|