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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loadtoagent",
3
- "version": "1.3.9",
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": ["arm64", "x64"]
127
+ "arch": [
128
+ "arm64",
129
+ "x64"
130
+ ]
128
131
  },
129
132
  {
130
133
  "target": "zip",
131
- "arch": ["arm64", "x64"]
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
- desktopList._overflowObserver = new ResizeObserver(updateProjectOverflow);
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);
@@ -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
- content.innerHTML = detailLoading
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
- composer.innerHTML = showComposer ? agentCommandComposer(session, { conversation: true }) : "";
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
- 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>
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 : '', { background: options.focus === false }),
156
+ args: resumeLaunchArgs(support, sendDraft ? prompt : ''),
157
157
  cwd,
158
158
  distro,
159
159
  bridgeId: agentSession.id,
160
160
  title,
161
- transient: options.focus === false,
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
  });
@@ -179,11 +179,9 @@
179
179
  return { supported: true, provider, sessionId, args };
180
180
  }
181
181
 
182
- function resumeLaunchArgs(support, prompt = '', options = {}) {
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) state.selectedId = visible[0].id;
706
- else if (state.mode === 'tmux') state.selectedTmux = tmuxRows()[0] || null;
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
  }