pilotswarm 0.5.57 → 0.5.59
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/mcp/dist/src/context.d.ts +2 -0
- package/mcp/dist/src/context.d.ts.map +1 -1
- package/mcp/dist/src/context.js +3 -1
- package/mcp/dist/src/context.js.map +1 -1
- package/mcp/dist/src/tools/agent-packages.js +1 -1
- package/mcp/dist/src/tools/agent-packages.js.map +1 -1
- package/mcp/dist/src/tools/capabilities.d.ts +4 -0
- package/mcp/dist/src/tools/capabilities.d.ts.map +1 -1
- package/mcp/dist/src/tools/capabilities.js +4 -0
- package/mcp/dist/src/tools/capabilities.js.map +1 -1
- package/package.json +2 -2
- package/tui/src/node-sdk-transport.js +5 -0
- package/ui/core/src/controller.js +136 -11
- package/ui/core/src/formatting.js +18 -9
- package/ui/core/src/history.js +162 -9
- package/ui/core/src/reducer.js +32 -5
- package/ui/core/src/selectors.js +160 -16
- package/ui/core/src/session-errors.js +16 -11
- package/ui/react/src/components.js +1 -0
- package/ui/react/src/web-app.js +230 -80
- package/web/api/live-plane.js +193 -0
- package/web/api/router.js +1 -1
- package/web/api/ws.js +160 -4
- package/web/authz.js +6 -1
- package/web/dist/assets/{abnfDiagram-VRR7QNED-CJOR2hPi.js → abnfDiagram-VRR7QNED-D9Rv2VpV.js} +1 -1
- package/web/dist/assets/{arc-Yh3kc_KB.js → arc-BATiucnq.js} +1 -1
- package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-DLRlUeyk.js → architectureDiagram-ZJ3FMSHR-CSTMt0si.js} +1 -1
- package/web/dist/assets/{blockDiagram-677ZJIJ3-x_TGH_fp.js → blockDiagram-677ZJIJ3-C_RGqfeY.js} +1 -1
- package/web/dist/assets/{c4Diagram-LMCZKHZV-C-j_bxeF.js → c4Diagram-LMCZKHZV-DH__s__0.js} +1 -1
- package/web/dist/assets/channel-CqgY9oZh.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-CaI5Xqwo.js → chunk-2Q5K7J3B-C197fD3B.js} +1 -1
- package/web/dist/assets/{chunk-32BRIVSS-Bh_Crac6.js → chunk-32BRIVSS-BjpuBroU.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-XM-3ajKG.js → chunk-5VM5RSS4-W_jL8euj.js} +1 -1
- package/web/dist/assets/{chunk-EX3LRPZG-ZwkulfFp.js → chunk-EX3LRPZG-a9h0dtlp.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-CT3Q7T2A.js → chunk-JWPE2WC7-CQN0qE7F.js} +1 -1
- package/web/dist/assets/{chunk-MOJQB5TN-_Uc0ksg3.js → chunk-MOJQB5TN-D-Y6KQ7t.js} +1 -1
- package/web/dist/assets/{chunk-RYQCIY6F-DosoId7u.js → chunk-RYQCIY6F-jPwseC5f.js} +1 -1
- package/web/dist/assets/{chunk-V7JOEXUC-KRjq1cwM.js → chunk-V7JOEXUC-DRNCEcQh.js} +1 -1
- package/web/dist/assets/{chunk-VR4S4FIN-B6Ul1z3S.js → chunk-VR4S4FIN-DzhUcoZ7.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-AikdVXm4.js → chunk-XXDRQBXY-ldJfyPXK.js} +1 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-Uz4DYZs2.js +1 -0
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-Uz4DYZs2.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-DTYwgEAa.js → cose-bilkent-JH36ORCC-BBVo21wR.js} +1 -1
- package/web/dist/assets/{cynefin-VYW2F7L2-C_8v2wIz.js → cynefin-VYW2F7L2-CgRQfdW1.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-TSTJHNR4-1-nw1zbQ.js → cynefinDiagram-TSTJHNR4-Ckz2X0t3.js} +1 -1
- package/web/dist/assets/{dagre-VKFMJZFB-CAjwRpvT.js → dagre-VKFMJZFB-C1SanJiY.js} +1 -1
- package/web/dist/assets/{diagram-FQU43EPY-Bg41nQw_.js → diagram-FQU43EPY-dBdEifVy.js} +1 -1
- package/web/dist/assets/{diagram-G47NLZAW-Cp6P-qMn.js → diagram-G47NLZAW-Byqc1VCB.js} +1 -1
- package/web/dist/assets/{diagram-NH7WQ7WH-DNdFuedg.js → diagram-NH7WQ7WH-9aeTaCsX.js} +1 -1
- package/web/dist/assets/{diagram-OA4YK3LP-DcHpELG2.js → diagram-OA4YK3LP-CL0a0o0k.js} +1 -1
- package/web/dist/assets/{diagram-WEI45ONY-B2_kyMw3.js → diagram-WEI45ONY-YlMQOJfY.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-CCIWWBDH-DztuAo3o.js → ebnfDiagram-CCIWWBDH-qMZQEnws.js} +1 -1
- package/web/dist/assets/{erDiagram-Q63AITRT-DNjKTOzp.js → erDiagram-Q63AITRT-C3CWBCtq.js} +1 -1
- package/web/dist/assets/{flowDiagram-23GEKE2U-DWjfeUM1.js → flowDiagram-23GEKE2U-BN79sybL.js} +1 -1
- package/web/dist/assets/{ganttDiagram-NO4QXBWP-Y0MgGVmy.js → ganttDiagram-NO4QXBWP-BOWhiAgB.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-IHSO6WYX--lDKAKVz.js → gitGraphDiagram-IHSO6WYX-CKX9OACp.js} +1 -1
- package/web/dist/assets/index-B4s2lTsK.js +24 -0
- package/web/dist/assets/{index-BxPfp0AO.css → index-DhGdE2Nd.css} +1 -1
- package/web/dist/assets/{infoDiagram-FWYZ7A6U-CKmH8_YO.js → infoDiagram-FWYZ7A6U-QQEunPNs.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-D88R7zUc.js → ishikawaDiagram-FXEZZL3T-CvMwrnLQ.js} +1 -1
- package/web/dist/assets/{journeyDiagram-5HDEW3XC-Ck7EMrsY.js → journeyDiagram-5HDEW3XC-giNpEVpj.js} +1 -1
- package/web/dist/assets/{kanban-definition-HUTT4EX6-hc-mOfSG.js → kanban-definition-HUTT4EX6-CUPPpo27.js} +1 -1
- package/web/dist/assets/{linear-BVhU4csP.js → linear-AXqadfOR.js} +1 -1
- package/web/dist/assets/{mermaid.core-C9u4FOCX.js → mermaid.core-C78wdk8Y.js} +4 -4
- package/web/dist/assets/{mindmap-definition-LN4V7U3C-DatQu9Xp.js → mindmap-definition-LN4V7U3C-BGUmh9bK.js} +1 -1
- package/web/dist/assets/{papaparse.min-BdnoXc8M.js → papaparse.min-Bh8RLCk_.js} +1 -1
- package/web/dist/assets/{pegDiagram-2B236MQR-Cr_nZ_jR.js → pegDiagram-2B236MQR-BHsKXKhK.js} +1 -1
- package/web/dist/assets/{pieDiagram-ENE6RG2P-CcAbSIcY.js → pieDiagram-ENE6RG2P-D3EvWTdH.js} +1 -1
- package/web/dist/assets/pilotswarm-UV6T1_QI.js +98 -0
- package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-CgJw5laM.js → quadrantDiagram-ABIIQ3AL-CbkxfLkV.js} +1 -1
- package/web/dist/assets/{railroadDiagram-RFXS5EU6-CHeun9SN.js → railroadDiagram-RFXS5EU6-H4M6JSDW.js} +1 -1
- package/web/dist/assets/{requirementDiagram-TGXJPOKE-D1kpDj8W.js → requirementDiagram-TGXJPOKE-BJo7JW1v.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-HTMAVEWB-C5vUSUaX.js → sankeyDiagram-HTMAVEWB-DL19MeOP.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-T1aPSxk1.js → sequenceDiagram-DBY2YBRQ-DyVFc5pQ.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-j-wh2LVX.js → sizeCapture-X5ZJPWSS-CKbcKIEQ.js} +1 -1
- package/web/dist/assets/{stateDiagram-2N3HPSRC-DfUZZR8N.js → stateDiagram-2N3HPSRC-BSukSXzS.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-BhKG3pJB.js +1 -0
- package/web/dist/assets/{swimlanes-5IMT3BWC-OpASJiRw.js → swimlanes-5IMT3BWC-BUd7UhsF.js} +2 -2
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-CgoFkXJE.js +8 -0
- package/web/dist/assets/{timeline-definition-FHXFAJF6-BBnc_z1U.js → timeline-definition-FHXFAJF6-Dt1gludn.js} +1 -1
- package/web/dist/assets/{vennDiagram-L72KCM5P-BlPQe9p4.js → vennDiagram-L72KCM5P-BHxt00GV.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-EHGQE667-DBcBFUx2.js → wardleyDiagram-EHGQE667-BT3fIDbe.js} +1 -1
- package/web/dist/assets/{xychartDiagram-FW5EYKEG-BQUSGL6g.js → xychartDiagram-FW5EYKEG-ChoYDS2b.js} +1 -1
- package/web/dist/index.html +3 -3
- package/web/runtime.js +89 -44
- package/web/server.js +9 -0
- package/web/dist/assets/channel-BIn92AzB.js +0 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-B7ktW6XM.js +0 -1
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-B7ktW6XM.js +0 -1
- package/web/dist/assets/index-rPji8Uud.js +0 -24
- package/web/dist/assets/pilotswarm--kvfB_6q.js +0 -99
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-ilKtTdHG.js +0 -1
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-C1PthnB4.js +0 -8
package/ui/core/src/selectors.js
CHANGED
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
normalizeArtifactEntries,
|
|
36
36
|
normalizeSessionPause,
|
|
37
37
|
} from "./state.js";
|
|
38
|
-
import { isRecoverableTransportErrorText } from "./session-errors.js";
|
|
39
38
|
|
|
40
39
|
export const ACTIVE_HIGHLIGHT_BACKGROUND = "activeHighlightBackground";
|
|
41
40
|
export const ACTIVE_HIGHLIGHT_FOREGROUND = "activeHighlightForeground";
|
|
@@ -297,9 +296,15 @@ function isTerminalOrchestrationStatus(status) {
|
|
|
297
296
|
|
|
298
297
|
function getSessionErrorVisualKind(session) {
|
|
299
298
|
const status = getSessionVisualStatus(session);
|
|
300
|
-
if (session?.orchestrationStatus === "Failed"
|
|
299
|
+
if (session?.orchestrationStatus === "Failed") return "failed";
|
|
300
|
+
if (isTerminalOrchestrationStatus(session?.orchestrationStatus)) return null;
|
|
301
|
+
if (status === "failed") return "failed";
|
|
301
302
|
if (status === "error") return "warning";
|
|
302
|
-
|
|
303
|
+
// The CMS row may already say idle/waiting while the live detail still
|
|
304
|
+
// carries the retry. Keep its card visible until detail sync clears it.
|
|
305
|
+
if (!["completed", "cancelled", "terminated", "input_required"].includes(session?.status)
|
|
306
|
+
&& String(session?.error || "").trim()
|
|
307
|
+
&& !isTerminalOrchestrationStatus(session?.orchestrationStatus)) return "warning";
|
|
303
308
|
return null;
|
|
304
309
|
}
|
|
305
310
|
|
|
@@ -457,17 +462,65 @@ export function manualOrderContainerKey(session, pinned = false) {
|
|
|
457
462
|
* The one owner-badge descriptor both lists render from. Same person ⇒ same
|
|
458
463
|
* initials and same colour, wherever they appear.
|
|
459
464
|
*/
|
|
460
|
-
export function ownerBadgeFor(owner, { isMine = false } = {}) {
|
|
465
|
+
export function ownerBadgeFor(owner, { isMine = false, hueByKey = null } = {}) {
|
|
461
466
|
if (!owner) return null;
|
|
462
467
|
const name = ownerDisplayName(owner, owner?.email || "");
|
|
468
|
+
const key = ownerBadgeKey(owner, owner?.email);
|
|
463
469
|
return {
|
|
464
470
|
initials: ownerInitials(owner),
|
|
465
|
-
|
|
471
|
+
// The list-wide assignment when the caller has one (no two people in
|
|
472
|
+
// the same list share a colour); the bare hash otherwise.
|
|
473
|
+
hue: hueByKey?.get?.(key) ?? ownerBadgeHue(key),
|
|
466
474
|
name: name || "unknown user",
|
|
467
475
|
isMine: Boolean(isMine),
|
|
468
476
|
};
|
|
469
477
|
}
|
|
470
478
|
|
|
479
|
+
/**
|
|
480
|
+
* One colour per PERSON in a list, no two alike while the palette lasts.
|
|
481
|
+
*
|
|
482
|
+
* The hash alone puts different people on the same colour far too often
|
|
483
|
+
* (three people, twelve colours: one collision in four), and a badge whose
|
|
484
|
+
* colour does not tell people apart is not doing its job. So: everyone
|
|
485
|
+
* gets their hash hue first; anyone landing on a taken hue walks to the next
|
|
486
|
+
* free one. Walk order is the sorted identity key, so the outcome depends
|
|
487
|
+
* only on WHO is in the list — the same people get the same colours across
|
|
488
|
+
* reloads and in both panes, and a newcomer only ever takes a free colour.
|
|
489
|
+
*/
|
|
490
|
+
export function assignOwnerBadgeHues(keys) {
|
|
491
|
+
const sorted = [...new Set([...keys].map((k) => String(k || "").trim().toLowerCase()).filter(Boolean))].sort();
|
|
492
|
+
const taken = new Set();
|
|
493
|
+
const out = new Map();
|
|
494
|
+
for (const key of sorted) {
|
|
495
|
+
let hue = ownerBadgeHue(key);
|
|
496
|
+
if (taken.size < OWNER_BADGE_HUES) {
|
|
497
|
+
let steps = 0;
|
|
498
|
+
while (taken.has(hue) && steps < OWNER_BADGE_HUES) { hue = (hue + 1) % OWNER_BADGE_HUES; steps += 1; }
|
|
499
|
+
}
|
|
500
|
+
taken.add(hue);
|
|
501
|
+
out.set(key, hue);
|
|
502
|
+
}
|
|
503
|
+
return out;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// The assignment for every human owner in the session catalog, memoised on
|
|
507
|
+
// the catalog's identity: the row selector runs per row and per dispatch.
|
|
508
|
+
let ownerHueMapCache = null;
|
|
509
|
+
export function ownerHueMapForSessions(byId) {
|
|
510
|
+
if (ownerHueMapCache && ownerHueMapCache.byId === byId) return ownerHueMapCache.map;
|
|
511
|
+
const keys = [];
|
|
512
|
+
for (const session of Object.values(byId || {})) {
|
|
513
|
+
if (!session || session.isSystem || session.isGroup) continue;
|
|
514
|
+
const owner = session.owner;
|
|
515
|
+
const ownerKey = ownerKeyForOwner(owner);
|
|
516
|
+
if (!ownerKey || ownerKey === SYSTEM_OWNER_KEY) continue;
|
|
517
|
+
keys.push(ownerBadgeKey(owner, owner?.email));
|
|
518
|
+
}
|
|
519
|
+
const map = assignOwnerBadgeHues(keys);
|
|
520
|
+
ownerHueMapCache = { byId, map };
|
|
521
|
+
return map;
|
|
522
|
+
}
|
|
523
|
+
|
|
471
524
|
function ownerDirectoryFromSessions(byId) {
|
|
472
525
|
const directory = new Map();
|
|
473
526
|
for (const session of Object.values(byId || {})) {
|
|
@@ -507,11 +560,11 @@ function resolvePackageOwner(pkg, directory) {
|
|
|
507
560
|
* the badge is telling owners APART at a glance, which two-letter initials
|
|
508
561
|
* alone do poorly (and did not do at all while they were all "?").
|
|
509
562
|
*
|
|
510
|
-
*
|
|
563
|
+
* Twelve hues, all legible on both the light and dark portal grounds; the
|
|
511
564
|
* viewer's own rows are styled separately by the caller, so this palette
|
|
512
565
|
* never has to encode "mine".
|
|
513
566
|
*/
|
|
514
|
-
const OWNER_BADGE_HUES =
|
|
567
|
+
export const OWNER_BADGE_HUES = 12;
|
|
515
568
|
|
|
516
569
|
export function ownerBadgeHue(key) {
|
|
517
570
|
const text = String(key || "").trim().toLowerCase();
|
|
@@ -1358,6 +1411,7 @@ export function selectSessionRows(state) {
|
|
|
1358
1411
|
? ownerBadgeFor(effectiveSessionOwner(session, state.sessions.byId), {
|
|
1359
1412
|
isMine: Boolean(ownerKeyForOwner(state?.auth?.principal)
|
|
1360
1413
|
&& ownerKeyForOwner(effectiveSessionOwner(session, state.sessions.byId)) === ownerKeyForOwner(state?.auth?.principal)),
|
|
1414
|
+
hueByKey: ownerHueMapForSessions(state.sessions.byId),
|
|
1361
1415
|
})
|
|
1362
1416
|
: null,
|
|
1363
1417
|
orderable: isManuallyOrderableSession(session),
|
|
@@ -1601,7 +1655,7 @@ function buildSessionErrorMessage(session, events = []) {
|
|
|
1601
1655
|
: `${errorText}\n\nThe orchestration is still running, so this may be transient.`;
|
|
1602
1656
|
|
|
1603
1657
|
return {
|
|
1604
|
-
id: `session-error:${session.sessionId}
|
|
1658
|
+
id: `session-error:${session.sessionId}`,
|
|
1605
1659
|
role: "system",
|
|
1606
1660
|
text: body,
|
|
1607
1661
|
time: "",
|
|
@@ -1904,7 +1958,11 @@ function describeChatMessageHeader(message, options = {}) {
|
|
|
1904
1958
|
const sharedContext = options?.sharedContext === true;
|
|
1905
1959
|
const isUser = message?.role === "user";
|
|
1906
1960
|
const isSelf = isUser && senderKey && viewerKey && senderKey === viewerKey;
|
|
1907
|
-
|
|
1961
|
+
// A user-stamped message whose key is not the viewer's IS from someone
|
|
1962
|
+
// else, whatever the session's visibility says: a targeted share leaves
|
|
1963
|
+
// the session "private" but still puts another person's messages in the
|
|
1964
|
+
// transcript. Naming them never needs the shared-context gate.
|
|
1965
|
+
const fromOtherPerson = isUser && Boolean(senderKey) && Boolean(viewerKey) && !isSelf;
|
|
1908
1966
|
// Owner tag: the sender is the session owner (or, for a sender-less own
|
|
1909
1967
|
// message, the viewer themselves is the owner).
|
|
1910
1968
|
const senderIsOwner = sharedContext && isUser && ownerKey && (
|
|
@@ -2427,7 +2485,22 @@ function splashArtWidth(text) {
|
|
|
2427
2485
|
.reduce((widest, line) => Math.max(widest, stripTerminalMarkupTags(line).length), 0);
|
|
2428
2486
|
}
|
|
2429
2487
|
|
|
2488
|
+
// Immutable history items survive live ticks. Do not re-parse thousands of
|
|
2489
|
+
// committed lines because the last message gained a handful of characters.
|
|
2490
|
+
const chatMessageLinesCache = new WeakMap();
|
|
2430
2491
|
function buildChatMessageLines(message, maxWidth, options = {}) {
|
|
2492
|
+
if (!message || typeof message !== "object") return buildChatMessageLinesUncached(message, maxWidth, options);
|
|
2493
|
+
const key = JSON.stringify([maxWidth, options]);
|
|
2494
|
+
let entries = chatMessageLinesCache.get(message);
|
|
2495
|
+
if (entries?.has(key)) return entries.get(key);
|
|
2496
|
+
const lines = buildChatMessageLinesUncached(message, maxWidth, options);
|
|
2497
|
+
if (!entries) { entries = new Map(); chatMessageLinesCache.set(message, entries); }
|
|
2498
|
+
if (entries.size >= 4) entries.delete(entries.keys().next().value);
|
|
2499
|
+
entries.set(key, lines);
|
|
2500
|
+
return lines;
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
function buildChatMessageLinesUncached(message, maxWidth, options = {}) {
|
|
2431
2504
|
if (message?.splash) {
|
|
2432
2505
|
// Swap in the narrow-viewport variant when the main art would
|
|
2433
2506
|
// overflow the pane (mobile portal, narrow terminals). When the
|
|
@@ -2447,6 +2520,36 @@ function buildChatMessageLines(message, maxWidth, options = {}) {
|
|
|
2447
2520
|
return buildThinkingCardLines(message, maxWidth);
|
|
2448
2521
|
}
|
|
2449
2522
|
|
|
2523
|
+
// The browser gives an in-flight assistant answer its own stable surface.
|
|
2524
|
+
// Previously it was flattened into the ordinary transcript on every
|
|
2525
|
+
// snapshot. As partial markdown changed shape (plain lines -> table/code
|
|
2526
|
+
// block), React could replace several adjacent nodes and the whole answer
|
|
2527
|
+
// appeared to flash. One keyed card keeps the outer DOM and its shading in
|
|
2528
|
+
// place while only the body grows. The TUI deliberately keeps its existing
|
|
2529
|
+
// line-oriented rendering.
|
|
2530
|
+
if ((message?.liveTurn || message?.streamSettling || message?.assistantPreview) && options.tableMode === "sentinel") {
|
|
2531
|
+
// Carry raw markdown: collapsed previews never parse their hidden body.
|
|
2532
|
+
return [{
|
|
2533
|
+
kind: "assistantPreview",
|
|
2534
|
+
width: maxWidth,
|
|
2535
|
+
text: "Message preview",
|
|
2536
|
+
previewKey: `assistant:${message?.liveKey || message?.messageId || message?.id || "active-turn"}`,
|
|
2537
|
+
body: decorateArtifactLinksForChat(message?.text || ""),
|
|
2538
|
+
reasoningText: String(message?.reasoningText || message?.liveReasoningText || ""),
|
|
2539
|
+
isLive: message?.liveTurn === true,
|
|
2540
|
+
final: message?.responseFinal === true,
|
|
2541
|
+
truncated: message?.truncated === true,
|
|
2542
|
+
liveStartedAt: Number(message?.liveStartedAt || message?.createdAt) || 0,
|
|
2543
|
+
time: message?.time || "",
|
|
2544
|
+
headerRuns: buildChatMessagePrefix(message, options),
|
|
2545
|
+
}];
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
if (message?.liveTurn && options.tableMode !== "sentinel" && message.reasoningText) {
|
|
2549
|
+
const reasoning = buildThinkingCardLines({ ...message, text: message.reasoningText }, maxWidth);
|
|
2550
|
+
return message.text ? [...reasoning, ...buildChatMessageLines({ ...message, reasoningText: "" }, maxWidth, options)] : reasoning;
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2450
2553
|
// Chrome-less message: render the text as plain markdown directly into
|
|
2451
2554
|
// the chat pane (no card border, no speaker prefix, no timestamp). Used
|
|
2452
2555
|
// for the session summary view so the summary feels like the pane's
|
|
@@ -2559,6 +2662,7 @@ function buildChatMessageLines(message, maxWidth, options = {}) {
|
|
|
2559
2662
|
&& (!message?.cardTitle || String(message.cardTitle).toLowerCase() === "system");
|
|
2560
2663
|
return buildMessageCardLines({
|
|
2561
2664
|
title: message?.cardTitle || (message?.role === "system" ? "System" : "PilotSwarm"),
|
|
2665
|
+
cardKey: message?.id,
|
|
2562
2666
|
timestamp: formatTimestamp(message?.createdAt || message?.time),
|
|
2563
2667
|
body: decorateArtifactLinksForChat(message?.text || ""),
|
|
2564
2668
|
width: Math.max(20, maxWidth),
|
|
@@ -2589,6 +2693,7 @@ function buildChatMessageLines(message, maxWidth, options = {}) {
|
|
|
2589
2693
|
|
|
2590
2694
|
if (startsWithStructuredBlock(tintedMarkdownLines)) {
|
|
2591
2695
|
const structured = prefix.length > 0 ? [prefix, ...tintedMarkdownLines] : tintedMarkdownLines;
|
|
2696
|
+
if (message?.streaming) appendStreamingCaret(structured);
|
|
2592
2697
|
return attachmentChipLines.length > 0 ? [...structured, ...attachmentChipLines] : structured;
|
|
2593
2698
|
}
|
|
2594
2699
|
|
|
@@ -2610,10 +2715,23 @@ function buildChatMessageLines(message, maxWidth, options = {}) {
|
|
|
2610
2715
|
renderedLines.push(lineRuns);
|
|
2611
2716
|
}
|
|
2612
2717
|
}
|
|
2718
|
+
if (message?.streaming) {
|
|
2719
|
+
appendStreamingCaret(renderedLines);
|
|
2720
|
+
}
|
|
2613
2721
|
renderedLines.push(...attachmentChipLines);
|
|
2614
2722
|
return renderedLines;
|
|
2615
2723
|
}
|
|
2616
2724
|
|
|
2725
|
+
function appendStreamingCaret(lines) {
|
|
2726
|
+
const lastRuns = [...lines].reverse().find((line) => Array.isArray(line)
|
|
2727
|
+
&& line.some((run) => String(run?.text || "").trim()));
|
|
2728
|
+
if (lastRuns) {
|
|
2729
|
+
lastRuns.push({ text: " ▍", color: "cyan", role: "streamingCaret" });
|
|
2730
|
+
} else {
|
|
2731
|
+
lines.push([{ text: "▍", color: "cyan", role: "streamingCaret" }]);
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2617
2735
|
// Image attachment chips under a message. In sentinel mode (browser portal)
|
|
2618
2736
|
// a single structured line carries the refs so the web renderer can show
|
|
2619
2737
|
// authenticated thumbnails; everywhere else (TUI) each file renders as a
|
|
@@ -2730,6 +2848,10 @@ function liveActivityPhaseText(item) {
|
|
|
2730
2848
|
case "assistant.intent": return "planning\u2026";
|
|
2731
2849
|
case "assistant.message_start":
|
|
2732
2850
|
case "assistant.message": return "writing reply\u2026";
|
|
2851
|
+
case "model.call_start": {
|
|
2852
|
+
const model = String(item?.text || "").replace(/^.*\bcalling\s+/i, "").trim();
|
|
2853
|
+
return `calling ${model || "model"}\u2026`;
|
|
2854
|
+
}
|
|
2733
2855
|
case "session.compaction_start": return "compacting context\u2026";
|
|
2734
2856
|
case "session.compaction_complete": return "context compacted";
|
|
2735
2857
|
case "session.dehydrated": return "dehydrating\u2026";
|
|
@@ -2891,12 +3013,19 @@ export function selectChatLines(state, maxWidth = 80, options = {}) {
|
|
|
2891
3013
|
const activeSessionId = state?.sessions?.activeSessionId;
|
|
2892
3014
|
const activeSession = activeSessionId ? state?.sessions?.byId?.[activeSessionId] : null;
|
|
2893
3015
|
const ownerKey = ownerKeyForOwner(activeSession?.owner);
|
|
2894
|
-
// A shared context = the session is shared deployment-wide,
|
|
2895
|
-
//
|
|
2896
|
-
//
|
|
3016
|
+
// A shared context = the session is shared deployment-wide, the viewer is
|
|
3017
|
+
// not its owner (someone shared it with them), or someone other than the
|
|
3018
|
+
// viewer has written to it (the owner shared it with a writer; the session
|
|
3019
|
+
// row still says "private" — targeted shares are not on the row, but the
|
|
3020
|
+
// other person's stamped messages are in the transcript). Only then does
|
|
3021
|
+
// the transcript name speakers and tag the owner.
|
|
2897
3022
|
const sharedContext = Boolean(activeSession && (
|
|
2898
3023
|
(activeSession.visibility && activeSession.visibility !== "private")
|
|
2899
3024
|
|| (ownerKey && viewerKey && ownerKey !== viewerKey)
|
|
3025
|
+
|| (viewerKey && messages.some((message) => message?.role === "user"
|
|
3026
|
+
&& message?.sender?.kind === "user"
|
|
3027
|
+
&& ownerKeyForOwner(message.sender)
|
|
3028
|
+
&& ownerKeyForOwner(message.sender) !== viewerKey))
|
|
2900
3029
|
));
|
|
2901
3030
|
const buildOptions = {
|
|
2902
3031
|
...(options?.tableMode ? { tableMode: options.tableMode } : {}),
|
|
@@ -4001,6 +4130,8 @@ export function selectAdminConsole(state) {
|
|
|
4001
4130
|
|
|
4002
4131
|
const systemGhcpKey = admin.systemGhcpKey || { supported: false, loading: false, configured: false, changedBy: null, changedAt: null, error: null };
|
|
4003
4132
|
const isAdmin = Boolean(profile?.isAdmin);
|
|
4133
|
+
const adminScope = state.auth?.adminScope ?? profile?.adminScope ?? "unrestricted";
|
|
4134
|
+
const resourceAdmin = isAdmin && adminScope === "unrestricted";
|
|
4004
4135
|
const modelProviderState = admin.modelProviders || {};
|
|
4005
4136
|
const providerRows = Array.isArray(modelProviderState.providers) ? modelProviderState.providers : [];
|
|
4006
4137
|
const modelRows = Array.isArray(modelProviderState.models) ? modelProviderState.models : [];
|
|
@@ -4153,7 +4284,7 @@ export function selectAdminConsole(state) {
|
|
|
4153
4284
|
const pkgState = admin.packages || {};
|
|
4154
4285
|
const pkgList = Array.isArray(pkgState.list) ? pkgState.list : [];
|
|
4155
4286
|
const ownsPackage = (pkg) => Boolean(
|
|
4156
|
-
|
|
4287
|
+
resourceAdmin
|
|
4157
4288
|
|| (pkg?.owner && principal
|
|
4158
4289
|
&& pkg.owner.provider === principal.provider
|
|
4159
4290
|
&& pkg.owner.subject === principal.subject),
|
|
@@ -4165,6 +4296,8 @@ export function selectAdminConsole(state) {
|
|
|
4165
4296
|
const canEditPackage = (pkg) => ownsPackage(pkg) || Boolean(pkg?.canEdit);
|
|
4166
4297
|
// Built once per render, not per row.
|
|
4167
4298
|
const ownerDirectory = ownerDirectoryFromSessions(state?.sessions?.byId);
|
|
4299
|
+
// Same assignment as the session list, so the same person is the same colour in both panes.
|
|
4300
|
+
const packageHueByKey = ownerHueMapForSessions(state?.sessions?.byId);
|
|
4168
4301
|
// Same rule as the session list: the owner chip only earns its place when
|
|
4169
4302
|
// this list actually holds more than one owner. Shared packages belong to
|
|
4170
4303
|
// the deployment, not a person, so they neither carry a chip nor count
|
|
@@ -4212,7 +4345,7 @@ export function selectAdminConsole(state) {
|
|
|
4212
4345
|
// PERSON's, matching the session list exactly.
|
|
4213
4346
|
ownerBadge: (pkg.scope === "shared" || !decoratePackageOwners)
|
|
4214
4347
|
? null
|
|
4215
|
-
: ownerBadgeFor(resolvePackageOwner(pkg, ownerDirectory)),
|
|
4348
|
+
: ownerBadgeFor(resolvePackageOwner(pkg, ownerDirectory), { hueByKey: packageHueByKey }),
|
|
4216
4349
|
// Highlight only while the Agents section is active — otherwise the
|
|
4217
4350
|
// GitHub Keys screen shows a double selection.
|
|
4218
4351
|
selected: rowMatchesSelection(pkg),
|
|
@@ -4314,8 +4447,11 @@ export function selectAdminConsole(state) {
|
|
|
4314
4447
|
? workerRows.filter((worker) => installedRowFor(worker)?.semver === activeVersion.semver
|
|
4315
4448
|
&& installedRowFor(worker)?.status === "ok").length
|
|
4316
4449
|
: 0;
|
|
4317
|
-
|
|
4318
|
-
|
|
4450
|
+
// The periodically refreshed list carries current editor grants; an
|
|
4451
|
+
// older open detail must not preserve a revoked edit affordance.
|
|
4452
|
+
const permissionSource = summary ?? detail;
|
|
4453
|
+
const canManage = permissionSource ? ownsPackage(permissionSource) : false;
|
|
4454
|
+
const canEdit = permissionSource ? canEditPackage(permissionSource) : false;
|
|
4319
4455
|
packageDetail = {
|
|
4320
4456
|
name: pkgState.selectedName,
|
|
4321
4457
|
loading: Boolean(pkgState.detailLoading),
|
|
@@ -4542,6 +4678,8 @@ export function selectAdminConsole(state) {
|
|
|
4542
4678
|
loadError: admin.loadError || null,
|
|
4543
4679
|
principal,
|
|
4544
4680
|
isAdmin,
|
|
4681
|
+
adminScope,
|
|
4682
|
+
adminPolicyLabel: adminScope === "cluster" ? "Cluster-scoped admin · system-session access retained" : "Unrestricted admin access",
|
|
4545
4683
|
section,
|
|
4546
4684
|
settingsTree,
|
|
4547
4685
|
packages: packagesView,
|
|
@@ -4581,6 +4719,8 @@ export function selectAdminConsole(state) {
|
|
|
4581
4719
|
loadError: admin.loadError || null,
|
|
4582
4720
|
principal,
|
|
4583
4721
|
isAdmin,
|
|
4722
|
+
adminScope,
|
|
4723
|
+
adminPolicyLabel: adminScope === "cluster" ? "Cluster-scoped admin · system-session access retained" : "Unrestricted admin access",
|
|
4584
4724
|
section,
|
|
4585
4725
|
settingsTree,
|
|
4586
4726
|
packages: packagesView,
|
|
@@ -4626,6 +4766,8 @@ export function selectAdminConsole(state) {
|
|
|
4626
4766
|
loadError: admin.loadError || null,
|
|
4627
4767
|
principal,
|
|
4628
4768
|
isAdmin,
|
|
4769
|
+
adminScope,
|
|
4770
|
+
adminPolicyLabel: adminScope === "cluster" ? "Cluster-scoped admin · system-session access retained" : "Unrestricted admin access",
|
|
4629
4771
|
section,
|
|
4630
4772
|
settingsTree,
|
|
4631
4773
|
packages: packagesView,
|
|
@@ -4674,6 +4816,8 @@ export function selectAdminConsole(state) {
|
|
|
4674
4816
|
loadError: admin.loadError || null,
|
|
4675
4817
|
principal,
|
|
4676
4818
|
isAdmin,
|
|
4819
|
+
adminScope,
|
|
4820
|
+
adminPolicyLabel: adminScope === "cluster" ? "Cluster-scoped admin · system-session access retained" : "Unrestricted admin access",
|
|
4677
4821
|
section,
|
|
4678
4822
|
settingsTree,
|
|
4679
4823
|
packages: packagesView,
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
export function
|
|
2
|
-
|
|
3
|
-
return /\bConnection is closed\b/i.test(value)
|
|
4
|
-
|| /\bLive Copilot connection lost\b/i.test(value);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function shouldKeepRecoverableTransportWarning(previousSession, nextSession) {
|
|
8
|
-
if (!isRecoverableTransportErrorText(previousSession?.error)) return false;
|
|
1
|
+
export function shouldKeepSessionWarning(previousSession, nextSession) {
|
|
2
|
+
if (!String(previousSession?.error || "").trim()) return false;
|
|
9
3
|
if (nextSession?.error !== undefined) return false;
|
|
10
|
-
const nextStatus = String(nextSession?.status || "");
|
|
11
|
-
if (nextStatus !== "running") return false;
|
|
12
4
|
if (["Completed", "Failed", "Terminated"].includes(String(nextSession?.orchestrationStatus || ""))) return false;
|
|
13
|
-
return
|
|
5
|
+
if (["completed", "failed", "cancelled", "terminated"].includes(previousSession?.status)) return false;
|
|
6
|
+
const nextStatus = String(nextSession?.status || "");
|
|
7
|
+
if (["running", "error"].includes(nextStatus)) return true;
|
|
8
|
+
if (!["idle", "waiting", "unknown", "pending", ""].includes(nextStatus)) return false;
|
|
9
|
+
// Catalog/detail snapshots can lag a retry. A genuinely newer idle detail
|
|
10
|
+
// (or a new durable wait) clears it; a same-age snapshot is not evidence
|
|
11
|
+
// of recovery. Do not infer retry semantics from provider error wording.
|
|
12
|
+
const previousVersion = Number(previousSession?.statusVersion);
|
|
13
|
+
const nextVersion = Number(nextSession?.statusVersion);
|
|
14
|
+
if (previousVersion > 0 && nextVersion > 0) return nextVersion <= previousVersion;
|
|
15
|
+
const time = (value) => typeof value === "number" ? value : Date.parse(value || "");
|
|
16
|
+
const previousAt = time(previousSession?.updatedAt);
|
|
17
|
+
const nextAt = time(nextSession?.updatedAt);
|
|
18
|
+
return previousAt > 0 && nextAt > 0 && nextAt <= previousAt;
|
|
14
19
|
}
|
|
@@ -2180,6 +2180,7 @@ function buildAdminModelProviderLines(view) {
|
|
|
2180
2180
|
|
|
2181
2181
|
function buildAdminConsoleLines(view) {
|
|
2182
2182
|
const lines = [];
|
|
2183
|
+
if (view.isAdmin && view.adminPolicyLabel) lines.push([{ text: view.adminPolicyLabel, color: "gray" }]);
|
|
2183
2184
|
lines.push([
|
|
2184
2185
|
{ text: "Signed in as ", color: "gray" },
|
|
2185
2186
|
{ text: formatAdminPrincipalLabelTui(view.principal), color: "white", bold: true },
|