pilotswarm 0.5.25 → 0.5.27

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 (70) hide show
  1. package/package.json +1 -1
  2. package/ui/core/src/controller.js +49 -4
  3. package/ui/core/src/selectors.js +52 -6
  4. package/ui/react/src/web-app.js +426 -80
  5. package/web/dist/assets/{abnfDiagram-VRR7QNED-C4qRR7va.js → abnfDiagram-VRR7QNED-DOZGtiQC.js} +1 -1
  6. package/web/dist/assets/{arc-BQw2V2pL.js → arc-CNT314TJ.js} +1 -1
  7. package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-DMdWnLGG.js → architectureDiagram-ZJ3FMSHR-Cmi5R1em.js} +1 -1
  8. package/web/dist/assets/{blockDiagram-677ZJIJ3-DZnALw8j.js → blockDiagram-677ZJIJ3-CFQt6Pnc.js} +1 -1
  9. package/web/dist/assets/{c4Diagram-LMCZKHZV-BGGZ42iX.js → c4Diagram-LMCZKHZV-v7-eGb6Y.js} +1 -1
  10. package/web/dist/assets/channel-DHDU75Fc.js +1 -0
  11. package/web/dist/assets/{chunk-2Q5K7J3B-Dastiegd.js → chunk-2Q5K7J3B-DoLSc3k6.js} +1 -1
  12. package/web/dist/assets/{chunk-32BRIVSS-B4XexRzf.js → chunk-32BRIVSS-l-i2NvX3.js} +1 -1
  13. package/web/dist/assets/{chunk-5VM5RSS4-D66MNGCN.js → chunk-5VM5RSS4-B3s5kXyR.js} +1 -1
  14. package/web/dist/assets/{chunk-EX3LRPZG-DeqPS4Xm.js → chunk-EX3LRPZG-Ckctm_wZ.js} +1 -1
  15. package/web/dist/assets/{chunk-JWPE2WC7-DIMWiXpM.js → chunk-JWPE2WC7-D5reV4oK.js} +1 -1
  16. package/web/dist/assets/{chunk-MOJQB5TN-vTyL0qgx.js → chunk-MOJQB5TN-B-bgmTfa.js} +1 -1
  17. package/web/dist/assets/{chunk-RYQCIY6F-C7U9MKGw.js → chunk-RYQCIY6F-CLliUW3T.js} +1 -1
  18. package/web/dist/assets/{chunk-V7JOEXUC-BzPwQ7n3.js → chunk-V7JOEXUC-CsAyO1PL.js} +1 -1
  19. package/web/dist/assets/{chunk-VR4S4FIN-CmDsyK3b.js → chunk-VR4S4FIN-Df6VMLs3.js} +1 -1
  20. package/web/dist/assets/{chunk-XXDRQBXY-ZJn6BaGL.js → chunk-XXDRQBXY-pOkdkM00.js} +1 -1
  21. package/web/dist/assets/classDiagram-OUVF2IWQ-DkWfQ9TO.js +1 -0
  22. package/web/dist/assets/classDiagram-v2-EOCWNBFH-DkWfQ9TO.js +1 -0
  23. package/web/dist/assets/{cose-bilkent-JH36ORCC-CAA8JZZM.js → cose-bilkent-JH36ORCC-DF9LzGIn.js} +1 -1
  24. package/web/dist/assets/{cynefin-VYW2F7L2-B6pX-lUD.js → cynefin-VYW2F7L2-COwlV5eq.js} +1 -1
  25. package/web/dist/assets/{cynefinDiagram-TSTJHNR4-sKTHU_UH.js → cynefinDiagram-TSTJHNR4-Cmf8ISYO.js} +1 -1
  26. package/web/dist/assets/{dagre-VKFMJZFB-EuakJ578.js → dagre-VKFMJZFB-DanZ5sUs.js} +1 -1
  27. package/web/dist/assets/{diagram-FQU43EPY-DR3M_pic.js → diagram-FQU43EPY-xJWijzy7.js} +1 -1
  28. package/web/dist/assets/{diagram-G47NLZAW-BlS6yM0u.js → diagram-G47NLZAW-C2V1yOvV.js} +1 -1
  29. package/web/dist/assets/{diagram-NH7WQ7WH-CQNXe6dc.js → diagram-NH7WQ7WH-C8SrNQ_i.js} +1 -1
  30. package/web/dist/assets/{diagram-OA4YK3LP-BizBeSQo.js → diagram-OA4YK3LP-BATn2U0p.js} +1 -1
  31. package/web/dist/assets/{diagram-WEI45ONY-C-_KM6Gg.js → diagram-WEI45ONY-Dk1xkd8r.js} +1 -1
  32. package/web/dist/assets/{ebnfDiagram-CCIWWBDH-BapWEdic.js → ebnfDiagram-CCIWWBDH-JFfByguj.js} +1 -1
  33. package/web/dist/assets/{erDiagram-Q63AITRT-Btijn1Xj.js → erDiagram-Q63AITRT-DnlU9tGx.js} +1 -1
  34. package/web/dist/assets/{flowDiagram-23GEKE2U-D8zK8HkH.js → flowDiagram-23GEKE2U-Dnq_XrQ8.js} +1 -1
  35. package/web/dist/assets/{ganttDiagram-NO4QXBWP-DyjkOIMW.js → ganttDiagram-NO4QXBWP-MzgQ4Gkh.js} +1 -1
  36. package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-3r9_QeYN.js → gitGraphDiagram-IHSO6WYX-C2LwEO5a.js} +1 -1
  37. package/web/dist/assets/{index-m0kBr7aj.js → index-BkjhSyx0.js} +2 -2
  38. package/web/dist/assets/{index-BEhdfIki.css → index-DyL_-2Du.css} +1 -1
  39. package/web/dist/assets/{infoDiagram-FWYZ7A6U-Ccd8ACUz.js → infoDiagram-FWYZ7A6U-UMiiYK4p.js} +1 -1
  40. package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-DPRUC8V2.js → ishikawaDiagram-FXEZZL3T-CtzROVDb.js} +1 -1
  41. package/web/dist/assets/{journeyDiagram-5HDEW3XC-CkvayMsL.js → journeyDiagram-5HDEW3XC-C5UU06Ip.js} +1 -1
  42. package/web/dist/assets/{kanban-definition-HUTT4EX6-CAK9Z-ha.js → kanban-definition-HUTT4EX6-DYwJ4PpM.js} +1 -1
  43. package/web/dist/assets/{linear-B-_ZjN2w.js → linear-Dg7W_aPc.js} +1 -1
  44. package/web/dist/assets/{mermaid.core-BN9c--2r.js → mermaid.core-OUiCmXKB.js} +4 -4
  45. package/web/dist/assets/{mindmap-definition-LN4V7U3C-DuD_AHRU.js → mindmap-definition-LN4V7U3C-ffFo5mVE.js} +1 -1
  46. package/web/dist/assets/papaparse.min-DPfbFiel.js +10 -0
  47. package/web/dist/assets/{pegDiagram-2B236MQR-Cc1v58KA.js → pegDiagram-2B236MQR-DJ0r7gkW.js} +1 -1
  48. package/web/dist/assets/{pieDiagram-ENE6RG2P-Bc32--ur.js → pieDiagram-ENE6RG2P-BnpWDtAd.js} +1 -1
  49. package/web/dist/assets/pilotswarm-CU6jtK4w.js +94 -0
  50. package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-DpPTc2EP.js → quadrantDiagram-ABIIQ3AL-Bcxo8lQJ.js} +1 -1
  51. package/web/dist/assets/{railroadDiagram-RFXS5EU6-DXMzwejw.js → railroadDiagram-RFXS5EU6-YtDm2Hhr.js} +1 -1
  52. package/web/dist/assets/{requirementDiagram-TGXJPOKE-B3ImAxw-.js → requirementDiagram-TGXJPOKE-DIIFSM-x.js} +1 -1
  53. package/web/dist/assets/{sankeyDiagram-HTMAVEWB-hmVMffE2.js → sankeyDiagram-HTMAVEWB-BH3EkrzA.js} +1 -1
  54. package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-D53fk6uF.js → sequenceDiagram-DBY2YBRQ-DjD5j0HL.js} +1 -1
  55. package/web/dist/assets/{sizeCapture-X5ZJPWSS-4U-I5d2g.js → sizeCapture-X5ZJPWSS-L5FPJ9w8.js} +1 -1
  56. package/web/dist/assets/{stateDiagram-2N3HPSRC-0Y5TgWhW.js → stateDiagram-2N3HPSRC-o-MgnF-b.js} +1 -1
  57. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-DkGZm6xe.js +1 -0
  58. package/web/dist/assets/{swimlanes-5IMT3BWC-Umhy4qUu.js → swimlanes-5IMT3BWC-B_7FbL2G.js} +2 -2
  59. package/web/dist/assets/swimlanesDiagram-G3AALYLV-D9vnXGSI.js +8 -0
  60. package/web/dist/assets/{timeline-definition-FHXFAJF6-D5hOg3Qu.js → timeline-definition-FHXFAJF6-C8qUL6LV.js} +1 -1
  61. package/web/dist/assets/{vennDiagram-L72KCM5P-QVIcE3UD.js → vennDiagram-L72KCM5P-wS-9VnAs.js} +1 -1
  62. package/web/dist/assets/{wardleyDiagram-EHGQE667-BEj1M2cB.js → wardleyDiagram-EHGQE667-Cz8fglhX.js} +1 -1
  63. package/web/dist/assets/{xychartDiagram-FW5EYKEG-xojRIXIf.js → xychartDiagram-FW5EYKEG-CmsR-UfQ.js} +1 -1
  64. package/web/dist/index.html +3 -3
  65. package/web/dist/assets/channel-CB157dQ5.js +0 -1
  66. package/web/dist/assets/classDiagram-OUVF2IWQ-B6JwBYlv.js +0 -1
  67. package/web/dist/assets/classDiagram-v2-EOCWNBFH-B6JwBYlv.js +0 -1
  68. package/web/dist/assets/pilotswarm-BgzdDy09.js +0 -93
  69. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-DqIcGfAY.js +0 -1
  70. package/web/dist/assets/swimlanesDiagram-G3AALYLV-DA9txLi6.js +0 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pilotswarm",
3
- "version": "0.5.25",
3
+ "version": "0.5.27",
4
4
  "description": "PilotSwarm application package: terminal UI, browser portal + Web API server, and MCP server \u2014 one install, three bins.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1070,6 +1070,19 @@ function normalizePreviewPayload(filename, rawContent, contentType = "", metadat
1070
1070
 
1071
1071
  /** How long a fetched artifact list is trusted before a refetch. */
1072
1072
  const FILES_LIST_TTL_MS = 10_000;
1073
+ // How long keyboard navigation must rest on a session before it is fetched.
1074
+ // Long enough that scrolling past a row costs nothing, short enough that
1075
+ // landing on one feels immediate.
1076
+ const SESSION_NAV_SETTLE_MS = 140;
1077
+ // Ceiling on the window a session re-opens with, in events (5 history pages).
1078
+ //
1079
+ // `loadedEventLimit` is sticky: scrolling back through a long session escalates
1080
+ // it 300 -> 1000 -> 3000 -> 10000, and ensureSessionHistory then re-requested
1081
+ // that expanded size on EVERY switch-in, for the life of the tab. So one trip
1082
+ // through the history of a busy session made it permanently expensive to open —
1083
+ // 10k events re-fetched, re-derived and re-laid-out each time. Paging back
1084
+ // still expands freely while you are in the session; re-entering starts bounded.
1085
+ const HISTORY_REENTRY_MAX_EVENTS = 1_500;
1073
1086
 
1074
1087
  export class PilotSwarmUiController {
1075
1088
  constructor({ store, transport }) {
@@ -2292,9 +2305,12 @@ export class PilotSwarmUiController {
2292
2305
  async ensureSessionHistory(sessionId, { force = false } = {}) {
2293
2306
  if (!sessionId) return null;
2294
2307
  const existingHistory = this.getState().history.bySessionId.get(sessionId);
2295
- const requestedLimit = Math.max(
2296
- DEFAULT_HISTORY_EVENT_LIMIT,
2297
- Number(existingHistory?.loadedEventLimit ?? DEFAULT_HISTORY_EVENT_LIMIT) || DEFAULT_HISTORY_EVENT_LIMIT,
2308
+ const requestedLimit = Math.min(
2309
+ HISTORY_REENTRY_MAX_EVENTS,
2310
+ Math.max(
2311
+ DEFAULT_HISTORY_EVENT_LIMIT,
2312
+ Number(existingHistory?.loadedEventLimit ?? DEFAULT_HISTORY_EVENT_LIMIT) || DEFAULT_HISTORY_EVENT_LIMIT,
2313
+ ),
2298
2314
  );
2299
2315
  if (!force && existingHistory?.events) {
2300
2316
  return existingHistory;
@@ -6140,7 +6156,36 @@ export class PilotSwarmUiController {
6140
6156
  const currentIndex = Math.max(0, rows.findIndex((row) => row.sessionId === currentId));
6141
6157
  const nextIndex = Math.max(0, Math.min(rows.length - 1, currentIndex + delta));
6142
6158
  const nextId = rows[nextIndex].sessionId;
6143
- await this.loadSession(nextId);
6159
+ this.navigateToSession(nextId);
6160
+ }
6161
+
6162
+ /**
6163
+ * Move the selection now; fetch the session once the movement settles.
6164
+ *
6165
+ * `loadSession` force-refetches history, syncs detail, re-attaches the live
6166
+ * event stream and refreshes the inspector — four round trips. Driving that
6167
+ * from every keypress meant scrolling a list of sessions fired a request
6168
+ * pair per row, and against a remote deployment the UI ran behind the
6169
+ * cursor by however long the network took. The highlight is local state and
6170
+ * moves immediately; only the fetching waits for the user to stop.
6171
+ *
6172
+ * A CLICK still loads immediately — that is a deliberate choice of one
6173
+ * session, not a scroll through many.
6174
+ */
6175
+ navigateToSession(sessionId) {
6176
+ if (!sessionId) return;
6177
+ if (this.getState().sessions.activeSessionId !== sessionId) {
6178
+ this.dispatch({ type: "sessions/selected", sessionId });
6179
+ // Same per-session reset loadSession does, so the sequence tab does
6180
+ // not show another session's turn while the fetch is pending.
6181
+ this.dispatch({ type: "ui/sequenceExpandedTurns", turns: [] });
6182
+ this.dispatch({ type: "ui/sequenceSelectedTurn", turn: null });
6183
+ }
6184
+ if (this.sessionNavSettleTimer) clearTimeout(this.sessionNavSettleTimer);
6185
+ this.sessionNavSettleTimer = setTimeout(() => {
6186
+ this.sessionNavSettleTimer = null;
6187
+ this.loadSession(sessionId).catch(() => {});
6188
+ }, SESSION_NAV_SETTLE_MS);
6144
6189
  }
6145
6190
 
6146
6191
  getSessionPageSize() {
@@ -509,7 +509,7 @@ function getCollapseBadge(sessionId, entry, totalDescendantCounts, visibleDescen
509
509
  return { text: `[+${badgeCount}]`, color: "cyan" };
510
510
  }
511
511
 
512
- function formatCronTimestampForClient(value) {
512
+ export function formatCronTimestampForClient(value) {
513
513
  if (!value) return "scheduled";
514
514
  const date = value instanceof Date ? value : new Date(value);
515
515
  if (Number.isNaN(date.getTime())) return "scheduled";
@@ -771,6 +771,14 @@ function buildSessionRowView(entry, session, state, totalDescendantCounts, visib
771
771
  badgeRuns: [],
772
772
  selectedMetaRuns: detailRuns,
773
773
  detailRuns,
774
+ // Total descendants, for renderers that want the real number rather
775
+ // than the collapse badge (which counts only HIDDEN children, so it is
776
+ // null whenever the subtree is expanded).
777
+ // NOTE: `detailRuns` above computes its own childCount by indexing this
778
+ // Map with brackets, which always yields undefined — so its "N children"
779
+ // segment has never rendered. Left alone deliberately: fixing it would
780
+ // change TUI and mobile output.
781
+ childCount: totalDescendantCounts?.get?.(session?.sessionId) || 0,
774
782
  // Structured row description for renderers that draw real chrome
775
783
  // instead of terminal runs (the portal's rich session list): status
776
784
  // becomes a dot, depth becomes indentation, the owner/child/cron
@@ -840,6 +848,23 @@ function collectFilterExceptionIds(sessions) {
840
848
  return ids;
841
849
  }
842
850
 
851
+ // Per-row memo, keyed by the `flat` list so it survives selection changes but
852
+ // resets whenever the tree itself is rebuilt.
853
+ //
854
+ // WHY: moving the selection re-runs this selector (once from moveSession, once
855
+ // from the portal's useMemo) and used to rebuild EVERY row — including
856
+ // flattenRunsText over each row's runs — to change one boolean on two of them.
857
+ // At fleet scale that is the cost of a keypress. Now only rows whose inputs
858
+ // actually moved are rebuilt, and the rest keep their previous object
859
+ // identity, which also lets the renderer skip them.
860
+ const sessionRowMemo = new WeakMap();
861
+
862
+ function sameRowDeps(a, b) {
863
+ if (!a || a.length !== b.length) return false;
864
+ for (let i = 0; i < a.length; i += 1) if (a[i] !== b[i]) return false;
865
+ return true;
866
+ }
867
+
843
868
  export function selectSessionRows(state) {
844
869
  const totalDescendantCounts = getTotalDescendantCounts(state.sessions.byId);
845
870
  const visibleDescendantCounts = getVisibleDescendantCounts(state.sessions.flat, state.sessions.byId);
@@ -850,25 +875,46 @@ export function selectSessionRows(state) {
850
875
  const selectedSet = new Set(Array.isArray(state.sessions?.selectedIds) ? state.sessions.selectedIds : []);
851
876
  const filterExceptionIds = collectFilterExceptionIds(state.sessions);
852
877
 
878
+ let memo = sessionRowMemo.get(state.sessions.flat);
879
+ if (!memo) {
880
+ memo = new Map();
881
+ sessionRowMemo.set(state.sessions.flat, memo);
882
+ }
883
+
853
884
  return state.sessions.flat.map((entry) => {
854
885
  const session = state.sessions.byId[entry.sessionId];
886
+ const active = entry.sessionId === state.sessions.activeSessionId;
887
+ const pinned = pinnedSet.has(entry.sessionId);
888
+ const selected = selectedSet.has(entry.sessionId);
889
+ // Every input the row below is derived from. Identity comparison is
890
+ // enough: the reducer replaces these objects rather than mutating them.
891
+ const deps = [
892
+ entry, session, active, pinned, selected, query, ownerFilter, auth,
893
+ state.connection?.mode, state.sessions.selectMode,
894
+ totalDescendantCounts, visibleDescendantCounts,
895
+ ];
896
+ const cached = memo.get(entry.sessionId);
897
+ if (cached && sameRowDeps(cached.deps, deps)) return cached.row;
898
+
855
899
  const rowView = buildSessionRowView(entry, session, state, totalDescendantCounts, visibleDescendantCounts);
856
- return {
900
+ const row = {
857
901
  sessionId: entry.sessionId,
858
902
  text: flattenRunsText(rowView.runs),
859
903
  ...rowView,
860
904
  depth: entry.depth,
861
905
  status: session?.status,
862
906
  statusColor: sessionStatusColor(session, state.connection?.mode || "local"),
863
- active: entry.sessionId === state.sessions.activeSessionId,
907
+ active,
864
908
  isSystem: Boolean(session?.isSystem),
865
909
  isGroup: Boolean(session?.isGroup),
866
910
  hasChildren: entry.hasChildren,
867
911
  collapsed: entry.collapsed,
868
- pinned: pinnedSet.has(entry.sessionId),
869
- selected: selectedSet.has(entry.sessionId),
912
+ pinned,
913
+ selected,
870
914
  canPin: canPinSessionRow(session),
871
915
  };
916
+ memo.set(entry.sessionId, { deps, row });
917
+ return row;
872
918
  }).filter((row) => {
873
919
  if (filterExceptionIds?.has(row.sessionId)) return true;
874
920
  const session = state.sessions.byId[row.sessionId];
@@ -5320,7 +5366,7 @@ export function selectArtifactPickerModal(state, maxWidth = 88) {
5320
5366
 
5321
5367
  // ── Stats Inspector Pane ────────────────────────────────────────────
5322
5368
 
5323
- function formatCompactNumber(n) {
5369
+ export function formatCompactNumber(n) {
5324
5370
  if (n == null || !Number.isFinite(n)) return "0";
5325
5371
  if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
5326
5372
  if (n >= 10_000) return `${(n / 1_000).toFixed(1)}K`;