@genesislcap/ai-assistant 14.488.1 → 14.488.2-GENC-1351.2

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 (143) hide show
  1. package/dist/ai-assistant.api.json +5104 -1727
  2. package/dist/ai-assistant.d.ts +691 -102
  3. package/dist/custom-elements.json +1118 -9
  4. package/dist/dts/components/ai-driver/ai-driver.d.ts +32 -0
  5. package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
  6. package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts +2 -0
  7. package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts.map +1 -0
  8. package/dist/dts/components/chat-driver/chat-driver.d.ts +16 -0
  9. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  10. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +15 -0
  11. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  12. package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts +2 -0
  13. package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts.map +1 -0
  14. package/dist/dts/components/settings-modal/settings-modal.styles.d.ts.map +1 -1
  15. package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
  16. package/dist/dts/config/config.d.ts +70 -0
  17. package/dist/dts/config/config.d.ts.map +1 -1
  18. package/dist/dts/config/define-stateful-agent.d.ts +73 -1
  19. package/dist/dts/config/define-stateful-agent.d.ts.map +1 -1
  20. package/dist/dts/index.d.ts +1 -0
  21. package/dist/dts/index.d.ts.map +1 -1
  22. package/dist/dts/main/main.d.ts +183 -63
  23. package/dist/dts/main/main.d.ts.map +1 -1
  24. package/dist/dts/main/main.styles.d.ts.map +1 -1
  25. package/dist/dts/main/main.template.d.ts.map +1 -1
  26. package/dist/dts/main/main.types.d.ts +55 -0
  27. package/dist/dts/main/main.types.d.ts.map +1 -1
  28. package/dist/dts/state/ai-assistant-slice.d.ts +66 -0
  29. package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
  30. package/dist/dts/state/ai-assistant-slice.test.d.ts +2 -0
  31. package/dist/dts/state/ai-assistant-slice.test.d.ts.map +1 -0
  32. package/dist/dts/state/debug-event-log.d.ts +1 -1
  33. package/dist/dts/state/debug-event-log.d.ts.map +1 -1
  34. package/dist/dts/state/persistence/diagnostics-cursors.d.ts +40 -0
  35. package/dist/dts/state/persistence/diagnostics-cursors.d.ts.map +1 -0
  36. package/dist/dts/state/persistence/diagnostics.d.ts +58 -0
  37. package/dist/dts/state/persistence/diagnostics.d.ts.map +1 -0
  38. package/dist/dts/state/persistence/diagnostics.test.d.ts +2 -0
  39. package/dist/dts/state/persistence/diagnostics.test.d.ts.map +1 -0
  40. package/dist/dts/state/persistence/index.d.ts +7 -0
  41. package/dist/dts/state/persistence/index.d.ts.map +1 -0
  42. package/dist/dts/state/persistence/persister-registry.d.ts +17 -0
  43. package/dist/dts/state/persistence/persister-registry.d.ts.map +1 -0
  44. package/dist/dts/state/persistence/session-persistence-provider.d.ts +132 -0
  45. package/dist/dts/state/persistence/session-persistence-provider.d.ts.map +1 -0
  46. package/dist/dts/state/persistence/session-persistence-provider.test.d.ts +2 -0
  47. package/dist/dts/state/persistence/session-persistence-provider.test.d.ts.map +1 -0
  48. package/dist/dts/state/persistence/session-persistence.integration.test.d.ts +2 -0
  49. package/dist/dts/state/persistence/session-persistence.integration.test.d.ts.map +1 -0
  50. package/dist/dts/state/persistence/session-persister.d.ts +154 -0
  51. package/dist/dts/state/persistence/session-persister.d.ts.map +1 -0
  52. package/dist/dts/state/persistence/session-persister.test.d.ts +2 -0
  53. package/dist/dts/state/persistence/session-persister.test.d.ts.map +1 -0
  54. package/dist/dts/state/persistence/session-snapshot.d.ts +209 -0
  55. package/dist/dts/state/persistence/session-snapshot.d.ts.map +1 -0
  56. package/dist/dts/state/persistence/session-snapshot.test.d.ts +2 -0
  57. package/dist/dts/state/persistence/session-snapshot.test.d.ts.map +1 -0
  58. package/dist/dts/state/session-store.d.ts +6 -0
  59. package/dist/dts/state/session-store.d.ts.map +1 -1
  60. package/dist/dts/utils/history-transform.d.ts +38 -0
  61. package/dist/dts/utils/history-transform.d.ts.map +1 -1
  62. package/dist/dts/utils/history-transform.test.d.ts +2 -0
  63. package/dist/dts/utils/history-transform.test.d.ts.map +1 -0
  64. package/dist/dts/utils/message-partition.d.ts +27 -0
  65. package/dist/dts/utils/message-partition.d.ts.map +1 -1
  66. package/dist/dts/utils/resolve-preference-baseline.d.ts +43 -0
  67. package/dist/dts/utils/resolve-preference-baseline.d.ts.map +1 -0
  68. package/dist/dts/utils/resolve-preference-baseline.test.d.ts +2 -0
  69. package/dist/dts/utils/resolve-preference-baseline.test.d.ts.map +1 -0
  70. package/dist/dts/utils/with-timeout.d.ts +12 -0
  71. package/dist/dts/utils/with-timeout.d.ts.map +1 -0
  72. package/dist/esm/components/chat-driver/chat-driver.compact.test.js +93 -0
  73. package/dist/esm/components/chat-driver/chat-driver.js +92 -6
  74. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +32 -0
  75. package/dist/esm/components/orchestrating-driver/orchestrating-driver.pin.test.js +48 -0
  76. package/dist/esm/components/settings-modal/settings-modal.styles.js +24 -0
  77. package/dist/esm/components/settings-modal/settings-modal.template.js +14 -1
  78. package/dist/esm/config/define-stateful-agent.js +81 -0
  79. package/dist/esm/index.js +1 -0
  80. package/dist/esm/main/main.js +610 -84
  81. package/dist/esm/main/main.styles.js +55 -0
  82. package/dist/esm/main/main.template.js +94 -5
  83. package/dist/esm/main/main.types.js +27 -0
  84. package/dist/esm/state/ai-assistant-slice.js +71 -25
  85. package/dist/esm/state/ai-assistant-slice.test.js +65 -0
  86. package/dist/esm/state/debug-event-log.js +8 -0
  87. package/dist/esm/state/persistence/diagnostics-cursors.js +34 -0
  88. package/dist/esm/state/persistence/diagnostics.js +81 -0
  89. package/dist/esm/state/persistence/diagnostics.test.js +72 -0
  90. package/dist/esm/state/persistence/index.js +3 -0
  91. package/dist/esm/state/persistence/persister-registry.js +41 -0
  92. package/dist/esm/state/persistence/session-persistence-provider.js +95 -0
  93. package/dist/esm/state/persistence/session-persistence-provider.test.js +110 -0
  94. package/dist/esm/state/persistence/session-persistence.integration.test.js +109 -0
  95. package/dist/esm/state/persistence/session-persister.js +577 -0
  96. package/dist/esm/state/persistence/session-persister.test.js +239 -0
  97. package/dist/esm/state/persistence/session-snapshot.js +231 -0
  98. package/dist/esm/state/persistence/session-snapshot.test.js +243 -0
  99. package/dist/esm/utils/history-transform.js +101 -0
  100. package/dist/esm/utils/history-transform.test.js +137 -0
  101. package/dist/esm/utils/message-partition.js +30 -2
  102. package/dist/esm/utils/message-partition.test.js +20 -1
  103. package/dist/esm/utils/resolve-preference-baseline.js +53 -0
  104. package/dist/esm/utils/resolve-preference-baseline.test.js +69 -0
  105. package/dist/esm/utils/with-timeout.js +28 -0
  106. package/dist/tsconfig.tsbuildinfo +1 -1
  107. package/package.json +17 -17
  108. package/src/components/ai-driver/ai-driver.ts +36 -0
  109. package/src/components/chat-driver/chat-driver.compact.test.ts +131 -0
  110. package/src/components/chat-driver/chat-driver.ts +99 -6
  111. package/src/components/orchestrating-driver/orchestrating-driver.pin.test.ts +76 -0
  112. package/src/components/orchestrating-driver/orchestrating-driver.ts +36 -0
  113. package/src/components/settings-modal/settings-modal.styles.ts +24 -0
  114. package/src/components/settings-modal/settings-modal.template.ts +27 -1
  115. package/src/config/config.ts +71 -0
  116. package/src/config/define-stateful-agent.ts +134 -0
  117. package/src/index.ts +1 -0
  118. package/src/main/main.styles.ts +55 -0
  119. package/src/main/main.template.ts +121 -5
  120. package/src/main/main.ts +569 -64
  121. package/src/main/main.types.ts +59 -0
  122. package/src/state/ai-assistant-slice.test.ts +82 -0
  123. package/src/state/ai-assistant-slice.ts +120 -25
  124. package/src/state/debug-event-log.ts +18 -1
  125. package/src/state/persistence/diagnostics-cursors.ts +66 -0
  126. package/src/state/persistence/diagnostics.test.ts +95 -0
  127. package/src/state/persistence/diagnostics.ts +103 -0
  128. package/src/state/persistence/index.ts +10 -0
  129. package/src/state/persistence/persister-registry.ts +49 -0
  130. package/src/state/persistence/session-persistence-provider.test.ts +136 -0
  131. package/src/state/persistence/session-persistence-provider.ts +202 -0
  132. package/src/state/persistence/session-persistence.integration.test.ts +134 -0
  133. package/src/state/persistence/session-persister.test.ts +325 -0
  134. package/src/state/persistence/session-persister.ts +622 -0
  135. package/src/state/persistence/session-snapshot.test.ts +339 -0
  136. package/src/state/persistence/session-snapshot.ts +332 -0
  137. package/src/utils/history-transform.test.ts +197 -0
  138. package/src/utils/history-transform.ts +117 -0
  139. package/src/utils/message-partition.test.ts +27 -1
  140. package/src/utils/message-partition.ts +41 -2
  141. package/src/utils/resolve-preference-baseline.test.ts +84 -0
  142. package/src/utils/resolve-preference-baseline.ts +80 -0
  143. package/src/utils/with-timeout.ts +24 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-snapshot.test.d.ts","sourceRoot":"","sources":["../../../../src/state/persistence/session-snapshot.test.ts"],"names":[],"mappings":""}
@@ -19,7 +19,11 @@ declare function makeStore(devTools: boolean | undefined): import("@genesislcap/
19
19
  setActiveAgent(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<Omit<import("..").AgentConfig, "toolHandlers"> | undefined>): void;
20
20
  setPinnedAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string | null>): void;
21
21
  setFlowOwnerAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string | null>): void;
22
+ setFlowActivationPrompt(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string | null>): void;
22
23
  setAgentPickerOpen(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<boolean>): void;
24
+ setSessionMenuOpen(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<boolean>): void;
25
+ setCompacting(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<boolean>): void;
26
+ setRestoring(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<boolean>): void;
23
27
  setInputValue(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string>): void;
24
28
  setLiveSubAgentTrace(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<import("@genesislcap/foundation-ai").ChatMessage[]>): void;
25
29
  setLiveSubAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<string | null>): void;
@@ -27,6 +31,8 @@ declare function makeStore(devTools: boolean | undefined): import("@genesislcap/
27
31
  removeInputOverride(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<{
28
32
  id: string;
29
33
  }>): void;
34
+ loadSession(state: import("immer").WritableDraft<AiAssistantSessionState>, action: import("@reduxjs/toolkit").PayloadAction<import("./ai-assistant-slice").LoadSessionPayload>): void;
35
+ resetSession(state: import("immer").WritableDraft<AiAssistantSessionState>): void;
30
36
  }, "aiAssistant", "aiAssistant", {}>[]>;
31
37
  /**
32
38
  * Returns the session store for the given key, creating one if it does not
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../src/state/session-store.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AAE9B,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAIvD,iBAAS,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;wCAM/C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAMnF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,CAAC"}
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../../src/state/session-store.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AAE9B,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAIvD,iBAAS,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wCAM/C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAMnF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,CAAC"}
@@ -10,4 +10,42 @@ export declare function transformHistoryForAgent(history: ChatMessage[], agentNa
10
10
  * verbatim; older messages have their tool payloads masked.
11
11
  */
12
12
  export declare function applyHistoryCap(history: readonly ChatMessage[], cap: number): ChatMessage[];
13
+ /** Recent messages kept verbatim when compacting; everything earlier is summarized. */
14
+ export declare const COMPACT_KEEP_RECENT_MESSAGES = 4;
15
+ /** Don't bother compacting unless at least this many messages would be summarized. */
16
+ export declare const COMPACT_MIN_MESSAGES_TO_COMPACT = 4;
17
+ /**
18
+ * Index at which the verbatim tail begins — everything before it gets summarized.
19
+ * The tail starts on a clean turn boundary (a `user` message): a user message
20
+ * never appears between a `tool_use` and its `tool_result`, so cutting there
21
+ * guarantees the summarized region is tool-pair-balanced (compaction only runs
22
+ * when idle, so history is turn-complete). Returns `null` when there is nothing
23
+ * worth compacting (too short, or no earlier user boundary) — e.g. a freshly
24
+ * restored session whose only user turn is the current one.
25
+ */
26
+ export declare function findCompactionCut(history: readonly ChatMessage[]): number | null;
27
+ /**
28
+ * Build the one-shot summarizer prompt for a run of older messages (used with
29
+ * `AIProvider.prompt`). Mirrors the `<conversation_history>` block convention
30
+ * that suggestions use. Any existing `compacted-summary` at the head is folded
31
+ * in (rendered as prior-summary context), so rolling re-compaction produces one
32
+ * combined summary.
33
+ */
34
+ export declare function buildCompactionSummaryPrompt(toCompact: readonly ChatMessage[]): {
35
+ systemPrompt: string;
36
+ userMessage: string;
37
+ };
38
+ /**
39
+ * Normalize history for a provider request (GENC-1351 §5.7): translate each
40
+ * `compacted-summary` message into a `user` message that wraps its summary in a
41
+ * `<conversation_summary>` block, so the raw display-only role never reaches a
42
+ * transport. Other roles pass through untouched. Pure — returns a new array.
43
+ *
44
+ * Applied at the request-build chokepoint *before* per-agent masking, so the
45
+ * guarantee holds for every provider/transport regardless of any optional
46
+ * per-agent history transform.
47
+ */
48
+ export declare function normalizeForProvider(history: readonly ChatMessage[]): ChatMessage[];
49
+ /** Human-facing divider label for a `compacted-summary` message. */
50
+ export declare function compactionDividerLabel(m: ChatMessage): string;
13
51
  //# sourceMappingURL=history-transform.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"history-transform.d.ts","sourceRoot":"","sources":["../../../src/utils/history-transform.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAoB9D;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE,CAKjG;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,EAAE,CAG3F"}
1
+ {"version":3,"file":"history-transform.d.ts","sourceRoot":"","sources":["../../../src/utils/history-transform.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAoB9D;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE,CAKjG;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,EAAE,CAG3F;AAID,uFAAuF;AACvF,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,sFAAsF;AACtF,eAAO,MAAM,+BAA+B,IAAI,CAAC;AAEjD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,GAAG,IAAI,CAWhF;AAkCD;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,SAAS,WAAW,EAAE,GAAG;IAC/E,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB,CAYA;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,EAAE,CAanF;AAED,oEAAoE;AACpE,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAG7D"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=history-transform.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history-transform.test.d.ts","sourceRoot":"","sources":["../../../src/utils/history-transform.test.ts"],"names":[],"mappings":""}
@@ -1,4 +1,24 @@
1
1
  import type { ChatMessage } from '@genesislcap/foundation-ai';
2
+ /**
3
+ * Display-only session-restore notices (GENC-1351 §6): the "Restored…" divider on
4
+ * a successful reload restore, and the "Couldn't restore…" divider when a saved
5
+ * session existed but couldn't be brought back (timeout / error / unusable
6
+ * snapshot). Both are `system-event` rows, stripped from the persisted snapshot so
7
+ * they never become permanent history — and, unlike agent-switch indicators, they
8
+ * belong to the persistence ("remember chat") feature, so `filterVisibleMessages`
9
+ * gates them by `showRestoreNotices` rather than `showAgentSwitchIndicator`.
10
+ *
11
+ * @internal
12
+ */
13
+ export declare const SESSION_RESTORED_LABEL = "Restored previous conversation";
14
+ /** @internal */
15
+ export declare const SESSION_RESTORE_FAILED_LABEL = "Couldn't restore the previous conversation";
16
+ /** @internal */
17
+ export declare const isSessionRestoredMarker: (m: ChatMessage) => boolean;
18
+ /** @internal */
19
+ export declare const isSessionRestoreFailedMarker: (m: ChatMessage) => boolean;
20
+ /** Either restore notice — both display-only, stripped before persist. @internal */
21
+ export declare const isSessionNoticeMarker: (m: ChatMessage) => boolean;
2
22
  /**
3
23
  * Toggle state that drives which messages appear in the scrolling list. Mirrors
4
24
  * the cog-menu switches; `showAgentSwitchIndicator` is pre-resolved by the
@@ -10,6 +30,13 @@ export interface MessageFilterState {
10
30
  showThinkingSteps: boolean;
11
31
  showNarration: boolean;
12
32
  showAgentSwitchIndicator: boolean;
33
+ /**
34
+ * Whether session-restore notices show — driven by the persistence ("remember
35
+ * chat") toggle, since restore is that feature's concern (GENC-1351 §6). Kept
36
+ * separate from `showAgentSwitchIndicator` so those two unrelated switches don't
37
+ * cross-control each other.
38
+ */
39
+ showRestoreNotices: boolean;
13
40
  }
14
41
  /**
15
42
  * The trailing interaction to render in a pinned slot, separate from the
@@ -1 +1 @@
1
- {"version":3,"file":"message-partition.d.ts","sourceRoot":"","sources":["../../../src/utils/message-partition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,OAAO,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,wBAAwB,EAAE,OAAO,CAAC;CACnC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAG7E;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,EAAE,kBAAkB,GAC1B,WAAW,EAAE,CA8Bf"}
1
+ {"version":3,"file":"message-partition.d.ts","sourceRoot":"","sources":["../../../src/utils/message-partition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,mCAAmC,CAAC;AACvE,gBAAgB;AAChB,eAAO,MAAM,4BAA4B,+CAA+C,CAAC;AACzF,gBAAgB;AAChB,eAAO,MAAM,uBAAuB,GAAI,GAAG,WAAW,KAAG,OACU,CAAC;AACpE,gBAAgB;AAChB,eAAO,MAAM,4BAA4B,GAAI,GAAG,WAAW,KAAG,OACW,CAAC;AAC1E,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,GAAI,GAAG,WAAW,KAAG,OACQ,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,OAAO,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,wBAAwB,EAAE,OAAO,CAAC;IAClC;;;;;OAKG;IACH,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAG7E;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,EAAE,kBAAkB,GAC1B,WAAW,EAAE,CAsCf"}
@@ -0,0 +1,43 @@
1
+ import type { ChatConfig } from '@genesislcap/foundation-ai';
2
+ import { type AiAssistantAnimation } from '../main/main.types';
3
+ import type { SessionPreferences } from '../state/persistence/session-persistence-provider';
4
+ /**
5
+ * The effective user-facing preference state for a freshly created session store
6
+ * (GENC-1351 #2), resolved from the two inputs that feed it.
7
+ *
8
+ * @internal
9
+ */
10
+ export interface ResolvedPreferenceBaseline {
11
+ showToolCalls: boolean;
12
+ showThinkingSteps: boolean;
13
+ showNarration: boolean;
14
+ showAgentSwitchIndicator: boolean;
15
+ enabledAnimations: AiAssistantAnimation[];
16
+ /**
17
+ * The remembered "remember chat" toggle — present only when stored prefs carried
18
+ * an `enabled` value. Left `undefined` otherwise so the caller keeps the
19
+ * host-configured default rather than forcing a value.
20
+ */
21
+ enabled?: boolean;
22
+ }
23
+ /**
24
+ * Resolve the effective preference baseline for a fresh store, applying the
25
+ * precedence (lowest → highest):
26
+ *
27
+ * slice defaults < `chatConfig.ui` defaults < stored user preferences
28
+ *
29
+ * The `chatConfig.ui` layer is the fallback when the user has no stored prefs
30
+ * (e.g. a first-ever load, or after the persistence backend is wiped) — the case
31
+ * the old "read config once on connect" path got wrong whenever `chatConfig` had
32
+ * not bound yet. Stored prefs, when present, override only the fields they carry;
33
+ * absent fields keep the config default. Pure, so the precedence is unit-testable
34
+ * independent of the element/store wiring.
35
+ *
36
+ * Config-supplied animations are normalised through {@link resolveExclusiveLoadingStyle}
37
+ * (a raw config list may name both loading styles); stored animations are applied
38
+ * as-is, since they were already normalised when saved.
39
+ *
40
+ * @internal
41
+ */
42
+ export declare function resolvePreferenceBaseline(ui: ChatConfig['ui'], stored: SessionPreferences | null | undefined): ResolvedPreferenceBaseline;
43
+ //# sourceMappingURL=resolve-preference-baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-preference-baseline.d.ts","sourceRoot":"","sources":["../../../src/utils/resolve-preference-baseline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAsB,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mDAAmD,CAAC;AAG5F;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IACzC,aAAa,EAAE,OAAO,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,wBAAwB,EAAE,OAAO,CAAC;IAClC,iBAAiB,EAAE,oBAAoB,EAAE,CAAC;IAC1C;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC,EACpB,MAAM,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,GAC5C,0BAA0B,CAgC5B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=resolve-preference-baseline.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-preference-baseline.test.d.ts","sourceRoot":"","sources":["../../../src/utils/resolve-preference-baseline.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Resolve `promise`, or **reject** if it doesn't settle within `ms`. Rejecting
3
+ * (rather than resolving to a fallback) lets restore tell a genuine timeout apart
4
+ * from a clean "nothing stored" result, so it can surface a notice for the former
5
+ * and stay silent for the latter. Because the loser of the race is abandoned, a
6
+ * late-resolving `load()` is discarded and can never clobber a now-live session;
7
+ * a late *rejection* is swallowed so it doesn't surface as an unhandled rejection.
8
+ *
9
+ * @internal
10
+ */
11
+ export declare function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T>;
12
+ //# sourceMappingURL=with-timeout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with-timeout.d.ts","sourceRoot":"","sources":["../../../src/utils/with-timeout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAahF"}
@@ -0,0 +1,93 @@
1
+ import { __awaiter } from "tslib";
2
+ import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
3
+ // Side-effect import — MUST come before `./chat-driver` so the driver subclasses
4
+ // jsdom's EventTarget rather than Node's native one (CustomEvent dispatch then
5
+ // works). Mirrors `chat-driver.test.ts`.
6
+ import './align-event-globals';
7
+ import { ChatDriver } from './chat-driver';
8
+ // GENC-1351 §5.7 — driver-level compaction: the atomic, destructive replace.
9
+ // A minimal fake registry stands in for a provider, so no network is involved.
10
+ const asMsg = (o) => o;
11
+ /** A registry whose default provider summarizes via `prompt` (undefined = unsupported). */
12
+ function registryWith(prompt) {
13
+ const provider = { prompt, getStatus: () => __awaiter(this, void 0, void 0, function* () { return ({ model: 'test-model' }); }) };
14
+ return { default: () => provider };
15
+ }
16
+ /** 10 messages alternating user/assistant (user at even indices); last carries usage. */
17
+ function tenTurnHistory() {
18
+ return Array.from({ length: 10 }, (_, i) => asMsg(Object.assign({ role: i % 2 === 0 ? 'user' : 'assistant', content: `m${i}`, timestamp: `2026-01-01T00:00:${String(i).padStart(2, '0')}.000Z` }, (i === 9 ? { inputTokens: 1234 } : {}))));
19
+ }
20
+ const Suite = createLogicSuite('chat-driver-compact');
21
+ Suite('compact() destructively replaces older turns with one head summary + metadata', () => __awaiter(void 0, void 0, void 0, function* () {
22
+ var _a, _b, _c;
23
+ const driver = new ChatDriver(registryWith(() => __awaiter(void 0, void 0, void 0, function* () { return 'THE SUMMARY'; })));
24
+ driver.loadHistory(tenTurnHistory());
25
+ const before = driver.getHistory().length;
26
+ const summary = yield driver.compact();
27
+ assert.ok(summary, 'returns the created summary message');
28
+ assert.is(summary.role, 'compacted-summary');
29
+ assert.is(summary.content, 'THE SUMMARY');
30
+ assert.is((_a = summary.compaction) === null || _a === void 0 ? void 0 : _a.tokensBefore, 1234, 'captures pre-compaction input tokens');
31
+ assert.is((_b = summary.compaction) === null || _b === void 0 ? void 0 : _b.model, 'test-model', 'records the summarizing model');
32
+ const after = driver.getHistory();
33
+ assert.ok(after.length < before, 'history shrank (destructive)');
34
+ assert.is(after[0].role, 'compacted-summary', 'summary sits at the head');
35
+ assert.is(after[0].timestamp, '2026-01-01T00:00:00.000Z', 'summary inherits the covered region start → sorts to the head in time-ordered views');
36
+ assert.is((_c = after[0].compaction) === null || _c === void 0 ? void 0 : _c.compactedCount, before - (after.length - 1));
37
+ assert.ok(after.slice(1).every((m) => m.role !== 'compacted-summary'), 'exactly one summary — no marker chain');
38
+ }));
39
+ Suite('compact() is atomic on failure — no splice, surfaces a "sorry" message', () => __awaiter(void 0, void 0, void 0, function* () {
40
+ const driver = new ChatDriver(registryWith(() => __awaiter(void 0, void 0, void 0, function* () {
41
+ throw new Error('network');
42
+ })));
43
+ const original = tenTurnHistory();
44
+ driver.loadHistory(original);
45
+ const result = yield driver.compact();
46
+ assert.is(result, null, 'returns null on failure');
47
+ const after = driver.getHistory();
48
+ assert.ok(after.every((m) => m.role !== 'compacted-summary'), 'nothing compacted');
49
+ // The covered turns are untouched (atomic); only a brief apology is appended so
50
+ // the user knows it failed (GENC-1351 follow-up — surfaced like a turn error).
51
+ assert.is(after.length, original.length + 1, 'only an apology appended');
52
+ assert.is(after[after.length - 1].role, 'assistant');
53
+ assert.ok(/sorry/i.test(after[after.length - 1].content), 'apology is an agent message');
54
+ }));
55
+ Suite('compact() rolls a prior summary into a single fresh one', () => __awaiter(void 0, void 0, void 0, function* () {
56
+ const driver = new ChatDriver(registryWith(() => __awaiter(void 0, void 0, void 0, function* () { return 'COMBINED'; })));
57
+ driver.loadHistory([
58
+ asMsg({ role: 'compacted-summary', content: 'old', compaction: { compactedCount: 5 } }),
59
+ ...tenTurnHistory(),
60
+ ]);
61
+ const summary = yield driver.compact();
62
+ assert.ok(summary);
63
+ const after = driver.getHistory();
64
+ assert.is(after.filter((m) => m.role === 'compacted-summary').length, 1, 'still exactly one summary after re-compaction');
65
+ assert.is(after[0].content, 'COMBINED');
66
+ }));
67
+ Suite('compact() no-ops when there is nothing worth compacting', () => __awaiter(void 0, void 0, void 0, function* () {
68
+ const driver = new ChatDriver(registryWith(() => __awaiter(void 0, void 0, void 0, function* () { return 'unused'; })));
69
+ driver.loadHistory([
70
+ asMsg({ role: 'user', content: 'hi' }),
71
+ asMsg({ role: 'assistant', content: 'yo' }),
72
+ ]);
73
+ assert.is(yield driver.compact(), null);
74
+ assert.is(driver.getHistory().length, 2, 'short history untouched');
75
+ }));
76
+ Suite('compact() no-ops when the default provider cannot summarize', () => __awaiter(void 0, void 0, void 0, function* () {
77
+ const driver = new ChatDriver(registryWith(undefined));
78
+ driver.loadHistory(tenTurnHistory());
79
+ assert.is(yield driver.compact(), null);
80
+ assert.is(driver.getHistory().length, 10, 'history untouched without prompt support');
81
+ }));
82
+ Suite("canCompact reflects compact()-would-act, keyed on the driver's own history", () => {
83
+ const rich = new ChatDriver(registryWith(() => __awaiter(void 0, void 0, void 0, function* () { return 'x'; })));
84
+ rich.loadHistory(tenTurnHistory());
85
+ assert.is(rich.canCompact(), true, 'enough history → gate open');
86
+ const thin = new ChatDriver(registryWith(() => __awaiter(void 0, void 0, void 0, function* () { return 'x'; })));
87
+ thin.loadHistory([
88
+ asMsg({ role: 'user', content: 'hi' }),
89
+ asMsg({ role: 'assistant', content: 'yo' }),
90
+ ]);
91
+ assert.is(thin.canCompact(), false, 'too short → gate closed (same source compact() uses)');
92
+ });
93
+ Suite.run();
@@ -4,7 +4,7 @@ import { agenticActivityBus } from '../../channel/ai-activity-bus';
4
4
  import { resolveChatProvider } from '../../config/validate-providers';
5
5
  import { clearSession, getMetaEvents, mergeMetaEvents, recordMetaEvent, recordTurnError, recordTurnRetry, } from '../../state/debug-event-log';
6
6
  import { applyCondensation } from '../../utils/condense-history';
7
- import { applyHistoryCap } from '../../utils/history-transform';
7
+ import { applyHistoryCap, buildCompactionSummaryPrompt, findCompactionCut, normalizeForProvider, } from '../../utils/history-transform';
8
8
  import { logger } from '../../utils/logger';
9
9
  import { TOOL_FOLD_SYMBOL } from '../../utils/tool-fold';
10
10
  const DEFAULT_MAX_TOOL_ITERATIONS = 50;
@@ -765,6 +765,89 @@ export class ChatDriver extends EventTarget {
765
765
  .slice(0, count));
766
766
  });
767
767
  }
768
+ /**
769
+ * Whether `compact()` would do anything right now — a summarizable run of older
770
+ * turns exists behind a clean boundary. Uses the same `history` `compact()`
771
+ * acts on, so the UI's gate can't disagree with the action (GENC-1351 follow-up).
772
+ */
773
+ canCompact() {
774
+ return findCompactionCut(this.history) != null;
775
+ }
776
+ /**
777
+ * Destructively compact older turns into a single `compacted-summary` message
778
+ * (GENC-1351 §5.7). Summarizes everything before a clean recent-tail boundary
779
+ * via the registry-default provider's one-shot `prompt()`, then **atomically**
780
+ * replaces the covered turns with one summary message at the head — history is
781
+ * mutated only on success. Rolling: any existing head summary is folded into
782
+ * the new one. Returns the created summary message, or `null` when there is
783
+ * nothing worth compacting or the default provider cannot summarize.
784
+ */
785
+ compact() {
786
+ return __awaiter(this, void 0, void 0, function* () {
787
+ var _a, _b, _c, _d, _e, _f;
788
+ if (this.busy)
789
+ return null;
790
+ const defaultProvider = this.providerRegistry.default();
791
+ if (!defaultProvider.prompt) {
792
+ logger.warn('ChatDriver: default AI provider has no prompt(); cannot compact');
793
+ return null;
794
+ }
795
+ const history = this.history;
796
+ const cut = findCompactionCut(history);
797
+ if (cut == null)
798
+ return null;
799
+ const toCompact = history.slice(0, cut);
800
+ const tail = history.slice(cut);
801
+ // Summarize BEFORE mutating history — a failed/empty call leaves the
802
+ // transcript untouched (atomic generate-then-replace).
803
+ const { systemPrompt, userMessage } = buildCompactionSummaryPrompt(toCompact);
804
+ // Surface a failure the way turn errors are surfaced — a brief "sorry" agent
805
+ // message — while leaving the transcript itself untouched (atomic). Retrying
806
+ // is safe (nothing was spliced).
807
+ const failMessage = "Sorry, I couldn't compact the conversation just now. Nothing was changed — please try again.";
808
+ let summaryText;
809
+ try {
810
+ summaryText = (_b = (_a = (yield defaultProvider.prompt(userMessage, { systemPrompt }))) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : '';
811
+ }
812
+ catch (e) {
813
+ logger.warn(`ChatDriver: compaction summary failed: ${e instanceof Error ? e.message : e}`);
814
+ this.appendToHistory({ role: 'assistant', content: failMessage });
815
+ return null;
816
+ }
817
+ if (!summaryText) {
818
+ logger.warn('ChatDriver: compaction summary was empty; leaving history untouched');
819
+ this.appendToHistory({ role: 'assistant', content: failMessage });
820
+ return null;
821
+ }
822
+ let tokensBefore;
823
+ for (let i = history.length - 1; i >= 0; i -= 1) {
824
+ if (history[i].inputTokens != null) {
825
+ tokensBefore = history[i].inputTokens;
826
+ break;
827
+ }
828
+ }
829
+ const status = yield ((_c = defaultProvider.getStatus) === null || _c === void 0 ? void 0 : _c.call(defaultProvider));
830
+ const createdAt = new Date().toISOString();
831
+ const summaryMessage = {
832
+ role: 'compacted-summary',
833
+ content: summaryText,
834
+ // Stamp with the covered region's start, not "now": the summary stands in
835
+ // for the oldest messages and sits at the head of history, so this keeps it
836
+ // sorted to the head in any timestamp-ordered view (the debug timeline) and
837
+ // history time-monotonic. Its true creation time is in compaction.createdAt.
838
+ timestamp: (_e = (_d = toCompact[0]) === null || _d === void 0 ? void 0 : _d.timestamp) !== null && _e !== void 0 ? _e : createdAt,
839
+ compaction: {
840
+ compactedCount: toCompact.length,
841
+ coveredThroughTimestamp: (_f = toCompact[toCompact.length - 1]) === null || _f === void 0 ? void 0 : _f.timestamp,
842
+ tokensBefore,
843
+ model: status === null || status === void 0 ? void 0 : status.model,
844
+ createdAt,
845
+ },
846
+ };
847
+ this.loadHistory([summaryMessage, ...tail]);
848
+ return summaryMessage;
849
+ });
850
+ }
768
851
  isBusy() {
769
852
  return this.busy;
770
853
  }
@@ -1595,9 +1678,11 @@ export class ChatDriver extends EventTarget {
1595
1678
  const primer = [...((_b = this.primerHistory) !== null && _b !== void 0 ? _b : []), ...(transientPrimer !== null && transientPrimer !== void 0 ? transientPrimer : [])];
1596
1679
  const baseHistory = firstLlmCall ? this.history.slice(0, -1) : this.history;
1597
1680
  firstLlmCall = false;
1598
- // Collapse stale tool payloads this agent declared via `condenseWhen`, then
1599
- // pipe through any externally-set transform (e.g. multi-agent masking).
1600
- // Both run on a copy — stored history is untouched.
1681
+ // Collapse stale tool payloads this agent declared via `condenseWhen`
1682
+ // (GENC-1369), then translate any compacted-summary → a
1683
+ // <conversation_summary> user message (GENC-1351 §5.7) before any per-agent
1684
+ // masking, so the raw display-only role can never reach a transport. Each
1685
+ // step runs on a copy — stored history is untouched.
1601
1686
  const condensedHistory = applyCondensation([...baseHistory], this.condensePolicies, {
1602
1687
  modelCall: this.modelCallSeq,
1603
1688
  turn: this.turnSeq,
@@ -1608,9 +1693,10 @@ export class ChatDriver extends EventTarget {
1608
1693
  // current via `endPhase()`.
1609
1694
  phaseEnded: (phaseEpoch) => phaseEpoch < this.currentPhaseEpoch,
1610
1695
  }, (detail) => recordMetaEvent(this.sessionKey, 'context.condensed', detail));
1696
+ const normalizedHistory = normalizeForProvider(condensedHistory);
1611
1697
  const historyForProvider = this.providerHistoryTransform
1612
- ? this.providerHistoryTransform(condensedHistory)
1613
- : condensedHistory;
1698
+ ? this.providerHistoryTransform(normalizedHistory)
1699
+ : normalizedHistory;
1614
1700
  const historyForCall = [...primer, ...historyForProvider];
1615
1701
  // Resolve the per-turn temperature and tool-call mode the same way the
1616
1702
  // provider is resolved — static value or a function of the turn context
@@ -83,6 +83,13 @@ export class OrchestratingDriver extends EventTarget {
83
83
  * choice.
84
84
  */
85
85
  this.flowOwnerAgentName = null;
86
+ /**
87
+ * Per-agent restored state for a rehydrated session, keyed by agent name.
88
+ * Primed by the host on restore; each entry is passed to that agent's
89
+ * `onActivate` as `ctx.restored` on its next activation, then deleted so a
90
+ * later re-activation starts fresh.
91
+ */
92
+ this.restoredAgentStates = {};
86
93
  this.sessionKey = (_a = options.sessionKey) !== null && _a !== void 0 ? _a : '';
87
94
  this.maxHandoffs = (_b = options.maxHandoffs) !== null && _b !== void 0 ? _b : DEFAULT_MAX_HANDOFFS;
88
95
  this.classifierHistoryLength =
@@ -174,6 +181,14 @@ export class OrchestratingDriver extends EventTarget {
174
181
  getRawHistory() {
175
182
  return this.chatDriver.getHistory();
176
183
  }
184
+ /** {@inheritDoc AiDriver.compact} */
185
+ compact() {
186
+ return this.chatDriver.compact();
187
+ }
188
+ /** {@inheritDoc AiDriver.canCompact} */
189
+ canCompact() {
190
+ return this.chatDriver.canCompact();
191
+ }
177
192
  /** Delegates to the inner {@link ChatDriver} — turns are captured there. */
178
193
  getTurnSnapshots() {
179
194
  return this.chatDriver.getTurnSnapshots();
@@ -267,6 +282,14 @@ export class OrchestratingDriver extends EventTarget {
267
282
  return this.chatDriver.continueFromHistory(transientPrimer);
268
283
  });
269
284
  }
285
+ /** {@inheritDoc AiDriver.primeRestoredAgentState} */
286
+ primeRestoredAgentState(snapshots) {
287
+ this.restoredAgentStates = Object.assign({}, snapshots);
288
+ }
289
+ /** {@inheritDoc AiDriver.getActiveAgent} */
290
+ getActiveAgent() {
291
+ return this.activeAgent;
292
+ }
270
293
  applyAgent(agent) {
271
294
  return __awaiter(this, void 0, void 0, function* () {
272
295
  var _a;
@@ -278,6 +301,8 @@ export class OrchestratingDriver extends EventTarget {
278
301
  recordMetaEvent(this.sessionKey, 'agent.handoff', {
279
302
  from: (_a = previousAgent === null || previousAgent === void 0 ? void 0 : previousAgent.name) !== null && _a !== void 0 ? _a : null,
280
303
  to: agent.name,
304
+ // GENC-1351: was a restored snapshot waiting for this agent at activation?
305
+ restored: this.restoredAgentStates[agent.name] !== undefined,
281
306
  });
282
307
  }
283
308
  // Fire lifecycle hooks around the swap — outgoing first, then incoming.
@@ -296,12 +321,19 @@ export class OrchestratingDriver extends EventTarget {
296
321
  }
297
322
  }
298
323
  if (isSwitch && agent.onActivate) {
324
+ // On a session restore, hand this agent its persisted state exactly once
325
+ // (consumed here) so a stateful machine resumes at the right step; a later
326
+ // re-activation of the same agent then starts fresh.
327
+ const restored = this.restoredAgentStates[agent.name];
328
+ if (restored !== undefined)
329
+ delete this.restoredAgentStates[agent.name];
299
330
  try {
300
331
  yield agent.onActivate({
301
332
  agentName: agent.name,
302
333
  sessionKey: this.sessionKey,
303
334
  previousAgentName: previousAgent === null || previousAgent === void 0 ? void 0 : previousAgent.name,
304
335
  signal: this.lifecycleAbortController.signal,
336
+ restored,
305
337
  });
306
338
  }
307
339
  catch (e) {
@@ -0,0 +1,48 @@
1
+ import { __awaiter } from "tslib";
2
+ import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
3
+ // Side-effect import — MUST precede `./orchestrating-driver` so the driver
4
+ // subclasses jsdom's EventTarget (CustomEvent dispatch then works in node).
5
+ import '../chat-driver/align-event-globals';
6
+ import { OrchestratingDriver } from './orchestrating-driver';
7
+ // GENC-1351 — a set routing pin must be honoured on the next turn. This is the
8
+ // invariant that broke on restore (found via the §5.8 diagnostics log): the pin
9
+ // was hydrated into the store but never pushed to the driver, so the first
10
+ // post-restore message fell through to the classifier and re-routed away from the
11
+ // pinned agent. `Guided Booking` here is `excludeFromClassifier`, so the classifier
12
+ // can NEVER pick it — a turn landing on it proves the pin (not classification)
13
+ // routed it. The element-side fix (restore → `driver.setPinnedAgent`) is what makes
14
+ // the pin reach the driver; this locks the driver half of the contract.
15
+ const makeRegistry = (provider) => ({
16
+ get: () => provider,
17
+ default: () => provider,
18
+ defaultName: () => 'test',
19
+ names: () => ['test'],
20
+ getStatus: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
21
+ listStatuses: () => __awaiter(void 0, void 0, void 0, function* () { return []; }),
22
+ });
23
+ /** A provider that ends every turn immediately with a plain-text reply (no tools). */
24
+ const noToolProvider = () => ({
25
+ chat: (_history, _userMessage, _options) => __awaiter(void 0, void 0, void 0, function* () { return ({ role: 'assistant', content: 'ok' }); }),
26
+ });
27
+ const agent = (overrides) => (Object.assign({ description: 'test agent' }, overrides));
28
+ const makeDriver = () => new OrchestratingDriver(makeRegistry(noToolProvider()), [
29
+ agent({ name: 'Genesis Assistant' }), // classifier-eligible specialist
30
+ agent({ name: 'Guided Booking', excludeFromClassifier: true }), // pin-only
31
+ ], { sessionKey: 'k' });
32
+ const Suite = createLogicSuite('orchestrating-driver-pin');
33
+ Suite('a set pin routes the next turn to the pinned agent (bypassing the classifier)', () => __awaiter(void 0, void 0, void 0, function* () {
34
+ var _a;
35
+ const driver = makeDriver();
36
+ driver.setPinnedAgent('Guided Booking');
37
+ yield driver.sendMessage('hi');
38
+ assert.is((_a = driver.getActiveAgent()) === null || _a === void 0 ? void 0 : _a.name, 'Guided Booking', 'pinned agent ran; it is excludeFromClassifier, so only the pin could have routed it');
39
+ }));
40
+ Suite('the pin is sticky across turns', () => __awaiter(void 0, void 0, void 0, function* () {
41
+ var _a;
42
+ const driver = makeDriver();
43
+ driver.setPinnedAgent('Guided Booking');
44
+ yield driver.sendMessage('one');
45
+ yield driver.sendMessage('two');
46
+ assert.is((_a = driver.getActiveAgent()) === null || _a === void 0 ? void 0 : _a.name, 'Guided Booking', 'still pinned on the second turn');
47
+ }));
48
+ Suite.run();
@@ -509,4 +509,28 @@ export const settingsModalStyles = css `
509
509
  max-width: 60%;
510
510
  line-height: 1.4;
511
511
  }
512
+
513
+ /*
514
+ * Inline spinner shown in the "Download agent log" button while the log builds
515
+ * (GENC-1351 §5.8) — the lifetime-stitch path is an async provider round-trip.
516
+ * Sized to the button label and tinted with currentColor so it tracks the
517
+ * button's text colour in every appearance / theme.
518
+ */
519
+ [part='download-button'] .download-spinner {
520
+ display: inline-block;
521
+ width: 12px;
522
+ height: 12px;
523
+ margin-right: calc(var(--design-unit) * 1px);
524
+ vertical-align: -1px;
525
+ border: 2px solid currentColor;
526
+ border-right-color: transparent;
527
+ border-radius: 50%;
528
+ animation: download-spin 0.6s linear infinite;
529
+ }
530
+
531
+ @keyframes download-spin {
532
+ to {
533
+ transform: rotate(360deg);
534
+ }
535
+ }
512
536
  `;
@@ -121,6 +121,9 @@ const chatBotSettingsTemplate = (switchTag, buttonTag) => html `
121
121
  }))}
122
122
  ${when((x) => { var _a; return ((_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.showAgentSwitchIndicator) != null; }, chatBotToggleRow('Show delegation', 'Show in the chat window when tasks are delegated to different agents.', switchTag, 'toggle-agent-switch', (x) => x.showAgentSwitchIndicator, (x, c) => {
123
123
  x.showAgentSwitchIndicator = readSwitchChecked(c.event);
124
+ }))}
125
+ ${when((x) => x.persistenceToggleable, chatBotToggleRow('Remember chat', 'Save this conversation so it can be restored after a page refresh. Turning this off deletes the saved copy.', switchTag, 'toggle-remember-chat', (x) => x.persistenceActive, (x, c) => {
126
+ x.setPersistenceEnabled(readSwitchChecked(c.event));
124
127
  }))}
125
128
  ${when((x) => { var _a, _b; return ((_b = (_a = x.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.animations) === null || _b === void 0 ? void 0 : _b.userConfigurable) === true; }, settingsToggleRow('Animations', 'Choose which loading and activity indicators appear while the assistant is working.', 'toggle-animations', animationsPickerTemplate(switchTag), 'column'))}
126
129
  `;
@@ -319,8 +322,18 @@ export const SettingsModalTemplate = (designSystemPrefix) => {
319
322
  <${buttonTag}
320
323
  part="download-button"
321
324
  appearance="outline"
325
+ ?disabled=${(x) => x.downloadingDebugLog}
322
326
  @click=${(x) => x.downloadDebugLog()}
323
- >Download</${buttonTag}>
327
+ >
328
+ ${when((x) => x.downloadingDebugLog, html `
329
+ <span
330
+ class="download-spinner"
331
+ part="download-spinner"
332
+ aria-hidden="true"
333
+ ></span>
334
+ `)}
335
+ ${(x) => (x.downloadingDebugLog ? 'Downloading…' : 'Download')}
336
+ </${buttonTag}>
324
337
  `)}
325
338
  </div>
326
339
  </section>