pilotswarm 0.5.62 → 0.5.64

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 (93) hide show
  1. package/mcp/dist/src/server.d.ts.map +1 -1
  2. package/mcp/dist/src/server.js +2 -0
  3. package/mcp/dist/src/server.js.map +1 -1
  4. package/mcp/dist/src/tools/features.d.ts +4 -0
  5. package/mcp/dist/src/tools/features.d.ts.map +1 -0
  6. package/mcp/dist/src/tools/features.js +35 -0
  7. package/mcp/dist/src/tools/features.js.map +1 -0
  8. package/package.json +2 -2
  9. package/tui/src/node-sdk-transport.js +13 -0
  10. package/ui/core/src/controller.js +330 -14
  11. package/ui/core/src/history.js +89 -14
  12. package/ui/core/src/moa.js +9 -0
  13. package/ui/core/src/native-tasks.js +197 -0
  14. package/ui/core/src/question-display.js +13 -0
  15. package/ui/core/src/reducer.js +31 -7
  16. package/ui/core/src/selectors.js +46 -13
  17. package/ui/core/src/session-errors.js +13 -6
  18. package/ui/core/src/session-warning.js +14 -0
  19. package/ui/core/src/state.js +7 -2
  20. package/ui/react/src/components.js +2 -1
  21. package/ui/react/src/feature-flags-panel.js +186 -0
  22. package/ui/react/src/native-task-card.js +59 -0
  23. package/ui/react/src/web-app.js +173 -33
  24. package/web/dist/assets/{abnfDiagram-VRR7QNED-DAiG41W-.js → abnfDiagram-VRR7QNED-gIPHF2gV.js} +1 -1
  25. package/web/dist/assets/{arc-CN_GY3DG.js → arc-DMW_q5Ml.js} +1 -1
  26. package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-DD9qiBOj.js → architectureDiagram-ZJ3FMSHR-DAKsrxcB.js} +1 -1
  27. package/web/dist/assets/{blockDiagram-677ZJIJ3-CZg2R8i6.js → blockDiagram-677ZJIJ3-BUjSLpP3.js} +1 -1
  28. package/web/dist/assets/{c4Diagram-LMCZKHZV-DuZxUZYd.js → c4Diagram-LMCZKHZV-Dw2nPoes.js} +1 -1
  29. package/web/dist/assets/channel-CcOa1LtY.js +1 -0
  30. package/web/dist/assets/{chunk-2Q5K7J3B-BX7lDvmf.js → chunk-2Q5K7J3B-BAL28Rv0.js} +1 -1
  31. package/web/dist/assets/{chunk-32BRIVSS-COupwzAw.js → chunk-32BRIVSS-DDlt9K8s.js} +1 -1
  32. package/web/dist/assets/{chunk-5VM5RSS4-C_IyERib.js → chunk-5VM5RSS4-BLQGJGoP.js} +1 -1
  33. package/web/dist/assets/{chunk-EX3LRPZG-CyfIuYKn.js → chunk-EX3LRPZG-XFMb3HRy.js} +1 -1
  34. package/web/dist/assets/{chunk-JWPE2WC7-Cj5lvEfN.js → chunk-JWPE2WC7-DWnKTQ72.js} +1 -1
  35. package/web/dist/assets/{chunk-MOJQB5TN-l907LpMA.js → chunk-MOJQB5TN-MltzhnwP.js} +1 -1
  36. package/web/dist/assets/{chunk-RYQCIY6F-PLru1fOZ.js → chunk-RYQCIY6F-Ce3q0-mJ.js} +1 -1
  37. package/web/dist/assets/{chunk-V7JOEXUC-C6GCxiE5.js → chunk-V7JOEXUC-ThXLYY88.js} +1 -1
  38. package/web/dist/assets/{chunk-VR4S4FIN-BGelrIxQ.js → chunk-VR4S4FIN-DbhAykiW.js} +1 -1
  39. package/web/dist/assets/{chunk-XXDRQBXY-BmJstjEv.js → chunk-XXDRQBXY-BpkNB0mD.js} +1 -1
  40. package/web/dist/assets/classDiagram-OUVF2IWQ-B2VhHRSV.js +1 -0
  41. package/web/dist/assets/classDiagram-v2-EOCWNBFH-B2VhHRSV.js +1 -0
  42. package/web/dist/assets/{cose-bilkent-JH36ORCC-CRabxOOK.js → cose-bilkent-JH36ORCC-rctqAPvj.js} +1 -1
  43. package/web/dist/assets/{cynefin-VYW2F7L2-CminxBoG.js → cynefin-VYW2F7L2-Dg1cltg6.js} +1 -1
  44. package/web/dist/assets/{cynefinDiagram-TSTJHNR4-yBazsQf4.js → cynefinDiagram-TSTJHNR4-Ci23Sv4Z.js} +1 -1
  45. package/web/dist/assets/{dagre-VKFMJZFB-DcZuZV2n.js → dagre-VKFMJZFB-BpWTGhpv.js} +1 -1
  46. package/web/dist/assets/{diagram-FQU43EPY-CHoFzsw6.js → diagram-FQU43EPY-B69JpiAe.js} +1 -1
  47. package/web/dist/assets/{diagram-G47NLZAW-B8leqMRl.js → diagram-G47NLZAW-BHMk1TOO.js} +1 -1
  48. package/web/dist/assets/{diagram-NH7WQ7WH-DNoVxpa3.js → diagram-NH7WQ7WH-CtvZIIsk.js} +1 -1
  49. package/web/dist/assets/{diagram-OA4YK3LP-gC3Q2X6-.js → diagram-OA4YK3LP-BFvKh800.js} +1 -1
  50. package/web/dist/assets/{diagram-WEI45ONY-CABqyjDl.js → diagram-WEI45ONY-CgB8sWPe.js} +1 -1
  51. package/web/dist/assets/{ebnfDiagram-CCIWWBDH-cSguXaDC.js → ebnfDiagram-CCIWWBDH-DyqPNydM.js} +1 -1
  52. package/web/dist/assets/{erDiagram-Q63AITRT-CoWX37V3.js → erDiagram-Q63AITRT-CwpvZQ3Q.js} +1 -1
  53. package/web/dist/assets/{flowDiagram-23GEKE2U-I9DHYjhG.js → flowDiagram-23GEKE2U-EBKSKxfC.js} +1 -1
  54. package/web/dist/assets/{ganttDiagram-NO4QXBWP-DaFnfYTt.js → ganttDiagram-NO4QXBWP-q5tMPAuC.js} +1 -1
  55. package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-CwiXVM_O.js → gitGraphDiagram-IHSO6WYX-DCRbi4KE.js} +1 -1
  56. package/web/dist/assets/index-BfF7e08d.js +24 -0
  57. package/web/dist/assets/index-uhc851H3.css +1 -0
  58. package/web/dist/assets/{infoDiagram-FWYZ7A6U-DtzIrIpW.js → infoDiagram-FWYZ7A6U-Bex5pSIX.js} +1 -1
  59. package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-B9nk829n.js → ishikawaDiagram-FXEZZL3T-Dj0li_TY.js} +1 -1
  60. package/web/dist/assets/{journeyDiagram-5HDEW3XC-xLaPm-ow.js → journeyDiagram-5HDEW3XC-BtEGEeVr.js} +1 -1
  61. package/web/dist/assets/{kanban-definition-HUTT4EX6-BnDqLYLC.js → kanban-definition-HUTT4EX6-BTFfZvYt.js} +1 -1
  62. package/web/dist/assets/{linear-Djc7nVo8.js → linear-C3SQCsip.js} +1 -1
  63. package/web/dist/assets/{mermaid.core-B5elhUtV.js → mermaid.core-DALDJDu8.js} +4 -4
  64. package/web/dist/assets/{mindmap-definition-LN4V7U3C-N-B7EJGs.js → mindmap-definition-LN4V7U3C-BZf3uw0u.js} +1 -1
  65. package/web/dist/assets/{papaparse.min-BqM-LJkq.js → papaparse.min-Bh9MZ-fk.js} +1 -1
  66. package/web/dist/assets/{pegDiagram-2B236MQR-BIollSMB.js → pegDiagram-2B236MQR-PuOleiMg.js} +1 -1
  67. package/web/dist/assets/{pieDiagram-ENE6RG2P-DkZY6iCd.js → pieDiagram-ENE6RG2P-CcFORUaW.js} +1 -1
  68. package/web/dist/assets/pilotswarm-B9rLtXpI.js +109 -0
  69. package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-iUDmQIyL.js → quadrantDiagram-ABIIQ3AL-CEm0mp4y.js} +1 -1
  70. package/web/dist/assets/{railroadDiagram-RFXS5EU6-BSCGJgV7.js → railroadDiagram-RFXS5EU6-DeH-uX0_.js} +1 -1
  71. package/web/dist/assets/{requirementDiagram-TGXJPOKE-9yqcGRUA.js → requirementDiagram-TGXJPOKE-CMbJVMc1.js} +1 -1
  72. package/web/dist/assets/{sankeyDiagram-HTMAVEWB-SPh1plT9.js → sankeyDiagram-HTMAVEWB-C9gL5w_b.js} +1 -1
  73. package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-C0nv5x71.js → sequenceDiagram-DBY2YBRQ-BUo8TT74.js} +1 -1
  74. package/web/dist/assets/{sizeCapture-X5ZJPWSS-DMXfUt1o.js → sizeCapture-X5ZJPWSS-C5lqG0ei.js} +1 -1
  75. package/web/dist/assets/{stateDiagram-2N3HPSRC-vjhtquua.js → stateDiagram-2N3HPSRC-DyeISrEU.js} +1 -1
  76. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-5FZbaloZ.js +1 -0
  77. package/web/dist/assets/{swimlanes-5IMT3BWC-Bqewmkt5.js → swimlanes-5IMT3BWC-DawuBk_a.js} +2 -2
  78. package/web/dist/assets/swimlanesDiagram-G3AALYLV-DL8PgtmM.js +8 -0
  79. package/web/dist/assets/{timeline-definition-FHXFAJF6-DYHsdXUL.js → timeline-definition-FHXFAJF6-BZJ2YJcm.js} +1 -1
  80. package/web/dist/assets/{vennDiagram-L72KCM5P-Cb7cOHSa.js → vennDiagram-L72KCM5P-BMXBVCfe.js} +1 -1
  81. package/web/dist/assets/{wardleyDiagram-EHGQE667-BHpO1byL.js → wardleyDiagram-EHGQE667-DWedKsrQ.js} +1 -1
  82. package/web/dist/assets/{xychartDiagram-FW5EYKEG-DHDvGPtH.js → xychartDiagram-FW5EYKEG--XdfYceC.js} +1 -1
  83. package/web/dist/index.html +3 -3
  84. package/web/runtime.js +19 -0
  85. package/web/server.js +2 -2
  86. package/web/dist/assets/channel-B50-C7oJ.js +0 -1
  87. package/web/dist/assets/classDiagram-OUVF2IWQ-CIJlKS0m.js +0 -1
  88. package/web/dist/assets/classDiagram-v2-EOCWNBFH-CIJlKS0m.js +0 -1
  89. package/web/dist/assets/index-CHlrgp0s.css +0 -1
  90. package/web/dist/assets/index-bf7RCrAf.js +0 -24
  91. package/web/dist/assets/pilotswarm-Di5s6c69.js +0 -105
  92. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-CYIeYmPN.js +0 -1
  93. package/web/dist/assets/swimlanesDiagram-G3AALYLV-BeKhH9C9.js +0 -8
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA0BlD,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,SAAS,CA4G7D"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA2BlD,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,SAAS,CA6G7D"}
@@ -5,6 +5,7 @@ import { registerAgentTools } from "./tools/agents.js";
5
5
  import { registerFactsTools } from "./tools/facts.js";
6
6
  import { registerModelTools } from "./tools/models.js";
7
7
  import { registerProviderTools } from "./tools/providers.js";
8
+ import { registerFeatureTools } from "./tools/features.js";
8
9
  import { registerCapabilityTools } from "./tools/capabilities.js";
9
10
  import { registerEnhancedFactTools } from "./tools/facts-enhanced.js";
10
11
  import { registerGraphTools } from "./tools/graph.js";
@@ -78,6 +79,7 @@ export function createMcpServer(ctx) {
78
79
  registerFactsTools(server, ctx);
79
80
  registerModelTools(server, ctx);
80
81
  registerProviderTools(server, ctx);
82
+ registerFeatureTools(server, ctx);
81
83
  registerCapabilityTools(server, ctx);
82
84
  registerTurnControlTools(server, ctx);
83
85
  registerGroupTools(server, ctx);
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ServerContext } from "../context.js";
3
+ export declare function registerFeatureTools(server: McpServer, ctx: ServerContext): void;
4
+ //# sourceMappingURL=features.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features.d.ts","sourceRoot":"","sources":["../../../src/tools/features.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGnD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,aAAa,QA8BzE"}
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ import { FEATURE_OPERATION_SPECS, featureToolParameters } from "pilotswarm-sdk";
3
+ import { jsonResult, withToolErrors } from "../util/respond.js";
4
+ export function registerFeatureTools(server, ctx) {
5
+ // Web mode ignores this value and derives identity from authentication.
6
+ // Direct mode requires an explicitly configured identity/admin grant for
7
+ // feature policy; holding a catalog handle alone does not opt it in.
8
+ const provider = process.env.PILOTSWARM_MCP_ACTOR_PROVIDER;
9
+ const subject = process.env.PILOTSWARM_MCP_ACTOR_SUBJECT;
10
+ const viewer = {
11
+ principal: provider && subject ? { provider, subject } : null,
12
+ isAdmin: !ctx.webMode && ctx.admin && process.env.PILOTSWARM_MCP_FEATURE_ADMIN === "true",
13
+ };
14
+ for (const spec of FEATURE_OPERATION_SPECS) {
15
+ const schema = featureToolParameters(spec);
16
+ const inputSchema = {};
17
+ for (const [key, property] of Object.entries(schema.properties)) {
18
+ let field = property.type === "boolean" ? z.boolean()
19
+ : property.type === "integer" ? z.number().int().min(property.minimum ?? 0).max(property.maximum ?? Number.MAX_SAFE_INTEGER)
20
+ : z.string().min(property.minLength ?? 0).max(property.maxLength ?? 1024);
21
+ if (property.description)
22
+ field = field.describe(property.description);
23
+ inputSchema[key] = schema.required.includes(key) ? field : field.optional();
24
+ }
25
+ server.registerTool(spec.name, { title: spec.name.replaceAll("_", " "), description: spec.description, inputSchema }, withToolErrors(async (args) => {
26
+ const method = ctx.mgmt[spec.method].bind(ctx.mgmt);
27
+ const values = spec.kind === "audit" ? [args.limit]
28
+ : spec.kind === "users" ? [args.query]
29
+ : spec.kind === "read" ? ("targetUser" in spec ? [args.userId] : [])
30
+ : "targetUser" in spec ? [args.userId, args] : [args];
31
+ return jsonResult(await method(viewer, ...values));
32
+ }));
33
+ }
34
+ }
35
+ //# sourceMappingURL=features.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features.js","sourceRoot":"","sources":["../../../src/tools/features.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pilotswarm",
3
- "version": "0.5.62",
3
+ "version": "0.5.64",
4
4
  "description": "PilotSwarm application package: terminal UI, browser portal + Web API server, and MCP server — one install, three bins.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -81,7 +81,7 @@
81
81
  "ink": "^6.8.0",
82
82
  "jose": "^6.2.2",
83
83
  "mermaid": "^11.16.0",
84
- "pilotswarm-sdk": "0.5.62",
84
+ "pilotswarm-sdk": "0.5.64",
85
85
  "react": "^19.2.4",
86
86
  "react-dom": "^19.2.4",
87
87
  "ws": "^8.18.2"
@@ -1553,6 +1553,19 @@ export class NodeSdkTransport {
1553
1553
  return this.mgmt.listProviders(await this._modelProviderViewer());
1554
1554
  }
1555
1555
 
1556
+ async listFeatureFlags() { return this.mgmt.listFeatureFlags(await this._modelProviderViewer()); }
1557
+ async getClusterFeatureFlags() { return this.mgmt.getClusterFeatureFlags(await this._modelProviderViewer()); }
1558
+ async getMyFeatureFlags() { return this.mgmt.getMyFeatureFlags(await this._modelProviderViewer()); }
1559
+ async getUserFeatureFlags(userId) { return this.mgmt.getUserFeatureFlags(await this._modelProviderViewer(), userId); }
1560
+ async setClusterFeatureFlag(input) { return this.mgmt.setClusterFeatureFlag(await this._modelProviderViewer(), input); }
1561
+ async resetClusterFeatureFlag(input) { return this.mgmt.resetClusterFeatureFlag(await this._modelProviderViewer(), input); }
1562
+ async setMyFeatureFlag(input) { return this.mgmt.setMyFeatureFlag(await this._modelProviderViewer(), input); }
1563
+ async unsetMyFeatureFlag(input) { return this.mgmt.unsetMyFeatureFlag(await this._modelProviderViewer(), input); }
1564
+ async setUserFeatureFlag(userId, input) { return this.mgmt.setUserFeatureFlag(await this._modelProviderViewer(), userId, input); }
1565
+ async unsetUserFeatureFlag(userId, input) { return this.mgmt.unsetUserFeatureFlag(await this._modelProviderViewer(), userId, input); }
1566
+ async listFeatureFlagChanges(limit) { return this.mgmt.listFeatureFlagChanges(await this._modelProviderViewer(), limit); }
1567
+ async listFeatureFlagUsers(query) { return this.mgmt.listFeatureFlagUsers(await this._modelProviderViewer(), query); }
1568
+
1556
1569
  async getModelDefaults() {
1557
1570
  return this.mgmt.getModelDefaults(await this._modelProviderViewer());
1558
1571
  }
@@ -1,3 +1,4 @@
1
+ import { applyNativeTaskSnapshot } from "./native-tasks.js";
1
2
  import { UI_COMMANDS, FOCUS_REGIONS, INSPECTOR_TABS, cycleValue } from "./commands.js";
2
3
  import { BUDGET_SERIES_DAYS, BUDGET_SERIES_RANGES, canvasKey as canvasPrefKey } from "./state.js";
3
4
  import { parseAgentSourceLink } from "./repo-links.js";
@@ -55,6 +56,23 @@ import {
55
56
  import { findArtifactEntry } from "./state.js";
56
57
  import { getTheme, listThemes } from "./themes/index.js";
57
58
 
59
+ function preserveNativeTaskProgress(history, current, sessionId) {
60
+ const retained = current?.nativeTaskSnapshot;
61
+ if (!retained?.ownerId) return history;
62
+ // Replay has reconstructed task rows from durable events. Reapply the
63
+ // latest live projection even when a paging rebuild carried its old
64
+ // metadata through; individual task revision/terminal guards still let
65
+ // newer durable truth win and prevent another owner from taking a row.
66
+ return applyNativeTaskSnapshot({ ...history, nativeTaskSnapshot: undefined }, {
67
+ version: 1,
68
+ ...retained,
69
+ tasks: (current.chat || [])
70
+ .filter(item => item.kind === "native-task-group")
71
+ .flatMap(group => group.tasks)
72
+ .filter(task => task.ownerId === retained.ownerId),
73
+ }, { sessionId, seq: retained.liveSeq });
74
+ }
75
+
58
76
  /**
59
77
  * The copy selector for a package LIST row: which same-named copy it is.
60
78
  * Scope shadowing means one name can be a shared package AND a user copy at
@@ -2523,6 +2541,8 @@ export class PilotSwarmUiController {
2523
2541
  }
2524
2542
 
2525
2543
  async stop() {
2544
+ this._featureRefreshSerial = (this._featureRefreshSerial || 0) + 1;
2545
+ this._featureUserSerial = (this._featureUserSerial || 0) + 1;
2526
2546
  for (const timer of this._liveTurnIdleTimers?.values() || []) clearTimeout(timer);
2527
2547
  this._liveTurnIdleTimers?.clear();
2528
2548
  if (this.catalogTimer) clearInterval(this.catalogTimer);
@@ -2593,6 +2613,7 @@ export class PilotSwarmUiController {
2593
2613
  // navigation-intent branch returns before the tail): the worker
2594
2614
  // registry must refresh on every tick of the loop that provably runs.
2595
2615
  this.refreshWorkerRegistryIfStale().catch(() => {});
2616
+ this.refreshFeatureFlagsIfStale().catch(() => {});
2596
2617
  // Refreshes OVERLAP. The catalog loop ticks every 4s and a dozen
2597
2618
  // actions call this directly, while one run awaits several sequential
2598
2619
  // round-trips (N catalog pages, the folder list, up to two getSession
@@ -3003,6 +3024,7 @@ export class PilotSwarmUiController {
3003
3024
  }
3004
3025
  }
3005
3026
  }
3027
+ history = preserveNativeTaskProgress(history, current, sessionId);
3006
3028
  this.dispatch({
3007
3029
  type: "history/set",
3008
3030
  sessionId,
@@ -3237,6 +3259,7 @@ export class PilotSwarmUiController {
3237
3259
  if (this.getState().ui?.budgetOpen) this.closeBudget();
3238
3260
  this.dispatch({ type: "admin/visibility", visible: true });
3239
3261
  await this.refreshAdminProfile().catch(() => {});
3262
+ if (this.getState().admin?.section === "features") void this.refreshFeatureFlags();
3240
3263
  void this.refreshAdminModelProviders().catch(() => {});
3241
3264
  void this.refreshAdminAgentPackages().catch(() => {});
3242
3265
  }
@@ -3707,6 +3730,7 @@ export class PilotSwarmUiController {
3707
3730
 
3708
3731
  setAdminSection(section) {
3709
3732
  this.dispatch({ type: "admin/section", section });
3733
+ if (section === "features") void this.refreshFeatureFlags();
3710
3734
  if (section === "providers") {
3711
3735
  const providers = this.getState().admin?.modelProviders;
3712
3736
  if (!providers?.fetchedAt) void this.refreshAdminModelProviders().catch(() => {});
@@ -3722,6 +3746,251 @@ export class PilotSwarmUiController {
3722
3746
  }
3723
3747
  }
3724
3748
 
3749
+ async selectFeatureScope(mode, userId = null) {
3750
+ if (!["mine", "cluster", "users"].includes(mode)) return;
3751
+ if (this.getState().admin?.features?.saving || mode !== "mine" && !this._featureIsAdmin()) return;
3752
+ if (mode === "users" && userId !== null && (!Number.isSafeInteger(userId) || userId <= 0)) return;
3753
+ this.dispatch({ type: "admin/features", patch: { mode, userId: mode === "users" ? userId : null,
3754
+ data: null, error: null, notice: null, fetchedAt: null, dirtyDrafts: {} } });
3755
+ await this.refreshFeatureFlags();
3756
+ }
3757
+
3758
+ _featureIsAdmin() {
3759
+ const state = this.getState();
3760
+ const role = state.auth?.authorization?.role;
3761
+ return state.admin?.profile?.isAdmin === true && (!role || role === "admin" || role === "anonymous");
3762
+ }
3763
+
3764
+ _featureContext() {
3765
+ const state = this.getState();
3766
+ const features = state.admin.features;
3767
+ return JSON.stringify([features.generation, features.mode, features.userId,
3768
+ state.auth?.principal?.provider, state.auth?.principal?.subject, state.auth?.authorization?.role,
3769
+ state.admin.profile?.provider, state.admin.profile?.subject, state.admin.profile?.isAdmin]);
3770
+ }
3771
+
3772
+ async _featureRequest(read) {
3773
+ let timeout;
3774
+ try {
3775
+ return await Promise.race([Promise.resolve().then(read), new Promise((_, reject) => {
3776
+ timeout = setTimeout(() => reject(new Error("Feature request timed out. Retry or refresh the settings.")), 10_000);
3777
+ timeout?.unref?.();
3778
+ })]);
3779
+ } finally { clearTimeout(timeout); }
3780
+ }
3781
+
3782
+ async refreshFeatureFlagsIfStale() {
3783
+ const admin = this.getState().admin;
3784
+ const features = admin?.features;
3785
+ if (!admin?.visible || admin.section !== "features" || features?.loading || features?.saving
3786
+ || Object.keys(features?.dirtyDrafts || {}).length
3787
+ || features?.fetchedAt && Date.now() - features.fetchedAt < 20_000) return;
3788
+ await this.refreshFeatureFlags({ background: true });
3789
+ }
3790
+
3791
+ setFeatureDraftDirty(featureKey, dirty) {
3792
+ const dirtyDrafts = { ...this.getState().admin.features.dirtyDrafts };
3793
+ if (dirty) dirtyDrafts[featureKey] = true;
3794
+ else delete dirtyDrafts[featureKey];
3795
+ this.dispatch({ type: "admin/features", patch: { dirtyDrafts } });
3796
+ }
3797
+
3798
+ _featureDraftKey(featureKey) {
3799
+ const features = this.getState().admin.features;
3800
+ return JSON.stringify([features.mode, features.mode === "users" ? features.userId : null, featureKey]);
3801
+ }
3802
+
3803
+ getFeatureDraft(featureKey) {
3804
+ return this.getState().admin.features.drafts?.[this._featureDraftKey(featureKey)];
3805
+ }
3806
+
3807
+ getFeatureDraftCount(mode) {
3808
+ return Object.values(this.getState().admin.features.drafts || {}).filter(draft => draft.mode === mode).length;
3809
+ }
3810
+
3811
+ setFeatureDraft(featureKey, values) {
3812
+ const features = this.getState().admin.features;
3813
+ if (features.saving || features.mode !== "mine" && !this._featureIsAdmin()
3814
+ || features.mode === "users" && !features.userId) return;
3815
+ const flag = features.data?.flags?.find(item => item.featureKey === featureKey);
3816
+ if (!flag?.supported) return;
3817
+ const key = this._featureDraftKey(featureKey);
3818
+ const existing = features.drafts?.[key];
3819
+ const stored = features.mode === "cluster" ? flag.cluster : flag.user;
3820
+ // Null is an operation (remove the override), not merely a pair of
3821
+ // values equal to today's defaults. Keep reset intent when a row exists.
3822
+ const equal = values === null ? !stored : features.mode === "cluster"
3823
+ ? values.enabled === (flag.cluster?.enabled ?? flag.defaultEnabled)
3824
+ && values.allowUserOverride === (flag.cluster?.allowUserOverride ?? flag.defaultAllowUserOverride)
3825
+ : Boolean(stored) && values.enabled === stored.enabled;
3826
+ const drafts = { ...features.drafts };
3827
+ if (equal && !existing?.uncertain && !existing?.needsReview) delete drafts[key];
3828
+ else drafts[key] = { featureKey, mode: features.mode, userId: features.userId,
3829
+ values: values === null ? null : { ...values }, expectedRevision: existing?.expectedRevision ?? flag.revision,
3830
+ ...(existing?.uncertain ? { uncertain: true } : {}), ...(existing?.needsReview ? { needsReview: true } : {}) };
3831
+ this.dispatch({ type: "admin/features", patch: { drafts, error: null, notice: null } });
3832
+ }
3833
+
3834
+ async discardFeatureDraft(featureKey) {
3835
+ if (this.getState().admin.features.saving) return;
3836
+ const draft = this.getFeatureDraft(featureKey);
3837
+ const context = this._featureContext();
3838
+ // A lost response may hide a committed save. Reconcile it before
3839
+ // discarding so the old cached value cannot look authoritative.
3840
+ if (draft?.uncertain && !await this.refreshFeatureFlags()) return;
3841
+ if (context !== this._featureContext() || this.getFeatureDraft(featureKey) !== draft) return;
3842
+ if (draft?.uncertain) {
3843
+ const revision = this.getState().admin.features.data?.flags?.find(flag => flag.featureKey === featureKey)?.revision;
3844
+ // A GET at the old revision cannot rule out the timed-out write
3845
+ // committing later. A newer revision fences its compare-and-set.
3846
+ if (!revision || BigInt(revision) <= BigInt(draft.expectedRevision)) {
3847
+ this.dispatch({ type: "admin/features", patch: { error: "The earlier save is still unconfirmed. Retry Save or refresh before discarding changes." } });
3848
+ return;
3849
+ }
3850
+ }
3851
+ const drafts = { ...this.getState().admin.features.drafts };
3852
+ delete drafts[this._featureDraftKey(featureKey)];
3853
+ this.dispatch({ type: "admin/features", patch: { drafts, error: null, notice: null } });
3854
+ }
3855
+
3856
+ reviewFeatureDraft(featureKey) {
3857
+ const features = this.getState().admin.features;
3858
+ if (features.saving || features.loading || features.mode !== "mine" && !this._featureIsAdmin()) return;
3859
+ const key = this._featureDraftKey(featureKey);
3860
+ const draft = features.drafts?.[key];
3861
+ const flag = features.data?.flags?.find(item => item.featureKey === featureKey);
3862
+ if (!draft || !flag?.supported || draft.needsReview && draft.expectedRevision === flag.revision) return;
3863
+ this.dispatch({ type: "admin/features", patch: { drafts: { ...features.drafts,
3864
+ [key]: { ...draft, expectedRevision: flag.revision, needsReview: false, uncertain: false } }, error: null, notice: "Latest saved setting reviewed. Your changes are still unsaved." } });
3865
+ }
3866
+
3867
+ async saveFeatureDraft(featureKey) {
3868
+ const draft = this.getFeatureDraft(featureKey);
3869
+ if (!draft) return;
3870
+ const flag = this.getState().admin.features.data?.flags?.find(item => item.featureKey === featureKey);
3871
+ if (draft.needsReview || draft.expectedRevision !== flag?.revision) {
3872
+ this.dispatch({ type: "admin/features", patch: { error: "Settings changed elsewhere. Review the latest saved setting before saving your changes." } });
3873
+ return;
3874
+ }
3875
+ await this.saveFeatureFlag(featureKey, draft.values, { draft });
3876
+ }
3877
+
3878
+ async searchFeatureUsers(query = "") {
3879
+ if (!this._featureIsAdmin()) return;
3880
+ const serial = this._featureUserSerial = (this._featureUserSerial || 0) + 1;
3881
+ const context = this._featureContext();
3882
+ this.dispatch({ type: "admin/features", patch: { userQuery: String(query), usersLoading: true, metadataError: null } });
3883
+ const current = () => this._featureUserSerial === serial && this._featureContext() === context;
3884
+ try {
3885
+ const users = await this._featureRequest(() => this.transport.listFeatureFlagUsers(String(query)));
3886
+ if (current()) {
3887
+ const features = this.getState().admin.features;
3888
+ const selected = features.users?.find(user => user.userId === features.userId);
3889
+ const selectedUserOutsideResults = Boolean(selected && !users.some(user => user.userId === selected.userId));
3890
+ // Searching must not remove the identity/option of the user
3891
+ // whose saved policy and draft remain active below the picker.
3892
+ this.dispatch({ type: "admin/features", patch: {
3893
+ users: selectedUserOutsideResults ? [selected, ...users] : users,
3894
+ selectedUserOutsideResults, usersLoading: false,
3895
+ } });
3896
+ }
3897
+ } catch (error) {
3898
+ if (current()) this.dispatch({ type: "admin/features", patch: { usersLoading: false,
3899
+ metadataError: `User directory: ${error?.message || error}` } });
3900
+ }
3901
+ }
3902
+
3903
+ async refreshFeatureFlags({ background = false } = {}) {
3904
+ const serial = (this._featureRefreshSerial ?? 0) + 1;
3905
+ this._featureRefreshSerial = serial;
3906
+ const features = this.getState().admin?.features || { mode: "mine" };
3907
+ if (features.mode !== "mine" && !this._featureIsAdmin()) return;
3908
+ const context = this._featureContext();
3909
+ const current = () => this._featureRefreshSerial === serial && this._featureContext() === context;
3910
+ this.dispatch({ type: "admin/features", patch: { loading: true, ...(background ? {} : { error: null }) } });
3911
+ // Directory/adoption are independent diagnostics: neither may block policy.
3912
+ if (!background && this._featureIsAdmin()) {
3913
+ void this.searchFeatureUsers(features.userQuery || "");
3914
+ void this.refreshWorkerRegistryIfStale();
3915
+ }
3916
+ try {
3917
+ const data = await this._featureRequest(() => features.mode === "cluster" ? this.transport.getClusterFeatureFlags()
3918
+ : features.mode === "users" ? features.userId ? this.transport.getUserFeatureFlags(features.userId) : null
3919
+ : this.transport.getMyFeatureFlags());
3920
+ if (!current()) return false;
3921
+ this.dispatch({ type: "admin/features", patch: { data, loading: false, fetchedAt: Date.now() } });
3922
+ return true;
3923
+ } catch (error) {
3924
+ if (current()) this.dispatch({ type: "admin/features", patch: { loading: false, fetchedAt: Date.now(), error: String(error?.message || error) } });
3925
+ return false;
3926
+ }
3927
+ }
3928
+
3929
+ async saveFeatureFlag(featureKey, values, { draft } = {}) {
3930
+ const features = this.getState().admin?.features;
3931
+ if (!features || features.saving || features.loading || features.mode !== "mine" && !this._featureIsAdmin()) return;
3932
+ const definition = features.data?.flags?.find(flag => flag.featureKey === featureKey);
3933
+ if (!definition?.supported || features.mode === "users" && !features.userId) return;
3934
+ const target = this._featureContext();
3935
+ const draftKey = this._featureDraftKey(featureKey);
3936
+ const expectedRevision = draft?.expectedRevision ?? definition.revision;
3937
+ const signature = JSON.stringify([target, featureKey, expectedRevision, values]);
3938
+ this._featureMutationRetries ??= new Map();
3939
+ const requestId = this._featureMutationRetries.get(signature) ?? globalThis.crypto.randomUUID();
3940
+ this._featureMutationRetries.set(signature, requestId);
3941
+ const input = { featureKey, expectedRevision, requestId, ...(values || {}) };
3942
+ this.dispatch({ type: "admin/features", patch: { saving: true, error: null, notice: null } });
3943
+ try {
3944
+ const outcome = await this._featureRequest(() => {
3945
+ if (features.mode === "cluster") return this.transport[values ? "setClusterFeatureFlag" : "resetClusterFeatureFlag"](input);
3946
+ if (features.mode === "users") return this.transport[values ? "setUserFeatureFlag" : "unsetUserFeatureFlag"](features.userId, input);
3947
+ return this.transport[values ? "setMyFeatureFlag" : "unsetMyFeatureFlag"](input);
3948
+ });
3949
+ this._featureMutationRetries.delete(signature);
3950
+ if (this._featureContext() === target) {
3951
+ // The acknowledged mutation is authoritative even if the
3952
+ // following GET fails. Do not show the old value as saved.
3953
+ if (/^[1-9]\d*$/.test(String(outcome?.revision)) && Object.hasOwn(outcome, "setting")) {
3954
+ const updated = { ...definition, revision: outcome.revision,
3955
+ [features.mode === "cluster" ? "cluster" : "user"]: outcome.setting };
3956
+ const allowUser = updated.cluster?.allowUserOverride ?? updated.defaultAllowUserOverride;
3957
+ updated.effective = allowUser && updated.user ? updated.user.enabled
3958
+ : updated.cluster?.enabled ?? updated.defaultEnabled;
3959
+ updated.source = allowUser && updated.user ? "user" : updated.cluster ? "cluster" : "default";
3960
+ updated.userOverrideIgnored = Boolean(updated.user && !allowUser);
3961
+ const data = this.getState().admin.features.data;
3962
+ this.dispatch({ type: "admin/features", patch: { data: { ...data,
3963
+ flags: data.flags.map(flag => flag.featureKey === featureKey ? updated : flag) } } });
3964
+ }
3965
+ if (draft && this.getState().admin.features.drafts?.[draftKey] === draft) {
3966
+ const drafts = { ...this.getState().admin.features.drafts };
3967
+ delete drafts[draftKey];
3968
+ this.dispatch({ type: "admin/features", patch: { drafts } });
3969
+ }
3970
+ await this.refreshFeatureFlags();
3971
+ if (this._featureContext() === target) this.dispatch({ type: "admin/features", patch: { notice: "Setting saved." } });
3972
+ }
3973
+ } catch (error) {
3974
+ if (this._featureContext() === target) {
3975
+ if (draft && (error?.status === 409 || error?.code === "FEATURE_CONFLICT")) {
3976
+ this._featureMutationRetries.delete(signature);
3977
+ this.dispatch({ type: "admin/features", patch: { drafts: { ...this.getState().admin.features.drafts,
3978
+ [draftKey]: { ...draft, needsReview: true } } } });
3979
+ const refreshed = await this.refreshFeatureFlags({ background: true });
3980
+ if (this._featureContext() === target) this.dispatch({ type: "admin/features", patch: {
3981
+ error: refreshed ? "Settings changed elsewhere. Review the latest saved setting before saving your changes."
3982
+ : "Settings changed elsewhere, but the latest setting could not be loaded. Refresh settings before reviewing and saving your changes." } });
3983
+ return;
3984
+ }
3985
+ if (draft && !(error?.status >= 400 && error?.status < 500)) {
3986
+ this.dispatch({ type: "admin/features", patch: { drafts: { ...this.getState().admin.features.drafts,
3987
+ [draftKey]: { ...draft, uncertain: true } } } });
3988
+ }
3989
+ this.dispatch({ type: "admin/features", patch: { error: String(error?.message || error) } });
3990
+ }
3991
+ } finally { if (this._featureContext() === target) this.dispatch({ type: "admin/features", patch: { saving: false } }); }
3992
+ }
3993
+
3725
3994
  /** Node Map: select a node (toggles off when re-selected). Scopes Activity. */
3726
3995
  selectNodeMapNode(label) {
3727
3996
  this.dispatch({ type: "ui/nodeMapSelect", label: label ? String(label) : null });
@@ -5784,6 +6053,12 @@ export class PilotSwarmUiController {
5784
6053
  // history would corrupt the replay cursor — and they must never
5785
6054
  // appear in the transcript.
5786
6055
  if (event.transient === true) {
6056
+ if (event.eventType === "session.native_tasks_tick") {
6057
+ const existing = this.getState().history.bySessionId.get(sessionId);
6058
+ const history = applyNativeTaskSnapshot(existing, event.data, { sessionId, seq: event.liveSeq });
6059
+ if (history && history !== existing) this.dispatch({ type: "history/set", sessionId, history });
6060
+ return true;
6061
+ }
5787
6062
  if (event.eventType === "assistant.live_tick") {
5788
6063
  // A worker can die leaving a reasoning-only retained row.
5789
6064
  // On re-entry its message ID cannot match a durable final;
@@ -5838,10 +6113,11 @@ export class PilotSwarmUiController {
5838
6113
  }
5839
6114
  const existing = this.getState().history.bySessionId.get(sessionId)
5840
6115
  || { chat: [], activity: [], events: [], lastSeq: 0 };
6116
+ const liveHistory = this.preservePausedChatWindow(sessionId, existing);
5841
6117
  this.dispatch({
5842
6118
  type: "history/set",
5843
6119
  sessionId,
5844
- history: applyLiveTurnToHistory(existing, event.data, {
6120
+ history: applyLiveTurnToHistory(liveHistory, event.data, {
5845
6121
  sessionId,
5846
6122
  seq: event.liveSeq,
5847
6123
  createdAt: Date.now(),
@@ -5878,7 +6154,10 @@ export class PilotSwarmUiController {
5878
6154
  this._liveTurnIdleTimers.delete(sessionId);
5879
6155
  }
5880
6156
  const state = this.getState();
5881
- const existing = state.history.bySessionId.get(sessionId) || { chat: [], activity: [], lastSeq: 0 };
6157
+ const existing = this.preservePausedChatWindow(
6158
+ sessionId,
6159
+ state.history.bySessionId.get(sessionId) || { chat: [], activity: [], events: [], lastSeq: 0 },
6160
+ );
5882
6161
  if (event.seq <= (existing.lastSeq || 0)) return false;
5883
6162
  this.dispatch({
5884
6163
  type: "history/set",
@@ -5979,6 +6258,31 @@ export class PilotSwarmUiController {
5979
6258
  return true;
5980
6259
  }
5981
6260
 
6261
+ /**
6262
+ * Do not evict the first rendered item while its active chat is paused.
6263
+ * A fixed-size history window normally drops one old item for each live
6264
+ * append. Keeping the same numeric scrollTop then moves the actual text the
6265
+ * user is reading. Grow the in-memory window just enough for arrivals made
6266
+ * during that pause; normal bottom-follow appends resume bounded trimming.
6267
+ */
6268
+ preservePausedChatWindow(sessionId, history) {
6269
+ const state = this.getState();
6270
+ if (state.sessions.activeSessionId !== sessionId || state.ui.followBottom?.chat !== false) {
6271
+ return history;
6272
+ }
6273
+ const currentLimit = Math.max(
6274
+ DEFAULT_HISTORY_EVENT_LIMIT,
6275
+ Number(history?.loadedEventLimit ?? DEFAULT_HISTORY_EVENT_LIMIT) || DEFAULT_HISTORY_EVENT_LIMIT,
6276
+ );
6277
+ const requiredLimit = Math.max(
6278
+ currentLimit,
6279
+ (Array.isArray(history?.events) ? history.events.length : 0) + 1,
6280
+ (Array.isArray(history?.chat) ? history.chat.length : 0) + 4,
6281
+ (Array.isArray(history?.activity) ? history.activity.length : 0) + 1,
6282
+ );
6283
+ return requiredLimit === currentLimit ? history : { ...history, loadedEventLimit: requiredLimit };
6284
+ }
6285
+
5982
6286
  async syncSessionEvents(sessionId) {
5983
6287
  if (!sessionId || typeof this.transport.getSessionEvents !== "function") return;
5984
6288
  const existing = this.getState().history.bySessionId.get(sessionId);
@@ -9178,6 +9482,7 @@ export class PilotSwarmUiController {
9178
9482
  }
9179
9483
 
9180
9484
  paneUsesStickyBottomFollow(pane, state = this.getState()) {
9485
+ if (pane === "chat") return true;
9181
9486
  if (pane === "activity") return true;
9182
9487
  if (pane === "inspector") return state.ui.inspectorTab === "logs";
9183
9488
  return false;
@@ -9251,6 +9556,15 @@ export class PilotSwarmUiController {
9251
9556
  const current = this.getPaneVisualScrollOffset(pane, state);
9252
9557
  const nextOffset = Math.max(0, Math.min(current - delta, maxOffset));
9253
9558
  this.applyPaneVisualScrollOffset(pane, nextOffset, { followBottom: nextOffset >= maxOffset }, state);
9559
+ if (pane === "chat" && delta > 0) {
9560
+ if (current <= 0) {
9561
+ this.handleChatTopHistoryScrollIntent(nextOffset).catch(() => {});
9562
+ } else if (nextOffset <= 0) {
9563
+ this.armChatTopHistoryLoad();
9564
+ }
9565
+ } else if (pane === "chat" && delta < 0) {
9566
+ this.disarmChatTopHistoryLoad();
9567
+ }
9254
9568
  return;
9255
9569
  }
9256
9570
  const current = Math.max(0, Math.min(Number(state.ui.scroll?.[pane]) || 0, maxOffset));
@@ -9273,6 +9587,10 @@ export class PilotSwarmUiController {
9273
9587
  const nextOffset = Math.max(0, Math.min(Number(offset) || 0, maxOffset));
9274
9588
  if (this.paneUsesStickyBottomFollow(pane, state)) {
9275
9589
  this.applyPaneVisualScrollOffset(pane, nextOffset, { followBottom: nextOffset >= maxOffset }, state);
9590
+ if (pane === "chat") {
9591
+ if (maxOffset > 0 && nextOffset <= 0) this.armChatTopHistoryLoad();
9592
+ else this.disarmChatTopHistoryLoad();
9593
+ }
9276
9594
  return;
9277
9595
  }
9278
9596
  this.dispatch({ type: "ui/scroll", pane, offset: nextOffset });
@@ -9308,6 +9626,7 @@ export class PilotSwarmUiController {
9308
9626
 
9309
9627
  await this.maybeAutoExpandActiveHistory(requestedScrollOffset, {
9310
9628
  pages: AUTO_HISTORY_SCROLL_PAGE_COUNT,
9629
+ preserveDomAnchor: options.preserveDomAnchor === true,
9311
9630
  // Always bypass the offset gate. Every caller of this method fires
9312
9631
  // only when the DOM scroller is already AT the top — a direct
9313
9632
  // measurement. The gate is a second, weaker guess that compares a
@@ -9369,7 +9688,7 @@ export class PilotSwarmUiController {
9369
9688
  const pane = this.getScrollablePaneForFocus();
9370
9689
  if (!pane) return;
9371
9690
  if (this.paneUsesStickyBottomFollow(pane)) {
9372
- this.applyPaneVisualScrollOffset(pane, 0, { followBottom: false });
9691
+ this.scrollPaneTo(pane, 0);
9373
9692
  return;
9374
9693
  }
9375
9694
  const inspectorUsesBottomScroll = pane === "inspector" && this.inspectorUsesBottomScroll();
@@ -9649,6 +9968,7 @@ export class PilotSwarmUiController {
9649
9968
  requestedScrollOffset: targetOffset,
9650
9969
  autoTriggered: true,
9651
9970
  pages: options.pages,
9971
+ preserveDomAnchor: options.preserveDomAnchor === true,
9652
9972
  // Chat-driven pull: page backward over renderable message types
9653
9973
  // only, so each page is transcript instead of raw event noise.
9654
9974
  eventTypes: CHAT_HISTORY_EVENT_TYPES,
@@ -9766,6 +10086,7 @@ export class PilotSwarmUiController {
9766
10086
  lastSeq: events[events.length - 1]?.seq || 0,
9767
10087
  };
9768
10088
  }
10089
+ history = preserveNativeTaskProgress(history, this.getState().history.bySessionId.get(sessionId), sessionId);
9769
10090
  this.dispatch({
9770
10091
  type: "history/set",
9771
10092
  sessionId,
@@ -9785,9 +10106,12 @@ export class PilotSwarmUiController {
9785
10106
  pane: "chat",
9786
10107
  offset: 0,
9787
10108
  });
9788
- } else if (preserveChatView && previousScrollOffset > 0 && !options.autoTriggered) {
9789
- // EXPLICIT "load older" (the TUI's `e`): jump up to the start
9790
- // of what was just fetched the user asked to SEE it.
10109
+ } else if (preserveChatView && options.autoTriggered && !options.preserveDomAnchor) {
10110
+ // Terminal panes store paused chat as a top offset. Prepending
10111
+ // must advance that offset by the rendered rows inserted above
10112
+ // it, preserving the same visible message. Browser panes use
10113
+ // measured DOM height because rich cards and wrapping do not
10114
+ // match terminal row metrics.
9791
10115
  const nextState = this.getState();
9792
10116
  const nextRenderedLines = this.getActiveChatRenderMetrics(nextState).totalLines;
9793
10117
  const addedLines = Math.max(0, nextRenderedLines - previousRenderedLines);
@@ -9799,14 +10123,6 @@ export class PilotSwarmUiController {
9799
10123
  });
9800
10124
  }
9801
10125
  }
9802
- // AUTO-triggered (scrolling reached the top): change NOTHING. The
9803
- // offset is distance-from-BOTTOM, which prepended content cannot
9804
- // move — leaving it alone keeps the viewport glued to the exact
9805
- // messages the user was reading, and they scroll on into the new
9806
- // page naturally. Adding the delta here teleported the view to
9807
- // the TOP of a 3.3x-larger window — observed as "loading history
9808
- // takes you way back instead of a few pages up".
9809
-
9810
10126
  const stateLabel = history.hasOlderEvents
9811
10127
  ? options.autoTriggered
9812
10128
  ? `Loaded older history page from CMS (${history.loadedEventCount} events loaded)`