pilotswarm 0.5.0 → 0.5.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.
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { appendAnimatedDotsToRuns, useAnimatedDots } from "./chat-status.js";
2
+ import { appendAnimatedDotsToRuns, useAnimatedDots, useSpinnerFrame } from "./chat-status.js";
3
3
  import {
4
4
  applyActiveHighlightRuns,
5
5
  computeLegacyLayout,
@@ -8,6 +8,7 @@ import {
8
8
  selectAdminConsole,
9
9
  selectAdminGhcpKeyEditorModal,
10
10
  selectChatPaneChrome,
11
+ selectLiveActivityLines,
11
12
  selectChatLines,
12
13
  selectActivityPane,
13
14
  selectArtifactUploadModal,
@@ -27,6 +28,7 @@ import {
27
28
  selectSessionOwnerFilterModal,
28
29
  selectStatusBar,
29
30
  selectThemePickerModal,
31
+ selectHelpModal,
30
32
  selectConfirmModal,
31
33
  selectVisibleSessionRows,
32
34
  } from "pilotswarm/ui-core";
@@ -375,6 +377,20 @@ const ChatPane = React.memo(function ChatPane({ controller, width, height, frame
375
377
  { text: "Check env credentials and model provider config, then relaunch.", color: "yellow" },
376
378
  ]
377
379
  : selectChatLines(selectorState, contentWidth)), [chatView.branding.splash, chatView.connectionError, contentWidth, selectorState, startupError]);
380
+ const running = String(chatView.activeSession?.status || "").toLowerCase() === "running";
381
+ const spinnerFrame = useSpinnerFrame(running);
382
+ const liveActivityLines = React.useMemo(
383
+ () => (startupError ? [] : selectLiveActivityLines(selectorState, { spinnerFrame, maxWidth: contentWidth })),
384
+ [selectorState, startupError, spinnerFrame, contentWidth],
385
+ );
386
+ // Concat the isolated live-activity block so the spinner tick only recomputes
387
+ // the small block, not the whole transcript.
388
+ const chatLines = React.useMemo(
389
+ () => (liveActivityLines.length > 0
390
+ ? [...elements, [{ text: "", color: null }], ...liveActivityLines]
391
+ : elements),
392
+ [elements, liveActivityLines],
393
+ );
378
394
 
379
395
  return React.createElement(platform.Panel, {
380
396
  title: chrome.title,
@@ -383,7 +399,7 @@ const ChatPane = React.memo(function ChatPane({ controller, width, height, frame
383
399
  focused: chatView.focused,
384
400
  width,
385
401
  height,
386
- lines: elements,
402
+ lines: chatLines,
387
403
  scrollOffset: chatView.chatScroll,
388
404
  scrollMode: "bottom",
389
405
  paneId: "chat",
@@ -568,6 +584,8 @@ const InspectorSequencePane = React.memo(function InspectorSequencePane({ contro
568
584
  ? state.orchestration.bySessionId?.[state.sessions.activeSessionId] || null
569
585
  : null,
570
586
  histories: state.history.bySessionId,
587
+ expandedTurns: state.ui.sequenceExpandedTurns,
588
+ selectedTurn: state.ui.sequenceSelectedTurn,
571
589
  }), shallowEqualObject);
572
590
  const selectorState = React.useMemo(() => ({
573
591
  sessions: {
@@ -592,8 +610,14 @@ const InspectorSequencePane = React.memo(function InspectorSequencePane({ contro
592
610
  sequenceState.histories,
593
611
  ]);
594
612
  const inspector = React.useMemo(
595
- () => selectInspector(selectorState, { width: contentWidth }),
596
- [contentWidth, selectorState],
613
+ () => selectInspector(selectorState, {
614
+ width: contentWidth,
615
+ sequenceExpansion: {
616
+ expandedTurns: sequenceState.expandedTurns || [],
617
+ selectedTurn: sequenceState.selectedTurn ?? null,
618
+ },
619
+ }),
620
+ [contentWidth, selectorState, sequenceState.expandedTurns, sequenceState.selectedTurn],
597
621
  );
598
622
 
599
623
  return renderInspectorPanel(platform, inspector, meta, width, height, frame);
@@ -1497,6 +1521,41 @@ function ArtifactPickerModalContainer({ controller }) {
1497
1521
  return React.createElement(ArtifactPickerModal, { state });
1498
1522
  }
1499
1523
 
1524
+ function HelpModal({ state }) {
1525
+ const platform = useUiPlatform();
1526
+ const modal = selectHelpModal(state);
1527
+ if (!modal) return null;
1528
+ const viewport = typeof platform.getViewport === "function"
1529
+ ? platform.getViewport()
1530
+ : { width: 120, height: 40 };
1531
+ const width = Math.max(56, Math.min(modal.idealWidth || 80, (viewport.width || 120) - 8));
1532
+ const height = Math.max(10, Math.min(modal.rows.length + 2, (viewport.height || 40) - 6));
1533
+ const contentRows = Math.max(1, height - 2);
1534
+ const maxOffset = Math.max(0, modal.rows.length - contentRows);
1535
+ const scrollOffset = Math.max(0, Math.min(modal.selectedRowIndex - Math.floor(contentRows / 2), maxOffset));
1536
+ return React.createElement(platform.Overlay, null,
1537
+ React.createElement(platform.Column, { width },
1538
+ React.createElement(platform.Panel, {
1539
+ title: modal.title,
1540
+ color: "cyan",
1541
+ focused: false,
1542
+ width,
1543
+ height,
1544
+ lines: modal.rows,
1545
+ scrollOffset,
1546
+ scrollMode: "top",
1547
+ fillColor: "surface",
1548
+ }),
1549
+ ));
1550
+ }
1551
+
1552
+ function HelpModalContainer({ controller }) {
1553
+ const state = useControllerSelector(controller, (rootState) => ({
1554
+ ui: { modal: rootState.ui.modal },
1555
+ }), shallowEqualObject);
1556
+ return React.createElement(HelpModal, { state });
1557
+ }
1558
+
1500
1559
  function renderFilterModal(platform, modal) {
1501
1560
  if (!modal) return null;
1502
1561
  const viewport = typeof platform.getViewport === "function"
@@ -1849,6 +1908,8 @@ export function SharedPilotSwarmApp({ controller, versionLabel = null }) {
1849
1908
  const platform = useUiPlatform();
1850
1909
  const layoutState = useControllerSelector(controller, (state) => ({
1851
1910
  paneAdjust: state.ui.layout?.paneAdjust ?? 0,
1911
+ sessionPaneAdjust: state.ui.layout?.sessionPaneAdjust ?? 0,
1912
+ activityPaneAdjust: state.ui.layout?.activityPaneAdjust ?? 0,
1852
1913
  promptRows: getPromptInputRows(state.ui.prompt),
1853
1914
  inspectorTab: state.ui.inspectorTab,
1854
1915
  filesFullscreen: Boolean(state.files?.fullscreen),
@@ -1862,8 +1923,8 @@ export function SharedPilotSwarmApp({ controller, versionLabel = null }) {
1862
1923
  const viewportWidth = layoutState.viewportWidth;
1863
1924
  const viewportHeight = layoutState.viewportHeight;
1864
1925
  const layout = React.useMemo(
1865
- () => computeLegacyLayout({ width: viewportWidth, height: viewportHeight }, layoutState.paneAdjust, layoutState.promptRows, 0, 0, layoutState.fullscreenPane),
1866
- [layoutState.fullscreenPane, layoutState.paneAdjust, layoutState.promptRows, viewportHeight, viewportWidth],
1926
+ () => computeLegacyLayout({ width: viewportWidth, height: viewportHeight }, layoutState.paneAdjust, layoutState.promptRows, layoutState.sessionPaneAdjust, layoutState.activityPaneAdjust, layoutState.fullscreenPane),
1927
+ [layoutState.fullscreenPane, layoutState.paneAdjust, layoutState.sessionPaneAdjust, layoutState.activityPaneAdjust, layoutState.promptRows, viewportHeight, viewportWidth],
1867
1928
  );
1868
1929
  const frames = buildWorkspacePaneFrames(layout);
1869
1930
  const sessionRows = Math.max(3, (layout.fullscreenPane === "sessions" ? layout.bodyHeight : layout.sessionPaneHeight) - 2);
@@ -1973,6 +2034,7 @@ export function SharedPilotSwarmApp({ controller, versionLabel = null }) {
1973
2034
  React.createElement(RenameSessionModalContainer, { controller }),
1974
2035
  React.createElement(ArtifactUploadModalContainer, { controller }),
1975
2036
  React.createElement(ArtifactPickerModalContainer, { controller }),
2037
+ React.createElement(HelpModalContainer, { controller }),
1976
2038
  React.createElement(ModelPickerModalContainer, { controller }),
1977
2039
  React.createElement(ReasoningEffortPickerModalContainer, { controller }),
1978
2040
  React.createElement(ThemePickerModalContainer, { controller }),
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { appendAnimatedDotsToRuns, useAnimatedDots } from "./chat-status.js";
2
+ import { appendAnimatedDotsToRuns, useAnimatedDots, useSpinnerFrame } from "./chat-status.js";
3
3
  import {
4
4
  UI_COMMANDS,
5
5
  INSPECTOR_TABS,
@@ -17,6 +17,7 @@ import {
17
17
  selectAdminConsole,
18
18
  selectArtifactPickerModal,
19
19
  selectArtifactUploadModal,
20
+ selectLiveActivityLines,
20
21
  selectChatLines,
21
22
  selectChatPaneChrome,
22
23
  selectOutboxOverlayLines,
@@ -2059,6 +2060,7 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
2059
2060
  inspectorTab: state.ui.inspectorTab,
2060
2061
  chatViewMode: state.ui.chatViewMode || "transcript",
2061
2062
  activeSessionIsGroup: Boolean(activeSessionId && state.sessions.byId[activeSessionId]?.isGroup),
2063
+ activeSessionStatus: activeSessionId ? String(state.sessions.byId[activeSessionId]?.status || "").toLowerCase() : "",
2062
2064
  focused: state.ui.focusRegion === "chat",
2063
2065
  scroll: state.ui.scroll.chat,
2064
2066
  contentWidth,
@@ -2110,6 +2112,20 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
2110
2112
  () => selectChatLines(selectorState, viewState.contentWidth, { tableMode: "sentinel" }),
2111
2113
  [selectorState, viewState.contentWidth],
2112
2114
  );
2115
+ const spinnerFrame = useSpinnerFrame(viewState.activeSessionStatus === "running");
2116
+ const liveActivityLines = React.useMemo(
2117
+ () => selectLiveActivityLines(selectorState, { spinnerFrame, maxWidth: viewState.contentWidth }),
2118
+ [selectorState, spinnerFrame, viewState.contentWidth],
2119
+ );
2120
+ // Concatenate the isolated live-activity block onto the (expensive) message
2121
+ // lines so the spinner tick only recomputes the small block, not the whole
2122
+ // transcript. The block clears itself once the reply lands.
2123
+ const chatLines = React.useMemo(
2124
+ () => (liveActivityLines.length > 0
2125
+ ? [...lines, [{ text: "", color: null }], ...liveActivityLines]
2126
+ : lines),
2127
+ [lines, liveActivityLines],
2128
+ );
2113
2129
  const outboxLines = React.useMemo(
2114
2130
  () => selectOutboxOverlayLines(selectorState, viewState.contentWidth, { tableMode: "sentinel" }),
2115
2131
  [selectorState, viewState.contentWidth],
@@ -2128,7 +2144,7 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
2128
2144
  actions: null,
2129
2145
  color: chrome.color,
2130
2146
  focused: viewState.focused,
2131
- lines,
2147
+ lines: chatLines,
2132
2148
  bottomStickyLines: outboxLines,
2133
2149
  scrollOffset: viewState.scroll,
2134
2150
  scrollMode: "bottom",