pilotswarm 0.5.60 → 0.5.61

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 (74) hide show
  1. package/README.md +3 -2
  2. package/package.json +2 -2
  3. package/ui/core/src/controller.js +17 -0
  4. package/ui/core/src/moa.js +1 -1
  5. package/ui/core/src/reducer.js +51 -47
  6. package/ui/react/src/web-app.js +120 -67
  7. package/web/dist/assets/{abnfDiagram-VRR7QNED-CXEHJkTQ.js → abnfDiagram-VRR7QNED-CCVkGWsr.js} +1 -1
  8. package/web/dist/assets/{arc-lInHlEVm.js → arc-y040btO3.js} +1 -1
  9. package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-vrocx6re.js → architectureDiagram-ZJ3FMSHR-24Yt5esy.js} +1 -1
  10. package/web/dist/assets/{blockDiagram-677ZJIJ3-B1KA5QIB.js → blockDiagram-677ZJIJ3-BlbS-_GI.js} +1 -1
  11. package/web/dist/assets/{c4Diagram-LMCZKHZV-Du9xJMjz.js → c4Diagram-LMCZKHZV-_mamn0Fv.js} +1 -1
  12. package/web/dist/assets/channel-COFs9dqk.js +1 -0
  13. package/web/dist/assets/{chunk-2Q5K7J3B-BuHucQOQ.js → chunk-2Q5K7J3B-TCbIB2Ni.js} +1 -1
  14. package/web/dist/assets/{chunk-32BRIVSS-T-AE5Yz0.js → chunk-32BRIVSS-C9q5KNKG.js} +1 -1
  15. package/web/dist/assets/{chunk-5VM5RSS4-B_kTsNzt.js → chunk-5VM5RSS4-BvoqfU8P.js} +1 -1
  16. package/web/dist/assets/{chunk-EX3LRPZG-CtqgceBQ.js → chunk-EX3LRPZG-zFJjZeNd.js} +1 -1
  17. package/web/dist/assets/{chunk-JWPE2WC7-C_Fv9QUd.js → chunk-JWPE2WC7-Ce_6C5M-.js} +1 -1
  18. package/web/dist/assets/{chunk-MOJQB5TN-D5GeF8ZZ.js → chunk-MOJQB5TN-CPjpLM65.js} +1 -1
  19. package/web/dist/assets/{chunk-RYQCIY6F-Cg63wDst.js → chunk-RYQCIY6F-CsmVUSDH.js} +1 -1
  20. package/web/dist/assets/{chunk-V7JOEXUC-Bz6G3lev.js → chunk-V7JOEXUC-CgrmPPOd.js} +1 -1
  21. package/web/dist/assets/{chunk-VR4S4FIN-DKs_hvUD.js → chunk-VR4S4FIN-BxAHDR7h.js} +1 -1
  22. package/web/dist/assets/{chunk-XXDRQBXY-B9AtUlbn.js → chunk-XXDRQBXY-BGfFoO5_.js} +1 -1
  23. package/web/dist/assets/classDiagram-OUVF2IWQ-B5-KmsCx.js +1 -0
  24. package/web/dist/assets/classDiagram-v2-EOCWNBFH-B5-KmsCx.js +1 -0
  25. package/web/dist/assets/{cose-bilkent-JH36ORCC-rAG6oS0i.js → cose-bilkent-JH36ORCC-B6KcngJW.js} +1 -1
  26. package/web/dist/assets/{cynefin-VYW2F7L2-CUy2C8UT.js → cynefin-VYW2F7L2-DaY7l4Ce.js} +1 -1
  27. package/web/dist/assets/{cynefinDiagram-TSTJHNR4-C1W8blXH.js → cynefinDiagram-TSTJHNR4-BldfGsoa.js} +1 -1
  28. package/web/dist/assets/{dagre-VKFMJZFB-DC0XqwcC.js → dagre-VKFMJZFB-C4T9X5Bs.js} +1 -1
  29. package/web/dist/assets/{diagram-FQU43EPY-COAY9zCP.js → diagram-FQU43EPY-DCVA_t-e.js} +1 -1
  30. package/web/dist/assets/{diagram-G47NLZAW-DLPr8eWG.js → diagram-G47NLZAW-pbTZGgE5.js} +1 -1
  31. package/web/dist/assets/{diagram-NH7WQ7WH-DoNgwoDz.js → diagram-NH7WQ7WH-P8Iks122.js} +1 -1
  32. package/web/dist/assets/{diagram-OA4YK3LP-D3-5TINE.js → diagram-OA4YK3LP-hEjZPipp.js} +1 -1
  33. package/web/dist/assets/{diagram-WEI45ONY-DplCkBJC.js → diagram-WEI45ONY-CVAzVCGu.js} +1 -1
  34. package/web/dist/assets/{ebnfDiagram-CCIWWBDH-dvcVh7yr.js → ebnfDiagram-CCIWWBDH-BLL398qt.js} +1 -1
  35. package/web/dist/assets/{erDiagram-Q63AITRT-Cde8cTY6.js → erDiagram-Q63AITRT-CLTADyUG.js} +1 -1
  36. package/web/dist/assets/{flowDiagram-23GEKE2U-MGMNsh2E.js → flowDiagram-23GEKE2U-BAdlWAt0.js} +1 -1
  37. package/web/dist/assets/{ganttDiagram-NO4QXBWP-BiKw7AfS.js → ganttDiagram-NO4QXBWP-C7Z2wavr.js} +1 -1
  38. package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-vC7fsylq.js → gitGraphDiagram-IHSO6WYX-BI546pYu.js} +1 -1
  39. package/web/dist/assets/index-CvW-4eXF.css +1 -0
  40. package/web/dist/assets/index-DsPaDrf9.js +24 -0
  41. package/web/dist/assets/{infoDiagram-FWYZ7A6U-Du1uirB0.js → infoDiagram-FWYZ7A6U-8C7iQydo.js} +1 -1
  42. package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-Dk--Qshr.js → ishikawaDiagram-FXEZZL3T-Co4STVxQ.js} +1 -1
  43. package/web/dist/assets/{journeyDiagram-5HDEW3XC-iDE4zf_N.js → journeyDiagram-5HDEW3XC-DbUt11q-.js} +1 -1
  44. package/web/dist/assets/{kanban-definition-HUTT4EX6-D7WI8iRR.js → kanban-definition-HUTT4EX6-DUNcFmme.js} +1 -1
  45. package/web/dist/assets/{linear-B1_kxCBK.js → linear-Cjaxj94d.js} +1 -1
  46. package/web/dist/assets/{mermaid.core-DNmC5O4Y.js → mermaid.core-B3cPFf4f.js} +4 -4
  47. package/web/dist/assets/{mindmap-definition-LN4V7U3C-DIbLKLMA.js → mindmap-definition-LN4V7U3C-DaZtxB4N.js} +1 -1
  48. package/web/dist/assets/{papaparse.min-BdWinjY-.js → papaparse.min-vfdso2fs.js} +1 -1
  49. package/web/dist/assets/{pegDiagram-2B236MQR-CkxxR07G.js → pegDiagram-2B236MQR-DkBpYV7m.js} +1 -1
  50. package/web/dist/assets/{pieDiagram-ENE6RG2P-Dxvp1mn7.js → pieDiagram-ENE6RG2P-By3tI-vc.js} +1 -1
  51. package/web/dist/assets/pilotswarm-BL_NbZug.js +98 -0
  52. package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-CrL0hjZW.js → quadrantDiagram-ABIIQ3AL-DZmVm0xK.js} +1 -1
  53. package/web/dist/assets/{railroadDiagram-RFXS5EU6-CmVmFBPa.js → railroadDiagram-RFXS5EU6-BC5Mbz7q.js} +1 -1
  54. package/web/dist/assets/{requirementDiagram-TGXJPOKE-DJc3NRXb.js → requirementDiagram-TGXJPOKE-CyZNX_O1.js} +1 -1
  55. package/web/dist/assets/{sankeyDiagram-HTMAVEWB-BJNqWqBb.js → sankeyDiagram-HTMAVEWB-7603NEmu.js} +1 -1
  56. package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-C0wddECV.js → sequenceDiagram-DBY2YBRQ-CXJ-i2Xj.js} +1 -1
  57. package/web/dist/assets/{sizeCapture-X5ZJPWSS-Cw0dqPWS.js → sizeCapture-X5ZJPWSS-CKT0ATnS.js} +1 -1
  58. package/web/dist/assets/{stateDiagram-2N3HPSRC-C5793RHZ.js → stateDiagram-2N3HPSRC-BIDPX4YU.js} +1 -1
  59. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-Dr8oD9T6.js +1 -0
  60. package/web/dist/assets/{swimlanes-5IMT3BWC-Bhpm77Zu.js → swimlanes-5IMT3BWC-CPoy6vnO.js} +2 -2
  61. package/web/dist/assets/swimlanesDiagram-G3AALYLV-BV0EQRlB.js +8 -0
  62. package/web/dist/assets/{timeline-definition-FHXFAJF6-CwbE4NsH.js → timeline-definition-FHXFAJF6-BGdNkplk.js} +1 -1
  63. package/web/dist/assets/{vennDiagram-L72KCM5P-DZnJEL_w.js → vennDiagram-L72KCM5P-ByFzarUb.js} +1 -1
  64. package/web/dist/assets/{wardleyDiagram-EHGQE667-DwTgBDev.js → wardleyDiagram-EHGQE667-CAk0SYuv.js} +1 -1
  65. package/web/dist/assets/{xychartDiagram-FW5EYKEG-BiPjN3Qc.js → xychartDiagram-FW5EYKEG-Np7h6_A0.js} +1 -1
  66. package/web/dist/index.html +3 -3
  67. package/web/dist/assets/channel-6wPrR6sC.js +0 -1
  68. package/web/dist/assets/classDiagram-OUVF2IWQ-vvXyIfbC.js +0 -1
  69. package/web/dist/assets/classDiagram-v2-EOCWNBFH-vvXyIfbC.js +0 -1
  70. package/web/dist/assets/index-D9Ab1VTY.css +0 -1
  71. package/web/dist/assets/index-DDAyy127.js +0 -24
  72. package/web/dist/assets/pilotswarm-493nqGmy.js +0 -98
  73. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-qGaRayHj.js +0 -1
  74. package/web/dist/assets/swimlanesDiagram-G3AALYLV-BXjNM_Zw.js +0 -8
package/README.md CHANGED
@@ -263,6 +263,7 @@ Docs: [Quick Start](https://github.com/affandar/pilotswarm/blob/main/docs/quicks
263
263
 
264
264
  ## Master of Agents
265
265
 
266
- The desktop portal includes a personal multi-session workspace with resizable
267
- chat/canvas panels, one focused composer, and zen mode. Layouts save automatically.
266
+ The portal includes a personal multi-session workspace with resizable desktop
267
+ chat/canvas panels, one focused composer, and zen mode. Phones show one panel
268
+ at a time with a layout minimap and swipe navigation. Layouts save automatically.
268
269
  See the [MoA guide](https://github.com/affandar/PilotSwarm/blob/main/docs/user-guide/master-of-agents.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pilotswarm",
3
- "version": "0.5.60",
3
+ "version": "0.5.61",
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",
@@ -81,7 +81,7 @@
81
81
  "ink": "^6.8.0",
82
82
  "jose": "^6.2.2",
83
83
  "mermaid": "^11.16.0",
84
- "pilotswarm-sdk": "0.5.60",
84
+ "pilotswarm-sdk": "0.5.61",
85
85
  "react": "^19.2.4",
86
86
  "react-dom": "^19.2.4",
87
87
  "ws": "^8.18.2"
@@ -362,6 +362,16 @@ function timestampMs(value) {
362
362
  }
363
363
 
364
364
  function isSameOrOlderSessionUpdate(previousSession, nextSession) {
365
+ // Detail patch construction must use the same ordering as the reducer.
366
+ // Client event timestamps can exceed the server's next status write.
367
+ const version = (value) => {
368
+ if (value == null || value === "" || typeof value === "boolean") return null;
369
+ const number = Number(value);
370
+ return Number.isSafeInteger(number) && number > 0 ? number : null;
371
+ };
372
+ const previousVersion = version(previousSession?.statusVersion);
373
+ const nextVersion = version(nextSession?.statusVersion);
374
+ if (previousVersion != null && nextVersion != null) return nextVersion <= previousVersion;
365
375
  const previousUpdatedAt = timestampMs(previousSession?.updatedAt);
366
376
  const nextUpdatedAt = timestampMs(nextSession?.updatedAt);
367
377
  return previousUpdatedAt > 0 && nextUpdatedAt > 0 && nextUpdatedAt <= previousUpdatedAt;
@@ -473,6 +483,13 @@ function buildSessionMergePatch(previousSession, nextSession) {
473
483
  changed = true;
474
484
  }
475
485
 
486
+ // Ordering metadata is required even when unchanged. A conflicting status
487
+ // at the same version must not masquerade as an unversioned live event.
488
+ if (changed) {
489
+ for (const key of ["statusVersion", "updatedAt"]) {
490
+ if (nextSession[key] !== undefined) patch[key] = nextSession[key];
491
+ }
492
+ }
476
493
  return changed ? patch : null;
477
494
  }
478
495
 
@@ -27,7 +27,7 @@ export function normalizeMoa(value) {
27
27
  const safeTree = layout => { try { return normalizeMoaLayout(layout).tree; } catch { return null; } };
28
28
  // Version 2 has one layout. A deliberately cleared layout must stay empty.
29
29
  if (value?.version === 2 || (object(value) && Object.hasOwn(value, "tree"))) {
30
- return { version: 2, tree: safeTree(value) };
30
+ return { version: 2, tree: safeTree(value), ...(Number.isFinite(value.aspectRatio) && value.aspectRatio >= 0.2 && value.aspectRatio <= 8 ? { aspectRatio: value.aspectRatio } : {}) };
31
31
  }
32
32
  // Migrate the selected legacy dashboard when populated; otherwise keep
33
33
  // the first populated dashboard rather than a previously selected blank tab.
@@ -319,62 +319,66 @@ function isSameOrOlderSessionUpdate(previousSession, nextSession) {
319
319
  return previousAt > 0 && nextAt > 0 && nextAt <= previousAt;
320
320
  }
321
321
 
322
- // A run that has ended for good. These are authoritative and must always land,
323
- // even from an update that looks stale stranding a finished session as
324
- // "running" is far worse than a brief wrong status.
322
+ // Completion can come from orchestration metadata without another custom
323
+ // status write. Allow it at the same version, but never at an older version.
325
324
  const TERMINAL_SESSION_STATUSES = new Set(["completed", "failed", "cancelled", "error"]);
326
325
 
327
- // Session state is written from several concurrent sources: the session-list
328
- // poll, live events, and a per-session detail fetch. They do not agree
329
- // instant-to-instant, and the list poll in particular can report a status the
330
- // orchestration has already moved on from — observed live as:
331
- //
332
- // sessions/loaded running + waiting -> waiting prevAt == incomingAt
333
- // sessions/merged waiting + running -> running (105ms later)
334
- //
335
- // while the server's own session row and orchestration both read "running"
336
- // throughout. Consumers gated on status === "running" the live-activity
337
- // strip, the composer's Stop button blink off for the width of that gap.
338
- //
339
- // Refuse ANY non-terminal downgrade out of "running" unless the incoming
340
- // update is genuinely newer. Deliberately not restricted to idle-like
341
- // statuses: the status actually observed clobbering a live run was "waiting",
342
- // and an earlier version of this guard missed the bug by excluding it.
343
- // Terminal statuses are exempt so a finished run can never be stranded.
344
- //
345
- // "Genuinely newer" is decided by the server's monotonic statusVersion when
346
- // both sides carry one — wall-clock updatedAt is only the fallback. The
347
- // timestamp heuristic alone can WEDGE a finished turn as "running" forever:
348
- // client-side event merges (turn_completed, context usage) inflate the held
349
- // session's updatedAt past the server row's idle-write timestamp, so every
350
- // subsequent idle poll compares as stale and only a terminal status could
351
- // ever land. Observed live (2026-07-21, local portal): server row idle at
352
- // statusVersion 5 while the client showed "Working.." indefinitely.
353
- function shouldPreserveRunningStatus(previousSession, nextSession) {
354
- const nextStatus = String(nextSession?.status ?? "").toLowerCase();
355
- if (previousSession?.status !== "running"
356
- || nextStatus === "running"
357
- || TERMINAL_SESSION_STATUSES.has(nextStatus)) {
358
- return false;
359
- }
360
- const previousVersion = Number(previousSession?.statusVersion);
361
- const nextVersion = Number(nextSession?.statusVersion);
362
- if (Number.isFinite(previousVersion) && previousVersion > 0
363
- && Number.isFinite(nextVersion) && nextVersion > 0) {
364
- // Monotonic server counter: a higher version is authoritative and
365
- // must land; a same-or-lower version is provably stale and is held
366
- // regardless of what the timestamps claim.
367
- return nextVersion <= previousVersion;
326
+ function sessionStatusVersion(session) {
327
+ const value = session?.statusVersion;
328
+ if (value == null || value === "" || typeof value === "boolean") return null;
329
+ const version = Number(value);
330
+ return Number.isSafeInteger(version) && version > 0 ? version : null;
331
+ }
332
+
333
+ // List polls and detail requests can arrive out of order. Compare the server
334
+ // counter in both directions: an old running row must not revive an idle
335
+ // session, and an old idle row must not hide a running one. Keep the version
336
+ // with its status; accepting an older counter would let the next stale row win.
337
+ function shouldPreserveSessionStatus(previousSession, nextSession) {
338
+ const previousVersion = sessionStatusVersion(previousSession);
339
+ const nextVersion = sessionStatusVersion(nextSession);
340
+ if (previousVersion != null && nextVersion != null) {
341
+ if (nextVersion < previousVersion) return true;
342
+ if (nextVersion > previousVersion) return false;
368
343
  }
369
- return isSameOrOlderSessionUpdate(previousSession, nextSession);
344
+ const previousStatus = previousSession?.status;
345
+ const nextStatus = nextSession?.status;
346
+ if (!previousStatus || !nextStatus || previousStatus === nextStatus
347
+ || TERMINAL_SESSION_STATUSES.has(nextStatus)) return false;
348
+ if (previousVersion != null && nextVersion != null) return true;
349
+ // Legacy rows and live event patches may lack a counter. Do not guess
350
+ // their order without timestamps. Preserve the terminal-status escape
351
+ // hatch, since updatedAt also changes for non-status events.
352
+ if (previousStatus === "running") return isSameOrOlderSessionUpdate(previousSession, nextSession);
353
+ // An unversioned new turn may share its timestamp with the preceding idle
354
+ // row. Equal timestamps alone are not evidence that a start is stale.
355
+ const previousAt = sessionUpdateTimestampMs(previousSession);
356
+ const nextAt = sessionUpdateTimestampMs(nextSession);
357
+ return nextStatus === "running" && previousAt > 0 && nextAt > 0 && nextAt < previousAt;
370
358
  }
371
359
 
360
+ // These values describe the same run as status. Mixing an old question or
361
+ // pause reason with a newer status can change composer routing and controls.
362
+ const SESSION_STATUS_FIELDS = new Set([
363
+ "status", "statusVersion", "updatedAt", "orchestrationStatus",
364
+ "pendingQuestion", "waitReason", "pauseState", "error", "result",
365
+ ]);
366
+
372
367
  function mergeDefinedSessionFields(previousSession = {}, nextSession = {}) {
373
368
  let merged = previousSession || {};
374
- const preserveRunning = shouldPreserveRunningStatus(previousSession, nextSession);
369
+ const preserveStatus = shouldPreserveSessionStatus(previousSession, nextSession);
370
+ const previousVersion = sessionStatusVersion(previousSession);
375
371
  for (const [key, value] of Object.entries(nextSession || {})) {
376
372
  if (value === undefined) continue;
377
- if (key === "status" && preserveRunning) continue;
373
+ if (preserveStatus && SESSION_STATUS_FIELDS.has(key)) continue;
374
+ // Repeated snapshots of one server version cannot roll its timestamp
375
+ // back and then admit an older unversioned running update.
376
+ if (key === "updatedAt" && previousVersion != null
377
+ && previousVersion === sessionStatusVersion(nextSession)
378
+ && sessionUpdateTimestampMs(nextSession) < sessionUpdateTimestampMs(previousSession)) continue;
379
+ // An absent/invalid counter must not erase the last known server version.
380
+ if (key === "statusVersion" && previousVersion != null
381
+ && sessionStatusVersion(nextSession) == null) continue;
378
382
  if (key === "pendingQuestion" && isAnsweredPendingQuestion(previousSession, value)) {
379
383
  if (merged === previousSession) {
380
384
  merged = { ...(previousSession || {}) };
@@ -36,6 +36,7 @@ import {
36
36
  selectArtifactPickerModal,
37
37
  selectArtifactUploadModal,
38
38
  selectLiveActivityLines,
39
+ selectActiveOutboxMessages,
39
40
  selectChatLines,
40
41
  selectChatPaneChrome,
41
42
  selectOutboxOverlayLines,
@@ -3979,7 +3980,7 @@ function focusRegionForPaneKey(paneKey, override = null) {
3979
3980
  return PANE_KEY_FOCUS_REGIONS[String(paneKey || "")] || null;
3980
3981
  }
3981
3982
 
3982
- 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, focusRegion = null, panelRef = null, ariaLive = null }) {
3983
+ function ScrollLinesPanel({ title, titleRight = null, color, focused, actions, lines, stickyLines = [], bottomStickyLines = [], reserveBottomSticky = false, scrollOffset = 0, scrollMode = "top", paneKey, controller, className = "", panelClassName = "", topContent = null, bottomContent = null, structuredBlocks = false, stickyBottom = false, renderBody = null, focusRegion = null, panelRef = null, ariaLive = null }) {
3983
3984
  const themeId = useControllerSelector(controller, (state) => state.ui.themeId);
3984
3985
  const theme = getTheme(themeId);
3985
3986
  const ref = React.useRef(null);
@@ -4098,9 +4099,9 @@ function ScrollLinesPanel({ title, titleRight = null, color, focused, actions, l
4098
4099
  ? React.createElement(StructuredChatBlocks, { lines: normalizedLines, theme, controller })
4099
4100
  : normalizedLines.map((line, index) => React.createElement(Line, { key: `line:${index}`, line, theme })),
4100
4101
  ),
4101
- normalizedBottomSticky.length > 0
4102
+ (normalizedBottomSticky.length > 0 || reserveBottomSticky)
4102
4103
  ? React.createElement("div", {
4103
- className: "ps-panel-bottom-sticky",
4104
+ className: `ps-panel-bottom-sticky${reserveBottomSticky ? " is-reserved" : ""}`,
4104
4105
  },
4105
4106
  normalizedBottomSticky.map((line, index) => React.createElement(Line, { key: `bottom-sticky:${index}`, line, theme })),
4106
4107
  )
@@ -6407,7 +6408,7 @@ function SessionModifyModal({ controller, sessionId, initialTitle, currentModel,
6407
6408
  error ? React.createElement("div", { className: "ps-share-error" }, error) : null));
6408
6409
  }
6409
6410
 
6410
- function SessionComposer({ controller, onReadOnlyFocus = null }) {
6411
+ function SessionComposer({ controller, onReadOnlyFocus = null, mobile = false, compact = false }) {
6411
6412
  const modal = useControllerSelector(controller, state => state.ui.modal);
6412
6413
  const session = useControllerSelector(controller, state => state.sessions.byId[state.sessions.activeSessionId]);
6413
6414
  const { access } = useActiveSessionAccess(controller, session?.sessionId, session?.isGroup);
@@ -6418,10 +6419,30 @@ function SessionComposer({ controller, onReadOnlyFocus = null }) {
6418
6419
  ? React.createElement("div", { className: "ps-composer-readonly" }, session.serviceKind
6419
6420
  ? "⚗ Service session — runtime machinery. Its transcript is a read-only trace; it does not accept messages."
6420
6421
  : `You have view access to this session. Ask ${access.owner?.displayName || access.owner?.email || "the owner"} for write access to participate.`)
6421
- : React.createElement(PromptComposer, { controller, mobile: false, active: true }));
6422
- }
6423
-
6424
- function ChatPane({ controller, mobile = false, fullWidth = false, showComposer = true }) {
6422
+ : React.createElement(PromptComposer, { controller, mobile, compact, autoFocus: !mobile, active: true }));
6423
+ }
6424
+
6425
+ // The compact focus views use their existing header for activity. The same
6426
+ // selector powers the desktop footer, so stale-status and elapsed-time rules
6427
+ // remain identical; queued prompt bodies stay readable in the transcript.
6428
+ function SessionHeaderStatus({ controller }) {
6429
+ const state = useControllerSelector(controller, s => s);
6430
+ const session = state.sessions.byId[state.sessions.activeSessionId];
6431
+ const spinnerFrame = useSpinnerFrame(session?.status === "running");
6432
+ const activity = selectLiveActivityLines(state, { spinnerFrame, maxWidth: 120 });
6433
+ const status = activity[0]?.map(run => run.text || "").join("")
6434
+ || (session ? String(session.status || "idle").replace(/^./, c => c.toUpperCase()) : "");
6435
+ const messages = selectActiveOutboxMessages(state);
6436
+ const queue = ["pending", "queued", "cancelling", "rejected"].map(phase => {
6437
+ const count = messages.filter(message => message.pendingPhase === phase).length;
6438
+ return count ? `${count} ${phase}` : "";
6439
+ }).filter(Boolean).join(" · ");
6440
+ return React.createElement("div", { className: "ps-mobile-session-status", "aria-label": "Session status" },
6441
+ React.createElement("span", { className: "ps-mobile-activity", title: status }, status),
6442
+ queue ? React.createElement("span", { className: "ps-mobile-queue", title: queue }, queue) : null);
6443
+ }
6444
+
6445
+ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer = true, onEnterZen = null, activityInHeader = false }) {
6425
6446
  const themeId = useControllerSelector(controller, (state) => state.ui.themeId);
6426
6447
  const theme = getTheme(themeId);
6427
6448
  const viewState = useControllerSelector(controller, (state) => {
@@ -6532,6 +6553,10 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
6532
6553
  () => (pinnedActivityLines.length > 0 ? [...outboxLines, ...pinnedActivityLines] : outboxLines),
6533
6554
  [outboxLines, pinnedActivityLines],
6534
6555
  );
6556
+ const transcriptLines = React.useMemo(
6557
+ () => activityInHeader && outboxLines.length ? [...lines, ...outboxLines] : lines,
6558
+ [activityInHeader, lines, outboxLines],
6559
+ );
6535
6560
  // Read-only gating: a view-only viewer (shared_read / read grant, no write)
6536
6561
  // gets an explanatory notice instead of the composer. The visibility chip
6537
6562
  // and Share affordance now live in the session list "Modify" modal and the
@@ -6587,11 +6612,12 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
6587
6612
  titleRight: mobile && titleRight ? compactTitleRuns(titleRight, 18) : titleRight,
6588
6613
  // Chat/Summary toggling lives on the top toolbar so the pane chrome
6589
6614
  // stays clean. The toolbar button is disabled for group sessions.
6590
- actions: null,
6615
+ actions: mobile && onEnterZen ? React.createElement(IconButton, { className: "ps-mini-button", icon: React.createElement(ExpandGlyph), label: "Enter mobile zen", onClick: onEnterZen }) : null,
6591
6616
  color: chrome.color,
6592
6617
  focused: viewState.focused,
6593
- lines,
6594
- bottomStickyLines: stickyBottom,
6618
+ lines: transcriptLines,
6619
+ bottomStickyLines: activityInHeader ? EMPTY_ARRAY : stickyBottom,
6620
+ reserveBottomSticky: !activityInHeader,
6595
6621
  scrollOffset: viewState.scroll,
6596
6622
  scrollMode: "bottom",
6597
6623
  paneKey: "chat",
@@ -6941,7 +6967,7 @@ function useKeyboardTakeover(enabled) {
6941
6967
  * detail sub-panel). This replaced chat-focus mode, which was a second way to
6942
6968
  * say "chat only" with its own chrome and its own exit.
6943
6969
  */
6944
- function MobileWorkspace({ controller, layout = "split" }) {
6970
+ function MobileWorkspace({ controller, layout = "split", onEnterZen }) {
6945
6971
  const sessionPane = React.createElement(SessionPane, {
6946
6972
  controller,
6947
6973
  panelClassName: "ps-mobile-session-pane",
@@ -6949,7 +6975,7 @@ function MobileWorkspace({ controller, layout = "split" }) {
6949
6975
  if (layout === "chat") {
6950
6976
  return React.createElement("div", { className: "ps-mobile-workspace is-chat-only" },
6951
6977
  React.createElement("div", { className: "ps-mobile-chat-pane" },
6952
- React.createElement(ChatPane, { controller, mobile: true, fullWidth: true })));
6978
+ React.createElement(ChatPane, { controller, mobile: true, fullWidth: true, onEnterZen })));
6953
6979
  }
6954
6980
  if (layout === "sessions") {
6955
6981
  return React.createElement("div", { className: "ps-mobile-workspace is-sessions-only" },
@@ -6962,7 +6988,7 @@ function MobileWorkspace({ controller, layout = "split" }) {
6962
6988
  return React.createElement("div", { className: "ps-mobile-workspace" },
6963
6989
  sessionPane,
6964
6990
  React.createElement("div", { className: "ps-mobile-chat-pane" },
6965
- React.createElement(ChatPane, { controller, mobile: true, fullWidth: true })));
6991
+ React.createElement(ChatPane, { controller, mobile: true, fullWidth: true, onEnterZen })));
6966
6992
  }
6967
6993
 
6968
6994
  /** Frame-and-easel glyph for the Canvas toggle. */
@@ -8688,7 +8714,7 @@ function formatAttachmentSize(sizeBytes) {
8688
8714
  return `${bytes} B`;
8689
8715
  }
8690
8716
 
8691
- function PromptComposer({ controller, mobile, active = true, onAfterSend = null, autoFocus = true }) {
8717
+ function PromptComposer({ controller, mobile, compact = false, active = true, onAfterSend = null, autoFocus = true }) {
8692
8718
  const promptState = useControllerSelector(controller, (state) => {
8693
8719
  const activeSessionId = state.sessions.activeSessionId;
8694
8720
  const activeSession = activeSessionId ? state.sessions.byId[activeSessionId] || null : null;
@@ -8720,7 +8746,7 @@ function PromptComposer({ controller, mobile, active = true, onAfterSend = null,
8720
8746
  const selectedQueued = promptState.selectedOutboxPhase === "queued";
8721
8747
  const selectedCancelling = promptState.selectedOutboxPhase === "cancelling";
8722
8748
  const selectedReadOnly = selectedQueued || selectedCancelling;
8723
- const placeholder = promptState.answerMode
8749
+ const placeholder = mobile && !promptState.editingPending ? (promptState.answerMode ? "Answer…" : "Message…") : promptState.answerMode
8724
8750
  ? "Type an answer and press Enter"
8725
8751
  : promptState.editingPending
8726
8752
  ? selectedCancelling
@@ -8883,7 +8909,7 @@ function PromptComposer({ controller, mobile, active = true, onAfterSend = null,
8883
8909
  : "❯";
8884
8910
 
8885
8911
  return React.createElement("div", {
8886
- className: `ps-prompt-shell${mobile ? " is-mobile" : ""}${dragOver ? " is-drag-over" : ""}`,
8912
+ className: `ps-prompt-shell${compact ? " is-compact" : ""}${mobile ? " is-mobile" : ""}${dragOver ? " is-drag-over" : ""}`,
8887
8913
  ...(canAttachImages ? {
8888
8914
  onDragOver: (event) => {
8889
8915
  if (event.dataTransfer?.types?.includes?.("Files")) {
@@ -9001,7 +9027,7 @@ function PromptComposer({ controller, mobile, active = true, onAfterSend = null,
9001
9027
  // pointerdown preventDefault on every action keeps focus in the
9002
9028
  // textarea — on a phone, tapping Send must not collapse the
9003
9029
  // keyboard between messages.
9004
- canAttachImages
9030
+ canAttachImages && !compact
9005
9031
  ? React.createElement(React.Fragment, null,
9006
9032
  React.createElement("input", {
9007
9033
  ref: attachInputRef,
@@ -9038,7 +9064,7 @@ function PromptComposer({ controller, mobile, active = true, onAfterSend = null,
9038
9064
  onClick: cancelPending,
9039
9065
  }, selectedQueued ? "Delete" : "Cancel")
9040
9066
  : null,
9041
- promptState.canStopTurn || stoppingTurn
9067
+ !compact && (promptState.canStopTurn || stoppingTurn)
9042
9068
  ? React.createElement("button", {
9043
9069
  type: "button",
9044
9070
  className: `ps-stop-button${stoppingTurn ? " is-stopping" : ""}`,
@@ -9094,14 +9120,29 @@ function IconButton({ icon, label, onClick, disabled = false, active = false, cl
9094
9120
  const timerRef = React.useRef(null);
9095
9121
  const longPressRef = React.useRef(false);
9096
9122
 
9097
- // Keep the tooltip within the viewport horizontally the leftmost/rightmost
9098
- // buttons would otherwise clip off the edge (the tooltip is center-anchored).
9123
+ // Measure wrapped text against the visual viewport, including the keyboard.
9099
9124
  React.useLayoutEffect(() => {
9100
- if (!tip || !tipRef.current || typeof window === "undefined") return;
9101
- const half = tipRef.current.offsetWidth / 2;
9102
- const margin = 6;
9103
- const clampedX = Math.max(half + margin, Math.min(tip.x, window.innerWidth - half - margin));
9104
- tipRef.current.style.left = `${clampedX}px`;
9125
+ if (!tip || !tipRef.current || !btnRef.current) return;
9126
+ const el = tipRef.current, viewport = window.visualViewport;
9127
+ const update = () => {
9128
+ const left = viewport?.offsetLeft || 0, top = viewport?.offsetTop || 0;
9129
+ const width = viewport?.width || window.innerWidth, height = viewport?.height || window.innerHeight;
9130
+ const margin = 6, r = btnRef.current.getBoundingClientRect();
9131
+ el.style.width = "max-content";
9132
+ el.style.maxWidth = `${Math.min(220, width - 2 * margin)}px`;
9133
+ el.style.maxHeight = `${height - 2 * margin}px`;
9134
+ const box = el.getBoundingClientRect();
9135
+ let below = tip.placement === "below";
9136
+ if (!below && r.top - box.height - margin < top) below = true;
9137
+ if (below && r.bottom + box.height + margin > top + height && r.top - box.height - margin >= top) below = false;
9138
+ const x = Math.max(left + margin, Math.min(r.left + r.width / 2 - box.width / 2, left + width - box.width - margin));
9139
+ const y = Math.max(top + margin, Math.min(below ? r.bottom + margin : r.top - box.height - margin, top + height - box.height - margin));
9140
+ el.style.transform = "none";
9141
+ el.style.left = `${x}px`; el.style.top = `${y}px`;
9142
+ };
9143
+ update(); viewport?.addEventListener("resize", update); viewport?.addEventListener("scroll", update);
9144
+ window.addEventListener("resize", update);
9145
+ return () => { viewport?.removeEventListener("resize", update); viewport?.removeEventListener("scroll", update); window.removeEventListener("resize", update); };
9105
9146
  }, [tip]);
9106
9147
 
9107
9148
  const reveal = (preferAbove = false) => {
@@ -9366,7 +9407,7 @@ function Toolbar({ controller, mobile, moa = null, canvasPaneOpen = false, onTog
9366
9407
  }]),
9367
9408
  ];
9368
9409
 
9369
- if (!mobile && moa?.desktop) buttonDefs.push({
9410
+ if (moa) buttonDefs.push({
9370
9411
  key: "moa", icon: React.createElement(MoaGlyph),
9371
9412
  label: moa.returnTo ? "Back to MoA — Master of Agents" : "Master of Agents",
9372
9413
  active: moa.active, disabled: !moa.loaded,
@@ -9430,8 +9471,9 @@ function Toolbar({ controller, mobile, moa = null, canvasPaneOpen = false, onTog
9430
9471
  ];
9431
9472
  return React.createElement("div", { className: "ps-toolbar is-mobile" },
9432
9473
  React.createElement("div", { className: "ps-toolbar-row ps-toolbar-row-primary" },
9433
- React.createElement("div", { className: "ps-toolbar-row-actions" }, buttonDefs.map(renderButton)),
9474
+ React.createElement("div", { className: "ps-toolbar-row-actions" }, buttonDefs.filter(def => def.key !== "moa").map(renderButton)),
9434
9475
  React.createElement("div", { className: "ps-toolbar-row-actions is-panes" },
9476
+ ...buttonDefs.filter(def => def.key === "moa").map(renderButton),
9435
9477
  paneDefs.map((def) => React.createElement(IconButton, {
9436
9478
  key: def.id === "workspace" ? "workspace" : "diagnostics",
9437
9479
  icon: React.createElement(def.icon),
@@ -10229,32 +10271,30 @@ function budgetPlural(n, one, many) {
10229
10271
  return `${n} ${n === 1 ? one : many}`;
10230
10272
  }
10231
10273
 
10232
- /**
10233
- * A model reference a session can run: `<provider>:<model>`.
10234
- *
10235
- * The catalog's `providerId` is the TYPE ("azure-openai"); a provider is an
10236
- * instance of that type with a name of its own, and both a model-scoped limit
10237
- * and a default tuple are refused by the database unless the model half names
10238
- * the provider it belongs to.
10239
- */
10240
- function budgetModelsForProvider(models, provider) {
10241
- if (!provider?.typeId) return [];
10242
- return models
10243
- .filter((model) => model.providerId === provider.typeId)
10244
- .map((model) => ({
10245
- qualifiedName: `${provider.name}:${model.modelName}`,
10246
- modelName: model.modelName,
10247
- }));
10248
- }
10249
-
10250
- /** The provider TYPES this deployment can instantiate, from its model catalog. */
10251
- function budgetProviderTypes(models) {
10252
- const seen = new Map();
10274
+ /** Runtime catalogs name a pool; type catalogs name a provider template. */
10275
+ function budgetModelsForProvider(models, provider, rawRows = []) {
10276
+ if (!provider?.name) return [];
10277
+ const choices = new Map();
10253
10278
  for (const model of models) {
10254
- if (!model.providerId || seen.has(model.providerId)) continue;
10255
- seen.set(model.providerId, { id: model.providerId, type: model.providerType || model.providerId });
10279
+ const matches = model.catalogKind === "runtime_provider"
10280
+ ? model.providerId === provider.name
10281
+ : model.catalogKind === "provider_type"
10282
+ ? model.providerId === provider.typeId
10283
+ : model.providerId === provider.name || model.providerId === provider.typeId;
10284
+ if (!matches) continue;
10285
+ const qualifiedName = `${provider.name}:${model.modelName}`;
10286
+ choices.set(qualifiedName, { qualifiedName, modelName: model.modelName });
10256
10287
  }
10257
- return [...seen.values()].sort((a, b) => a.id.localeCompare(b.id));
10288
+ // Existing caps remain editable even when the catalog is unavailable or a
10289
+ // model was retired. Never borrow a model reference from another pool.
10290
+ for (const row of rawRows) {
10291
+ if (row.providerName !== provider.name || row.rowKind !== "model"
10292
+ || !String(row.scope || "").startsWith(`${provider.name}:`)) continue;
10293
+ choices.set(row.scope, {
10294
+ qualifiedName: row.scope, modelName: row.scope.slice(provider.name.length + 1),
10295
+ });
10296
+ }
10297
+ return [...choices.values()];
10258
10298
  }
10259
10299
 
10260
10300
  /** listModels() is flat on the web transport and grouped on the direct one. */
@@ -10264,15 +10304,18 @@ function budgetNormalizeModels(raw) {
10264
10304
  : (Array.isArray(raw?.providers) ? raw.providers : (Array.isArray(raw?.models) ? raw.models : []));
10265
10305
  const flat = top.flatMap((entry) => (
10266
10306
  Array.isArray(entry?.models)
10267
- ? entry.models.map((model) => ({ ...model, providerId: model.providerId || entry.providerId }))
10307
+ ? entry.models.map((model) => ({
10308
+ ...model, providerId: model.providerId || entry.providerId,
10309
+ catalogKind: model.catalogKind || entry.catalogKind,
10310
+ }))
10268
10311
  : [entry]
10269
10312
  ));
10270
10313
  return flat
10271
10314
  .filter((model) => model && (model.modelName || model.qualifiedName))
10272
10315
  .map((model) => ({
10273
- modelName: model.modelName || String(model.qualifiedName).split(":").pop(),
10316
+ modelName: model.modelName || String(model.qualifiedName).split(":").slice(1).join(":"),
10274
10317
  providerId: model.providerId || "",
10275
- providerType: model.providerType || model.providerId || "",
10318
+ catalogKind: model.catalogKind || null,
10276
10319
  }));
10277
10320
  }
10278
10321
 
@@ -10810,7 +10853,7 @@ function ProviderSystemSpend({ view }) {
10810
10853
  * reachable.
10811
10854
  */
10812
10855
  function EditProviderSheet({
10813
- row, rawRows, models, isAdmin, busy, error, onCancel, onRun,
10856
+ row, rawRows, models, modelsLoading, modelsError, onRetryModels, isAdmin, busy, error, onCancel, onRun,
10814
10857
  initialPeriod = "day", initialScope = "*",
10815
10858
  }) {
10816
10859
  const shared = row.class === "shared";
@@ -10954,10 +10997,13 @@ function EditProviderSheet({
10954
10997
  className: "ps-budget-input", "aria-label": "Model this limit applies to",
10955
10998
  value: model, onChange: (event) => setModel(event.target.value),
10956
10999
  }, models.length === 0
10957
- ? React.createElement("option", { value: "" }, "No models are listed for this provider's type")
11000
+ ? React.createElement("option", { value: "" }, modelsLoading ? "Loading models…" : modelsError ? "Model catalog unavailable" : "No models listed for this provider")
10958
11001
  : models.map((option) => React.createElement("option", {
10959
11002
  key: option.qualifiedName, value: option.qualifiedName,
10960
- }, option.qualifiedName))) : null),
11003
+ }, option.qualifiedName))) : null,
11004
+ modelsError ? React.createElement("div", { className: "ps-budget-note is-warn", role: "status" },
11005
+ "Could not load the model catalog. Existing model limits remain editable. ",
11006
+ React.createElement("button", { type: "button", onClick: onRetryModels, disabled: modelsLoading }, "Retry")) : null),
10961
11007
  React.createElement(SheetField, {
10962
11008
  label: "Tokens",
10963
11009
  hint: parsed
@@ -11743,6 +11789,9 @@ function ProviderBudgetView({ controller }) {
11743
11789
  // a failure is not fatal — the controls that need it say they have nothing
11744
11790
  // to offer.
11745
11791
  const [models, setModels] = React.useState([]);
11792
+ const [modelsLoading, setModelsLoading] = React.useState(true);
11793
+ const [modelsError, setModelsError] = React.useState(false);
11794
+ const [modelsRequest, setModelsRequest] = React.useState(0);
11746
11795
  const [providerTypes, setProviderTypes] = React.useState(new Map());
11747
11796
  const [defaults, setDefaults] = React.useState(null);
11748
11797
  const readDefaults = React.useCallback(() => {
@@ -11753,11 +11802,14 @@ function ProviderBudgetView({ controller }) {
11753
11802
  }, [controller]);
11754
11803
  React.useEffect(() => {
11755
11804
  let live = true;
11756
- if (typeof controller.transport.listModels === "function") {
11757
- Promise.resolve(controller.transport.listModels())
11758
- .then((rows) => { if (live) setModels(budgetNormalizeModels(rows)); })
11759
- .catch(() => {});
11760
- }
11805
+ setModelsLoading(true);
11806
+ Promise.resolve().then(() => {
11807
+ if (typeof controller.transport.listModels !== "function") throw new Error("Model catalog unavailable");
11808
+ return controller.transport.listModels();
11809
+ }).then((rows) => {
11810
+ if (live) { setModels(budgetNormalizeModels(rows)); setModelsError(false); }
11811
+ }).catch(() => { if (live) setModelsError(true); })
11812
+ .finally(() => { if (live) setModelsLoading(false); });
11761
11813
  if (typeof controller.transport.listProviders === "function") {
11762
11814
  Promise.resolve(controller.transport.listProviders())
11763
11815
  .then((rows) => {
@@ -11770,7 +11822,7 @@ function ProviderBudgetView({ controller }) {
11770
11822
  }
11771
11823
  readDefaults();
11772
11824
  return () => { live = false; };
11773
- }, [controller, readDefaults]);
11825
+ }, [controller, readDefaults, modelsRequest]);
11774
11826
 
11775
11827
  // Opened by the toolbar, which loads it. Mounting with nothing behind it —
11776
11828
  // a restored state, a second mount — reads once rather than showing zeros.
@@ -11855,13 +11907,14 @@ function ProviderBudgetView({ controller }) {
11855
11907
  const modelsForSelected = selected
11856
11908
  ? budgetModelsForProvider(models, {
11857
11909
  name: selected.providerName, typeId: providerTypes.get(selected.providerName) || "",
11858
- })
11910
+ }, rawRows)
11859
11911
  : [];
11860
11912
 
11861
11913
  let sheetEl = null;
11862
11914
  if (sheet?.kind === "edit" && selectedProvider) {
11863
11915
  sheetEl = React.createElement(EditProviderSheet, {
11864
11916
  row: selectedProvider, rawRows, models: modelsForSelected, isAdmin: view.isAdmin,
11917
+ modelsLoading, modelsError, onRetryModels: () => setModelsRequest((value) => value + 1),
11865
11918
  // MAJOR 17: the sheet opens on the limit the reader was standing
11866
11919
  // on, rather than always on Daily / all models.
11867
11920
  initialPeriod: sheet.period || "day",
@@ -14994,7 +15047,7 @@ export function PilotSwarmWebApp({ controller, suspended = false, moa = null })
14994
15047
  // it — the layer below is a SIBLING of this content and covers it. Drawing
14995
15048
  // a pane here would only be invisible work.
14996
15049
  else if (mobilePane === "canvas") mobileContent = null;
14997
- else mobileContent = React.createElement(MobileWorkspace, { controller, layout: mobileMainLayout });
15050
+ else mobileContent = React.createElement(MobileWorkspace, { controller, layout: mobileMainLayout, onEnterZen: moa?.openMobileZen });
14998
15051
 
14999
15052
  // The phone's canvas layer: a sibling of the content region's pane, NOT a
15000
15053
  // child of any pane. That is deliberate — panes mount and unmount as the
@@ -15058,4 +15111,4 @@ export function PilotSwarmWebApp({ controller, suspended = false, moa = null })
15058
15111
  }
15059
15112
 
15060
15113
  // Browser hosts may compose these existing surfaces with isolated controllers.
15061
- export { ChatPane, CanvasFrame, SessionPane, SessionRowContent, SessionDetailBox, SessionComposer, ScopedModalLayer, ControllerContext };
15114
+ export { SessionHeaderStatus, ChatPane, CanvasFrame, SessionPane, SessionRowContent, SessionDetailBox, SessionComposer, ScopedModalLayer, ControllerContext };
@@ -1 +1 @@
1
- import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-D5GeF8ZZ.js";import{p as f}from"./chunk-JWPE2WC7-C_Fv9QUd.js";import{_ as n,l as o}from"./mermaid.core-DNmC5O4Y.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-CUy2C8UT.js";import"./pilotswarm-493nqGmy.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
1
+ import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-CPjpLM65.js";import{p as f}from"./chunk-JWPE2WC7-Ce_6C5M-.js";import{_ as n,l as o}from"./mermaid.core-B3cPFf4f.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-DaY7l4Ce.js";import"./pilotswarm-BL_NbZug.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
@@ -1 +1 @@
1
- import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-DNmC5O4Y.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
1
+ import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-B3cPFf4f.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};