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.
- package/README.md +3 -2
- package/package.json +2 -2
- package/ui/core/src/controller.js +17 -0
- package/ui/core/src/moa.js +1 -1
- package/ui/core/src/reducer.js +51 -47
- package/ui/react/src/web-app.js +120 -67
- package/web/dist/assets/{abnfDiagram-VRR7QNED-CXEHJkTQ.js → abnfDiagram-VRR7QNED-CCVkGWsr.js} +1 -1
- package/web/dist/assets/{arc-lInHlEVm.js → arc-y040btO3.js} +1 -1
- package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-vrocx6re.js → architectureDiagram-ZJ3FMSHR-24Yt5esy.js} +1 -1
- package/web/dist/assets/{blockDiagram-677ZJIJ3-B1KA5QIB.js → blockDiagram-677ZJIJ3-BlbS-_GI.js} +1 -1
- package/web/dist/assets/{c4Diagram-LMCZKHZV-Du9xJMjz.js → c4Diagram-LMCZKHZV-_mamn0Fv.js} +1 -1
- package/web/dist/assets/channel-COFs9dqk.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-BuHucQOQ.js → chunk-2Q5K7J3B-TCbIB2Ni.js} +1 -1
- package/web/dist/assets/{chunk-32BRIVSS-T-AE5Yz0.js → chunk-32BRIVSS-C9q5KNKG.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-B_kTsNzt.js → chunk-5VM5RSS4-BvoqfU8P.js} +1 -1
- package/web/dist/assets/{chunk-EX3LRPZG-CtqgceBQ.js → chunk-EX3LRPZG-zFJjZeNd.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-C_Fv9QUd.js → chunk-JWPE2WC7-Ce_6C5M-.js} +1 -1
- package/web/dist/assets/{chunk-MOJQB5TN-D5GeF8ZZ.js → chunk-MOJQB5TN-CPjpLM65.js} +1 -1
- package/web/dist/assets/{chunk-RYQCIY6F-Cg63wDst.js → chunk-RYQCIY6F-CsmVUSDH.js} +1 -1
- package/web/dist/assets/{chunk-V7JOEXUC-Bz6G3lev.js → chunk-V7JOEXUC-CgrmPPOd.js} +1 -1
- package/web/dist/assets/{chunk-VR4S4FIN-DKs_hvUD.js → chunk-VR4S4FIN-BxAHDR7h.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-B9AtUlbn.js → chunk-XXDRQBXY-BGfFoO5_.js} +1 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-B5-KmsCx.js +1 -0
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-B5-KmsCx.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-rAG6oS0i.js → cose-bilkent-JH36ORCC-B6KcngJW.js} +1 -1
- package/web/dist/assets/{cynefin-VYW2F7L2-CUy2C8UT.js → cynefin-VYW2F7L2-DaY7l4Ce.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-TSTJHNR4-C1W8blXH.js → cynefinDiagram-TSTJHNR4-BldfGsoa.js} +1 -1
- package/web/dist/assets/{dagre-VKFMJZFB-DC0XqwcC.js → dagre-VKFMJZFB-C4T9X5Bs.js} +1 -1
- package/web/dist/assets/{diagram-FQU43EPY-COAY9zCP.js → diagram-FQU43EPY-DCVA_t-e.js} +1 -1
- package/web/dist/assets/{diagram-G47NLZAW-DLPr8eWG.js → diagram-G47NLZAW-pbTZGgE5.js} +1 -1
- package/web/dist/assets/{diagram-NH7WQ7WH-DoNgwoDz.js → diagram-NH7WQ7WH-P8Iks122.js} +1 -1
- package/web/dist/assets/{diagram-OA4YK3LP-D3-5TINE.js → diagram-OA4YK3LP-hEjZPipp.js} +1 -1
- package/web/dist/assets/{diagram-WEI45ONY-DplCkBJC.js → diagram-WEI45ONY-CVAzVCGu.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-CCIWWBDH-dvcVh7yr.js → ebnfDiagram-CCIWWBDH-BLL398qt.js} +1 -1
- package/web/dist/assets/{erDiagram-Q63AITRT-Cde8cTY6.js → erDiagram-Q63AITRT-CLTADyUG.js} +1 -1
- package/web/dist/assets/{flowDiagram-23GEKE2U-MGMNsh2E.js → flowDiagram-23GEKE2U-BAdlWAt0.js} +1 -1
- package/web/dist/assets/{ganttDiagram-NO4QXBWP-BiKw7AfS.js → ganttDiagram-NO4QXBWP-C7Z2wavr.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-vC7fsylq.js → gitGraphDiagram-IHSO6WYX-BI546pYu.js} +1 -1
- package/web/dist/assets/index-CvW-4eXF.css +1 -0
- package/web/dist/assets/index-DsPaDrf9.js +24 -0
- package/web/dist/assets/{infoDiagram-FWYZ7A6U-Du1uirB0.js → infoDiagram-FWYZ7A6U-8C7iQydo.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-Dk--Qshr.js → ishikawaDiagram-FXEZZL3T-Co4STVxQ.js} +1 -1
- package/web/dist/assets/{journeyDiagram-5HDEW3XC-iDE4zf_N.js → journeyDiagram-5HDEW3XC-DbUt11q-.js} +1 -1
- package/web/dist/assets/{kanban-definition-HUTT4EX6-D7WI8iRR.js → kanban-definition-HUTT4EX6-DUNcFmme.js} +1 -1
- package/web/dist/assets/{linear-B1_kxCBK.js → linear-Cjaxj94d.js} +1 -1
- package/web/dist/assets/{mermaid.core-DNmC5O4Y.js → mermaid.core-B3cPFf4f.js} +4 -4
- package/web/dist/assets/{mindmap-definition-LN4V7U3C-DIbLKLMA.js → mindmap-definition-LN4V7U3C-DaZtxB4N.js} +1 -1
- package/web/dist/assets/{papaparse.min-BdWinjY-.js → papaparse.min-vfdso2fs.js} +1 -1
- package/web/dist/assets/{pegDiagram-2B236MQR-CkxxR07G.js → pegDiagram-2B236MQR-DkBpYV7m.js} +1 -1
- package/web/dist/assets/{pieDiagram-ENE6RG2P-Dxvp1mn7.js → pieDiagram-ENE6RG2P-By3tI-vc.js} +1 -1
- package/web/dist/assets/pilotswarm-BL_NbZug.js +98 -0
- package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-CrL0hjZW.js → quadrantDiagram-ABIIQ3AL-DZmVm0xK.js} +1 -1
- package/web/dist/assets/{railroadDiagram-RFXS5EU6-CmVmFBPa.js → railroadDiagram-RFXS5EU6-BC5Mbz7q.js} +1 -1
- package/web/dist/assets/{requirementDiagram-TGXJPOKE-DJc3NRXb.js → requirementDiagram-TGXJPOKE-CyZNX_O1.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-HTMAVEWB-BJNqWqBb.js → sankeyDiagram-HTMAVEWB-7603NEmu.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-C0wddECV.js → sequenceDiagram-DBY2YBRQ-CXJ-i2Xj.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-Cw0dqPWS.js → sizeCapture-X5ZJPWSS-CKT0ATnS.js} +1 -1
- package/web/dist/assets/{stateDiagram-2N3HPSRC-C5793RHZ.js → stateDiagram-2N3HPSRC-BIDPX4YU.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-Dr8oD9T6.js +1 -0
- package/web/dist/assets/{swimlanes-5IMT3BWC-Bhpm77Zu.js → swimlanes-5IMT3BWC-CPoy6vnO.js} +2 -2
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-BV0EQRlB.js +8 -0
- package/web/dist/assets/{timeline-definition-FHXFAJF6-CwbE4NsH.js → timeline-definition-FHXFAJF6-BGdNkplk.js} +1 -1
- package/web/dist/assets/{vennDiagram-L72KCM5P-DZnJEL_w.js → vennDiagram-L72KCM5P-ByFzarUb.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-EHGQE667-DwTgBDev.js → wardleyDiagram-EHGQE667-CAk0SYuv.js} +1 -1
- package/web/dist/assets/{xychartDiagram-FW5EYKEG-BiPjN3Qc.js → xychartDiagram-FW5EYKEG-Np7h6_A0.js} +1 -1
- package/web/dist/index.html +3 -3
- package/web/dist/assets/channel-6wPrR6sC.js +0 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-vvXyIfbC.js +0 -1
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-vvXyIfbC.js +0 -1
- package/web/dist/assets/index-D9Ab1VTY.css +0 -1
- package/web/dist/assets/index-DDAyy127.js +0 -24
- package/web/dist/assets/pilotswarm-493nqGmy.js +0 -98
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-qGaRayHj.js +0 -1
- 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
|
|
267
|
-
chat/canvas panels, one focused composer, and zen mode.
|
|
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.
|
|
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.
|
|
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
|
|
package/ui/core/src/moa.js
CHANGED
|
@@ -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.
|
package/ui/core/src/reducer.js
CHANGED
|
@@ -319,62 +319,66 @@ function isSameOrOlderSessionUpdate(previousSession, nextSession) {
|
|
|
319
319
|
return previousAt > 0 && nextAt > 0 && nextAt <= previousAt;
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
-
//
|
|
323
|
-
//
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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 || {}) };
|
package/ui/react/src/web-app.js
CHANGED
|
@@ -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:
|
|
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:
|
|
6422
|
-
}
|
|
6423
|
-
|
|
6424
|
-
|
|
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
|
-
//
|
|
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 ||
|
|
9101
|
-
const
|
|
9102
|
-
const
|
|
9103
|
-
const
|
|
9104
|
-
|
|
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 (
|
|
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
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
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
|
-
|
|
10255
|
-
|
|
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
|
-
|
|
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) => ({
|
|
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(":").
|
|
10316
|
+
modelName: model.modelName || String(model.qualifiedName).split(":").slice(1).join(":"),
|
|
10274
10317
|
providerId: model.providerId || "",
|
|
10275
|
-
|
|
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
|
|
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
|
-
|
|
11757
|
-
|
|
11758
|
-
|
|
11759
|
-
|
|
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 };
|
package/web/dist/assets/{abnfDiagram-VRR7QNED-CXEHJkTQ.js → abnfDiagram-VRR7QNED-CCVkGWsr.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-
|
|
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-
|
|
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};
|