agentgui 1.0.1052 → 1.0.1053

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": "agentgui",
3
- "version": "1.0.1052",
3
+ "version": "1.0.1053",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
@@ -3,7 +3,7 @@ import * as B from './backend.js';
3
3
 
4
4
  installStyles().catch(() => {});
5
5
 
6
- const { AppShell, WorkspaceShell, WorkspaceRail, Topbar, Crumb, Side, Status, Chat, ChatComposer, AgentChat, ConversationList, SessionDashboard, Row, Panel, PageHeader, TextField, Select, Btn, Icon, EventList, Spinner, Alert, FileGrid, FileSkeleton, sortFiles, FileToolbar, RootsPicker, BreadcrumbPath, EmptyState, FileViewer, FilePreviewPane, FilePreviewCode, FilePreviewText, FilePreviewMedia, ThemeToggle, ContextPane, PromptDialog, ConfirmDialog, DropZone, UploadProgress, FilterPills, SessionMeta, BulkBar, Checkbox, ShortcutList, FocusTrap, AgentListSkeleton } = C;
6
+ const { AppShell, WorkspaceShell, WorkspaceRail, Topbar, Crumb, Side, Status, Chat, ChatComposer, AgentChat, ConversationList, SessionDashboard, Row, Panel, PageHeader, TextField, Select, Btn, Icon, IconButton, EventList, Spinner, Alert, FileGrid, FileSkeleton, sortFiles, FileToolbar, RootsPicker, BreadcrumbPath, EmptyState, FileViewer, FilePreviewPane, FilePreviewCode, FilePreviewText, FilePreviewMedia, ThemeToggle, ContextPane, PromptDialog, ConfirmDialog, DropZone, UploadProgress, FilterPills, SessionMeta, BulkBar, Checkbox, ShortcutList, FocusTrap, AgentListSkeleton } = C;
7
7
 
8
8
  // One duration/bytes vocabulary across every surface: prefer the kit's shared
9
9
  // formatters (exported alongside the components), fall back to the local
@@ -437,6 +437,21 @@ async function stopActiveChat(sid) {
437
437
  state.active = (Array.isArray(state.active) ? state.active : []).filter(a => a.sessionId !== sid);
438
438
  await refreshActive();
439
439
  render();
440
+ // The backend accepted the cancel, but the underlying process can outlive
441
+ // this immediate refresh - if it's still showing active after a bounded
442
+ // wait, stop treating it as en-route-to-stopped: clear `stopping` (or the
443
+ // row wedges permanently disabled) and surface the same retry banner used
444
+ // on an outright rejection.
445
+ setTimeout(() => {
446
+ const st2 = state.live.stopping;
447
+ const stillActive = Array.isArray(state.active) && state.active.some(a => a.sessionId === sid);
448
+ if (st2 && st2.has(sid) && stillActive) {
449
+ st2.delete(sid);
450
+ state.live.bulkStopError = 'conversation did not stop - try again';
451
+ announce('stop failed');
452
+ render();
453
+ }
454
+ }, 9000);
440
455
  }
441
456
 
442
457
  function openLiveStream() {
@@ -507,6 +522,7 @@ function openLiveStream() {
507
522
  if (ev.type === 'tool_use') { t.tools++; t.toolRunning = true; t.toolName = ev.tool || ev.name || ''; }
508
523
  if (ev.type === 'tool_result') { t.toolRunning = false; t.toolName = ''; }
509
524
  if (ev.type === 'result' && ev.usage) { t.tokens = (t.tokens || 0) + (ev.usage.input_tokens || 0) + (ev.usage.output_tokens || 0); }
525
+ if (ev.type === 'result' && typeof ev.total_cost_usd === 'number') { t.cost = (t.cost || 0) + ev.total_cost_usd; }
510
526
  if (ev.isError) { t.errors++; t.lastErrorTs = t.last; }
511
527
  }
512
528
  state.live.tally.set(data.sid, t);
@@ -708,8 +724,13 @@ function workspaceRail() {
708
724
  action: { label: 'New chat', icon: 'pencil', onClick: () => { navTo('chat'); newChat(); } },
709
725
  items,
710
726
  // W18: a persistent theme toggle pinned to the rail bottom (the Claude-Desktop
711
- // / cowork pinned-bottom affordance). ThemeToggle is already imported.
712
- footer: ThemeToggle({ compact: true }),
727
+ // / cowork pinned-bottom affordance). A shortcuts-help icon sits alongside it -
728
+ // the '?' overlay (state.showShortcuts) was previously 100% keyboard-only
729
+ // tribal knowledge with zero visible entry point outside Settings.
730
+ footer: [
731
+ h('span', { key: 'rail-help' }, IconButton({ icon: 'help', title: 'Keyboard shortcuts', onClick: () => { state.showShortcuts = true; render(); } })),
732
+ h('span', { key: 'rail-theme' }, ThemeToggle({ compact: true })),
733
+ ],
713
734
  });
714
735
  }
715
736
 
@@ -1779,7 +1800,7 @@ function computeLiveSessions() {
1779
1800
  // Surface the in-page chat's own running cost and token count on its card
1780
1801
  // (the only session we hold reliable per-session data for; others omit it).
1781
1802
  cost: (state.chat.resumeSid && (r.claudeSessionId === state.chat.resumeSid || r.sessionId === state.chat.resumeSid))
1782
- ? (state.chat.totalCost || null) : null,
1803
+ ? (state.chat.totalCost || null) : (t && t.cost ? t.cost : null),
1783
1804
  tokens: (state.chat.resumeSid && (r.claudeSessionId === state.chat.resumeSid || r.sessionId === state.chat.resumeSid) && state.chat.usage)
1784
1805
  ? ((state.chat.usage.inputTokens || 0) + (state.chat.usage.outputTokens || 0)) : (t && t.tokens ? t.tokens : undefined),
1785
1806
  };
@@ -3121,6 +3142,15 @@ function resumeSidLabel(sid) {
3121
3142
  return label || UNTITLED_CONVERSATION;
3122
3143
  }
3123
3144
 
3145
+ // Same identity contract as the sessions rail/history rail/Live SessionCard:
3146
+ // a running row must show the resolved conversation title, not just agent
3147
+ // metadata - otherwise the same session reads as "nameless" here and named
3148
+ // everywhere else.
3149
+ function runningRowTitle(r) {
3150
+ const realSid = r.claudeSessionId || r.sessionId;
3151
+ return resumeSidLabel(realSid);
3152
+ }
3153
+
3124
3154
  function uniqueProjects() {
3125
3155
  const arr = Array.isArray(state.sessions) ? state.sessions : [];
3126
3156
  const seen = new Map();
@@ -3168,7 +3198,7 @@ function runningPanel() {
3168
3198
  // unkeyed one crashes webjsx applyDiff "reading 'key'").
3169
3199
  return h('div', { key: 'run' + r.sessionId, class: 'resume-banner', role: 'group' },
3170
3200
  h('span', { key: 'rd-' + r.sessionId, class: 'status-dot-disc ' + (isStopping ? 'status-dot-connecting' : 'status-dot-live'), 'aria-hidden': 'true' }),
3171
- h('span', { key: 'rl-' + r.sessionId, class: 't-meta' }, agentName + (r.model ? ' · ' + r.model : '') + (elapsedMs ? ' · ' + fmtDuration(elapsedMs) : '') + (r.cwd ? ' · ' + pathBasename(r.cwd) : '')),
3201
+ h('span', { key: 'rl-' + r.sessionId, class: 't-meta' }, runningRowTitle(r) + ' · ' + agentName + (r.model ? ' · ' + r.model : '') + (elapsedMs ? ' · ' + fmtDuration(elapsedMs) : '') + (r.cwd ? ' · ' + pathBasename(r.cwd) : '')),
3172
3202
  Btn({ key: 'open' + r.sessionId, onClick: () => navTo('live'), children: 'open in live' }),
3173
3203
  Btn({ key: 'stop' + r.sessionId, disabled: isStopping, onClick: () => stopActiveChat(r.sessionId), children: isStopping ? 'stopping…' : 'stop' }));
3174
3204
  }),