pilotswarm 0.5.44 → 0.5.47
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/README.md +12 -0
- package/mcp/dist/src/context.d.ts.map +1 -1
- package/mcp/dist/src/context.js +9 -1
- package/mcp/dist/src/context.js.map +1 -1
- package/mcp/dist/src/tools/agent-packages.d.ts.map +1 -1
- package/mcp/dist/src/tools/agent-packages.js +31 -0
- package/mcp/dist/src/tools/agent-packages.js.map +1 -1
- package/mcp/dist/src/tools/providers.d.ts.map +1 -1
- package/mcp/dist/src/tools/providers.js +15 -3
- package/mcp/dist/src/tools/providers.js.map +1 -1
- package/mcp/dist/src/util/respond.d.ts.map +1 -1
- package/mcp/dist/src/util/respond.js +6 -0
- package/mcp/dist/src/util/respond.js.map +1 -1
- package/package.json +2 -2
- package/tui/src/agents-cli.js +35 -0
- package/tui/src/app.js +7 -11
- package/tui/src/node-sdk-transport.js +70 -0
- package/ui/core/src/controller.js +313 -74
- package/ui/core/src/history.js +65 -10
- package/ui/core/src/reducer.js +51 -4
- package/ui/core/src/selectors.js +101 -20
- package/ui/core/src/state.js +11 -0
- package/ui/react/src/components.js +1 -0
- package/ui/react/src/web-app.js +938 -127
- package/web/api/canvas-plane.js +18 -6
- package/web/api/ws.js +22 -2
- package/web/auth/index.js +8 -1
- package/web/auth/normalize/entra.js +5 -1
- package/web/dist/assets/{abnfDiagram-VRR7QNED-67Q0WWge.js → abnfDiagram-VRR7QNED-VPL8m_9W.js} +1 -1
- package/web/dist/assets/{arc-BvcYFngS.js → arc-T5I3-c1B.js} +1 -1
- package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-CPBN0rk8.js → architectureDiagram-ZJ3FMSHR-DEP70bg6.js} +1 -1
- package/web/dist/assets/{blockDiagram-677ZJIJ3-D_hp1Oah.js → blockDiagram-677ZJIJ3-CvBu1MbO.js} +1 -1
- package/web/dist/assets/{c4Diagram-LMCZKHZV-DAOVGMIW.js → c4Diagram-LMCZKHZV-B5qwF4gm.js} +1 -1
- package/web/dist/assets/channel-D5LojeMw.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-DajZuaot.js → chunk-2Q5K7J3B-zVW8dS7X.js} +1 -1
- package/web/dist/assets/{chunk-32BRIVSS-C6pXBsgt.js → chunk-32BRIVSS-DWctxZZS.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-780edrmi.js → chunk-5VM5RSS4-Bq7_Vmf9.js} +1 -1
- package/web/dist/assets/{chunk-EX3LRPZG-C0e4NVuz.js → chunk-EX3LRPZG-B8CFB3v-.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-uRe83I6C.js → chunk-JWPE2WC7-B7gLPjgZ.js} +1 -1
- package/web/dist/assets/{chunk-MOJQB5TN-B0WPoPjh.js → chunk-MOJQB5TN-B2x6q12r.js} +1 -1
- package/web/dist/assets/{chunk-RYQCIY6F-eRFuHHyG.js → chunk-RYQCIY6F-XorU9Stx.js} +1 -1
- package/web/dist/assets/{chunk-V7JOEXUC-C8FDXprk.js → chunk-V7JOEXUC-CsLa6pw5.js} +1 -1
- package/web/dist/assets/{chunk-VR4S4FIN-DPFpLQdB.js → chunk-VR4S4FIN-BBv9mwmj.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-CoGQOPKW.js → chunk-XXDRQBXY-Cjm4mm6l.js} +1 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-C5edc5Or.js +1 -0
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-C5edc5Or.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-BTBSfO5v.js → cose-bilkent-JH36ORCC-Zw9ZQbVl.js} +1 -1
- package/web/dist/assets/{cynefin-VYW2F7L2-DgTkQEoK.js → cynefin-VYW2F7L2-BYnR9YLM.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-TSTJHNR4-DiBMlKoP.js → cynefinDiagram-TSTJHNR4-Cx8L0dOv.js} +1 -1
- package/web/dist/assets/{dagre-VKFMJZFB-Qv-4dcbk.js → dagre-VKFMJZFB-7GaqVcqz.js} +1 -1
- package/web/dist/assets/{diagram-FQU43EPY-CgyAb7yx.js → diagram-FQU43EPY-BWSRPJvU.js} +1 -1
- package/web/dist/assets/{diagram-G47NLZAW-BxN-KcGp.js → diagram-G47NLZAW-BELuAE1e.js} +1 -1
- package/web/dist/assets/{diagram-NH7WQ7WH-BiellpWz.js → diagram-NH7WQ7WH-DGXhPpri.js} +1 -1
- package/web/dist/assets/{diagram-OA4YK3LP-C8Ktgrwv.js → diagram-OA4YK3LP-DV8HqN0T.js} +1 -1
- package/web/dist/assets/{diagram-WEI45ONY-Bj5K1X91.js → diagram-WEI45ONY-Clv1X3ZL.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-CCIWWBDH-DNQI2XT9.js → ebnfDiagram-CCIWWBDH-TpQx36oi.js} +1 -1
- package/web/dist/assets/{erDiagram-Q63AITRT-D5Xkllu6.js → erDiagram-Q63AITRT-DVVWd00c.js} +1 -1
- package/web/dist/assets/{flowDiagram-23GEKE2U-DJpTtMuj.js → flowDiagram-23GEKE2U-Cdz_8_Dw.js} +1 -1
- package/web/dist/assets/{ganttDiagram-NO4QXBWP-C-EQlCJf.js → ganttDiagram-NO4QXBWP-zlco-o7u.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-C8vpSbvW.js → gitGraphDiagram-IHSO6WYX-CfAZW1En.js} +1 -1
- package/web/dist/assets/index-CMtzFJre.css +1 -0
- package/web/dist/assets/index-tQXCQojP.js +24 -0
- package/web/dist/assets/{infoDiagram-FWYZ7A6U-CKNzsR3r.js → infoDiagram-FWYZ7A6U-0R-8efwI.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-CjyQq3lf.js → ishikawaDiagram-FXEZZL3T-lPpoXGlp.js} +1 -1
- package/web/dist/assets/{journeyDiagram-5HDEW3XC-CK3EEkJt.js → journeyDiagram-5HDEW3XC-D05vSVqu.js} +1 -1
- package/web/dist/assets/{kanban-definition-HUTT4EX6-CHQ8YkPx.js → kanban-definition-HUTT4EX6-ClaDdESv.js} +1 -1
- package/web/dist/assets/{linear-CUYirs1h.js → linear-CUnhHMvY.js} +1 -1
- package/web/dist/assets/{mermaid.core-Di8sQC4g.js → mermaid.core-DaYS-X8Y.js} +4 -4
- package/web/dist/assets/{mindmap-definition-LN4V7U3C-D38dmHP5.js → mindmap-definition-LN4V7U3C-N480VHds.js} +1 -1
- package/web/dist/assets/{papaparse.min-DWBU_pXS.js → papaparse.min-CNTcNgd8.js} +1 -1
- package/web/dist/assets/{pegDiagram-2B236MQR-BU759QCo.js → pegDiagram-2B236MQR-CVWyJ5ck.js} +1 -1
- package/web/dist/assets/{pieDiagram-ENE6RG2P-BbpfHbsj.js → pieDiagram-ENE6RG2P-UR9uohPf.js} +1 -1
- package/web/dist/assets/pilotswarm-BG6hk0oE.js +97 -0
- package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-BYZ3XHZn.js → quadrantDiagram-ABIIQ3AL-D2SIOetn.js} +1 -1
- package/web/dist/assets/{railroadDiagram-RFXS5EU6-D18gkZBJ.js → railroadDiagram-RFXS5EU6-OgN6qgOU.js} +1 -1
- package/web/dist/assets/{requirementDiagram-TGXJPOKE-D6vo26Ib.js → requirementDiagram-TGXJPOKE-BSUURBBz.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-HTMAVEWB-CxQwXnRE.js → sankeyDiagram-HTMAVEWB-BBZhmSBZ.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-CfGisw4A.js → sequenceDiagram-DBY2YBRQ-C10IDNdW.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-BDK7-yA-.js → sizeCapture-X5ZJPWSS-B9787MHa.js} +1 -1
- package/web/dist/assets/{stateDiagram-2N3HPSRC-8IyidYSV.js → stateDiagram-2N3HPSRC-BmSBo7es.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-BqpsfoJI.js +1 -0
- package/web/dist/assets/{swimlanes-5IMT3BWC-CgNqXCvM.js → swimlanes-5IMT3BWC-DOXpv_KQ.js} +2 -2
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-k8WG_bWt.js +8 -0
- package/web/dist/assets/{timeline-definition-FHXFAJF6-DAJsGj6d.js → timeline-definition-FHXFAJF6-SjhGfPdI.js} +1 -1
- package/web/dist/assets/{vennDiagram-L72KCM5P-DDVdvG5k.js → vennDiagram-L72KCM5P-BiujYen6.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-EHGQE667-CiAs_m72.js → wardleyDiagram-EHGQE667-6nLXBvwc.js} +1 -1
- package/web/dist/assets/{xychartDiagram-FW5EYKEG-D6OLJtsD.js → xychartDiagram-FW5EYKEG-Di7ioTvW.js} +1 -1
- package/web/dist/index.html +3 -3
- package/web/runtime.js +192 -6
- package/web/server.js +22 -0
- package/web/dist/assets/channel-DiW7lUT8.js +0 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-CpAhBd5x.js +0 -1
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-CpAhBd5x.js +0 -1
- package/web/dist/assets/index-C2ioXNJJ.js +0 -24
- package/web/dist/assets/index-CVYm02UI.css +0 -1
- package/web/dist/assets/pilotswarm-DrM3pXOV.js +0 -97
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-F7qYkvpr.js +0 -1
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-CUKrHLkY.js +0 -8
|
@@ -579,7 +579,7 @@ function buildSessionOwnerFilterItems(state) {
|
|
|
579
579
|
id: "all",
|
|
580
580
|
kind: "all",
|
|
581
581
|
label: "All",
|
|
582
|
-
description: "
|
|
582
|
+
description: "Every session you can see — your own, ones shared with you, and system sessions.",
|
|
583
583
|
},
|
|
584
584
|
{
|
|
585
585
|
id: "system",
|
|
@@ -893,7 +893,127 @@ export function agentPickerInitialIndex(items = []) {
|
|
|
893
893
|
return firstClosed >= 0 ? firstClosed : 0;
|
|
894
894
|
}
|
|
895
895
|
|
|
896
|
-
|
|
896
|
+
/** How the picker may be ordered. `used` is per-person; the rest are stable. */
|
|
897
|
+
export const AGENT_PICKER_SORTS = ["used", "name", "package"];
|
|
898
|
+
export const AGENT_PICKER_DEFAULT_SORT = "used";
|
|
899
|
+
|
|
900
|
+
/** Profile-settings key holding this person's per-agent start counts. */
|
|
901
|
+
export const AGENT_USAGE_SETTING = "agentPickerUsage";
|
|
902
|
+
|
|
903
|
+
/** Everything a query is matched against, lowercased once per agent. */
|
|
904
|
+
function agentHaystack(agent) {
|
|
905
|
+
return [
|
|
906
|
+
agent?.label, agent?.agentName, agent?.title, agent?.description,
|
|
907
|
+
agent?.packageTitle, agent?.packageName,
|
|
908
|
+
...(Array.isArray(agent?.tools) ? agent.tools : []),
|
|
909
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Lexical match: every whitespace-separated term must appear somewhere in the
|
|
914
|
+
* agent's text. AND rather than OR, because "rcakit pg" should narrow to one
|
|
915
|
+
* agent rather than widen to everything mentioning either word.
|
|
916
|
+
*
|
|
917
|
+
* The score only ranks matches against each other — a name hit outranks a
|
|
918
|
+
* description hit, and a prefix outranks a mid-word hit, so typing "rca" puts
|
|
919
|
+
* the agents CALLED rcakit above the ones that merely mention it.
|
|
920
|
+
*/
|
|
921
|
+
export function scoreAgentMatch(agent, query) {
|
|
922
|
+
const terms = String(query || "").toLowerCase().split(/\s+/).filter(Boolean);
|
|
923
|
+
if (terms.length === 0) return 0;
|
|
924
|
+
const hay = agentHaystack(agent);
|
|
925
|
+
const name = `${agent?.label || ""} ${agent?.agentName || ""}`.toLowerCase();
|
|
926
|
+
let score = 0;
|
|
927
|
+
for (const term of terms) {
|
|
928
|
+
if (!hay.includes(term)) return -1; // every term must land
|
|
929
|
+
if (name.startsWith(term)) score += 6;
|
|
930
|
+
else if (new RegExp(`\\b${term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`).test(name)) score += 4;
|
|
931
|
+
else if (name.includes(term)) score += 3;
|
|
932
|
+
else score += 1;
|
|
933
|
+
}
|
|
934
|
+
return score;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function agentUsageCount(usage, agent) {
|
|
938
|
+
if (!usage || !agent?.agentName) return 0;
|
|
939
|
+
return Number(usage[String(agent.agentName)]) || 0;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function compareAgentsForSort(sort, usage) {
|
|
943
|
+
const byName = (a, b) => String(a.label || a.agentName || "")
|
|
944
|
+
.localeCompare(String(b.label || b.agentName || ""), undefined, { sensitivity: "base" });
|
|
945
|
+
if (sort === "name") return byName;
|
|
946
|
+
if (sort === "package") {
|
|
947
|
+
return (a, b) => String(a.packageTitle || a.packageName || "")
|
|
948
|
+
.localeCompare(String(b.packageTitle || b.packageName || ""), undefined, { sensitivity: "base" })
|
|
949
|
+
|| byName(a, b);
|
|
950
|
+
}
|
|
951
|
+
// "used": most-started first, then alphabetical so the long tail of
|
|
952
|
+
// never-started agents is not in arbitrary order.
|
|
953
|
+
return (a, b) => agentUsageCount(usage, b) - agentUsageCount(usage, a) || byName(a, b);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* The picker list: ONE ROW PER AGENT.
|
|
958
|
+
*
|
|
959
|
+
* The old shape gave each package a header row and then repeated its agents
|
|
960
|
+
* beneath. Nearly every package holds exactly one agent, so that spent two
|
|
961
|
+
* rows saying the same thing twice and pushed the list past a screen. The
|
|
962
|
+
* package is still shown — as trailing metadata on the row it belongs to,
|
|
963
|
+
* and as a sort key — but it no longer costs a row of its own.
|
|
964
|
+
*/
|
|
965
|
+
export function buildAgentPickerItems(catalog = [], opts = {}) {
|
|
966
|
+
const {
|
|
967
|
+
query = "",
|
|
968
|
+
sort = AGENT_PICKER_DEFAULT_SORT,
|
|
969
|
+
usage = null,
|
|
970
|
+
genericItem = null,
|
|
971
|
+
} = opts || {};
|
|
972
|
+
|
|
973
|
+
// Generic sits ABOVE everything and never filters out on an empty query:
|
|
974
|
+
// it is not an agent, it is the absence of one, and it is the most common
|
|
975
|
+
// pick. A typed query does filter it, so search can reach past it.
|
|
976
|
+
const items = [];
|
|
977
|
+
const terms = String(query || "").trim();
|
|
978
|
+
if (genericItem && (!terms || scoreAgentMatch({ label: genericItem.label, description: genericItem.description }, terms) >= 0)) {
|
|
979
|
+
items.push({ ...genericItem, depth: 0 });
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Composition still decides whether an agent can be STARTED — `startedBy`
|
|
983
|
+
// resolving inside its own set is what makes it called-only. The flat list
|
|
984
|
+
// drops the nesting, not that derivation, so each set still goes through
|
|
985
|
+
// orderAgentsByComposition; only its ordering is discarded here.
|
|
986
|
+
const raw = catalog.filter((agent) => agent && agent.kind !== "section");
|
|
987
|
+
const sets = new Map();
|
|
988
|
+
for (const agent of raw) {
|
|
989
|
+
const key = agent.builtin ? "builtin" : agentPickerPackageKey(agent);
|
|
990
|
+
if (!sets.has(key)) sets.set(key, []);
|
|
991
|
+
sets.get(key).push(agent);
|
|
992
|
+
}
|
|
993
|
+
const agents = [];
|
|
994
|
+
for (const set of sets.values()) {
|
|
995
|
+
for (const agent of orderAgentsByComposition(set)) agents.push({ ...agent, depth: 0 });
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
const scored = terms
|
|
999
|
+
? agents.map((agent) => ({ agent, score: scoreAgentMatch(agent, terms) })).filter((row) => row.score >= 0)
|
|
1000
|
+
: agents.map((agent) => ({ agent, score: 0 }));
|
|
1001
|
+
|
|
1002
|
+
const compare = compareAgentsForSort(sort, usage);
|
|
1003
|
+
scored.sort((a, b) => (terms ? b.score - a.score : 0) || compare(a.agent, b.agent));
|
|
1004
|
+
|
|
1005
|
+
for (const { agent } of scored) {
|
|
1006
|
+
items.push({
|
|
1007
|
+
...agent,
|
|
1008
|
+
depth: 0,
|
|
1009
|
+
usageCount: agentUsageCount(usage, agent),
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
return items;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/** @deprecated the grouped shape; kept only for the TUI's collapse tests. */
|
|
1016
|
+
export function buildGroupedAgentPickerItems(catalog = [], collapsedKeys = [], genericItem = null) {
|
|
897
1017
|
const collapsed = new Set(collapsedKeys);
|
|
898
1018
|
const items = [];
|
|
899
1019
|
|
|
@@ -2614,6 +2734,14 @@ export class PilotSwarmUiController {
|
|
|
2614
2734
|
*/
|
|
2615
2735
|
async refreshWorkerRegistryIfStale() {
|
|
2616
2736
|
const workers = this.getState().admin?.workers;
|
|
2737
|
+
// The registry is fleet:admin. Polling it as a plain user is a 403
|
|
2738
|
+
// every few seconds and a red console for nothing.
|
|
2739
|
+
const role = this.getState().auth?.authorization?.role;
|
|
2740
|
+
const isAdmin = role === "admin" || role === "anonymous" || this.getState().admin?.profile?.isAdmin === true;
|
|
2741
|
+
if (!isAdmin) {
|
|
2742
|
+
this.dispatch({ type: "admin/workers/attempt", skip: "not admin" });
|
|
2743
|
+
return;
|
|
2744
|
+
}
|
|
2617
2745
|
if (workers?.loading) {
|
|
2618
2746
|
this.dispatch({ type: "admin/workers/attempt", skip: "already in flight" });
|
|
2619
2747
|
return;
|
|
@@ -3155,6 +3283,15 @@ export class PilotSwarmUiController {
|
|
|
3155
3283
|
}
|
|
3156
3284
|
}
|
|
3157
3285
|
|
|
3286
|
+
/**
|
|
3287
|
+
* Drop a failed provider mutation's error. Called when the sheet that
|
|
3288
|
+
* raised it closes — the failure was that sheet's, and a banner outliving
|
|
3289
|
+
* it describes a change the person already abandoned.
|
|
3290
|
+
*/
|
|
3291
|
+
dismissAdminModelProviderMutationError() {
|
|
3292
|
+
this.dispatch({ type: "admin/modelProviders/mutationDismissed" });
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3158
3295
|
async _runAdminModelProviderMutation(pending, run) {
|
|
3159
3296
|
this.dispatch({ type: "admin/modelProviders/mutationPending", pending });
|
|
3160
3297
|
try {
|
|
@@ -3180,6 +3317,28 @@ export class PilotSwarmUiController {
|
|
|
3180
3317
|
}));
|
|
3181
3318
|
}
|
|
3182
3319
|
|
|
3320
|
+
/**
|
|
3321
|
+
* Rotate a provider's key in place.
|
|
3322
|
+
*
|
|
3323
|
+
* Two ops, one per scope, the same split deleteAdminProvider uses: a
|
|
3324
|
+
* personal provider answers to its owner, a shared one to an admin. Both
|
|
3325
|
+
* change ONLY the key — the name, type, base URL, defaults, system-session
|
|
3326
|
+
* routing and usage history all stay, which is the whole reason this
|
|
3327
|
+
* exists rather than delete-and-recreate.
|
|
3328
|
+
*/
|
|
3329
|
+
async updateAdminProviderCredential({ name, credentials, shared = false } = {}) {
|
|
3330
|
+
const op = shared ? "updateSharedProviderCredential" : "updateMyProviderCredential";
|
|
3331
|
+
if (typeof this.transport[op] !== "function") {
|
|
3332
|
+
const error = shared
|
|
3333
|
+
? "Shared provider credential updates are not available on this transport."
|
|
3334
|
+
: "Provider credential updates are not available on this transport.";
|
|
3335
|
+
this.dispatch({ type: "admin/modelProviders/mutationFailed", error });
|
|
3336
|
+
return { ok: false, result: null, error };
|
|
3337
|
+
}
|
|
3338
|
+
return this._runAdminModelProviderMutation("updateCredential", () =>
|
|
3339
|
+
this.transport[op]({ name, credentials }));
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3183
3342
|
async deleteAdminProvider(name, { shared = false } = {}) {
|
|
3184
3343
|
const op = shared ? "deleteProvider" : "deleteMyProvider";
|
|
3185
3344
|
if (typeof this.transport[op] !== "function") {
|
|
@@ -3257,13 +3416,25 @@ export class PilotSwarmUiController {
|
|
|
3257
3416
|
name = `${base}-${suffix}`;
|
|
3258
3417
|
suffix += 1;
|
|
3259
3418
|
}
|
|
3260
|
-
this.dispatch({ type: "admin/modelProviders/createBegin", name, typeId, shared });
|
|
3419
|
+
this.dispatch({ type: "admin/modelProviders/createBegin", name, typeId, shared, mode: "create" });
|
|
3261
3420
|
}
|
|
3262
3421
|
|
|
3263
3422
|
beginAdminCreateGithubProvider() {
|
|
3264
3423
|
this.beginAdminCreateProvider({ shared: false });
|
|
3265
3424
|
}
|
|
3266
3425
|
|
|
3426
|
+
beginAdminUpdateProviderCredential(provider) {
|
|
3427
|
+
if (!provider?.name || provider.class === "shared") return;
|
|
3428
|
+
this.dispatch({
|
|
3429
|
+
type: "admin/modelProviders/createBegin",
|
|
3430
|
+
name: provider.name,
|
|
3431
|
+
typeId: provider.typeId,
|
|
3432
|
+
shared: false,
|
|
3433
|
+
mode: "update",
|
|
3434
|
+
stage: "credential",
|
|
3435
|
+
});
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3267
3438
|
cycleAdminProviderCreateType() {
|
|
3268
3439
|
const providers = selectAdminConsole(this.getState()).modelProviders;
|
|
3269
3440
|
const create = this.getState().admin?.modelProviders?.create;
|
|
@@ -3334,7 +3505,9 @@ export class PilotSwarmUiController {
|
|
|
3334
3505
|
};
|
|
3335
3506
|
// Clear the credential from shared state before any network await.
|
|
3336
3507
|
this.dispatch({ type: "admin/modelProviders/createSaving" });
|
|
3337
|
-
const outcome =
|
|
3508
|
+
const outcome = create.mode === "update"
|
|
3509
|
+
? await this.updateAdminProviderCredential({ name: input.name, credentials: input.credentials })
|
|
3510
|
+
: await this.createAdminProvider(input);
|
|
3338
3511
|
this.dispatch(outcome?.ok
|
|
3339
3512
|
? { type: "admin/modelProviders/createEnd" }
|
|
3340
3513
|
: { type: "admin/modelProviders/createFailed", error: outcome?.error });
|
|
@@ -3654,6 +3827,13 @@ export class PilotSwarmUiController {
|
|
|
3654
3827
|
case "delete":
|
|
3655
3828
|
await this.transport.deleteAgentPackage(name, selector);
|
|
3656
3829
|
break;
|
|
3830
|
+
// Editors live on the shared copy only — no selector.
|
|
3831
|
+
case "grantEditor":
|
|
3832
|
+
await this.transport.grantAgentPackageEditor(name, arg);
|
|
3833
|
+
break;
|
|
3834
|
+
case "revokeEditor":
|
|
3835
|
+
await this.transport.revokeAgentPackageEditor(name, arg);
|
|
3836
|
+
break;
|
|
3657
3837
|
default:
|
|
3658
3838
|
throw new Error(`unknown package action: ${kind}`);
|
|
3659
3839
|
}
|
|
@@ -5270,12 +5450,11 @@ export class PilotSwarmUiController {
|
|
|
5270
5450
|
// the authority; this is the friendly refusal). The canvas mutates —
|
|
5271
5451
|
// a shared viewer may be looking at a different revision than the one
|
|
5272
5452
|
// the creator is conversing through, so their clicks must not speak.
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
}
|
|
5453
|
+
// Who may ring is the SERVER's decision (owner, admin, or anyone with
|
|
5454
|
+
// session write — interactive-canvas-apps Part E). The client no
|
|
5455
|
+
// longer pre-refuses non-owners: a write-shared collaborator's click
|
|
5456
|
+
// must reach the agent, and the server's refusal for a read-only
|
|
5457
|
+
// viewer comes back as the result's reason.
|
|
5279
5458
|
const contract = this.getState().canvas.bySessionId[sessionId]?.responseContract || null;
|
|
5280
5459
|
const verdict = validateCanvasAction(contract, message);
|
|
5281
5460
|
if (!verdict.ok) return Promise.resolve(verdict);
|
|
@@ -5386,6 +5565,23 @@ export class PilotSwarmUiController {
|
|
|
5386
5565
|
return true;
|
|
5387
5566
|
}
|
|
5388
5567
|
|
|
5568
|
+
/**
|
|
5569
|
+
* Listen for canvas KV changes on a session (the live path of the canvas
|
|
5570
|
+
* KV store). Returns an unsubscriber. Frames use this to post
|
|
5571
|
+
* `canvas-kv-change` into their page.
|
|
5572
|
+
*/
|
|
5573
|
+
subscribeCanvasKv(sessionId, callback) {
|
|
5574
|
+
if (!sessionId || typeof callback !== "function") return () => {};
|
|
5575
|
+
if (!this._canvasKvListeners) this._canvasKvListeners = new Map();
|
|
5576
|
+
if (!this._canvasKvListeners.has(sessionId)) this._canvasKvListeners.set(sessionId, new Set());
|
|
5577
|
+
const set = this._canvasKvListeners.get(sessionId);
|
|
5578
|
+
set.add(callback);
|
|
5579
|
+
return () => {
|
|
5580
|
+
set.delete(callback);
|
|
5581
|
+
if (set.size === 0) this._canvasKvListeners.delete(sessionId);
|
|
5582
|
+
};
|
|
5583
|
+
}
|
|
5584
|
+
|
|
5389
5585
|
applyCanvasDataEvent(sessionId, event) {
|
|
5390
5586
|
this.dispatch({
|
|
5391
5587
|
type: "canvas/data",
|
|
@@ -5413,6 +5609,17 @@ export class PilotSwarmUiController {
|
|
|
5413
5609
|
if (event.eventType === "session.canvas_data") {
|
|
5414
5610
|
this.applyCanvasDataEvent(sessionId, event);
|
|
5415
5611
|
}
|
|
5612
|
+
if (event.eventType === "session.canvas_kv") {
|
|
5613
|
+
// KV changes never touch the reducer: they go straight to
|
|
5614
|
+
// the canvas frames listening for this session, which post
|
|
5615
|
+
// them into the page as `canvas-kv-change`.
|
|
5616
|
+
const listeners = this._canvasKvListeners?.get(sessionId);
|
|
5617
|
+
if (listeners) {
|
|
5618
|
+
for (const cb of listeners) {
|
|
5619
|
+
try { cb(event.data); } catch { /* one frame's problem */ }
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
}
|
|
5416
5623
|
if (event.eventType === "session.canvas_plane_released") {
|
|
5417
5624
|
// The plane died for this session (server error/rollback).
|
|
5418
5625
|
// Clear the reducer's takeover so legacy durable ticks — the
|
|
@@ -5606,6 +5813,10 @@ export class PilotSwarmUiController {
|
|
|
5606
5813
|
try {
|
|
5607
5814
|
const requestOptions = this.applyActiveGroupDefault(options);
|
|
5608
5815
|
const created = await this.transport.createSessionForAgent(agentName, requestOptions);
|
|
5816
|
+
// Count the start for this person's "most used" ordering. Deliberately
|
|
5817
|
+
// not awaited: the picker's sort order must never delay opening the
|
|
5818
|
+
// session that was just created, and a lost count only reorders a list.
|
|
5819
|
+
this._recordAgentPickerUse(agentName);
|
|
5609
5820
|
await this.placeCreatedSessionInGroup(created, requestOptions.groupId ?? null);
|
|
5610
5821
|
await this.refreshSessions();
|
|
5611
5822
|
await this.loadSession(created.sessionId);
|
|
@@ -6101,18 +6312,14 @@ export class PilotSwarmUiController {
|
|
|
6101
6312
|
}
|
|
6102
6313
|
: null;
|
|
6103
6314
|
|
|
6104
|
-
//
|
|
6105
|
-
//
|
|
6106
|
-
//
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
AGENT_PICKER_MINE_KEY,
|
|
6113
|
-
...new Set(catalog.filter((item) => !item.builtin).map((item) => agentPickerPackageKey(item))),
|
|
6114
|
-
];
|
|
6115
|
-
const items = buildAgentPickerItems(catalog, collapsed, genericItem);
|
|
6315
|
+
// Per-person start counts, so "most used" means most used BY YOU.
|
|
6316
|
+
// Best-effort: a profile that cannot be read just sorts alphabetically
|
|
6317
|
+
// rather than failing the picker open.
|
|
6318
|
+
const usage = await this._loadAgentPickerUsage();
|
|
6319
|
+
const sort = AGENT_PICKER_SORTS.includes(this._agentPickerSort)
|
|
6320
|
+
? this._agentPickerSort
|
|
6321
|
+
: AGENT_PICKER_DEFAULT_SORT;
|
|
6322
|
+
const items = buildAgentPickerItems(catalog, { query: "", sort, usage, genericItem });
|
|
6116
6323
|
|
|
6117
6324
|
this.dispatch({
|
|
6118
6325
|
type: "ui/modal",
|
|
@@ -6121,8 +6328,10 @@ export class PilotSwarmUiController {
|
|
|
6121
6328
|
title: "Select agent for new session",
|
|
6122
6329
|
catalog,
|
|
6123
6330
|
genericItem,
|
|
6124
|
-
collapsed,
|
|
6125
6331
|
items,
|
|
6332
|
+
query: "",
|
|
6333
|
+
sort,
|
|
6334
|
+
usage,
|
|
6126
6335
|
// Land somewhere Enter does something useful. First choice is a
|
|
6127
6336
|
// real agent row; with none visible (no generic, no baked
|
|
6128
6337
|
// agents, every package closed) fall back to the first CLOSED
|
|
@@ -7673,61 +7882,96 @@ export class PilotSwarmUiController {
|
|
|
7673
7882
|
* cursor shifts every index below it, and re-using the old number would
|
|
7674
7883
|
* silently move the highlight onto a different agent.
|
|
7675
7884
|
*/
|
|
7676
|
-
|
|
7885
|
+
/**
|
|
7886
|
+
* This person's per-agent start counts, from their profile settings.
|
|
7887
|
+
* Best-effort by design: a profile that cannot be read sorts the picker
|
|
7888
|
+
* alphabetically instead of refusing to open.
|
|
7889
|
+
*/
|
|
7890
|
+
async _loadAgentPickerUsage() {
|
|
7891
|
+
try {
|
|
7892
|
+
if (typeof this.transport.getCurrentUserProfile !== "function") return null;
|
|
7893
|
+
const profile = await this.transport.getCurrentUserProfile();
|
|
7894
|
+
const raw = profile?.profileSettings?.[AGENT_USAGE_SETTING];
|
|
7895
|
+
if (!raw || typeof raw !== "object") return {};
|
|
7896
|
+
const usage = {};
|
|
7897
|
+
for (const [name, count] of Object.entries(raw)) {
|
|
7898
|
+
const n = Number(count);
|
|
7899
|
+
if (Number.isFinite(n) && n > 0) usage[name] = n;
|
|
7900
|
+
}
|
|
7901
|
+
return usage;
|
|
7902
|
+
} catch {
|
|
7903
|
+
return null;
|
|
7904
|
+
}
|
|
7905
|
+
}
|
|
7906
|
+
|
|
7907
|
+
/**
|
|
7908
|
+
* Count one start. Read-modify-write against the whole settings document,
|
|
7909
|
+
* because that is the shape the API takes — so a concurrent write from
|
|
7910
|
+
* another tab can lose a count. A miscount only reorders a picker, which
|
|
7911
|
+
* is not worth a locking protocol.
|
|
7912
|
+
*/
|
|
7913
|
+
async _recordAgentPickerUse(agentName) {
|
|
7914
|
+
const name = String(agentName || "").trim();
|
|
7915
|
+
if (!name) return;
|
|
7916
|
+
try {
|
|
7917
|
+
if (typeof this.transport.getCurrentUserProfile !== "function"
|
|
7918
|
+
|| typeof this.transport.setCurrentUserProfileSettings !== "function") return;
|
|
7919
|
+
const profile = await this.transport.getCurrentUserProfile();
|
|
7920
|
+
const settings = { ...(profile?.profileSettings || {}) };
|
|
7921
|
+
const usage = { ...(settings[AGENT_USAGE_SETTING] || {}) };
|
|
7922
|
+
usage[name] = (Number(usage[name]) || 0) + 1;
|
|
7923
|
+
settings[AGENT_USAGE_SETTING] = usage;
|
|
7924
|
+
// Into state before the network: the portal's own profile save
|
|
7925
|
+
// fires on the very activeSessionId change this create causes,
|
|
7926
|
+
// and it writes the whole document from state. If state does not
|
|
7927
|
+
// have the new count by then, that save erases it.
|
|
7928
|
+
this.dispatch({ type: "ui/agentPickerUsage", usage });
|
|
7929
|
+
await this.transport.setCurrentUserProfileSettings({ settings });
|
|
7930
|
+
} catch {
|
|
7931
|
+
// Never let bookkeeping fail a session create.
|
|
7932
|
+
}
|
|
7933
|
+
}
|
|
7934
|
+
|
|
7935
|
+
/** Re-run the list against a new query or sort, keeping the selection. */
|
|
7936
|
+
_refreshAgentPicker(patch) {
|
|
7677
7937
|
const modal = this.getState().ui.modal;
|
|
7678
|
-
if (!modal || modal.type !== "sessionAgentPicker"
|
|
7679
|
-
const
|
|
7680
|
-
const
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7938
|
+
if (!modal || modal.type !== "sessionAgentPicker") return;
|
|
7939
|
+
const next = { ...modal, ...patch };
|
|
7940
|
+
const items = buildAgentPickerItems(next.catalog || [], {
|
|
7941
|
+
query: next.query,
|
|
7942
|
+
sort: next.sort,
|
|
7943
|
+
usage: next.usage,
|
|
7944
|
+
genericItem: next.genericItem || null,
|
|
7945
|
+
});
|
|
7946
|
+
// Keep the same agent selected when it survives the new list; land on
|
|
7947
|
+
// the first row when it does not, so Enter always does something.
|
|
7687
7948
|
const selectedId = modal.items?.[modal.selectedIndex || 0]?.id;
|
|
7688
|
-
|
|
7689
|
-
if (nextIndex < 0) {
|
|
7690
|
-
// The selected row was inside the section that just closed — land
|
|
7691
|
-
// on that section's own header, which is where it went.
|
|
7692
|
-
nextIndex = Math.max(0, items.findIndex((item) => item.sectionKey === sectionKey));
|
|
7693
|
-
}
|
|
7949
|
+
const keptIndex = items.findIndex((item) => item.id === selectedId);
|
|
7694
7950
|
this.dispatch({
|
|
7695
7951
|
type: "ui/modal",
|
|
7696
|
-
modal: { ...
|
|
7952
|
+
modal: { ...next, items, selectedIndex: keptIndex >= 0 ? keptIndex : 0 },
|
|
7697
7953
|
});
|
|
7698
7954
|
}
|
|
7699
7955
|
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
* the cursor is already on a leaf — the same shape as the session tree.
|
|
7704
|
-
*/
|
|
7705
|
-
moveAgentPickerSection(delta) {
|
|
7706
|
-
const modal = this.getState().ui.modal;
|
|
7707
|
-
if (!modal || modal.type !== "sessionAgentPicker") return false;
|
|
7708
|
-
const items = Array.isArray(modal.items) ? modal.items : [];
|
|
7709
|
-
const index = Math.max(0, Math.min(Number(modal.selectedIndex) || 0, items.length - 1));
|
|
7710
|
-
const item = items[index];
|
|
7711
|
-
if (!item) return false;
|
|
7956
|
+
setAgentPickerQuery(query) {
|
|
7957
|
+
this._refreshAgentPicker({ query: String(query ?? "") });
|
|
7958
|
+
}
|
|
7712
7959
|
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
return false;
|
|
7719
|
-
}
|
|
7960
|
+
setAgentPickerSort(sort) {
|
|
7961
|
+
if (!AGENT_PICKER_SORTS.includes(sort)) return;
|
|
7962
|
+
this._agentPickerSort = sort; // remembered for the next open
|
|
7963
|
+
this._refreshAgentPicker({ sort });
|
|
7964
|
+
}
|
|
7720
7965
|
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
}
|
|
7966
|
+
/**
|
|
7967
|
+
* Left/right on the agent picker: nothing.
|
|
7968
|
+
*
|
|
7969
|
+
* These keys used to open and close package headings. The picker is a flat
|
|
7970
|
+
* list of agents now, so there is nothing to expand — and returning false
|
|
7971
|
+
* matters, because that is what lets the keystroke reach the search box and
|
|
7972
|
+
* move the caret instead of being swallowed here.
|
|
7973
|
+
*/
|
|
7974
|
+
moveAgentPickerSection() {
|
|
7731
7975
|
return false;
|
|
7732
7976
|
}
|
|
7733
7977
|
|
|
@@ -7950,11 +8194,6 @@ export class PilotSwarmUiController {
|
|
|
7950
8194
|
}
|
|
7951
8195
|
if (modal.type === "sessionAgentPicker") {
|
|
7952
8196
|
const item = modal.items?.[modal.selectedIndex || 0];
|
|
7953
|
-
// Enter on a heading opens or closes it; the dialog stays up.
|
|
7954
|
-
if (item?.kind === "section") {
|
|
7955
|
-
this.toggleAgentPickerSection(item.sectionKey);
|
|
7956
|
-
return;
|
|
7957
|
-
}
|
|
7958
8197
|
// A called-only agent is rendered so the package's composition is
|
|
7959
8198
|
// visible, but starting one cold is not a thing you can do. Refuse
|
|
7960
8199
|
// here rather than letting the create fail after the dialog closes.
|
package/ui/core/src/history.js
CHANGED
|
@@ -129,6 +129,18 @@ export function stripLeadingRehydrationNoticeText(text) {
|
|
|
129
129
|
return normalized;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// The runtime neutralizes a FORGED `[SYSTEM:` marker by inserting a zero-width
|
|
133
|
+
// character after the bracket, so a collaborator cannot inject system guidance
|
|
134
|
+
// (orchestration/utils.ts). That neutralizer runs when the queued message is
|
|
135
|
+
// consumed — AFTER the runtime has appended its own timer/cron notice to the
|
|
136
|
+
// same text — so a GENUINE notice on a message from anyone but the session
|
|
137
|
+
// owner arrives neutralized too. Match both spellings, or those notices print
|
|
138
|
+
// as raw prose in the transcript.
|
|
139
|
+
const NOTICE_MARKER_INVISIBLES = "[\\u200b-\\u200f\\u2060-\\u2064\\ufeff]*";
|
|
140
|
+
const SYSTEM_NOTICE_LINE_RE = new RegExp(`^\\s*\\[${NOTICE_MARKER_INVISIBLES}SYSTEM:`, "i");
|
|
141
|
+
const SYSTEM_NOTICE_SINGLE_LINE_RE = new RegExp(`^\\s*\\[${NOTICE_MARKER_INVISIBLES}SYSTEM:\\s*(.*?)\\]\\s*$`, "i");
|
|
142
|
+
const SYSTEM_NOTICE_OPENING_RE = new RegExp(`^\\s*\\[${NOTICE_MARKER_INVISIBLES}SYSTEM:\\s*`, "i");
|
|
143
|
+
|
|
132
144
|
function splitSystemNoticeSegments(text) {
|
|
133
145
|
const lines = String(text || "").replace(/\r\n/g, "\n").split("\n");
|
|
134
146
|
const segments = [];
|
|
@@ -145,13 +157,13 @@ function splitSystemNoticeSegments(text) {
|
|
|
145
157
|
|
|
146
158
|
for (let index = 0; index < lines.length;) {
|
|
147
159
|
const line = lines[index];
|
|
148
|
-
if (
|
|
160
|
+
if (!SYSTEM_NOTICE_LINE_RE.test(line)) {
|
|
149
161
|
textLines.push(line);
|
|
150
162
|
index += 1;
|
|
151
163
|
continue;
|
|
152
164
|
}
|
|
153
165
|
|
|
154
|
-
const singleLineMatch =
|
|
166
|
+
const singleLineMatch = SYSTEM_NOTICE_SINGLE_LINE_RE.exec(line);
|
|
155
167
|
if (singleLineMatch) {
|
|
156
168
|
flushText();
|
|
157
169
|
segments.push({
|
|
@@ -162,7 +174,7 @@ function splitSystemNoticeSegments(text) {
|
|
|
162
174
|
continue;
|
|
163
175
|
}
|
|
164
176
|
|
|
165
|
-
const noticeLines = [line.replace(
|
|
177
|
+
const noticeLines = [line.replace(SYSTEM_NOTICE_OPENING_RE, "")];
|
|
166
178
|
let closingIndex = -1;
|
|
167
179
|
for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
|
|
168
180
|
const closingLine = lines[cursor];
|
|
@@ -254,7 +266,7 @@ function isInternalSystemLikeText(text) {
|
|
|
254
266
|
const normalized = normalizeMessageText(text);
|
|
255
267
|
if (!normalized) return false;
|
|
256
268
|
|
|
257
|
-
return
|
|
269
|
+
return SYSTEM_NOTICE_LINE_RE.test(normalized)
|
|
258
270
|
|| /^\[CHILD_UPDATE\b/i.test(normalized)
|
|
259
271
|
|| /^\[SESSION_MESSAGE(?:_RESPONSE)?\b/i.test(normalized)
|
|
260
272
|
|| /^Buffered child updates arrived /i.test(normalized)
|
|
@@ -469,13 +481,19 @@ function buildChatMessage(event, role) {
|
|
|
469
481
|
const sessionMessageCard = buildSessionMessageChatCard(event, rawText);
|
|
470
482
|
if (sessionMessageCard) return sessionMessageCard;
|
|
471
483
|
|
|
484
|
+
// The attribution prefix and any embedded system notice come off BEFORE
|
|
485
|
+
// the canvas check. A collaborator pressing a button in the drawn page
|
|
486
|
+
// arrives as `[FROM: …]\n[canvas-action] {…}\n\n[SYSTEM: …]`: testing the
|
|
487
|
+
// raw text misses it on both ends, and the message then prints as an
|
|
488
|
+
// ordinary chat line with its raw JSON payload showing.
|
|
489
|
+
const visibleText = stripLeadingSenderMarker(extractVisibleChatText(rawText, role));
|
|
490
|
+
|
|
472
491
|
// A structured canvas response — the browser sent it on the viewer's
|
|
473
492
|
// behalf after validating it against the drawn contract. One item shape
|
|
474
493
|
// from ONE builder, so bulk load and live append cannot disagree. The
|
|
475
|
-
// portal
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
const parsed = parseCanvasActionContent(rawText);
|
|
494
|
+
// portal collapses it to one row; the TUI shows a compact line.
|
|
495
|
+
if (role === "user" && isCanvasActionContent(visibleText)) {
|
|
496
|
+
const parsed = parseCanvasActionContent(visibleText);
|
|
479
497
|
if (parsed) {
|
|
480
498
|
return {
|
|
481
499
|
id: `${event.sessionId}:${event.seq}`,
|
|
@@ -483,7 +501,7 @@ function buildChatMessage(event, role) {
|
|
|
483
501
|
role: "user",
|
|
484
502
|
action: parsed.action,
|
|
485
503
|
data: parsed.data,
|
|
486
|
-
text:
|
|
504
|
+
text: visibleText,
|
|
487
505
|
time: formatTimestamp(event.createdAt),
|
|
488
506
|
createdAt: event.createdAt instanceof Date ? event.createdAt.getTime() : new Date(event.createdAt).getTime(),
|
|
489
507
|
// Without the ids, the distinct-ids rule can never fire and two
|
|
@@ -496,7 +514,7 @@ function buildChatMessage(event, role) {
|
|
|
496
514
|
}
|
|
497
515
|
}
|
|
498
516
|
|
|
499
|
-
const text =
|
|
517
|
+
const text = visibleText;
|
|
500
518
|
if (!hasVisibleMessageText(text)) return null;
|
|
501
519
|
const clientMessageIds = Array.isArray(event?.data?.clientMessageIds)
|
|
502
520
|
? event.data.clientMessageIds.filter((id) => typeof id === "string" && id)
|
|
@@ -524,6 +542,27 @@ function buildChatMessage(event, role) {
|
|
|
524
542
|
};
|
|
525
543
|
}
|
|
526
544
|
|
|
545
|
+
// The per-turn system prompt, as the SDK summarises it for CMS: a marker line
|
|
546
|
+
// naming the size with a 120-char snippet, never the whole 170k-char prompt.
|
|
547
|
+
// Matched narrowly on purpose — it is the ONE system.message worth a row in
|
|
548
|
+
// the conversation, and widening this would move rehydration notices, child
|
|
549
|
+
// updates and sub-agent chatter in with it.
|
|
550
|
+
const SYSTEM_PROMPT_NOTICE_RE = /rebuilt the full system prompt for model input/i;
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Is this the system prompt itself, rather than internal chatter?
|
|
554
|
+
*
|
|
555
|
+
* It stays in the ACTIVITY feed as before — that is the raw diagnostic
|
|
556
|
+
* stream — and additionally earns one collapsed row in the transcript, the
|
|
557
|
+
* same affordance as a sub-agent response or a canvas action. Visible enough
|
|
558
|
+
* to open when you want to know what the model was told; folded away the rest
|
|
559
|
+
* of the time.
|
|
560
|
+
*/
|
|
561
|
+
export function isSystemPromptNotice(event) {
|
|
562
|
+
if (event?.eventType !== "system.message") return false;
|
|
563
|
+
return SYSTEM_PROMPT_NOTICE_RE.test(messageTextFromEvent(event) || "");
|
|
564
|
+
}
|
|
565
|
+
|
|
527
566
|
function shouldRenderSystemMessageAsActivity(event) {
|
|
528
567
|
// Most system.message events stay out of the chat pane. They include the
|
|
529
568
|
// per-turn system prompt sent to the LLM plus internal rehydration notices,
|
|
@@ -1102,6 +1141,13 @@ export function buildHistoryModel(events = [], options = {}) {
|
|
|
1102
1141
|
if (shouldRenderSystemMessageAsActivity(event)) {
|
|
1103
1142
|
const activityItem = formatActivity(event);
|
|
1104
1143
|
if (activityItem) activity.push(activityItem);
|
|
1144
|
+
// The system prompt also earns a collapsed row in the
|
|
1145
|
+
// transcript. It stays in the activity feed above — that is
|
|
1146
|
+
// the raw stream — and this is the readable handle on it.
|
|
1147
|
+
if (isSystemPromptNotice(event)) {
|
|
1148
|
+
const message = buildChatMessage(event, "system");
|
|
1149
|
+
if (message) chat.push(message);
|
|
1150
|
+
}
|
|
1105
1151
|
} else {
|
|
1106
1152
|
const message = buildChatMessage(event, "system");
|
|
1107
1153
|
if (message) chat.push(message);
|
|
@@ -1229,6 +1275,15 @@ export function appendEventToHistory(history, event) {
|
|
|
1229
1275
|
next.activity.push(activityItem);
|
|
1230
1276
|
next.activity = clampHistoryItems(next.activity, loadedEventLimit);
|
|
1231
1277
|
}
|
|
1278
|
+
// Same as the bulk path: the system prompt is also worth one
|
|
1279
|
+
// collapsed row in the conversation.
|
|
1280
|
+
if (isSystemPromptNotice(event)) {
|
|
1281
|
+
const message = buildChatMessage(event, "system");
|
|
1282
|
+
if (message) {
|
|
1283
|
+
next.chat.push(message);
|
|
1284
|
+
next.chat = clampHistoryItems(dedupeChatMessages(next.chat), loadedEventLimit);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1232
1287
|
} else {
|
|
1233
1288
|
const message = buildChatMessage(event, "system");
|
|
1234
1289
|
if (!message) return next;
|