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/history.js
CHANGED
|
@@ -18,6 +18,10 @@ export const HISTORY_EVENT_LIMIT_STEPS = [
|
|
|
18
18
|
export const CHAT_HISTORY_EVENT_TYPES = [
|
|
19
19
|
"user.message",
|
|
20
20
|
"assistant.message",
|
|
21
|
+
// Needed to distinguish interim assistant output from the final answer
|
|
22
|
+
// when an older transcript page is loaded.
|
|
23
|
+
"session.turn_completed",
|
|
24
|
+
"session.turn_stopped",
|
|
21
25
|
"system.message",
|
|
22
26
|
// Session regeneration boundary — rendered as an inline epoch divider in
|
|
23
27
|
// the transcript, so it must survive backward chat-history paging.
|
|
@@ -246,14 +250,18 @@ function comparableMessageText(message) {
|
|
|
246
250
|
export function parseAskedAndAnsweredExchange(text) {
|
|
247
251
|
const source = String(text || "").replace(/\r\n/g, "\n").trim();
|
|
248
252
|
const prefix = 'The user was asked: "';
|
|
249
|
-
const marker = '"\nThe user responded: "';
|
|
250
|
-
|
|
251
253
|
if (!source.startsWith(prefix) || !source.endsWith('"')) return null;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const
|
|
254
|
+
// Multi-writer sessions include the answering participant in the runtime
|
|
255
|
+
// wrapper. This is still an agent question followed by a human answer,
|
|
256
|
+
// not a human-authored copy of the entire question. Identity comes from
|
|
257
|
+
// the event's sender metadata, never from this display-only wrapper.
|
|
258
|
+
const marker = /"\nThe user responded(?: \(answered by [^\r\n]+?\))?: "/g;
|
|
259
|
+
marker.lastIndex = prefix.length;
|
|
260
|
+
const match = marker.exec(source);
|
|
261
|
+
if (!match) return null;
|
|
262
|
+
|
|
263
|
+
const question = source.slice(prefix.length, match.index);
|
|
264
|
+
const answer = source.slice(marker.lastIndex, -1);
|
|
257
265
|
if (!question.trim() || !answer.trim()) return null;
|
|
258
266
|
return { question, answer };
|
|
259
267
|
}
|
|
@@ -395,6 +403,11 @@ function sharesClientMessageId(left, right) {
|
|
|
395
403
|
function areMessagesEquivalent(left, right) {
|
|
396
404
|
if (!left || !right) return false;
|
|
397
405
|
if (left.role !== right.role) return false;
|
|
406
|
+
// Identical text in separate model messages still represents separate
|
|
407
|
+
// preview slots (and one may later become the final answer).
|
|
408
|
+
if (left.role === "assistant" && left.messageId && right.messageId) {
|
|
409
|
+
return left.messageId === right.messageId;
|
|
410
|
+
}
|
|
398
411
|
// Canvas revision lines are platform-minted, exactly-once (one durable
|
|
399
412
|
// event per rev), and share identical text by design (the artifact://
|
|
400
413
|
// link) — the redelivery time-window below would eat rev N-1 on every
|
|
@@ -530,18 +543,118 @@ function buildChatMessage(event, role) {
|
|
|
530
543
|
const attachments = Array.isArray(event?.data?.attachments)
|
|
531
544
|
? event.data.attachments.filter((a) => a && typeof a.filename === "string" && a.filename)
|
|
532
545
|
: [];
|
|
546
|
+
const messageId = typeof event?.data?.messageId === "string" && event.data.messageId
|
|
547
|
+
? event.data.messageId
|
|
548
|
+
: null;
|
|
549
|
+
const chatRole = deriveChatRole(event, role, text);
|
|
533
550
|
return {
|
|
534
551
|
id: `${event.sessionId}:${event.seq}`,
|
|
535
|
-
role:
|
|
552
|
+
role: chatRole,
|
|
536
553
|
text,
|
|
537
554
|
time: formatTimestamp(event.createdAt),
|
|
538
555
|
createdAt: event.createdAt instanceof Date ? event.createdAt.getTime() : new Date(event.createdAt).getTime(),
|
|
539
556
|
...(clientMessageIds.length > 0 ? { clientMessageIds } : {}),
|
|
540
557
|
...(sender ? { sender } : {}),
|
|
541
558
|
...(attachments.length > 0 ? { attachments } : {}),
|
|
559
|
+
...(messageId ? { messageId } : {}),
|
|
560
|
+
...(chatRole === "assistant" ? {
|
|
561
|
+
assistantPreview: true,
|
|
562
|
+
responsePending: true,
|
|
563
|
+
responseFinal: false,
|
|
564
|
+
responseCanFinalize: !event?.data?.parentToolCallId && !event?.data?.toolRequests?.length,
|
|
565
|
+
...(typeof event?.data?.reasoningText === "string" && event.data.reasoningText
|
|
566
|
+
? { liveReasoningText: event.data.reasoningText } : {}),
|
|
567
|
+
} : {}),
|
|
542
568
|
};
|
|
543
569
|
}
|
|
544
570
|
|
|
571
|
+
// A saved assistant.message is not necessarily the answer: a single runTurn
|
|
572
|
+
// can contain many tool-loop utterances. Only its successful durable boundary
|
|
573
|
+
// promotes the last eligible message. Use the same rule for replay and append.
|
|
574
|
+
function settleAssistantResponses(chat, event) {
|
|
575
|
+
const resultType = event?.data?.resultType || event?.data?.result;
|
|
576
|
+
const successful = event?.eventType === "session.turn_completed"
|
|
577
|
+
&& (!resultType || resultType === "completed");
|
|
578
|
+
let finalIndex = -1;
|
|
579
|
+
for (let index = chat.length - 1; index >= 0; index -= 1) {
|
|
580
|
+
if (chat[index]?.responsePending) {
|
|
581
|
+
if (successful && chat[index].responseCanFinalize) finalIndex = index;
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
for (let index = 0; index < chat.length; index += 1) {
|
|
586
|
+
if (chat[index]?.responsePending) chat[index] = {
|
|
587
|
+
...chat[index], responsePending: false, responseFinal: index === finalIndex,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/** Apply one transient `turn` topic snapshot without touching durable events/seq. */
|
|
593
|
+
export function applyLiveTurnToHistory(history, payload, meta = {}) {
|
|
594
|
+
if (!payload) return history;
|
|
595
|
+
if (payload.phase === "idle") return clearLiveTurnFromHistory(history, null, payload.streamId || null);
|
|
596
|
+
if (payload.phase !== "live") return history;
|
|
597
|
+
const messageId = typeof payload.messageId === "string" && payload.messageId ? payload.messageId : null;
|
|
598
|
+
const reasoningId = typeof payload.reasoningId === "string" && payload.reasoningId ? payload.reasoningId : null;
|
|
599
|
+
const liveKey = messageId || (reasoningId ? `reasoning:${reasoningId}` : "active-turn");
|
|
600
|
+
const createdAt = Number(meta.createdAt) || Date.now();
|
|
601
|
+
const current = history || { chat: [], activity: [], events: [], lastSeq: 0 };
|
|
602
|
+
const oldChat = current.chat || [];
|
|
603
|
+
// Durable truth wins even when a delayed retained snapshot arrives later.
|
|
604
|
+
if ((messageId && oldChat.some((item) => !item.liveTurn && item.messageId === messageId))
|
|
605
|
+
|| (current.closedLiveKeys || []).includes(liveKey)
|
|
606
|
+
|| (payload.streamId && (current.closedLiveStreams || []).includes(payload.streamId))) return current;
|
|
607
|
+
const matches = (item) => item?.liveTurn === true && (
|
|
608
|
+
item?.liveKey === liveKey
|
|
609
|
+
|| (messageId && item?.messageId === messageId)
|
|
610
|
+
|| (reasoningId && item?.reasoningId === reasoningId && (!item.messageId || !messageId))
|
|
611
|
+
);
|
|
612
|
+
const firstMatch = oldChat.findIndex(matches);
|
|
613
|
+
const previous = firstMatch >= 0 ? oldChat[firstMatch] : null;
|
|
614
|
+
const chat = oldChat.filter((item) => !matches(item));
|
|
615
|
+
const reasoningText = typeof payload.reasoningText === "string" ? payload.reasoningText : "";
|
|
616
|
+
const text = typeof payload.text === "string" ? payload.text : "";
|
|
617
|
+
const liveStartedAt = Number(previous?.liveStartedAt || previous?.createdAt) || createdAt;
|
|
618
|
+
// Reasoning and answer text are one visual turn, not two independently
|
|
619
|
+
// mounted transcript rows. Keeping them in one keyed item lets the browser
|
|
620
|
+
// collapse reasoning above the answer and lets the durable final settle
|
|
621
|
+
// into the exact same React component instead of flashing between nodes.
|
|
622
|
+
const replacement = (reasoningText || text) ? [{
|
|
623
|
+
id: previous?.id || `live:${meta.sessionId || ""}:${liveKey}`,
|
|
624
|
+
role: "assistant",
|
|
625
|
+
...(messageId ? { messageId } : {}),
|
|
626
|
+
...(reasoningId ? { reasoningId } : {}),
|
|
627
|
+
liveKey: previous?.liveKey || liveKey,
|
|
628
|
+
liveTurn: true,
|
|
629
|
+
streamId: payload.streamId || previous?.streamId || null,
|
|
630
|
+
streaming: true,
|
|
631
|
+
truncated: payload.truncated === true,
|
|
632
|
+
text,
|
|
633
|
+
reasoningText,
|
|
634
|
+
liveStartedAt,
|
|
635
|
+
createdAt: liveStartedAt,
|
|
636
|
+
time: "",
|
|
637
|
+
}] : [];
|
|
638
|
+
chat.splice(firstMatch < 0 ? chat.length : Math.min(firstMatch, chat.length), 0, ...replacement);
|
|
639
|
+
return { ...current, chat };
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
export function clearLiveTurnFromHistory(history, messageId = null, streamId = null) {
|
|
643
|
+
if (!history?.chat) return history;
|
|
644
|
+
const chat = history.chat.filter((item) => !(
|
|
645
|
+
item?.liveTurn === true && (!messageId || item?.messageId === messageId) && (!streamId || item?.streamId === streamId)
|
|
646
|
+
));
|
|
647
|
+
const retired = history.chat.filter((item) => item.liveTurn && (!messageId || item.messageId === messageId) && (!streamId || item.streamId === streamId));
|
|
648
|
+
const closedLiveKeys = [...new Set([...(history.closedLiveKeys || []),
|
|
649
|
+
...retired.flatMap((item) => [item.liveKey, item.messageId, item.reasoningId && `reasoning:${item.reasoningId}`].filter(Boolean)),
|
|
650
|
+
])].slice(-256);
|
|
651
|
+
const closedLiveStreams = [...new Set([...(history.closedLiveStreams || []),
|
|
652
|
+
...(!messageId ? retired.map((item) => item.streamId).filter(Boolean) : []),
|
|
653
|
+
...(streamId ? [streamId] : []),
|
|
654
|
+
])].slice(-256);
|
|
655
|
+
return chat.length === history.chat.length && !streamId ? history : { ...history, chat, closedLiveKeys, closedLiveStreams };
|
|
656
|
+
}
|
|
657
|
+
|
|
545
658
|
// The Copilot SDK echoes the system prompt as a system.message on EVERY
|
|
546
659
|
// turn, and the worker keeps only a 120-char snippet of it (session-proxy.ts,
|
|
547
660
|
// "Full content omitted from CMS"). For one release (0.5.47) each echo also
|
|
@@ -765,6 +878,12 @@ function formatActivity(event) {
|
|
|
765
878
|
case "assistant.streaming_progress":
|
|
766
879
|
return null;
|
|
767
880
|
|
|
881
|
+
case "model.call_start": {
|
|
882
|
+
const model = String(event?.data?.model || event?.data?.modelId || "model").split(":").pop();
|
|
883
|
+
runs = buildLabeledActivityRuns(time, "[model]", "cyan", `calling ${model}`, "white");
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
|
|
768
887
|
case "session.artifact_presented": {
|
|
769
888
|
const data = (event?.data ?? {}) || {};
|
|
770
889
|
const filename = String(data.filename || "artifact");
|
|
@@ -1092,6 +1211,9 @@ export function buildHistoryModel(events = [], options = {}) {
|
|
|
1092
1211
|
|
|
1093
1212
|
for (const event of events) {
|
|
1094
1213
|
storedEvents.push(event);
|
|
1214
|
+
if (["user.message", "session.turn_completed", "session.turn_stopped", "session.epoch_committed"].includes(event.eventType)) {
|
|
1215
|
+
settleAssistantResponses(chat, event);
|
|
1216
|
+
}
|
|
1095
1217
|
if (event.eventType === "user.message") {
|
|
1096
1218
|
activity.push(...buildEmbeddedSystemNoticeActivityItems(event, "user"));
|
|
1097
1219
|
const message = buildChatMessage(event, "user");
|
|
@@ -1167,6 +1289,8 @@ export function appendEventToHistory(history, event) {
|
|
|
1167
1289
|
? existingEvents
|
|
1168
1290
|
: [...existingEvents, event].slice(-loadedEventLimit);
|
|
1169
1291
|
const next = {
|
|
1292
|
+
closedLiveKeys: history?.closedLiveKeys || [],
|
|
1293
|
+
closedLiveStreams: history?.closedLiveStreams || [],
|
|
1170
1294
|
chat: clampHistoryItems(history?.chat || [], loadedEventLimit),
|
|
1171
1295
|
activity: clampHistoryItems(history?.activity || [], loadedEventLimit),
|
|
1172
1296
|
events: nextEvents,
|
|
@@ -1184,6 +1308,10 @@ export function appendEventToHistory(history, event) {
|
|
|
1184
1308
|
stoppedMessageIds: Array.isArray(history?.stoppedMessageIds) ? history.stoppedMessageIds : [],
|
|
1185
1309
|
};
|
|
1186
1310
|
|
|
1311
|
+
if (["user.message", "session.turn_completed", "session.turn_stopped", "session.epoch_committed"].includes(event.eventType)) {
|
|
1312
|
+
settleAssistantResponses(next.chat, event);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1187
1315
|
// A stopped turn leaves a durable session.turn_stopped carrying the
|
|
1188
1316
|
// interrupted prompt's clientMessageIds. Record them and retroactively
|
|
1189
1317
|
// flag any matching transcript message (the user.message usually lands
|
|
@@ -1201,6 +1329,7 @@ export function appendEventToHistory(history, event) {
|
|
|
1201
1329
|
: m
|
|
1202
1330
|
));
|
|
1203
1331
|
}
|
|
1332
|
+
Object.assign(next, clearLiveTurnFromHistory(next));
|
|
1204
1333
|
return next;
|
|
1205
1334
|
}
|
|
1206
1335
|
|
|
@@ -1241,10 +1370,34 @@ export function appendEventToHistory(history, event) {
|
|
|
1241
1370
|
next.activity = clampHistoryItems(next.activity, loadedEventLimit);
|
|
1242
1371
|
const message = buildChatMessage(event, "assistant");
|
|
1243
1372
|
if (!message) return next;
|
|
1244
|
-
|
|
1373
|
+
const liveIndex = message.messageId
|
|
1374
|
+
? next.chat.findIndex((item) => item?.liveTurn === true && item?.messageId === message.messageId)
|
|
1375
|
+
: -1;
|
|
1376
|
+
const liveItem = liveIndex >= 0 ? next.chat[liveIndex] : null;
|
|
1377
|
+
Object.assign(next, clearLiveTurnFromHistory(next, message.messageId || null));
|
|
1378
|
+
// Preserve the model-call slot when several assistant messages are
|
|
1379
|
+
// interleaved: the durable final replaces its own live bubble in
|
|
1380
|
+
// place instead of jumping behind a later streaming message.
|
|
1381
|
+
if (liveIndex >= 0) {
|
|
1382
|
+
const settledAt = Date.now();
|
|
1383
|
+
const liveStartedAt = Number(liveItem?.liveStartedAt || liveItem?.createdAt) || settledAt;
|
|
1384
|
+
// Keep the disclosure identity even for short streams. Committing
|
|
1385
|
+
// an interim message must not reset expansion or its scroll box.
|
|
1386
|
+
Object.assign(message, {
|
|
1387
|
+
streamSettling: true,
|
|
1388
|
+
liveKey: liveItem?.liveKey || message.messageId,
|
|
1389
|
+
liveStartedAt,
|
|
1390
|
+
liveReasoningText: message.liveReasoningText || liveItem?.reasoningText || "",
|
|
1391
|
+
});
|
|
1392
|
+
next.chat.splice(Math.min(liveIndex, next.chat.length), 0, message);
|
|
1393
|
+
}
|
|
1394
|
+
else next.chat.push(message);
|
|
1245
1395
|
next.chat = clampHistoryItems(dedupeChatMessages(next.chat), loadedEventLimit);
|
|
1246
1396
|
return next;
|
|
1247
1397
|
}
|
|
1398
|
+
if (event.eventType === "session.turn_completed" || event.eventType === "session.turn_stopped") {
|
|
1399
|
+
Object.assign(next, clearLiveTurnFromHistory(next));
|
|
1400
|
+
}
|
|
1248
1401
|
if (event.eventType === "system.message") {
|
|
1249
1402
|
if (shouldRenderSystemMessageAsActivity(event)) {
|
|
1250
1403
|
const activityItem = formatActivity(event);
|
package/ui/core/src/reducer.js
CHANGED
|
@@ -817,6 +817,9 @@ function normalizePromptAttachments(prompt, attachments) {
|
|
|
817
817
|
}
|
|
818
818
|
|
|
819
819
|
export function appReducer(state, action) {
|
|
820
|
+
const sessionId = action.sessionId ?? action.session?.sessionId;
|
|
821
|
+
const contentUpdate = /^(history|files|canvas|orchestration|executionHistory|sessionStats|outbox)\//.test(action.type) || action.type === "sessions/merged";
|
|
822
|
+
if (sessionId && contentUpdate && state.sessions?.goneIds?.includes(sessionId)) return state;
|
|
820
823
|
const next = baseReducer(state, action);
|
|
821
824
|
if (next === state) return next;
|
|
822
825
|
return reconcileSessionView(state, next, action);
|
|
@@ -1567,7 +1570,10 @@ function baseReducer(state, action) {
|
|
|
1567
1570
|
if (!goneId) return state;
|
|
1568
1571
|
const hadRow = Boolean(state.sessions.byId[goneId]);
|
|
1569
1572
|
const wasActive = state.sessions.activeSessionId === goneId;
|
|
1570
|
-
|
|
1573
|
+
const hadContent = state.history.bySessionId.has(goneId)
|
|
1574
|
+
|| [state.files, state.canvas, state.orchestration, state.executionHistory, state.outbox, state.sessionStats]
|
|
1575
|
+
.some((slice) => Object.hasOwn(slice?.bySessionId || {}, goneId));
|
|
1576
|
+
if (!hadRow && !wasActive && !hadContent) return state;
|
|
1571
1577
|
const evictedById = { ...state.sessions.byId };
|
|
1572
1578
|
delete evictedById[goneId];
|
|
1573
1579
|
// Re-derive: the evicted row may have been the running CHILD that
|
|
@@ -1578,13 +1584,29 @@ function baseReducer(state, action) {
|
|
|
1578
1584
|
const selectedIds = Array.isArray(state.sessions.selectedIds)
|
|
1579
1585
|
? state.sessions.selectedIds.filter((id) => id !== goneId)
|
|
1580
1586
|
: state.sessions.selectedIds;
|
|
1581
|
-
|
|
1582
|
-
|
|
1587
|
+
const history = new Map(state.history.bySessionId);
|
|
1588
|
+
history.delete(goneId);
|
|
1589
|
+
const discard = (slice) => {
|
|
1590
|
+
if (!slice?.bySessionId) return slice;
|
|
1591
|
+
const bySessionId = { ...slice.bySessionId };
|
|
1592
|
+
delete bySessionId[goneId];
|
|
1593
|
+
return { ...slice, bySessionId };
|
|
1594
|
+
};
|
|
1595
|
+
return {
|
|
1596
|
+
...state,
|
|
1597
|
+
history: { ...state.history, bySessionId: history },
|
|
1598
|
+
files: discard(state.files),
|
|
1599
|
+
orchestration: discard(state.orchestration),
|
|
1600
|
+
executionHistory: discard(state.executionHistory),
|
|
1601
|
+
canvas: discard(state.canvas),
|
|
1602
|
+
outbox: discard(state.outbox),
|
|
1603
|
+
sessionStats: discard(state.sessionStats),
|
|
1583
1604
|
sessions: {
|
|
1584
1605
|
...state.sessions,
|
|
1585
1606
|
byId,
|
|
1586
1607
|
selectedIds,
|
|
1587
1608
|
activeSessionId: wasActive ? null : state.sessions.activeSessionId,
|
|
1609
|
+
goneIds: [...new Set([...(state.sessions.goneIds || []), goneId])].slice(-1000),
|
|
1588
1610
|
},
|
|
1589
1611
|
};
|
|
1590
1612
|
}
|
|
@@ -1681,6 +1703,7 @@ function baseReducer(state, action) {
|
|
|
1681
1703
|
const nextSessions = {
|
|
1682
1704
|
...state.sessions,
|
|
1683
1705
|
byId: rowStatusById,
|
|
1706
|
+
goneIds: (state.sessions.goneIds || []).filter((id) => !action.sessions.some((row) => row.sessionId === id)),
|
|
1684
1707
|
collapsedIds,
|
|
1685
1708
|
collapsedIdsExplicit: previousCollapsedIdsExplicit,
|
|
1686
1709
|
listingSeen: true,
|
|
@@ -2940,7 +2963,10 @@ function baseReducer(state, action) {
|
|
|
2940
2963
|
const list = Array.isArray(action.list) ? action.list : [];
|
|
2941
2964
|
// Keep the selection when the package still exists; otherwise clear
|
|
2942
2965
|
// the dependent detail/workspace state with it.
|
|
2943
|
-
const
|
|
2966
|
+
const selector = previous.selectedSelector;
|
|
2967
|
+
const stillThere = previous.selectedName && list.some((p) => p.name === previous.selectedName
|
|
2968
|
+
&& (!selector?.scope || p.scope === selector.scope)
|
|
2969
|
+
&& (!selector?.owner || (p.owner?.provider === selector.owner.provider && p.owner?.subject === selector.owner.subject)));
|
|
2944
2970
|
return {
|
|
2945
2971
|
...state,
|
|
2946
2972
|
admin: {
|
|
@@ -2954,7 +2980,8 @@ function baseReducer(state, action) {
|
|
|
2954
2980
|
fetchedAt: Date.now(),
|
|
2955
2981
|
selectedName: stillThere ? previous.selectedName : null,
|
|
2956
2982
|
selectedSelector: stillThere ? (previous.selectedSelector ?? null) : null,
|
|
2957
|
-
...(stillThere ? {} : {
|
|
2983
|
+
...(stillThere ? {} : { selectionSeq: (previous.selectionSeq ?? 0) + 1, detail: null, changelog: null,
|
|
2984
|
+
workspace: { ...previous.workspace, tree: null, selectedPath: null, file: null } }),
|
|
2958
2985
|
},
|
|
2959
2986
|
},
|
|
2960
2987
|
};
|