@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.
- package/dist/ai-assistant.api.json +5104 -1727
- package/dist/ai-assistant.d.ts +691 -102
- package/dist/custom-elements.json +1118 -9
- package/dist/dts/components/ai-driver/ai-driver.d.ts +32 -0
- package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts +2 -0
- package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts.map +1 -0
- package/dist/dts/components/chat-driver/chat-driver.d.ts +16 -0
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +15 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts +2 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts.map +1 -0
- package/dist/dts/components/settings-modal/settings-modal.styles.d.ts.map +1 -1
- package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
- package/dist/dts/config/config.d.ts +70 -0
- package/dist/dts/config/config.d.ts.map +1 -1
- package/dist/dts/config/define-stateful-agent.d.ts +73 -1
- package/dist/dts/config/define-stateful-agent.d.ts.map +1 -1
- package/dist/dts/index.d.ts +1 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +183 -63
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.styles.d.ts.map +1 -1
- package/dist/dts/main/main.template.d.ts.map +1 -1
- package/dist/dts/main/main.types.d.ts +55 -0
- package/dist/dts/main/main.types.d.ts.map +1 -1
- package/dist/dts/state/ai-assistant-slice.d.ts +66 -0
- package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
- package/dist/dts/state/ai-assistant-slice.test.d.ts +2 -0
- package/dist/dts/state/ai-assistant-slice.test.d.ts.map +1 -0
- package/dist/dts/state/debug-event-log.d.ts +1 -1
- package/dist/dts/state/debug-event-log.d.ts.map +1 -1
- package/dist/dts/state/persistence/diagnostics-cursors.d.ts +40 -0
- package/dist/dts/state/persistence/diagnostics-cursors.d.ts.map +1 -0
- package/dist/dts/state/persistence/diagnostics.d.ts +58 -0
- package/dist/dts/state/persistence/diagnostics.d.ts.map +1 -0
- package/dist/dts/state/persistence/diagnostics.test.d.ts +2 -0
- package/dist/dts/state/persistence/diagnostics.test.d.ts.map +1 -0
- package/dist/dts/state/persistence/index.d.ts +7 -0
- package/dist/dts/state/persistence/index.d.ts.map +1 -0
- package/dist/dts/state/persistence/persister-registry.d.ts +17 -0
- package/dist/dts/state/persistence/persister-registry.d.ts.map +1 -0
- package/dist/dts/state/persistence/session-persistence-provider.d.ts +132 -0
- package/dist/dts/state/persistence/session-persistence-provider.d.ts.map +1 -0
- package/dist/dts/state/persistence/session-persistence-provider.test.d.ts +2 -0
- package/dist/dts/state/persistence/session-persistence-provider.test.d.ts.map +1 -0
- package/dist/dts/state/persistence/session-persistence.integration.test.d.ts +2 -0
- package/dist/dts/state/persistence/session-persistence.integration.test.d.ts.map +1 -0
- package/dist/dts/state/persistence/session-persister.d.ts +154 -0
- package/dist/dts/state/persistence/session-persister.d.ts.map +1 -0
- package/dist/dts/state/persistence/session-persister.test.d.ts +2 -0
- package/dist/dts/state/persistence/session-persister.test.d.ts.map +1 -0
- package/dist/dts/state/persistence/session-snapshot.d.ts +209 -0
- package/dist/dts/state/persistence/session-snapshot.d.ts.map +1 -0
- package/dist/dts/state/persistence/session-snapshot.test.d.ts +2 -0
- package/dist/dts/state/persistence/session-snapshot.test.d.ts.map +1 -0
- package/dist/dts/state/session-store.d.ts +6 -0
- package/dist/dts/state/session-store.d.ts.map +1 -1
- package/dist/dts/utils/history-transform.d.ts +38 -0
- package/dist/dts/utils/history-transform.d.ts.map +1 -1
- package/dist/dts/utils/history-transform.test.d.ts +2 -0
- package/dist/dts/utils/history-transform.test.d.ts.map +1 -0
- package/dist/dts/utils/message-partition.d.ts +27 -0
- package/dist/dts/utils/message-partition.d.ts.map +1 -1
- package/dist/dts/utils/resolve-preference-baseline.d.ts +43 -0
- package/dist/dts/utils/resolve-preference-baseline.d.ts.map +1 -0
- package/dist/dts/utils/resolve-preference-baseline.test.d.ts +2 -0
- package/dist/dts/utils/resolve-preference-baseline.test.d.ts.map +1 -0
- package/dist/dts/utils/with-timeout.d.ts +12 -0
- package/dist/dts/utils/with-timeout.d.ts.map +1 -0
- package/dist/esm/components/chat-driver/chat-driver.compact.test.js +93 -0
- package/dist/esm/components/chat-driver/chat-driver.js +92 -6
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +32 -0
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.pin.test.js +48 -0
- package/dist/esm/components/settings-modal/settings-modal.styles.js +24 -0
- package/dist/esm/components/settings-modal/settings-modal.template.js +14 -1
- package/dist/esm/config/define-stateful-agent.js +81 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/main/main.js +610 -84
- package/dist/esm/main/main.styles.js +55 -0
- package/dist/esm/main/main.template.js +94 -5
- package/dist/esm/main/main.types.js +27 -0
- package/dist/esm/state/ai-assistant-slice.js +71 -25
- package/dist/esm/state/ai-assistant-slice.test.js +65 -0
- package/dist/esm/state/debug-event-log.js +8 -0
- package/dist/esm/state/persistence/diagnostics-cursors.js +34 -0
- package/dist/esm/state/persistence/diagnostics.js +81 -0
- package/dist/esm/state/persistence/diagnostics.test.js +72 -0
- package/dist/esm/state/persistence/index.js +3 -0
- package/dist/esm/state/persistence/persister-registry.js +41 -0
- package/dist/esm/state/persistence/session-persistence-provider.js +95 -0
- package/dist/esm/state/persistence/session-persistence-provider.test.js +110 -0
- package/dist/esm/state/persistence/session-persistence.integration.test.js +109 -0
- package/dist/esm/state/persistence/session-persister.js +577 -0
- package/dist/esm/state/persistence/session-persister.test.js +239 -0
- package/dist/esm/state/persistence/session-snapshot.js +231 -0
- package/dist/esm/state/persistence/session-snapshot.test.js +243 -0
- package/dist/esm/utils/history-transform.js +101 -0
- package/dist/esm/utils/history-transform.test.js +137 -0
- package/dist/esm/utils/message-partition.js +30 -2
- package/dist/esm/utils/message-partition.test.js +20 -1
- package/dist/esm/utils/resolve-preference-baseline.js +53 -0
- package/dist/esm/utils/resolve-preference-baseline.test.js +69 -0
- package/dist/esm/utils/with-timeout.js +28 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -17
- package/src/components/ai-driver/ai-driver.ts +36 -0
- package/src/components/chat-driver/chat-driver.compact.test.ts +131 -0
- package/src/components/chat-driver/chat-driver.ts +99 -6
- package/src/components/orchestrating-driver/orchestrating-driver.pin.test.ts +76 -0
- package/src/components/orchestrating-driver/orchestrating-driver.ts +36 -0
- package/src/components/settings-modal/settings-modal.styles.ts +24 -0
- package/src/components/settings-modal/settings-modal.template.ts +27 -1
- package/src/config/config.ts +71 -0
- package/src/config/define-stateful-agent.ts +134 -0
- package/src/index.ts +1 -0
- package/src/main/main.styles.ts +55 -0
- package/src/main/main.template.ts +121 -5
- package/src/main/main.ts +569 -64
- package/src/main/main.types.ts +59 -0
- package/src/state/ai-assistant-slice.test.ts +82 -0
- package/src/state/ai-assistant-slice.ts +120 -25
- package/src/state/debug-event-log.ts +18 -1
- package/src/state/persistence/diagnostics-cursors.ts +66 -0
- package/src/state/persistence/diagnostics.test.ts +95 -0
- package/src/state/persistence/diagnostics.ts +103 -0
- package/src/state/persistence/index.ts +10 -0
- package/src/state/persistence/persister-registry.ts +49 -0
- package/src/state/persistence/session-persistence-provider.test.ts +136 -0
- package/src/state/persistence/session-persistence-provider.ts +202 -0
- package/src/state/persistence/session-persistence.integration.test.ts +134 -0
- package/src/state/persistence/session-persister.test.ts +325 -0
- package/src/state/persistence/session-persister.ts +622 -0
- package/src/state/persistence/session-snapshot.test.ts +339 -0
- package/src/state/persistence/session-snapshot.ts +332 -0
- package/src/utils/history-transform.test.ts +197 -0
- package/src/utils/history-transform.ts +117 -0
- package/src/utils/message-partition.test.ts +27 -1
- package/src/utils/message-partition.ts +41 -2
- package/src/utils/resolve-preference-baseline.test.ts +84 -0
- package/src/utils/resolve-preference-baseline.ts +80 -0
- 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
|
|
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 @@
|
|
|
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;
|
|
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 @@
|
|
|
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
|
|
1599
|
-
//
|
|
1600
|
-
//
|
|
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(
|
|
1613
|
-
:
|
|
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
|
-
>
|
|
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>
|