pilotswarm 0.5.46 → 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.
Files changed (82) hide show
  1. package/mcp/README.md +1 -0
  2. package/mcp/dist/src/tools/providers.d.ts.map +1 -1
  3. package/mcp/dist/src/tools/providers.js +15 -3
  4. package/mcp/dist/src/tools/providers.js.map +1 -1
  5. package/package.json +2 -2
  6. package/tui/src/app.js +7 -11
  7. package/tui/src/node-sdk-transport.js +16 -0
  8. package/ui/core/src/controller.js +265 -68
  9. package/ui/core/src/history.js +37 -0
  10. package/ui/core/src/reducer.js +51 -4
  11. package/ui/core/src/selectors.js +63 -15
  12. package/ui/core/src/state.js +11 -0
  13. package/ui/react/src/components.js +1 -0
  14. package/ui/react/src/web-app.js +611 -103
  15. package/web/dist/assets/{abnfDiagram-VRR7QNED-GXM5pEfT.js → abnfDiagram-VRR7QNED-VPL8m_9W.js} +1 -1
  16. package/web/dist/assets/{arc-D52-xt6J.js → arc-T5I3-c1B.js} +1 -1
  17. package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-Bxh49GyJ.js → architectureDiagram-ZJ3FMSHR-DEP70bg6.js} +1 -1
  18. package/web/dist/assets/{blockDiagram-677ZJIJ3-D8LSE94K.js → blockDiagram-677ZJIJ3-CvBu1MbO.js} +1 -1
  19. package/web/dist/assets/{c4Diagram-LMCZKHZV-Dcs1NVIx.js → c4Diagram-LMCZKHZV-B5qwF4gm.js} +1 -1
  20. package/web/dist/assets/channel-D5LojeMw.js +1 -0
  21. package/web/dist/assets/{chunk-2Q5K7J3B-Dy3zu7gI.js → chunk-2Q5K7J3B-zVW8dS7X.js} +1 -1
  22. package/web/dist/assets/{chunk-32BRIVSS-5v78VX3T.js → chunk-32BRIVSS-DWctxZZS.js} +1 -1
  23. package/web/dist/assets/{chunk-5VM5RSS4-DUJMCfdj.js → chunk-5VM5RSS4-Bq7_Vmf9.js} +1 -1
  24. package/web/dist/assets/{chunk-EX3LRPZG-DYyT1So9.js → chunk-EX3LRPZG-B8CFB3v-.js} +1 -1
  25. package/web/dist/assets/{chunk-JWPE2WC7-Buzzx5R2.js → chunk-JWPE2WC7-B7gLPjgZ.js} +1 -1
  26. package/web/dist/assets/{chunk-MOJQB5TN-B8fOWRD7.js → chunk-MOJQB5TN-B2x6q12r.js} +1 -1
  27. package/web/dist/assets/{chunk-RYQCIY6F-Bp04YO26.js → chunk-RYQCIY6F-XorU9Stx.js} +1 -1
  28. package/web/dist/assets/{chunk-V7JOEXUC-Gw6h8gPE.js → chunk-V7JOEXUC-CsLa6pw5.js} +1 -1
  29. package/web/dist/assets/{chunk-VR4S4FIN-ASf4dPE-.js → chunk-VR4S4FIN-BBv9mwmj.js} +1 -1
  30. package/web/dist/assets/{chunk-XXDRQBXY-C4B-MNNk.js → chunk-XXDRQBXY-Cjm4mm6l.js} +1 -1
  31. package/web/dist/assets/classDiagram-OUVF2IWQ-C5edc5Or.js +1 -0
  32. package/web/dist/assets/classDiagram-v2-EOCWNBFH-C5edc5Or.js +1 -0
  33. package/web/dist/assets/{cose-bilkent-JH36ORCC-C0S415ey.js → cose-bilkent-JH36ORCC-Zw9ZQbVl.js} +1 -1
  34. package/web/dist/assets/{cynefin-VYW2F7L2-LWUlL1XB.js → cynefin-VYW2F7L2-BYnR9YLM.js} +1 -1
  35. package/web/dist/assets/{cynefinDiagram-TSTJHNR4-ClnNDuqy.js → cynefinDiagram-TSTJHNR4-Cx8L0dOv.js} +1 -1
  36. package/web/dist/assets/{dagre-VKFMJZFB-CRMTDGPv.js → dagre-VKFMJZFB-7GaqVcqz.js} +1 -1
  37. package/web/dist/assets/{diagram-FQU43EPY-Cjt1nFI8.js → diagram-FQU43EPY-BWSRPJvU.js} +1 -1
  38. package/web/dist/assets/{diagram-G47NLZAW-DynrWO-8.js → diagram-G47NLZAW-BELuAE1e.js} +1 -1
  39. package/web/dist/assets/{diagram-NH7WQ7WH-CkEgxg4T.js → diagram-NH7WQ7WH-DGXhPpri.js} +1 -1
  40. package/web/dist/assets/{diagram-OA4YK3LP-DzB43wzH.js → diagram-OA4YK3LP-DV8HqN0T.js} +1 -1
  41. package/web/dist/assets/{diagram-WEI45ONY-DqxH3-sW.js → diagram-WEI45ONY-Clv1X3ZL.js} +1 -1
  42. package/web/dist/assets/{ebnfDiagram-CCIWWBDH-5NxDrqIa.js → ebnfDiagram-CCIWWBDH-TpQx36oi.js} +1 -1
  43. package/web/dist/assets/{erDiagram-Q63AITRT-OfiKzPj4.js → erDiagram-Q63AITRT-DVVWd00c.js} +1 -1
  44. package/web/dist/assets/{flowDiagram-23GEKE2U-DsqfPM3s.js → flowDiagram-23GEKE2U-Cdz_8_Dw.js} +1 -1
  45. package/web/dist/assets/{ganttDiagram-NO4QXBWP-N48fLign.js → ganttDiagram-NO4QXBWP-zlco-o7u.js} +1 -1
  46. package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-rn1cXRYh.js → gitGraphDiagram-IHSO6WYX-CfAZW1En.js} +1 -1
  47. package/web/dist/assets/index-CMtzFJre.css +1 -0
  48. package/web/dist/assets/{index-BHp6FirC.js → index-tQXCQojP.js} +5 -5
  49. package/web/dist/assets/{infoDiagram-FWYZ7A6U-CjJ3TLmy.js → infoDiagram-FWYZ7A6U-0R-8efwI.js} +1 -1
  50. package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-DvCylaIQ.js → ishikawaDiagram-FXEZZL3T-lPpoXGlp.js} +1 -1
  51. package/web/dist/assets/{journeyDiagram-5HDEW3XC-B_Ethynx.js → journeyDiagram-5HDEW3XC-D05vSVqu.js} +1 -1
  52. package/web/dist/assets/{kanban-definition-HUTT4EX6-BlQuhDqn.js → kanban-definition-HUTT4EX6-ClaDdESv.js} +1 -1
  53. package/web/dist/assets/{linear-DIwvxXrN.js → linear-CUnhHMvY.js} +1 -1
  54. package/web/dist/assets/{mermaid.core-CfAsl9Pc.js → mermaid.core-DaYS-X8Y.js} +4 -4
  55. package/web/dist/assets/{mindmap-definition-LN4V7U3C-BxJep4HE.js → mindmap-definition-LN4V7U3C-N480VHds.js} +1 -1
  56. package/web/dist/assets/{papaparse.min-BjO79nbw.js → papaparse.min-CNTcNgd8.js} +1 -1
  57. package/web/dist/assets/{pegDiagram-2B236MQR-yceXoFi0.js → pegDiagram-2B236MQR-CVWyJ5ck.js} +1 -1
  58. package/web/dist/assets/{pieDiagram-ENE6RG2P-BNBzpT5B.js → pieDiagram-ENE6RG2P-UR9uohPf.js} +1 -1
  59. package/web/dist/assets/pilotswarm-BG6hk0oE.js +97 -0
  60. package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-DCg1DTax.js → quadrantDiagram-ABIIQ3AL-D2SIOetn.js} +1 -1
  61. package/web/dist/assets/{railroadDiagram-RFXS5EU6-DQdy8xST.js → railroadDiagram-RFXS5EU6-OgN6qgOU.js} +1 -1
  62. package/web/dist/assets/{requirementDiagram-TGXJPOKE-N8PYSqS8.js → requirementDiagram-TGXJPOKE-BSUURBBz.js} +1 -1
  63. package/web/dist/assets/{sankeyDiagram-HTMAVEWB-DK9WhfTA.js → sankeyDiagram-HTMAVEWB-BBZhmSBZ.js} +1 -1
  64. package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-DEpALkWi.js → sequenceDiagram-DBY2YBRQ-C10IDNdW.js} +1 -1
  65. package/web/dist/assets/{sizeCapture-X5ZJPWSS-CKOEPe9x.js → sizeCapture-X5ZJPWSS-B9787MHa.js} +1 -1
  66. package/web/dist/assets/{stateDiagram-2N3HPSRC-Dwwyy0hH.js → stateDiagram-2N3HPSRC-BmSBo7es.js} +1 -1
  67. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-BqpsfoJI.js +1 -0
  68. package/web/dist/assets/{swimlanes-5IMT3BWC-hDoJpKdQ.js → swimlanes-5IMT3BWC-DOXpv_KQ.js} +2 -2
  69. package/web/dist/assets/swimlanesDiagram-G3AALYLV-k8WG_bWt.js +8 -0
  70. package/web/dist/assets/{timeline-definition-FHXFAJF6-CPXnQ7LH.js → timeline-definition-FHXFAJF6-SjhGfPdI.js} +1 -1
  71. package/web/dist/assets/{vennDiagram-L72KCM5P-CYsOwGTT.js → vennDiagram-L72KCM5P-BiujYen6.js} +1 -1
  72. package/web/dist/assets/{wardleyDiagram-EHGQE667-DKh__oEb.js → wardleyDiagram-EHGQE667-6nLXBvwc.js} +1 -1
  73. package/web/dist/assets/{xychartDiagram-FW5EYKEG-Bws7l8k9.js → xychartDiagram-FW5EYKEG-Di7ioTvW.js} +1 -1
  74. package/web/dist/index.html +3 -3
  75. package/web/runtime.js +12 -0
  76. package/web/dist/assets/channel-DE0-pG_Z.js +0 -1
  77. package/web/dist/assets/classDiagram-OUVF2IWQ-B0o6by6m.js +0 -1
  78. package/web/dist/assets/classDiagram-v2-EOCWNBFH-B0o6by6m.js +0 -1
  79. package/web/dist/assets/index-DEymVbi5.css +0 -1
  80. package/web/dist/assets/pilotswarm-DewDyJhu.js +0 -97
  81. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-CRKYt8KU.js +0 -1
  82. package/web/dist/assets/swimlanesDiagram-G3AALYLV-DtnCdOQI.js +0 -8
@@ -542,6 +542,27 @@ function buildChatMessage(event, role) {
542
542
  };
543
543
  }
544
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
+
545
566
  function shouldRenderSystemMessageAsActivity(event) {
546
567
  // Most system.message events stay out of the chat pane. They include the
547
568
  // per-turn system prompt sent to the LLM plus internal rehydration notices,
@@ -1120,6 +1141,13 @@ export function buildHistoryModel(events = [], options = {}) {
1120
1141
  if (shouldRenderSystemMessageAsActivity(event)) {
1121
1142
  const activityItem = formatActivity(event);
1122
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
+ }
1123
1151
  } else {
1124
1152
  const message = buildChatMessage(event, "system");
1125
1153
  if (message) chat.push(message);
@@ -1247,6 +1275,15 @@ export function appendEventToHistory(history, event) {
1247
1275
  next.activity.push(activityItem);
1248
1276
  next.activity = clampHistoryItems(next.activity, loadedEventLimit);
1249
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
+ }
1250
1287
  } else {
1251
1288
  const message = buildChatMessage(event, "system");
1252
1289
  if (!message) return next;
@@ -832,6 +832,17 @@ export function appReducer(state, action) {
832
832
  if (Boolean(action.enabled) === Boolean(state.ui.touchScale)) return state;
833
833
  return { ...state, ui: { ...state.ui, touchScale: Boolean(action.enabled) } };
834
834
 
835
+ case "ui/agentPickerUsage": {
836
+ const usage = action.usage && typeof action.usage === "object" && !Array.isArray(action.usage)
837
+ ? action.usage
838
+ : {};
839
+ return { ...state, ui: { ...state.ui, agentPickerUsage: usage } };
840
+ }
841
+
842
+ case "ui/sessionDetailCollapsed":
843
+ if (Boolean(action.collapsed) === Boolean(state.ui.sessionDetailCollapsed)) return state;
844
+ return { ...state, ui: { ...state.ui, sessionDetailCollapsed: Boolean(action.collapsed) } };
845
+
835
846
  case "profileSettings/apply": {
836
847
  const settings = action.settings && typeof action.settings === "object" && !Array.isArray(action.settings)
837
848
  ? action.settings
@@ -845,6 +856,11 @@ export function appReducer(state, action) {
845
856
  // say "rich" fall through to the transcript below.
846
857
  const hasTouchScale = Object.prototype.hasOwnProperty.call(settings, "touchScale")
847
858
  && typeof settings.touchScale === "boolean";
859
+ const hasSessionDetailCollapsed = Object.prototype.hasOwnProperty.call(settings, "sessionDetailCollapsed")
860
+ && typeof settings.sessionDetailCollapsed === "boolean";
861
+ const hasAgentPickerUsage = Object.prototype.hasOwnProperty.call(settings, "agentPickerUsage")
862
+ && settings.agentPickerUsage && typeof settings.agentPickerUsage === "object"
863
+ && !Array.isArray(settings.agentPickerUsage);
848
864
  const hasRightPaneMode = Object.prototype.hasOwnProperty.call(settings, "rightPaneMode")
849
865
  && (settings.rightPaneMode === "canvas" || settings.rightPaneMode === "panes");
850
866
  // The two independent columns. A profile written by a build that
@@ -921,6 +937,12 @@ export function appReducer(state, action) {
921
937
  ...selection.ui,
922
938
  themeId: hasTheme ? settings.themeId.trim() : selection.ui.themeId,
923
939
  touchScale: hasTouchScale ? settings.touchScale : selection.ui.touchScale,
940
+ sessionDetailCollapsed: hasSessionDetailCollapsed
941
+ ? settings.sessionDetailCollapsed
942
+ : selection.ui.sessionDetailCollapsed,
943
+ agentPickerUsage: hasAgentPickerUsage
944
+ ? settings.agentPickerUsage
945
+ : selection.ui.agentPickerUsage,
924
946
  rightPaneMode: hasRightPaneMode ? settings.rightPaneMode : selection.ui.rightPaneMode,
925
947
  ...(nextDesktopPanes
926
948
  ? { canvasOpen: nextDesktopPanes.canvasOpen, diagnosticsOpen: nextDesktopPanes.diagnosticsOpen, canvasZen: nextDesktopPanes.zen === true }
@@ -2458,6 +2480,29 @@ export function appReducer(state, action) {
2458
2480
  },
2459
2481
  };
2460
2482
  }
2483
+ /**
2484
+ * Put the banner down.
2485
+ *
2486
+ * A failed mutation writes mutation.error, which the Model Providers
2487
+ * pane renders as a red band. Nothing cleared it except the NEXT
2488
+ * mutation, so cancelling a failed sheet left the band sitting there
2489
+ * over an unchanged list, and it survived until something else
2490
+ * succeeded. The sheet that raised it owns it; when that sheet goes,
2491
+ * so does this.
2492
+ */
2493
+ case "admin/modelProviders/mutationDismissed": {
2494
+ if (!state.admin?.modelProviders?.mutation?.error) return state;
2495
+ return {
2496
+ ...state,
2497
+ admin: {
2498
+ ...state.admin,
2499
+ modelProviders: {
2500
+ ...state.admin.modelProviders,
2501
+ mutation: { ...state.admin.modelProviders.mutation, error: null },
2502
+ },
2503
+ },
2504
+ };
2505
+ }
2461
2506
  case "admin/modelProviders/select": {
2462
2507
  return {
2463
2508
  ...state,
@@ -2495,6 +2540,7 @@ export function appReducer(state, action) {
2495
2540
  }
2496
2541
  case "admin/modelProviders/createBegin": {
2497
2542
  const draft = String(action.name || "");
2543
+ const update = action.mode === "update";
2498
2544
  return {
2499
2545
  ...state,
2500
2546
  admin: {
@@ -2503,12 +2549,13 @@ export function appReducer(state, action) {
2503
2549
  ...state.admin.modelProviders,
2504
2550
  create: {
2505
2551
  editing: true,
2506
- stage: "name",
2507
- name: "",
2552
+ mode: update ? "update" : "create",
2553
+ stage: update || action.stage === "credential" ? "credential" : "name",
2554
+ name: update ? draft : "",
2508
2555
  typeId: String(action.typeId || ""),
2509
2556
  shared: action.shared === true,
2510
- draft,
2511
- cursorIndex: draft.length,
2557
+ draft: update ? "" : draft,
2558
+ cursorIndex: update ? 0 : draft.length,
2512
2559
  saving: false,
2513
2560
  error: null,
2514
2561
  },
@@ -1812,6 +1812,21 @@ function prefixRuns(text, color = "gray", options = {}) {
1812
1812
  // (block) chat renderer can consume the same labeling rules without the
1813
1813
  // terminal run/prefix shape. Returns { time, glyph, glyphColor, roleLabel,
1814
1814
  // roleColor }.
1815
+ /**
1816
+ * Is this the agent's own opening instruction rather than something a person
1817
+ * sent? The kickoff goes onto the queue as a user-role prompt — that is how a
1818
+ * turn starts — but it is stamped with a SYSTEM sender at the origin
1819
+ * (client.ts, createAgentSession). Anything a person actually sends carries a
1820
+ * `kind: "user"` sender stamped from the validated auth context.
1821
+ *
1822
+ * Sessions created before the stamp existed have no sender on that message, so
1823
+ * their kickoff still reads as "You". Nothing can distinguish it after the
1824
+ * fact; only new sessions are correct.
1825
+ */
1826
+ export function isAgentKickoffMessage(message) {
1827
+ return message?.role === "user" && message?.sender?.kind === "system";
1828
+ }
1829
+
1815
1830
  function describeChatMessageHeader(message, options = {}) {
1816
1831
  const time = formatTimestamp(message?.createdAt || message?.time);
1817
1832
  // A user.message is labeled from the CURRENT VIEWER's perspective: "You"
@@ -1837,7 +1852,12 @@ function describeChatMessageHeader(message, options = {}) {
1837
1852
  );
1838
1853
  const ownerSuffix = senderIsOwner ? " (owner)" : "";
1839
1854
 
1840
- const roleLabel = isUser
1855
+ const roleLabel = isAgentKickoffMessage(message)
1856
+ // Belt and braces: the chat pane collapses a kickoff before it reaches
1857
+ // here, but the TUI and any other renderer share this labeling, and
1858
+ // "You" is the one label this message must never carry.
1859
+ ? (sender?.display || "System")
1860
+ : isUser
1841
1861
  ? ((fromOtherPerson ? (sender.display || sender.subject || "User") : "You") + ownerSuffix)
1842
1862
  : message?.role === "assistant"
1843
1863
  ? "Agent"
@@ -2440,6 +2460,22 @@ function buildChatMessageLines(message, maxWidth, options = {}) {
2440
2460
  }
2441
2461
  }
2442
2462
 
2463
+ // An agent's opening instruction is plumbing, not conversation. It rides
2464
+ // the queue as a user-role prompt (that is how a turn starts), so without
2465
+ // this it rendered as a full chat message attributed to "You" — the
2466
+ // reader's own transcript opening with a wall of instructions they never
2467
+ // wrote. Collapsed to one line, openable, same affordance as a sub-agent
2468
+ // response.
2469
+ if (isAgentKickoffMessage(message)) {
2470
+ return [buildSystemNoticeLine({
2471
+ title: message?.sender?.display || "Agent kickoff",
2472
+ summary: "opening instruction",
2473
+ timestamp: formatTimestamp(message?.createdAt || message?.time),
2474
+ body: message?.text || "",
2475
+ color: "gray",
2476
+ })];
2477
+ }
2478
+
2443
2479
  if (message?.role !== "user" && message?.role !== "assistant") {
2444
2480
  const strippedSystemText = stripLeadingRehydrationNoticeText(message?.text || "");
2445
2481
  if (message?.role === "system" && !strippedSystemText) {
@@ -4638,6 +4674,7 @@ export function selectAdminProviderCreateModal(state, maxWidth = 76) {
4638
4674
  const create = admin.modelProviders?.create || {};
4639
4675
  if (!admin.visible || !create.editing) return null;
4640
4676
  const credentialStage = create.stage === "credential";
4677
+ const updating = create.mode === "update";
4641
4678
  const shared = create.shared === true;
4642
4679
  const ownTitle = /github/i.test(create.typeId || "")
4643
4680
  ? "Add GitHub Copilot provider"
@@ -4645,7 +4682,9 @@ export function selectAdminProviderCreateModal(state, maxWidth = 76) {
4645
4682
  const value = String(create.draft || "");
4646
4683
  return {
4647
4684
  type: "adminProviderCreate",
4648
- title: credentialStage
4685
+ title: updating
4686
+ ? `Update key for ${create.name}`
4687
+ : credentialStage
4649
4688
  ? (shared ? "Add shared model provider" : ownTitle)
4650
4689
  : (shared ? "Name the shared provider" : ownTitle),
4651
4690
  label: credentialStage ? "credential" : "provider name",
@@ -4667,7 +4706,7 @@ export function selectAdminProviderCreateModal(state, maxWidth = 76) {
4667
4706
  { text: "Letters, numbers, dot, dash, and underscore only.", color: "gray" },
4668
4707
  ],
4669
4708
  helpLines: credentialStage
4670
- ? ["Type/paste credential", "Enter create provider", "Esc cancel and clear"]
4709
+ ? ["Type/paste credential", `Enter ${updating ? "update key" : "create provider"}`, "Esc cancel and clear"]
4671
4710
  : ["Type provider name", "Tab next provider type", "Enter continue to credential", "Esc cancel"],
4672
4711
  };
4673
4712
  }
@@ -5227,16 +5266,17 @@ export function selectStatusBar(state) {
5227
5266
  }
5228
5267
  if (state.ui.modal?.type === "themePicker") {
5229
5268
  return {
5230
- left: "Select a shared portal/TUI theme",
5269
+ left: "Select a theme",
5231
5270
  right: "up/down move · enter apply · esc close",
5232
5271
  };
5233
5272
  }
5234
5273
  if (state.ui.modal?.type === "sessionAgentPicker") {
5235
5274
  return {
5236
5275
  left: "Select an agent for the new session",
5237
- // Enter no longer always creates — on a section header it opens or
5238
- // closes — and the disclosure keys have to be named somewhere.
5239
- right: "up/down move · ←/→ open/close · enter start · esc cancel",
5276
+ // Every row is an agent now, so Enter always starts one. The
5277
+ // arrows are no longer named because they no longer do anything
5278
+ // here — they belong to the search box, where they move the caret.
5279
+ right: "type to search · up/down move · enter start · esc cancel",
5240
5280
  };
5241
5281
  }
5242
5282
  if (state.ui.modal?.type === "sessionGroupPicker") {
@@ -6503,15 +6543,23 @@ export function selectSessionAgentPickerModal(state, maxWidth = 76) {
6503
6543
  ], contentWidth);
6504
6544
  } else {
6505
6545
  const callable = item?.kind === "generic" || item?.supportsDirectStart !== false;
6506
- // A sub-agent that cannot be started is shown, not hidden — seeing
6507
- // what a package is made of is most of the value of grouping it.
6508
- // Dimming plus the └ connector carries it; the detail pane spells
6509
- // out why. An extra "called only" tag on the row was redundant with
6510
- // both, and read as a sentence fragment.
6511
- const glyph = item?.kind === "generic" ? "○ " : (item?.depth || 0) > 0 && item?.parentAgentName ? "└ " : "★ ";
6546
+ // ONE LINE PER AGENT. The package is trailing metadata rather than a
6547
+ // row of its own, so the list is as long as the number of agents —
6548
+ // the grouped shape spent a second row on every single-agent package
6549
+ // saying the same thing twice.
6550
+ const glyph = item?.kind === "generic" ? "○ " : "★ ";
6551
+ const name = item?.title || item?.agentName || item?.id || "Agent";
6552
+ const pkg = item?.kind === "generic" ? "" : (item?.packageTitle || item?.packageName || "");
6553
+ // Only shown while sorting BY use, where the number is the sort key
6554
+ // and therefore explains the order. Everywhere else it is noise.
6555
+ const uses = modal.sort === "used" && Number(item?.usageCount) > 0
6556
+ ? ` ${item.usageCount}×`
6557
+ : "";
6512
6558
  labelRuns = fitRuns([
6513
6559
  { text: `${indent}${glyph}`, color: callable ? "yellow" : "gray" },
6514
- { text: item?.title || item?.agentName || item?.id || "Agent", color: callable ? "white" : "gray", bold: callable },
6560
+ { text: name, color: callable ? "white" : "gray", bold: callable },
6561
+ ...(pkg ? [{ text: ` ${pkg}`, color: "gray" }] : []),
6562
+ ...(uses ? [{ text: uses, color: "cyan" }] : []),
6515
6563
  ], contentWidth);
6516
6564
  }
6517
6565
 
@@ -8570,7 +8618,7 @@ export function selectThemePickerModal(state, maxWidth = 80) {
8570
8618
  [{
8571
8619
  text: previewingTheme
8572
8620
  ? `Previewing now. Cancel reverts to ${originalTheme?.label || "the previous theme"}.`
8573
- : "Currently active in this TUI session.",
8621
+ : "Currently active.",
8574
8622
  color: previewingTheme ? "yellow" : "green",
8575
8623
  }],
8576
8624
  buildThemeSwatchRuns([
@@ -359,6 +359,17 @@ export function createInitialState({ mode = "local", branding = null, docs = nul
359
359
  // portrait is not the only case — a wall display or a shared screen wants
360
360
  // the same thing — so it is a preference, not a viewport query.
361
361
  touchScale: false,
362
+ // The session detail box starts folded to its one-line summary. It
363
+ // is ten rows of reference detail against a list you are trying to
364
+ // read, and most of it is looked up once per session, not per
365
+ // glance. Expanding is one click and the choice persists.
366
+ sessionDetailCollapsed: true,
367
+ // Per-person agent start counts, {agentName: n}. Drives the
368
+ // picker's default "Most used" sort. State-owned so every writer
369
+ // of the profile document sees the same value at the same moment;
370
+ // a preserved ref refreshed by a 5s poll lost the count to the
371
+ // very next save.
372
+ agentPickerUsage: {},
362
373
  modal: null,
363
374
  fullscreenPane: null,
364
375
  layout: {
@@ -2152,6 +2152,7 @@ function buildAdminModelProviderLines(view) {
2152
2152
  if (providers.page !== "shared") {
2153
2153
  lines.push([
2154
2154
  { text: " e ", color: "green", bold: true }, { text: "add ", color: "gray" },
2155
+ { text: "U ", color: "yellow", bold: true }, { text: "update key ", color: "gray" },
2155
2156
  { text: "d ", color: "red", bold: true }, { text: "delete ", color: "gray" },
2156
2157
  { text: "u ", color: "cyan", bold: true }, { text: "my default ", color: "gray" },
2157
2158
  ...(view.isAdmin ? [{ text: "t ", color: "yellow", bold: true }, { text: "system use ", color: "gray" }] : []),