pilotswarm 0.5.63 → 0.5.65

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 (90) 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 +276 -0
  11. package/ui/core/src/history.js +89 -14
  12. package/ui/core/src/native-tasks.js +197 -0
  13. package/ui/core/src/question-display.js +13 -0
  14. package/ui/core/src/reducer.js +18 -1
  15. package/ui/core/src/selectors.js +46 -13
  16. package/ui/core/src/session-errors.js +13 -6
  17. package/ui/core/src/session-warning.js +14 -0
  18. package/ui/core/src/state.js +1 -0
  19. package/ui/react/src/feature-flags-panel.js +186 -0
  20. package/ui/react/src/native-task-card.js +59 -0
  21. package/ui/react/src/web-app.js +105 -15
  22. package/web/dist/assets/{abnfDiagram-VRR7QNED-C8iAmIqt.js → abnfDiagram-VRR7QNED-gIPHF2gV.js} +1 -1
  23. package/web/dist/assets/{arc-Cg8qs17j.js → arc-DMW_q5Ml.js} +1 -1
  24. package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-fYr0dRm3.js → architectureDiagram-ZJ3FMSHR-DAKsrxcB.js} +1 -1
  25. package/web/dist/assets/{blockDiagram-677ZJIJ3-Bpj79Dzq.js → blockDiagram-677ZJIJ3-BUjSLpP3.js} +1 -1
  26. package/web/dist/assets/{c4Diagram-LMCZKHZV-b26Lsm6m.js → c4Diagram-LMCZKHZV-Dw2nPoes.js} +1 -1
  27. package/web/dist/assets/channel-CcOa1LtY.js +1 -0
  28. package/web/dist/assets/{chunk-2Q5K7J3B-VLYQfjgG.js → chunk-2Q5K7J3B-BAL28Rv0.js} +1 -1
  29. package/web/dist/assets/{chunk-32BRIVSS-mVx--2Hg.js → chunk-32BRIVSS-DDlt9K8s.js} +1 -1
  30. package/web/dist/assets/{chunk-5VM5RSS4-jwnJmked.js → chunk-5VM5RSS4-BLQGJGoP.js} +1 -1
  31. package/web/dist/assets/{chunk-EX3LRPZG-DdfzAT1n.js → chunk-EX3LRPZG-XFMb3HRy.js} +1 -1
  32. package/web/dist/assets/{chunk-JWPE2WC7-BHm6Udd8.js → chunk-JWPE2WC7-DWnKTQ72.js} +1 -1
  33. package/web/dist/assets/{chunk-MOJQB5TN-Dn5qSVAS.js → chunk-MOJQB5TN-MltzhnwP.js} +1 -1
  34. package/web/dist/assets/{chunk-RYQCIY6F-D4IKMROn.js → chunk-RYQCIY6F-Ce3q0-mJ.js} +1 -1
  35. package/web/dist/assets/{chunk-V7JOEXUC-BhME3pRf.js → chunk-V7JOEXUC-ThXLYY88.js} +1 -1
  36. package/web/dist/assets/{chunk-VR4S4FIN-CHG_NBff.js → chunk-VR4S4FIN-DbhAykiW.js} +1 -1
  37. package/web/dist/assets/{chunk-XXDRQBXY-yw4QCgiL.js → chunk-XXDRQBXY-BpkNB0mD.js} +1 -1
  38. package/web/dist/assets/classDiagram-OUVF2IWQ-B2VhHRSV.js +1 -0
  39. package/web/dist/assets/classDiagram-v2-EOCWNBFH-B2VhHRSV.js +1 -0
  40. package/web/dist/assets/{cose-bilkent-JH36ORCC-DqwPj9Kx.js → cose-bilkent-JH36ORCC-rctqAPvj.js} +1 -1
  41. package/web/dist/assets/{cynefin-VYW2F7L2-CVokaaE_.js → cynefin-VYW2F7L2-Dg1cltg6.js} +1 -1
  42. package/web/dist/assets/{cynefinDiagram-TSTJHNR4-Bkhj9fuY.js → cynefinDiagram-TSTJHNR4-Ci23Sv4Z.js} +1 -1
  43. package/web/dist/assets/{dagre-VKFMJZFB-DVv-jnjb.js → dagre-VKFMJZFB-BpWTGhpv.js} +1 -1
  44. package/web/dist/assets/{diagram-FQU43EPY-DDlZjkrF.js → diagram-FQU43EPY-B69JpiAe.js} +1 -1
  45. package/web/dist/assets/{diagram-G47NLZAW-CNDUgqYa.js → diagram-G47NLZAW-BHMk1TOO.js} +1 -1
  46. package/web/dist/assets/{diagram-NH7WQ7WH-DywF8u8i.js → diagram-NH7WQ7WH-CtvZIIsk.js} +1 -1
  47. package/web/dist/assets/{diagram-OA4YK3LP-ZfN8nvLw.js → diagram-OA4YK3LP-BFvKh800.js} +1 -1
  48. package/web/dist/assets/{diagram-WEI45ONY-S94A6Avm.js → diagram-WEI45ONY-CgB8sWPe.js} +1 -1
  49. package/web/dist/assets/{ebnfDiagram-CCIWWBDH-Mpt3QUnw.js → ebnfDiagram-CCIWWBDH-DyqPNydM.js} +1 -1
  50. package/web/dist/assets/{erDiagram-Q63AITRT-Bh-eWbdV.js → erDiagram-Q63AITRT-CwpvZQ3Q.js} +1 -1
  51. package/web/dist/assets/{flowDiagram-23GEKE2U-BwRtDI6V.js → flowDiagram-23GEKE2U-EBKSKxfC.js} +1 -1
  52. package/web/dist/assets/{ganttDiagram-NO4QXBWP-CrZoBmcB.js → ganttDiagram-NO4QXBWP-q5tMPAuC.js} +1 -1
  53. package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-giXv1ynk.js → gitGraphDiagram-IHSO6WYX-DCRbi4KE.js} +1 -1
  54. package/web/dist/assets/{index-Br-gGWmc.js → index-CO-D6M4t.js} +6 -6
  55. package/web/dist/assets/index-uhc851H3.css +1 -0
  56. package/web/dist/assets/{infoDiagram-FWYZ7A6U-H_5oc4-7.js → infoDiagram-FWYZ7A6U-Bex5pSIX.js} +1 -1
  57. package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-BydiPDX0.js → ishikawaDiagram-FXEZZL3T-Dj0li_TY.js} +1 -1
  58. package/web/dist/assets/{journeyDiagram-5HDEW3XC-DS1HXms0.js → journeyDiagram-5HDEW3XC-BtEGEeVr.js} +1 -1
  59. package/web/dist/assets/{kanban-definition-HUTT4EX6-BjY5SyYu.js → kanban-definition-HUTT4EX6-BTFfZvYt.js} +1 -1
  60. package/web/dist/assets/{linear-BvOotjKL.js → linear-C3SQCsip.js} +1 -1
  61. package/web/dist/assets/{mermaid.core-0iRu7sp-.js → mermaid.core-DALDJDu8.js} +4 -4
  62. package/web/dist/assets/{mindmap-definition-LN4V7U3C-DB93zsvL.js → mindmap-definition-LN4V7U3C-BZf3uw0u.js} +1 -1
  63. package/web/dist/assets/{papaparse.min-f5Xncedc.js → papaparse.min-Bh9MZ-fk.js} +1 -1
  64. package/web/dist/assets/{pegDiagram-2B236MQR-Dh_U1cFA.js → pegDiagram-2B236MQR-PuOleiMg.js} +1 -1
  65. package/web/dist/assets/{pieDiagram-ENE6RG2P-Csv1qxMm.js → pieDiagram-ENE6RG2P-CcFORUaW.js} +1 -1
  66. package/web/dist/assets/pilotswarm-B9rLtXpI.js +109 -0
  67. package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-Bo0pcrBM.js → quadrantDiagram-ABIIQ3AL-CEm0mp4y.js} +1 -1
  68. package/web/dist/assets/{railroadDiagram-RFXS5EU6-CHPvHNWG.js → railroadDiagram-RFXS5EU6-DeH-uX0_.js} +1 -1
  69. package/web/dist/assets/{requirementDiagram-TGXJPOKE-DgX8l-6H.js → requirementDiagram-TGXJPOKE-CMbJVMc1.js} +1 -1
  70. package/web/dist/assets/{sankeyDiagram-HTMAVEWB-xR6MTs5a.js → sankeyDiagram-HTMAVEWB-C9gL5w_b.js} +1 -1
  71. package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-CD_45diB.js → sequenceDiagram-DBY2YBRQ-BUo8TT74.js} +1 -1
  72. package/web/dist/assets/{sizeCapture-X5ZJPWSS-BJVEXU0K.js → sizeCapture-X5ZJPWSS-C5lqG0ei.js} +1 -1
  73. package/web/dist/assets/{stateDiagram-2N3HPSRC-Bwu6kd23.js → stateDiagram-2N3HPSRC-DyeISrEU.js} +1 -1
  74. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-5FZbaloZ.js +1 -0
  75. package/web/dist/assets/{swimlanes-5IMT3BWC-BmhEzevT.js → swimlanes-5IMT3BWC-DawuBk_a.js} +2 -2
  76. package/web/dist/assets/swimlanesDiagram-G3AALYLV-DL8PgtmM.js +8 -0
  77. package/web/dist/assets/{timeline-definition-FHXFAJF6-DFPI_uTI.js → timeline-definition-FHXFAJF6-BZJ2YJcm.js} +1 -1
  78. package/web/dist/assets/{vennDiagram-L72KCM5P-DDTpTkBC.js → vennDiagram-L72KCM5P-BMXBVCfe.js} +1 -1
  79. package/web/dist/assets/{wardleyDiagram-EHGQE667-bV2_YVIL.js → wardleyDiagram-EHGQE667-DWedKsrQ.js} +1 -1
  80. package/web/dist/assets/{xychartDiagram-FW5EYKEG-C7bDIXAV.js → xychartDiagram-FW5EYKEG--XdfYceC.js} +1 -1
  81. package/web/dist/index.html +3 -3
  82. package/web/runtime.js +19 -0
  83. package/web/server.js +2 -2
  84. package/web/dist/assets/channel-C-ASbcRr.js +0 -1
  85. package/web/dist/assets/classDiagram-OUVF2IWQ-D2vlJIb1.js +0 -1
  86. package/web/dist/assets/classDiagram-v2-EOCWNBFH-D2vlJIb1.js +0 -1
  87. package/web/dist/assets/index-DRFDn87d.css +0 -1
  88. package/web/dist/assets/pilotswarm-DQNSWfNK.js +0 -105
  89. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-aypsN5jW.js +0 -1
  90. package/web/dist/assets/swimlanesDiagram-G3AALYLV-Duf5C4YK.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.63",
3
+ "version": "0.5.65",
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.63",
84
+ "pilotswarm-sdk": "0.5.65",
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;
@@ -9811,6 +10086,7 @@ export class PilotSwarmUiController {
9811
10086
  lastSeq: events[events.length - 1]?.seq || 0,
9812
10087
  };
9813
10088
  }
10089
+ history = preserveNativeTaskProgress(history, this.getState().history.bySessionId.get(sessionId), sessionId);
9814
10090
  this.dispatch({
9815
10091
  type: "history/set",
9816
10092
  sessionId,
@@ -2,6 +2,8 @@ import { formatHumanDurationSeconds, formatTimestamp, shortModelName, shortSessi
2
2
  import { isCanvasActionContent, parseCanvasActionContent } from "./canvas-actions.js";
3
3
  import { formatCompactionActivityRuns } from "./context-usage.js";
4
4
  import { canonicalSystemTitle } from "./system-titles.js";
5
+ import { matchesSessionError } from "./session-warning.js";
6
+ import { appendNativeTaskEvent } from "./native-tasks.js";
5
7
  import { buildSessionWarning } from "./session-errors.js";
6
8
  import { appendChatCall, CHAT_CALL_EVENT_TYPES } from "./chat-activity.js";
7
9
 
@@ -19,13 +21,20 @@ export const HISTORY_EVENT_LIMIT_STEPS = [
19
21
  // load transcript pages instead of raw-stream pages.
20
22
  export const CHAT_HISTORY_EVENT_TYPES = [
21
23
  ...CHAT_CALL_EVENT_TYPES,
22
- "session.error",
23
24
  "user.message",
24
25
  "assistant.message",
25
26
  // Needed to distinguish interim assistant output from the final answer
26
27
  // when an older transcript page is loaded.
27
28
  "session.turn_completed",
28
29
  "session.turn_stopped",
30
+ // Warnings belong at the error's position in the transcript, including
31
+ // after recovery and when paging backward through a noisy session.
32
+ "session.error",
33
+ "subagent.started",
34
+ "subagent.configured",
35
+ "subagent.completed",
36
+ "subagent.failed",
37
+ "native.task_updated",
29
38
  "system.message",
30
39
  // Session regeneration boundary — rendered as an inline epoch divider in
31
40
  // the transcript, so it must survive backward chat-history paging.
@@ -90,6 +99,63 @@ function buildRegenFailedItem(event) {
90
99
  };
91
100
  }
92
101
 
102
+ function buildSessionWarningItem(event) {
103
+ const completed = event.eventType === "session.turn_completed" && event.data?.resultType === "error";
104
+ if (event.eventType !== "session.error" && !completed) return null;
105
+ const warning = buildSessionWarning(event);
106
+ if (!warning) return null;
107
+ return {
108
+ ...warning,
109
+ errorText: warning.text,
110
+ sourceEventType: event.eventType,
111
+ ...(completed ? { turnCompletedSeq: event.seq } : {}),
112
+ time: formatTimestamp(event.createdAt),
113
+ };
114
+ }
115
+
116
+ function appendSessionWarning(chat, event) {
117
+ const warning = buildSessionWarningItem(event);
118
+ if (!warning) return;
119
+ if (chat.some((item) => item.id === warning.id
120
+ || (item.turnCompletedSeq != null && item.turnCompletedSeq === event.seq))) return;
121
+ // Tool disclosures can arrive between the SDK error and the failed-turn
122
+ // summary. They do not start a different conversation/failure episode.
123
+ const previousIndex = chat.findLastIndex(item => item.kind !== "chat-call" && item.kind !== "native-task-group");
124
+ const previous = chat[previousIndex];
125
+ // A runtime error and its failed-turn summary can describe one failure.
126
+ // Retain the original anchor; subsequent failed turns remain distinct.
127
+ if (previous?.kind === "session-warning"
128
+ && previous.sourceEventType === "session.error"
129
+ && previous.turnCompletedSeq == null
130
+ && event.eventType === "session.turn_completed"
131
+ && matchesSessionError(previous.errorText, warning.errorText)) {
132
+ chat[previousIndex] = { ...previous, turnCompletedSeq: event.seq };
133
+ return;
134
+ }
135
+ chat.push(warning);
136
+ }
137
+
138
+ const parentChatCallTypes = new Set(CHAT_CALL_EVENT_TYPES);
139
+ function appendParentChatCall(chat, event) {
140
+ if (!parentChatCallTypes.has(event?.eventType)) return;
141
+ const data = event?.data || {};
142
+ // Native tasks have their own abridged disclosure. Generic call cards
143
+ // would duplicate them and reveal the full native prompt/arguments.
144
+ const name = data.toolName || data.name;
145
+ const namespace = `${event.sessionId}:${data.durableSessionId || ""}`;
146
+ const keys = [data.toolCallId && `call:${data.toolCallId}`, data.requestId && `request:${data.requestId}`].filter(Boolean);
147
+ // An empty diagnostic completion has neither a call to update nor useful
148
+ // content. Keep it in Activity without evicting real conversation cards.
149
+ const hasContent = name || data.arguments != null || data.args != null || data.result != null || data.output != null
150
+ || data.error || data.partialOutput || data.progressMessage || data.childSessionId || data.agentId || data.task;
151
+ if (!keys.length && !hasContent) return;
152
+ const parentCall = !name && chat.some(item => item.kind === "chat-call" && item.namespace === namespace
153
+ && item.callKeys.some(key => keys.includes(key)));
154
+ const nativeCall = name === "task" || Boolean(!name && !parentCall && data.toolCallId && chat.some(item =>
155
+ item.kind === "native-task-group" && item.tasks.some(task => task.toolCallId === data.toolCallId)));
156
+ if (!nativeCall) appendChatCall(chat, event);
157
+ }
158
+
93
159
  function clampHistoryItems(items, maxItems) {
94
160
  const list = Array.isArray(items) ? items.filter(Boolean) : [];
95
161
  const safeMax = Math.max(DEFAULT_HISTORY_EVENT_LIMIT, Number(maxItems) || DEFAULT_HISTORY_EVENT_LIMIT);
@@ -881,7 +947,21 @@ function formatActivity(event) {
881
947
  const body = formatEventSnippet(event);
882
948
  let runs = null;
883
949
 
950
+ // Native child transcripts remain inspectable as events, but only their
951
+ // lifecycle belongs in the parent's activity feed.
952
+ if (event.eventType?.startsWith("native.") || event.eventType === "session.background_tasks_changed") return null;
884
953
  switch (event.eventType) {
954
+ case "subagent.started":
955
+ case "subagent.completed":
956
+ case "subagent.failed": {
957
+ const phase = event.eventType.slice("subagent.".length);
958
+ const name = event.data?.agentDisplayName || event.data?.agentName || "agent";
959
+ const duration = Number.isFinite(event.data?.durationMs)
960
+ ? ` (${formatHumanDurationSeconds(event.data.durationMs / 1000)})` : "";
961
+ runs = buildLabeledActivityRuns(time, "[native agent]", "cyan", `${name} ${phase}${duration}`, phase === "failed" ? "red" : "white");
962
+ break;
963
+ }
964
+ case "subagent.configured":
885
965
  case "assistant.usage":
886
966
  case "session.info":
887
967
  case "session.idle":
@@ -1226,11 +1306,9 @@ export function buildHistoryModel(events = [], options = {}) {
1226
1306
 
1227
1307
  for (const event of events) {
1228
1308
  storedEvents.push(event);
1229
- appendChatCall(chat, event);
1230
- if (event.eventType === "session.error") {
1231
- const warning = buildSessionWarning(event);
1232
- if (warning) chat.push(warning);
1233
- }
1309
+ appendNativeTaskEvent(chat, event);
1310
+ appendParentChatCall(chat, event);
1311
+ appendSessionWarning(chat, event);
1234
1312
  if (["user.message", "session.turn_completed", "session.turn_stopped", "session.epoch_committed"].includes(event.eventType)) {
1235
1313
  settleAssistantResponses(chat, event);
1236
1314
  }
@@ -1309,6 +1387,7 @@ export function appendEventToHistory(history, event) {
1309
1387
  ? existingEvents
1310
1388
  : [...existingEvents, event].slice(-loadedEventLimit);
1311
1389
  const next = {
1390
+ nativeTaskSnapshot: history?.nativeTaskSnapshot,
1312
1391
  closedLiveKeys: history?.closedLiveKeys || [],
1313
1392
  closedLiveStreams: history?.closedLiveStreams || [],
1314
1393
  chat: clampHistoryItems(history?.chat || [], loadedEventLimit),
@@ -1328,14 +1407,10 @@ export function appendEventToHistory(history, event) {
1328
1407
  stoppedMessageIds: Array.isArray(history?.stoppedMessageIds) ? history.stoppedMessageIds : [],
1329
1408
  };
1330
1409
 
1331
- if (event.eventType === "session.error") {
1332
- const warning = buildSessionWarning(event);
1333
- if (warning && !next.chat.some(item => item.id === warning.id)) {
1334
- next.chat = clampHistoryItems([...next.chat, warning], loadedEventLimit);
1335
- }
1336
- }
1337
-
1338
- appendChatCall(next.chat, event);
1410
+ appendNativeTaskEvent(next.chat, event);
1411
+ appendParentChatCall(next.chat, event);
1412
+ appendSessionWarning(next.chat, event);
1413
+ next.chat = clampHistoryItems(next.chat, loadedEventLimit);
1339
1414
 
1340
1415
  if (["user.message", "session.turn_completed", "session.turn_stopped", "session.epoch_committed"].includes(event.eventType)) {
1341
1416
  settleAssistantResponses(next.chat, event);