pilotswarm 0.5.21 → 0.5.23

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 (75) hide show
  1. package/package.json +2 -1
  2. package/tui/src/node-sdk-transport.js +22 -5
  3. package/ui/core/src/controller.js +1 -1
  4. package/ui/core/src/reducer.js +3 -2
  5. package/ui/core/src/selectors.js +142 -2
  6. package/ui/core/src/state.js +1 -1
  7. package/ui/react/src/web-app.js +541 -22
  8. package/web/dist/assets/abnfDiagram-VRR7QNED-BHkTpRkx.js +1 -0
  9. package/web/dist/assets/arc-BOgZyEWA.js +1 -0
  10. package/web/dist/assets/architectureDiagram-ZJ3FMSHR-DeE8fDMy.js +36 -0
  11. package/web/dist/assets/blockDiagram-677ZJIJ3-CzhdxVEu.js +132 -0
  12. package/web/dist/assets/c4Diagram-LMCZKHZV-Cc6p_IKP.js +10 -0
  13. package/web/dist/assets/channel-DhhWyKRW.js +1 -0
  14. package/web/dist/assets/chunk-2Q5K7J3B-BCo94LTM.js +1 -0
  15. package/web/dist/assets/chunk-32BRIVSS-DaklJ-6i.js +1 -0
  16. package/web/dist/assets/chunk-5VM5RSS4-BN_wg2pr.js +15 -0
  17. package/web/dist/assets/chunk-EX3LRPZG-DSFFLUoL.js +231 -0
  18. package/web/dist/assets/chunk-JWPE2WC7-DB4dAjza.js +1 -0
  19. package/web/dist/assets/chunk-MOJQB5TN-CRM5eGQS.js +88 -0
  20. package/web/dist/assets/chunk-RYQCIY6F-BFlkjazU.js +1 -0
  21. package/web/dist/assets/chunk-V7JOEXUC-Bnfh4AAv.js +206 -0
  22. package/web/dist/assets/chunk-VR4S4FIN-CQc4_qVM.js +1 -0
  23. package/web/dist/assets/chunk-XXDRQBXY-CZivyoDm.js +1 -0
  24. package/web/dist/assets/classDiagram-OUVF2IWQ-CQBxGpio.js +1 -0
  25. package/web/dist/assets/classDiagram-v2-EOCWNBFH-CQBxGpio.js +1 -0
  26. package/web/dist/assets/cose-bilkent-JH36ORCC-BUibhTD-.js +1 -0
  27. package/web/dist/assets/cynefin-VYW2F7L2-CRkjg5zF.js +166 -0
  28. package/web/dist/assets/cynefinDiagram-TSTJHNR4-BoeQn4Um.js +62 -0
  29. package/web/dist/assets/cytoscape.esm-D3_iZ_3b.js +321 -0
  30. package/web/dist/assets/dagre-VKFMJZFB-DGo7-PY5.js +4 -0
  31. package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  32. package/web/dist/assets/diagram-FQU43EPY-DMXd2bdO.js +3 -0
  33. package/web/dist/assets/diagram-G47NLZAW-CkYHblMZ.js +24 -0
  34. package/web/dist/assets/diagram-NH7WQ7WH-C9iwEe4T.js +24 -0
  35. package/web/dist/assets/diagram-OA4YK3LP-DpYLrfwb.js +30 -0
  36. package/web/dist/assets/diagram-WEI45ONY-BvrBukHb.js +41 -0
  37. package/web/dist/assets/ebnfDiagram-CCIWWBDH-DGl1r3zR.js +1 -0
  38. package/web/dist/assets/erDiagram-Q63AITRT-CoP5DBSb.js +85 -0
  39. package/web/dist/assets/flowDiagram-23GEKE2U-DVHtyYyr.js +156 -0
  40. package/web/dist/assets/ganttDiagram-NO4QXBWP-_JyR78yC.js +292 -0
  41. package/web/dist/assets/gitGraphDiagram-IHSO6WYX-Bj7sh0Db.js +106 -0
  42. package/web/dist/assets/graph-DOmOIIwC.js +1 -0
  43. package/web/dist/assets/{index-BDMVG8w6.js → index-CB6NfSZb.js} +3 -3
  44. package/web/dist/assets/{index-CxkLM8_a.css → index-SmXlZ1eu.css} +1 -1
  45. package/web/dist/assets/infoDiagram-FWYZ7A6U-BrvMeRmA.js +2 -0
  46. package/web/dist/assets/init-Gi6I4Gst.js +1 -0
  47. package/web/dist/assets/ishikawaDiagram-FXEZZL3T-lNDujpEC.js +70 -0
  48. package/web/dist/assets/journeyDiagram-5HDEW3XC-yoD2QRni.js +139 -0
  49. package/web/dist/assets/kanban-definition-HUTT4EX6-DtyMlypg.js +89 -0
  50. package/web/dist/assets/katex-HP8lGamR.js +257 -0
  51. package/web/dist/assets/layout-D-LzfAck.js +1 -0
  52. package/web/dist/assets/linear-DXPbXF9v.js +1 -0
  53. package/web/dist/assets/map-DxJ2ADlA.js +1 -0
  54. package/web/dist/assets/mermaid.core-DCEXqVUL.js +308 -0
  55. package/web/dist/assets/mindmap-definition-LN4V7U3C-DpKHDJFg.js +96 -0
  56. package/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  57. package/web/dist/assets/pegDiagram-2B236MQR-mxFI_8qh.js +1 -0
  58. package/web/dist/assets/pieDiagram-ENE6RG2P-sUWLrwMG.js +39 -0
  59. package/web/dist/assets/pilotswarm-CdEkcq2V.js +91 -0
  60. package/web/dist/assets/quadrantDiagram-ABIIQ3AL-Cph1z7ye.js +7 -0
  61. package/web/dist/assets/railroadDiagram-RFXS5EU6-BPKkt2va.js +1 -0
  62. package/web/dist/assets/requirementDiagram-TGXJPOKE-BB6DNHCy.js +84 -0
  63. package/web/dist/assets/sankeyDiagram-HTMAVEWB-BASvPQk2.js +40 -0
  64. package/web/dist/assets/sequenceDiagram-DBY2YBRQ-DMrNas01.js +162 -0
  65. package/web/dist/assets/sizeCapture-X5ZJPWSS-Ddq5yeZZ.js +1 -0
  66. package/web/dist/assets/stateDiagram-2N3HPSRC-DUb_IFST.js +1 -0
  67. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-C3nsHf5u.js +1 -0
  68. package/web/dist/assets/swimlanes-5IMT3BWC-Cs9JsGaf.js +2 -0
  69. package/web/dist/assets/swimlanesDiagram-G3AALYLV-C2xZA5V6.js +8 -0
  70. package/web/dist/assets/timeline-definition-FHXFAJF6-xuNF5fTm.js +120 -0
  71. package/web/dist/assets/vennDiagram-L72KCM5P-fsI1GFLy.js +34 -0
  72. package/web/dist/assets/wardleyDiagram-EHGQE667-DEc-kumC.js +78 -0
  73. package/web/dist/assets/xychartDiagram-FW5EYKEG-BCo6Qrfu.js +7 -0
  74. package/web/dist/index.html +3 -3
  75. package/web/dist/assets/pilotswarm-CjMx44so.js +0 -90
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pilotswarm",
3
- "version": "0.5.21",
3
+ "version": "0.5.23",
4
4
  "description": "PilotSwarm application package: terminal UI, browser portal + Web API server, and MCP server — one install, three bins.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -78,6 +78,7 @@
78
78
  "hono": "^4.12.10",
79
79
  "ink": "^6.8.0",
80
80
  "jose": "^6.2.2",
81
+ "mermaid": "^11.16.0",
81
82
  "pilotswarm-sdk": "^0.5.20",
82
83
  "react": "^19.2.4",
83
84
  "react-dom": "^19.2.4",
@@ -1690,8 +1690,13 @@ export class NodeSdkTransport {
1690
1690
  const payload = JSON.parse(body || "{}");
1691
1691
  const items = Array.isArray(payload?.items) ? payload.items : [];
1692
1692
  resolve(items
1693
- .map((item) => String(item?.metadata?.name || "").trim())
1694
- .filter(Boolean));
1693
+ .map((item) => ({
1694
+ name: String(item?.metadata?.name || "").trim(),
1695
+ containers: (Array.isArray(item?.spec?.containers) ? item.spec.containers : [])
1696
+ .map((container) => String(container?.name || "").trim())
1697
+ .filter(Boolean),
1698
+ }))
1699
+ .filter((pod) => pod.name));
1695
1700
  } catch (error) {
1696
1701
  reject(error);
1697
1702
  }
@@ -1709,6 +1714,9 @@ export class NodeSdkTransport {
1709
1714
  timestamps: "true",
1710
1715
  tailLines: String(options.tailLines ?? 500),
1711
1716
  });
1717
+ // Multi-container pods (sidecars, init containers) reject log reads
1718
+ // that don't name a container (400 BadRequest).
1719
+ if (options.container) params.set("container", options.container);
1712
1720
  const pathName = `/api/v1/namespaces/${encodeURIComponent(config.namespace)}/pods/${encodeURIComponent(podName)}/log?${params.toString()}`;
1713
1721
 
1714
1722
  return new Promise((resolve, reject) => {
@@ -1810,9 +1818,9 @@ export class NodeSdkTransport {
1810
1818
  this.logTailHandle = handle;
1811
1819
 
1812
1820
  this.listPodsFromKubeApi(config, labelSelector)
1813
- .then(async (podNames) => {
1821
+ .then(async (pods) => {
1814
1822
  if (handle.stopped || this.logTailHandle !== handle) return;
1815
- if (podNames.length === 0) {
1823
+ if (pods.length === 0) {
1816
1824
  this.emitSyntheticLogMessage(
1817
1825
  `No pods matched label selector ${JSON.stringify(labelSelector)} in namespace ${config.namespace}.`,
1818
1826
  "warn",
@@ -1820,8 +1828,17 @@ export class NodeSdkTransport {
1820
1828
  return;
1821
1829
  }
1822
1830
 
1831
+ // Container selection per pod: explicit K8S_LOG_CONTAINER wins,
1832
+ // else the conventional "worker" container when present, else
1833
+ // the pod's first container. Single-container pods work either
1834
+ // way; multi-container pods 400 without an explicit name.
1835
+ const configuredContainer = String(process.env.K8S_LOG_CONTAINER || "").trim();
1823
1836
  const results = await Promise.allSettled(
1824
- podNames.map((podName) => this.streamPodLogsFromKubeApi(config, podName, handle)),
1837
+ pods.map((pod) => {
1838
+ const container = configuredContainer
1839
+ || (pod.containers.includes("worker") ? "worker" : pod.containers[0]);
1840
+ return this.streamPodLogsFromKubeApi(config, pod.name, handle, container ? { container } : {});
1841
+ }),
1825
1842
  );
1826
1843
 
1827
1844
  if (handle.stopped || this.logTailHandle !== handle) return;
@@ -3870,7 +3870,7 @@ export class PilotSwarmUiController {
3870
3870
  }
3871
3871
 
3872
3872
  setChatViewMode(mode) {
3873
- if (mode !== "summary" && mode !== "transcript") return;
3873
+ if (mode !== "summary" && mode !== "transcript" && mode !== "rich") return;
3874
3874
  if (this.getActiveSession()?.isGroup) return;
3875
3875
  this.dispatch({ type: "ui/chatViewMode", mode });
3876
3876
  }
@@ -637,7 +637,8 @@ export function appReducer(state, action) {
637
637
  const hasOwnerFilter = Object.prototype.hasOwnProperty.call(settings, "sessionOwnerFilter");
638
638
  const hasLayout = Object.prototype.hasOwnProperty.call(settings, "layoutAdjustments");
639
639
  const hasChatViewMode = Object.prototype.hasOwnProperty.call(settings, "chatViewMode")
640
- && (settings.chatViewMode === "summary" || settings.chatViewMode === "transcript");
640
+ && (settings.chatViewMode === "summary" || settings.chatViewMode === "transcript"
641
+ || settings.chatViewMode === "rich");
641
642
  const hasPins = Object.prototype.hasOwnProperty.call(settings, "pinnedSessionIds");
642
643
  const hasCollapsed = Object.prototype.hasOwnProperty.call(settings, "collapsedSessionIds");
643
644
  // A pending/resolved deep-link intent outranks the profile's
@@ -701,7 +702,7 @@ export function appReducer(state, action) {
701
702
  }
702
703
 
703
704
  case "ui/chatViewMode":
704
- if (action.mode !== "summary" && action.mode !== "transcript") {
705
+ if (action.mode !== "summary" && action.mode !== "transcript" && action.mode !== "rich") {
705
706
  return state;
706
707
  }
707
708
  return {
@@ -229,7 +229,15 @@ function shouldDecorateSessionOwners(state) {
229
229
  // distinct human owner — otherwise the owner chip is pure noise on every
230
230
  // row (you are always "you"). A narrowed owner filter is an explicit
231
231
  // multi-user context, so honor that too.
232
- if (state.sessions?.ownerFilter && state.sessions.ownerFilter.all !== true) return true;
232
+ // A narrowed filter counts as an explicit multi-user context ONLY when it
233
+ // names specific owners. The DEFAULT signed-in filter already sets
234
+ // `all: false` AND `includeShared: true` (see
235
+ // defaultOwnerFilterForPrincipal), so testing either of those tagged every
236
+ // row in a single-user deployment with a pointless "you are you" chip.
237
+ // Everything else falls through to counting DISTINCT owners below, which
238
+ // is what the chip actually communicates.
239
+ const ownerFilter = state.sessions?.ownerFilter;
240
+ if (Array.isArray(ownerFilter?.ownerKeys) && ownerFilter.ownerKeys.length > 0) return true;
233
241
  const owners = new Set();
234
242
  for (const session of Object.values(state.sessions?.byId || {})) {
235
243
  if (!session || session.isSystem || session.isGroup) continue;
@@ -731,6 +739,37 @@ function buildSessionRowView(entry, session, state, totalDescendantCounts, visib
731
739
  badgeRuns: [],
732
740
  selectedMetaRuns: detailRuns,
733
741
  detailRuns,
742
+ // Structured row description for renderers that draw real chrome
743
+ // instead of terminal runs (the portal's rich session list): status
744
+ // becomes a dot, depth becomes indentation, the owner/child/cron
745
+ // badges become chips. Purely additive — `runs`/`titleRuns` above
746
+ // stay the terminal's source of truth and the TUI ignores this.
747
+ chrome: {
748
+ kind: session?.isGroup ? "group"
749
+ : session?.isSystem ? "system"
750
+ : session?.serviceKind ? "service"
751
+ : "session",
752
+ title: hasRealTitle ? rawTitle : shortId,
753
+ untitled: !hasRealTitle,
754
+ statusColor: sessionStatusColor(session, mode),
755
+ accentColor: mainColor,
756
+ pinned: isPinned,
757
+ selectMode,
758
+ checked: isSelected,
759
+ owner: shouldDecorateSessionOwners(state) && !session?.isSystem && !session?.isGroup
760
+ ? {
761
+ initials: effectiveOwner ? ownerInitials(effectiveOwner) : "?",
762
+ isMine: Boolean(ownerKeyForOwner(state?.auth?.principal)
763
+ && ownerKeyForOwner(effectiveOwner) === ownerKeyForOwner(state?.auth?.principal)),
764
+ }
765
+ : null,
766
+ childBadge: collapseBadge ? { text: collapseBadge.text, color: collapseBadge.color } : null,
767
+ cron: Boolean(cronBadge),
768
+ age: relTime || null,
769
+ model: modelLabel || null,
770
+ shortId,
771
+ ctx: ctxRuns.length > 0 ? { text: ctxRuns[0].text, color: ctxRuns[0].color } : null,
772
+ },
734
773
  };
735
774
  }
736
775
 
@@ -1374,7 +1413,12 @@ function prefixRuns(text, color = "gray", options = {}) {
1374
1413
  }];
1375
1414
  }
1376
1415
 
1377
- function buildChatMessagePrefix(message, options = {}) {
1416
+ // Header facts for one chat message — speaker label, label color, delivery
1417
+ // glyph, and timestamp — extracted from buildChatMessagePrefix so the rich
1418
+ // (block) chat renderer can consume the same labeling rules without the
1419
+ // terminal run/prefix shape. Returns { time, glyph, glyphColor, roleLabel,
1420
+ // roleColor }.
1421
+ function describeChatMessageHeader(message, options = {}) {
1378
1422
  const time = formatTimestamp(message?.createdAt || message?.time);
1379
1423
  // A user.message is labeled from the CURRENT VIEWER's perspective: "You"
1380
1424
  // for the viewer's own messages, the sender's name for anyone else. When
@@ -1459,6 +1503,15 @@ function buildChatMessagePrefix(message, options = {}) {
1459
1503
  : message?.role === "system"
1460
1504
  ? "yellow"
1461
1505
  : "white";
1506
+ // `fromOtherPerson` lets renderers drop the speaker label for the
1507
+ // viewer's own messages and the agent's (a 1:1 chat needs no "You:" /
1508
+ // "Agent:" on every turn) while still naming OTHER humans in a shared
1509
+ // session, where attribution is the whole point.
1510
+ return { time, glyph, glyphColor, roleLabel, roleColor, fromOtherPerson };
1511
+ }
1512
+
1513
+ function buildChatMessagePrefix(message, options = {}) {
1514
+ const { time, glyph, glyphColor, roleLabel, roleColor } = describeChatMessageHeader(message, options);
1462
1515
  const prefix = time ? `[${time}] ` : "";
1463
1516
  const runs = [...prefixRuns(prefix, "gray")];
1464
1517
  if (glyph) {
@@ -2319,6 +2372,93 @@ export function selectChatLines(state, maxWidth = 80, options = {}) {
2319
2372
  return lines.length > 0 ? lines : [{ text: "No messages yet.", color: "gray" }];
2320
2373
  }
2321
2374
 
2375
+ // True when a chat message is plain user/assistant prose the rich (block)
2376
+ // renderer can format as markdown directly. Everything with terminal-era
2377
+ // special handling — splash art, thinking cards, chrome-less summaries,
2378
+ // system/PilotSwarm cards, asked-and-answered exchanges, embedded
2379
+ // [SYSTEM: …] notice segments — stays on the line renderer, which already
2380
+ // knows every edge case.
2381
+ function isRichRenderableChatMessage(message) {
2382
+ if (!message) return false;
2383
+ if (message.kind === "epoch-divider" || message.kind === "regen-refused") return false;
2384
+ if (message.splash || message.thinking || message.noChrome) return false;
2385
+ if (message.role !== "user" && message.role !== "assistant") return false;
2386
+ if (message.role === "user" && parseAskedAndAnsweredExchange(message?.text || "")) return false;
2387
+ const segments = splitSystemNoticeSegments(message?.text || "");
2388
+ if (segments.some((segment) => segment.kind === "system")) return false;
2389
+ return true;
2390
+ }
2391
+
2392
+ /**
2393
+ * Message-level view of the active chat for the rich (desktop-style) web
2394
+ * renderer. Returns an ordered array of blocks:
2395
+ *
2396
+ * { kind: "message", id, role, header, text, pendingPhase, attachments }
2397
+ * — plain user/assistant prose. `header` is describeChatMessageHeader()
2398
+ * output; `text` is the artifact-link-decorated markdown source the
2399
+ * renderer formats itself; `attachments` is { sessionId, attachments }
2400
+ * (the sentinel shape) or null.
2401
+ *
2402
+ * { kind: "lines", variant, lines }
2403
+ * — everything else (splash, thinking cards, system cards, dividers),
2404
+ * pre-rendered through the exact same line builders the terminal
2405
+ * transcript uses, so no edge case has two implementations.
2406
+ *
2407
+ * The TUI never calls this; selectChatLines remains the terminal path.
2408
+ */
2409
+ export function selectChatBlocks(state, maxWidth = 80, options = {}) {
2410
+ const messages = selectActiveChat(state);
2411
+ if (!messages || messages.length === 0) return [];
2412
+
2413
+ const viewerKey = ownerKeyForOwner(state?.auth?.principal);
2414
+ const activeSessionId = state?.sessions?.activeSessionId;
2415
+ const activeSession = activeSessionId ? state?.sessions?.byId?.[activeSessionId] : null;
2416
+ const ownerKey = ownerKeyForOwner(activeSession?.owner);
2417
+ const sharedContext = Boolean(activeSession && (
2418
+ (activeSession.visibility && activeSession.visibility !== "private")
2419
+ || (ownerKey && viewerKey && ownerKey !== viewerKey)
2420
+ ));
2421
+ const buildOptions = {
2422
+ ...(options?.tableMode ? { tableMode: options.tableMode } : {}),
2423
+ ...(viewerKey ? { viewerKey } : {}),
2424
+ ...(ownerKey ? { ownerKey } : {}),
2425
+ ...(sharedContext ? { sharedContext: true } : {}),
2426
+ };
2427
+
2428
+ const blocks = [];
2429
+ for (const message of messages) {
2430
+ if (message?.kind === "epoch-divider") {
2431
+ blocks.push({ kind: "lines", variant: "divider", lines: [buildEpochDividerLine(message, maxWidth)] });
2432
+ continue;
2433
+ }
2434
+ if (message?.kind === "regen-refused") {
2435
+ blocks.push({ kind: "lines", variant: "divider", lines: [buildRegenRefusedLine(message, maxWidth)] });
2436
+ continue;
2437
+ }
2438
+ if (isRichRenderableChatMessage(message)) {
2439
+ const attachmentLines = buildAttachmentChipLines(message, { tableMode: "sentinel" });
2440
+ const attachmentSentinel = attachmentLines.find((line) => line?.kind === "imageAttachments") || null;
2441
+ blocks.push({
2442
+ kind: "message",
2443
+ id: message?.id ?? null,
2444
+ role: message.role,
2445
+ header: describeChatMessageHeader(message, buildOptions),
2446
+ text: decorateArtifactLinksForChat(message?.text || ""),
2447
+ pendingPhase: message?.pendingPhase || null,
2448
+ attachments: attachmentSentinel
2449
+ ? { sessionId: attachmentSentinel.sessionId, attachments: attachmentSentinel.attachments }
2450
+ : null,
2451
+ });
2452
+ continue;
2453
+ }
2454
+ const lines = buildChatMessageLines(message, maxWidth, buildOptions);
2455
+ if (lines.length > 0) {
2456
+ blocks.push({ kind: "lines", variant: "event", lines });
2457
+ }
2458
+ }
2459
+ return blocks;
2460
+ }
2461
+
2322
2462
  // A centered inline rule ("──── label ────") for transcript markers. The dash
2323
2463
  // runs are CAPPED (not stretched to maxWidth): the web portal wraps by pixel
2324
2464
  // width, and a maxWidth-long "─" run overflows a narrower pane and wraps the
@@ -129,7 +129,7 @@ export function createInitialState({ mode = "local", branding = null, themeId =
129
129
  inspectorTab: INSPECTOR_TABS[0],
130
130
  sequenceExpandedTurns: [],
131
131
  sequenceSelectedTurn: null,
132
- chatViewMode: chatViewMode === "summary" ? "summary" : "transcript",
132
+ chatViewMode: (chatViewMode === "summary" || chatViewMode === "rich") ? chatViewMode : "transcript",
133
133
  statsViewMode: "session",
134
134
  prompt: "",
135
135
  promptCursor: 0,