pilotswarm 0.5.0 → 0.5.2

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.
Files changed (37) hide show
  1. package/mcp/dist/src/session-id.d.ts +13 -0
  2. package/mcp/dist/src/session-id.d.ts.map +1 -0
  3. package/mcp/dist/src/session-id.js +15 -0
  4. package/mcp/dist/src/session-id.js.map +1 -0
  5. package/mcp/dist/src/tools/artifacts.d.ts.map +1 -1
  6. package/mcp/dist/src/tools/artifacts.js +5 -4
  7. package/mcp/dist/src/tools/artifacts.js.map +1 -1
  8. package/mcp/dist/src/tools/observability.d.ts.map +1 -1
  9. package/mcp/dist/src/tools/observability.js +2 -1
  10. package/mcp/dist/src/tools/observability.js.map +1 -1
  11. package/mcp/dist/src/tools/sessions.d.ts.map +1 -1
  12. package/mcp/dist/src/tools/sessions.js +9 -8
  13. package/mcp/dist/src/tools/sessions.js.map +1 -1
  14. package/mcp/dist/src/tools/turn-control.d.ts.map +1 -1
  15. package/mcp/dist/src/tools/turn-control.js +5 -4
  16. package/mcp/dist/src/tools/turn-control.js.map +1 -1
  17. package/package.json +3 -3
  18. package/tui/plugins/.mcp.json +1 -7
  19. package/tui/src/app.js +36 -5
  20. package/tui/src/node-sdk-transport.js +17 -0
  21. package/ui/core/src/commands.js +6 -0
  22. package/ui/core/src/controller.js +193 -10
  23. package/ui/core/src/reducer.js +118 -1
  24. package/ui/core/src/selectors.js +428 -21
  25. package/ui/core/src/state.js +17 -0
  26. package/ui/react/src/chat-status.js +22 -0
  27. package/ui/react/src/components.js +68 -6
  28. package/ui/react/src/web-app.js +67 -13
  29. package/web/dist/assets/index-BA1Gd5ZC.js +24 -0
  30. package/web/dist/assets/index-Jr_Wn0fg.css +1 -0
  31. package/web/dist/assets/pilotswarm-C6iSDexX.js +90 -0
  32. package/web/dist/assets/{react-IRUjK5Iz.js → react-CqzaLZob.js} +1 -1
  33. package/web/dist/index.html +4 -4
  34. package/web/runtime.js +23 -2
  35. package/web/dist/assets/index-B8ge3rzb.js +0 -24
  36. package/web/dist/assets/index-DXRIxmIv.css +0 -1
  37. package/web/dist/assets/pilotswarm-Dt1qY6hD.js +0 -90
@@ -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,
@@ -430,13 +431,19 @@ function compactTitleRuns(title, maxWidth = 40) {
430
431
  return [{ text: text.length > maxWidth ? `${text.slice(0, maxWidth - 1)}…` : text, color: "white", bold: true }];
431
432
  }
432
433
  const compactRuns = [];
434
+ // Gray runs are dropped on tight widths as decoration (ids, badges) —
435
+ // EXCEPT pure separators (" · "). Dropping those fuses adjacent values:
436
+ // "running · gpt-5.4 · ctx 33%" became "runninggpt-5.4ctx…".
437
+ const isSeparator = (run) => /^[\s·—-]+$/.test(String(run?.text || ""));
433
438
  let remaining = Math.max(8, maxWidth);
434
439
  for (const run of title) {
435
440
  if (remaining <= 0) break;
436
441
  const color = run?.color;
437
- if (color === "gray" && compactRuns.length > 0) continue;
442
+ if (color === "gray" && compactRuns.length > 0 && !isSeparator(run)) continue;
438
443
  const text = String(run?.text || "");
439
444
  if (!text) continue;
445
+ // Don't start a separator we can't follow with content.
446
+ if (isSeparator(run) && remaining < text.length + 2) break;
440
447
  const chunk = text.length > remaining && remaining > 1
441
448
  ? `${text.slice(0, remaining - 1)}…`
442
449
  : text.slice(0, remaining);
@@ -444,6 +451,10 @@ function compactTitleRuns(title, maxWidth = 40) {
444
451
  compactRuns.push({ ...run, text: chunk });
445
452
  remaining -= chunk.length;
446
453
  }
454
+ // Never end on a dangling separator.
455
+ while (compactRuns.length > 0 && isSeparator(compactRuns[compactRuns.length - 1])) {
456
+ compactRuns.pop();
457
+ }
447
458
  return compactRuns.length > 0 ? compactRuns : title;
448
459
  }
449
460
 
@@ -1714,6 +1725,7 @@ function PortalSequenceLines({ lines, theme, completionByTurn }) {
1714
1725
  );
1715
1726
  }
1716
1727
 
1728
+
1717
1729
  function ScrollLinesPanel({ title, titleRight = null, color, focused, actions, lines, stickyLines = [], bottomStickyLines = [], scrollOffset = 0, scrollMode = "top", paneKey, controller, className = "", panelClassName = "", topContent = null, bottomContent = null, structuredBlocks = false, stickyBottom = false, renderBody = null }) {
1718
1730
  const themeId = useControllerSelector(controller, (state) => state.ui.themeId);
1719
1731
  const theme = getTheme(themeId);
@@ -1735,11 +1747,21 @@ function ScrollLinesPanel({ title, titleRight = null, color, focused, actions, l
1735
1747
  });
1736
1748
  }, []);
1737
1749
 
1750
+ const [scrolledUp, setScrolledUp] = React.useState(false);
1751
+ const updateScrolledUp = React.useCallback((el) => {
1752
+ if (!el) return;
1753
+ const up = el.scrollHeight - el.scrollTop - el.clientHeight > 4;
1754
+ setScrolledUp((current) => (current === up ? current : up));
1755
+ }, []);
1738
1756
  const handleBodyScroll = React.useCallback((event) => {
1739
1757
  onScroll();
1758
+ updateScrolledUp(event.currentTarget);
1740
1759
  if (!preserveHorizontalScroll || syncingHorizontalRef.current) return;
1741
1760
  syncScrollLeft(event.currentTarget, stickyRef.current);
1742
- }, [onScroll, preserveHorizontalScroll, syncScrollLeft]);
1761
+ }, [onScroll, preserveHorizontalScroll, syncScrollLeft, updateScrolledUp]);
1762
+ React.useEffect(() => {
1763
+ updateScrolledUp(ref.current);
1764
+ });
1743
1765
 
1744
1766
  const handleStickyScroll = React.useCallback((event) => {
1745
1767
  if (!preserveHorizontalScroll || syncingHorizontalRef.current) return;
@@ -1757,7 +1779,7 @@ function ScrollLinesPanel({ title, titleRight = null, color, focused, actions, l
1757
1779
  normalizedSticky.map((line, index) => React.createElement(Line, { key: `sticky:${index}`, line, theme })),
1758
1780
  )
1759
1781
  : null,
1760
- React.createElement("div", { ref, className: `ps-scroll-panel ${className}`.trim(), onScroll: handleBodyScroll, onWheel, onTouchStart, onTouchMove, onTouchEnd, onTouchCancel: onTouchEnd },
1782
+ React.createElement("div", { ref, className: `ps-scroll-panel ${className}${scrolledUp ? " is-scrolled-up" : ""}`.trim(), onScroll: handleBodyScroll, onWheel, onTouchStart, onTouchMove, onTouchEnd, onTouchCancel: onTouchEnd },
1761
1783
  typeof renderBody === "function"
1762
1784
  ? renderBody(normalizedLines, theme)
1763
1785
  : structuredBlocks
@@ -2059,6 +2081,7 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
2059
2081
  inspectorTab: state.ui.inspectorTab,
2060
2082
  chatViewMode: state.ui.chatViewMode || "transcript",
2061
2083
  activeSessionIsGroup: Boolean(activeSessionId && state.sessions.byId[activeSessionId]?.isGroup),
2084
+ activeSessionStatus: activeSessionId ? String(state.sessions.byId[activeSessionId]?.status || "").toLowerCase() : "",
2062
2085
  focused: state.ui.focusRegion === "chat",
2063
2086
  scroll: state.ui.scroll.chat,
2064
2087
  contentWidth,
@@ -2110,10 +2133,23 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
2110
2133
  () => selectChatLines(selectorState, viewState.contentWidth, { tableMode: "sentinel" }),
2111
2134
  [selectorState, viewState.contentWidth],
2112
2135
  );
2136
+ const spinnerFrame = useSpinnerFrame(viewState.activeSessionStatus === "running");
2137
+ const liveActivityLines = React.useMemo(
2138
+ () => selectLiveActivityLines(selectorState, { spinnerFrame, maxWidth: viewState.contentWidth }),
2139
+ [selectorState, spinnerFrame, viewState.contentWidth],
2140
+ );
2141
+ // The live-activity line is pinned in the bottom-sticky strip (with the
2142
+ // outbox overlay), NOT appended to the transcript — it must stay put while
2143
+ // chat content scrolls, and it drops the instant the turn ends.
2144
+ const pinnedActivityLines = liveActivityLines;
2113
2145
  const outboxLines = React.useMemo(
2114
2146
  () => selectOutboxOverlayLines(selectorState, viewState.contentWidth, { tableMode: "sentinel" }),
2115
2147
  [selectorState, viewState.contentWidth],
2116
2148
  );
2149
+ const stickyBottom = React.useMemo(
2150
+ () => (pinnedActivityLines.length > 0 ? [...outboxLines, ...pinnedActivityLines] : outboxLines),
2151
+ [outboxLines, pinnedActivityLines],
2152
+ );
2117
2153
  const composer = showComposer && !viewState.activeSessionIsGroup && viewState.chatViewMode !== "summary"
2118
2154
  ? React.createElement("div", { className: "ps-chat-composer" },
2119
2155
  React.createElement(PromptComposer, { controller, mobile, active: true }))
@@ -2129,7 +2165,7 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
2129
2165
  color: chrome.color,
2130
2166
  focused: viewState.focused,
2131
2167
  lines,
2132
- bottomStickyLines: outboxLines,
2168
+ bottomStickyLines: stickyBottom,
2133
2169
  scrollOffset: viewState.scroll,
2134
2170
  scrollMode: "bottom",
2135
2171
  paneKey: "chat",
@@ -4089,11 +4125,27 @@ function AdminConsolePanel({ controller }) {
4089
4125
  : null,
4090
4126
  React.createElement("section", { className: "ps-admin-console__section" },
4091
4127
  React.createElement("h3", null, "GitHub Copilot key"),
4092
- React.createElement("p", { className: "ps-admin-console__hint" },
4093
- "Per-user override for the GitHub Copilot model provider token. ",
4094
- "When set, this key is used instead of the worker's env-supplied ",
4095
- "GITHUB_TOKEN for sessions you own. Clearing the key reverts to ",
4096
- "the worker default."),
4128
+ view.ghcpKey.storeAsSystem
4129
+ ? React.createElement("p", { className: "ps-admin-console__hint" },
4130
+ "System-wide key for platform-managed (ownerless) system ",
4131
+ "sessions agent tuners, repo cache managers, and other ",
4132
+ "sessions with no owner resolve this key for GitHub Copilot ",
4133
+ "models. Clearing it reverts them to the worker default.")
4134
+ : React.createElement("p", { className: "ps-admin-console__hint" },
4135
+ "Per-user override for the GitHub Copilot model provider token. ",
4136
+ "When set, this key is used instead of the worker's env-supplied ",
4137
+ "GITHUB_TOKEN for sessions you own. Clearing the key reverts to ",
4138
+ "the worker default."),
4139
+ view.isAdmin && view.systemGhcpKey.supported
4140
+ ? React.createElement("label", { className: "ps-admin-console__system-toggle" },
4141
+ React.createElement("input", {
4142
+ type: "checkbox",
4143
+ checked: view.ghcpKey.storeAsSystem,
4144
+ disabled: view.ghcpKey.saving,
4145
+ onChange: (event) => controller.setAdminGhcpKeyStoreAsSystem(event.target.checked),
4146
+ }),
4147
+ "Store as System key")
4148
+ : null,
4097
4149
  React.createElement("p", { className: `ps-admin-console__status${view.ghcpKey.error ? " is-error" : ""}` },
4098
4150
  view.ghcpKey.error || view.ghcpKey.statusText),
4099
4151
  view.ghcpKey.editing
@@ -4125,13 +4177,15 @@ function AdminConsolePanel({ controller }) {
4125
4177
  type: "button",
4126
4178
  className: "ps-primary-button",
4127
4179
  onClick: onBeginEdit,
4128
- }, view.ghcpKey.configured ? "Replace key" : "Set key"),
4129
- view.ghcpKey.configured
4180
+ }, view.ghcpKey.targetConfigured
4181
+ ? (view.ghcpKey.storeAsSystem ? "Replace System key" : "Replace key")
4182
+ : (view.ghcpKey.storeAsSystem ? "Set System key" : "Set key")),
4183
+ view.ghcpKey.targetConfigured
4130
4184
  ? React.createElement("button", {
4131
4185
  type: "button",
4132
4186
  className: "ps-mini-button",
4133
4187
  onClick: onClear,
4134
- }, "Clear key")
4188
+ }, view.ghcpKey.storeAsSystem ? "Clear System key" : "Clear key")
4135
4189
  : null,
4136
4190
  React.createElement("button", {
4137
4191
  type: "button",