pilotswarm 0.4.1 → 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.
Files changed (89) hide show
  1. package/mcp/README.md +88 -4
  2. package/mcp/dist/src/context.d.ts +36 -6
  3. package/mcp/dist/src/context.d.ts.map +1 -1
  4. package/mcp/dist/src/context.js +102 -8
  5. package/mcp/dist/src/context.js.map +1 -1
  6. package/mcp/dist/src/resources/artifacts.d.ts +5 -0
  7. package/mcp/dist/src/resources/artifacts.d.ts.map +1 -0
  8. package/mcp/dist/src/resources/artifacts.js +39 -0
  9. package/mcp/dist/src/resources/artifacts.js.map +1 -0
  10. package/mcp/dist/src/resources/capabilities.d.ts +4 -0
  11. package/mcp/dist/src/resources/capabilities.d.ts.map +1 -0
  12. package/mcp/dist/src/resources/capabilities.js +17 -0
  13. package/mcp/dist/src/resources/capabilities.js.map +1 -0
  14. package/mcp/dist/src/resources/graph.d.ts +5 -0
  15. package/mcp/dist/src/resources/graph.d.ts.map +1 -0
  16. package/mcp/dist/src/resources/graph.js +45 -0
  17. package/mcp/dist/src/resources/graph.js.map +1 -0
  18. package/mcp/dist/src/server.d.ts.map +1 -1
  19. package/mcp/dist/src/server.js +61 -2
  20. package/mcp/dist/src/server.js.map +1 -1
  21. package/mcp/dist/src/tools/agents.d.ts.map +1 -1
  22. package/mcp/dist/src/tools/agents.js +3 -2
  23. package/mcp/dist/src/tools/agents.js.map +1 -1
  24. package/mcp/dist/src/tools/artifacts.d.ts +12 -0
  25. package/mcp/dist/src/tools/artifacts.d.ts.map +1 -0
  26. package/mcp/dist/src/tools/artifacts.js +92 -0
  27. package/mcp/dist/src/tools/artifacts.js.map +1 -0
  28. package/mcp/dist/src/tools/capabilities.d.ts +27 -0
  29. package/mcp/dist/src/tools/capabilities.d.ts.map +1 -0
  30. package/mcp/dist/src/tools/capabilities.js +69 -0
  31. package/mcp/dist/src/tools/capabilities.js.map +1 -0
  32. package/mcp/dist/src/tools/debug.d.ts +4 -0
  33. package/mcp/dist/src/tools/debug.d.ts.map +1 -0
  34. package/mcp/dist/src/tools/debug.js +94 -0
  35. package/mcp/dist/src/tools/debug.js.map +1 -0
  36. package/mcp/dist/src/tools/facts-enhanced.d.ts +13 -0
  37. package/mcp/dist/src/tools/facts-enhanced.d.ts.map +1 -0
  38. package/mcp/dist/src/tools/facts-enhanced.js +80 -0
  39. package/mcp/dist/src/tools/facts-enhanced.js.map +1 -0
  40. package/mcp/dist/src/tools/graph.d.ts +12 -0
  41. package/mcp/dist/src/tools/graph.d.ts.map +1 -0
  42. package/mcp/dist/src/tools/graph.js +237 -0
  43. package/mcp/dist/src/tools/graph.js.map +1 -0
  44. package/mcp/dist/src/tools/groups.d.ts +10 -0
  45. package/mcp/dist/src/tools/groups.d.ts.map +1 -0
  46. package/mcp/dist/src/tools/groups.js +107 -0
  47. package/mcp/dist/src/tools/groups.js.map +1 -0
  48. package/mcp/dist/src/tools/models.d.ts.map +1 -1
  49. package/mcp/dist/src/tools/models.js +16 -4
  50. package/mcp/dist/src/tools/models.js.map +1 -1
  51. package/mcp/dist/src/tools/observability.d.ts +13 -0
  52. package/mcp/dist/src/tools/observability.d.ts.map +1 -0
  53. package/mcp/dist/src/tools/observability.js +174 -0
  54. package/mcp/dist/src/tools/observability.js.map +1 -0
  55. package/mcp/dist/src/tools/sessions.d.ts.map +1 -1
  56. package/mcp/dist/src/tools/sessions.js +120 -94
  57. package/mcp/dist/src/tools/sessions.js.map +1 -1
  58. package/mcp/dist/src/tools/system.d.ts +14 -0
  59. package/mcp/dist/src/tools/system.d.ts.map +1 -0
  60. package/mcp/dist/src/tools/system.js +110 -0
  61. package/mcp/dist/src/tools/system.js.map +1 -0
  62. package/mcp/dist/src/tools/turn-control.d.ts +13 -0
  63. package/mcp/dist/src/tools/turn-control.d.ts.map +1 -0
  64. package/mcp/dist/src/tools/turn-control.js +98 -0
  65. package/mcp/dist/src/tools/turn-control.js.map +1 -0
  66. package/mcp/dist/src/util/respond.d.ts +21 -0
  67. package/mcp/dist/src/util/respond.d.ts.map +1 -0
  68. package/mcp/dist/src/util/respond.js +42 -0
  69. package/mcp/dist/src/util/respond.js.map +1 -0
  70. package/package.json +5 -4
  71. package/tui/bin/tui.js +0 -3
  72. package/tui/src/app.js +36 -5
  73. package/tui/src/node-sdk-transport.js +24 -0
  74. package/ui/core/src/commands.js +6 -0
  75. package/ui/core/src/controller.js +137 -2
  76. package/ui/core/src/reducer.js +31 -1
  77. package/ui/core/src/selectors.js +414 -17
  78. package/ui/core/src/state.js +2 -0
  79. package/ui/react/src/chat-status.js +22 -0
  80. package/ui/react/src/components.js +68 -6
  81. package/ui/react/src/web-app.js +69 -8
  82. package/web/dist/assets/{index-xJ8IzIZY.js → index-BmqU2WsR.js} +4 -4
  83. package/web/dist/assets/index-D2-7fi56.css +1 -0
  84. package/web/dist/assets/pilotswarm-cIaYAE9e.js +90 -0
  85. package/web/dist/assets/{react-CEPDSRB6.js → react-CRYBfNR4.js} +1 -1
  86. package/web/dist/index.html +4 -4
  87. package/web/runtime.js +30 -0
  88. package/web/dist/assets/index-DmGOcKR-.css +0 -1
  89. package/web/dist/assets/pilotswarm-D9pEmenA.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,
@@ -342,9 +343,17 @@ function normalizeLines(lines) {
342
343
  // Markup lines carry preformatted art (splash screens). Tag them
343
344
  // so the chat renderer keeps each line intact: the pane's
344
345
  // pre-wrap/overflow-wrap rules would rewrap wide art lines at
345
- // arbitrary points and scramble the image.
346
- for (const parsedLine of parseTerminalMarkupRuns(line.value || "")) {
347
- normalized.push({ kind: "runs", runs: parsedLine, preserve: true });
346
+ // arbitrary points and scramble the image. When a splashAlt is
347
+ // present, emit BOTH variants tagged by name — CSS shows exactly
348
+ // one based on the real device viewport, which beats character
349
+ // metrics at deciding what actually fits.
350
+ const variants = line.splashAlt
351
+ ? [{ text: line.value, variant: "desktop" }, { text: line.splashAlt, variant: "mobile" }]
352
+ : [{ text: line.value, variant: null }];
353
+ for (const v of variants) {
354
+ for (const parsedLine of parseTerminalMarkupRuns(v.text || "")) {
355
+ normalized.push({ kind: "runs", runs: parsedLine, preserve: true, splashVariant: v.variant });
356
+ }
348
357
  }
349
358
  continue;
350
359
  }
@@ -1322,12 +1331,13 @@ function parseStructuredChatBlocks(lines = []) {
1322
1331
  // detection entirely and render as one preserve block, so no line
1323
1332
  // ever rewraps or gets reinterpreted as a structured element.
1324
1333
  if (currentLine?.preserve) {
1334
+ const variant = currentLine.splashVariant || null;
1325
1335
  const preserveLines = [];
1326
- while (index < lines.length && lines[index]?.preserve) {
1336
+ while (index < lines.length && lines[index]?.preserve && (lines[index].splashVariant || null) === variant) {
1327
1337
  preserveLines.push(lines[index]);
1328
1338
  index += 1;
1329
1339
  }
1330
- blocks.push({ type: "preserve", lines: preserveLines });
1340
+ blocks.push({ type: "preserve", lines: preserveLines, splashVariant: variant });
1331
1341
  continue;
1332
1342
  }
1333
1343
 
@@ -1483,7 +1493,8 @@ function StructuredChatBlocks({ lines, theme }) {
1483
1493
  return React.createElement(React.Fragment, null,
1484
1494
  blocks.map((block, index) => {
1485
1495
  if (block.type === "preserve") {
1486
- return React.createElement("div", { key: `preserve:${index}`, className: "ps-chat-preserve-block" },
1496
+ const variantClass = block.splashVariant ? ` is-splash-${block.splashVariant}` : "";
1497
+ return React.createElement("div", { key: `preserve:${index}`, className: `ps-chat-preserve-block${variantClass}` },
1487
1498
  block.lines.map((line, lineIndex) => React.createElement(Line, {
1488
1499
  key: `preserve:${index}:${lineIndex}`,
1489
1500
  line,
@@ -2049,6 +2060,7 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
2049
2060
  inspectorTab: state.ui.inspectorTab,
2050
2061
  chatViewMode: state.ui.chatViewMode || "transcript",
2051
2062
  activeSessionIsGroup: Boolean(activeSessionId && state.sessions.byId[activeSessionId]?.isGroup),
2063
+ activeSessionStatus: activeSessionId ? String(state.sessions.byId[activeSessionId]?.status || "").toLowerCase() : "",
2052
2064
  focused: state.ui.focusRegion === "chat",
2053
2065
  scroll: state.ui.scroll.chat,
2054
2066
  contentWidth,
@@ -2100,6 +2112,20 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
2100
2112
  () => selectChatLines(selectorState, viewState.contentWidth, { tableMode: "sentinel" }),
2101
2113
  [selectorState, viewState.contentWidth],
2102
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
+ );
2103
2129
  const outboxLines = React.useMemo(
2104
2130
  () => selectOutboxOverlayLines(selectorState, viewState.contentWidth, { tableMode: "sentinel" }),
2105
2131
  [selectorState, viewState.contentWidth],
@@ -2118,7 +2144,7 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
2118
2144
  actions: null,
2119
2145
  color: chrome.color,
2120
2146
  focused: viewState.focused,
2121
- lines,
2147
+ lines: chatLines,
2122
2148
  bottomStickyLines: outboxLines,
2123
2149
  scrollOffset: viewState.scroll,
2124
2150
  scrollMode: "bottom",
@@ -4145,6 +4171,41 @@ export function PilotSwarmWebApp({ controller }) {
4145
4171
  const viewport = useMeasuredViewport(viewportRef);
4146
4172
  const mainGridViewport = useMeasuredViewport(mainGridRef);
4147
4173
  const gridViewport = computeGridViewport(viewport);
4174
+
4175
+ // Publish the real viewport in character cells. Without this the ui-core
4176
+ // layout model runs on its 120×40 fallback everywhere in the browser, so
4177
+ // width-derived behavior (markdown wrap width, render metrics, the
4178
+ // splashMobile width swap) assumed a ~120-column terminal regardless of
4179
+ // device — phones kept getting desktop splash art narrower than ~76 cols.
4180
+ React.useEffect(() => {
4181
+ const publish = () => {
4182
+ const probe = document.createElement("span");
4183
+ probe.style.cssText = "position:absolute;visibility:hidden;white-space:pre;";
4184
+ // Measure with the font real lines render in — an off-context
4185
+ // probe can inherit a different size and skew the column count.
4186
+ const sample = document.querySelector(".ps-scroll-panel .ps-line") || document.querySelector(".ps-line");
4187
+ if (sample) probe.style.font = window.getComputedStyle(sample).font;
4188
+ probe.textContent = "0".repeat(100);
4189
+ (sample?.parentElement || document.body).appendChild(probe);
4190
+ const charWidth = (probe.getBoundingClientRect().width / 100) || 8;
4191
+ probe.remove();
4192
+ controller.dispatch({
4193
+ type: "ui/viewport",
4194
+ width: Math.floor(window.innerWidth / charWidth),
4195
+ height: Math.floor(window.innerHeight / SCROLL_ROW_HEIGHT),
4196
+ });
4197
+ };
4198
+ publish();
4199
+ let timer;
4200
+ const onResize = () => { clearTimeout(timer); timer = setTimeout(publish, 150); };
4201
+ window.addEventListener("resize", onResize);
4202
+ window.addEventListener("orientationchange", onResize);
4203
+ return () => {
4204
+ clearTimeout(timer);
4205
+ window.removeEventListener("resize", onResize);
4206
+ window.removeEventListener("orientationchange", onResize);
4207
+ };
4208
+ }, [controller]);
4148
4209
  const [chatFocusMode, setChatFocusMode] = React.useState(false);
4149
4210
  const [chatFocusPane, setChatFocusPane] = React.useState(null);
4150
4211
  const state = useControllerSelector(controller, (rootState) => ({