@genesislcap/ai-assistant 15.19.0 → 15.19.1
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/package.json +17 -18
- package/api-extractor.json +0 -11
- package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.compact.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.invocation-scope.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.invocation-scope.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.thinking-policy.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.trace-capture.test.d.ts.map +0 -1
- package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts +0 -2
- package/dist/dts/components/chat-driver/chat-driver.turn-usage.test.d.ts.map +0 -1
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts +0 -2
- package/dist/dts/components/chat-interaction-wrapper/chat-interaction-wrapper.test.d.ts.map +0 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts +0 -2
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts.map +0 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts +0 -2
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.cost.test.d.ts.map +0 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts +0 -2
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.pin.test.d.ts.map +0 -1
- package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts +0 -2
- package/dist/dts/components/settings-modal/settings-modal.styles.test.d.ts.map +0 -1
- package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts +0 -2
- package/dist/dts/components/settings-modal/settings-modal.template.test.d.ts.map +0 -1
- package/dist/dts/config/define-stateful-agent.test.d.ts +0 -2
- package/dist/dts/config/define-stateful-agent.test.d.ts.map +0 -1
- package/dist/dts/config/validate-providers.test.d.ts +0 -2
- package/dist/dts/config/validate-providers.test.d.ts.map +0 -1
- package/dist/dts/main/blocked-state.test.d.ts +0 -2
- package/dist/dts/main/blocked-state.test.d.ts.map +0 -1
- package/dist/dts/main/budget-meter.test.d.ts +0 -2
- package/dist/dts/main/budget-meter.test.d.ts.map +0 -1
- package/dist/dts/main/cost-session-banking.test.d.ts +0 -2
- package/dist/dts/main/cost-session-banking.test.d.ts.map +0 -1
- package/dist/dts/main/file-attachments.test.d.ts +0 -2
- package/dist/dts/main/file-attachments.test.d.ts.map +0 -1
- package/dist/dts/main/interaction-cost.test.d.ts +0 -2
- package/dist/dts/main/interaction-cost.test.d.ts.map +0 -1
- package/dist/dts/main/main.styles.test.d.ts +0 -2
- package/dist/dts/main/main.styles.test.d.ts.map +0 -1
- package/dist/dts/main/persistence-broken-sources.test.d.ts +0 -2
- package/dist/dts/main/persistence-broken-sources.test.d.ts.map +0 -1
- package/dist/dts/main/popout-interaction-gate.test.d.ts +0 -2
- package/dist/dts/main/popout-interaction-gate.test.d.ts.map +0 -1
- package/dist/dts/state/ai-assistant-slice.test.d.ts +0 -2
- package/dist/dts/state/ai-assistant-slice.test.d.ts.map +0 -1
- package/dist/dts/state/debug-event-log.test.d.ts +0 -2
- package/dist/dts/state/debug-event-log.test.d.ts.map +0 -1
- package/dist/dts/state/driver-registry.test.d.ts +0 -2
- package/dist/dts/state/driver-registry.test.d.ts.map +0 -1
- package/dist/dts/state/interaction-context.test.d.ts +0 -2
- package/dist/dts/state/interaction-context.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/diagnostics-cursors.test.d.ts +0 -2
- package/dist/dts/state/persistence/diagnostics-cursors.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/diagnostics.test.d.ts +0 -2
- package/dist/dts/state/persistence/diagnostics.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-persistence-provider.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-persistence-provider.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-persistence.integration.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-persistence.integration.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-persister.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-persister.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/session-snapshot.test.d.ts +0 -2
- package/dist/dts/state/persistence/session-snapshot.test.d.ts.map +0 -1
- package/dist/dts/state/persistence/stateful-restore.e2e.test.d.ts +0 -2
- package/dist/dts/state/persistence/stateful-restore.e2e.test.d.ts.map +0 -1
- package/dist/dts/utils/animation-exclusivity.test.d.ts +0 -2
- package/dist/dts/utils/animation-exclusivity.test.d.ts.map +0 -1
- package/dist/dts/utils/collect-session-models.test.d.ts +0 -2
- package/dist/dts/utils/collect-session-models.test.d.ts.map +0 -1
- package/dist/dts/utils/condense-history.test.d.ts +0 -2
- package/dist/dts/utils/condense-history.test.d.ts.map +0 -1
- package/dist/dts/utils/cost-session-history.test.d.ts +0 -2
- package/dist/dts/utils/cost-session-history.test.d.ts.map +0 -1
- package/dist/dts/utils/derive-cost-session-title.test.d.ts +0 -2
- package/dist/dts/utils/derive-cost-session-title.test.d.ts.map +0 -1
- package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts +0 -2
- package/dist/dts/utils/flatten-sub-agent-messages.test.d.ts.map +0 -1
- package/dist/dts/utils/history-transform.test.d.ts +0 -2
- package/dist/dts/utils/history-transform.test.d.ts.map +0 -1
- package/dist/dts/utils/message-partition.test.d.ts +0 -2
- package/dist/dts/utils/message-partition.test.d.ts.map +0 -1
- package/dist/dts/utils/resolve-cost-history-config.test.d.ts +0 -2
- package/dist/dts/utils/resolve-cost-history-config.test.d.ts.map +0 -1
- package/dist/dts/utils/resolve-preference-baseline.test.d.ts +0 -2
- package/dist/dts/utils/resolve-preference-baseline.test.d.ts.map +0 -1
- package/dist/dts/utils/strip-agent-handlers.test.d.ts +0 -2
- package/dist/dts/utils/strip-agent-handlers.test.d.ts.map +0 -1
- package/dist/dts/utils/sum-costs.test.d.ts +0 -2
- package/dist/dts/utils/sum-costs.test.d.ts.map +0 -1
- package/dist/dts/utils/sum-tokens.test.d.ts +0 -2
- package/dist/dts/utils/sum-tokens.test.d.ts.map +0 -1
- package/dist/dts/utils/sum-usage.test.d.ts +0 -2
- package/dist/dts/utils/sum-usage.test.d.ts.map +0 -1
- package/dist/dts/utils/usage-rows.test.d.ts +0 -2
- package/dist/dts/utils/usage-rows.test.d.ts.map +0 -1
- package/dist/esm/components/chat-driver/chat-driver.compact.test.js +0 -93
- package/dist/esm/components/chat-driver/chat-driver.invocation-scope.test.js +0 -238
- package/dist/esm/components/chat-driver/chat-driver.test.js +0 -2770
- package/dist/esm/components/chat-driver/chat-driver.thinking-policy.test.js +0 -137
- package/dist/esm/components/chat-driver/chat-driver.trace-capture.test.js +0 -200
- package/dist/esm/components/chat-driver/chat-driver.turn-usage.test.js +0 -268
- package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.test.js +0 -104
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.budget.test.js +0 -312
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.cost.test.js +0 -76
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.pin.test.js +0 -61
- package/dist/esm/components/settings-modal/settings-modal.styles.test.js +0 -80
- package/dist/esm/components/settings-modal/settings-modal.template.test.js +0 -91
- package/dist/esm/config/define-stateful-agent.test.js +0 -53
- package/dist/esm/config/validate-providers.test.js +0 -100
- package/dist/esm/main/blocked-state.test.js +0 -1065
- package/dist/esm/main/budget-meter.test.js +0 -359
- package/dist/esm/main/cost-session-banking.test.js +0 -538
- package/dist/esm/main/file-attachments.test.js +0 -164
- package/dist/esm/main/interaction-cost.test.js +0 -107
- package/dist/esm/main/main.styles.test.js +0 -161
- package/dist/esm/main/persistence-broken-sources.test.js +0 -180
- package/dist/esm/main/popout-interaction-gate.test.js +0 -54
- package/dist/esm/state/ai-assistant-slice.test.js +0 -229
- package/dist/esm/state/debug-event-log.test.js +0 -117
- package/dist/esm/state/driver-registry.test.js +0 -55
- package/dist/esm/state/interaction-context.test.js +0 -78
- package/dist/esm/state/persistence/diagnostics-cursors.test.js +0 -30
- package/dist/esm/state/persistence/diagnostics.test.js +0 -280
- package/dist/esm/state/persistence/session-persistence-provider.test.js +0 -110
- package/dist/esm/state/persistence/session-persistence.integration.test.js +0 -113
- package/dist/esm/state/persistence/session-persister.test.js +0 -613
- package/dist/esm/state/persistence/session-snapshot.test.js +0 -256
- package/dist/esm/state/persistence/stateful-restore.e2e.test.js +0 -158
- package/dist/esm/utils/animation-exclusivity.test.js +0 -52
- package/dist/esm/utils/collect-session-models.test.js +0 -53
- package/dist/esm/utils/condense-history.test.js +0 -547
- package/dist/esm/utils/cost-session-history.test.js +0 -174
- package/dist/esm/utils/derive-cost-session-title.test.js +0 -37
- package/dist/esm/utils/flatten-sub-agent-messages.test.js +0 -139
- package/dist/esm/utils/history-transform.test.js +0 -158
- package/dist/esm/utils/message-partition.test.js +0 -90
- package/dist/esm/utils/resolve-cost-history-config.test.js +0 -32
- package/dist/esm/utils/resolve-preference-baseline.test.js +0 -69
- package/dist/esm/utils/strip-agent-handlers.test.js +0 -81
- package/dist/esm/utils/sum-costs.test.js +0 -114
- package/dist/esm/utils/sum-tokens.test.js +0 -51
- package/dist/esm/utils/sum-usage.test.js +0 -164
- package/dist/esm/utils/usage-rows.test.js +0 -189
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/license.txt +0 -46
- package/sandbox/tsconfig.json +0 -8
- package/src/channel/ai-activity-bus.ts +0 -209
- package/src/channel/ai-activity-channel.ts +0 -146
- package/src/chat-driver-node.ts +0 -159
- package/src/components/activity-halo/activity-halo.ts +0 -106
- package/src/components/agent-picker/agent-picker.constants.ts +0 -2
- package/src/components/agent-picker/agent-picker.styles.ts +0 -24
- package/src/components/agent-picker/agent-picker.template.ts +0 -54
- package/src/components/agent-picker/agent-picker.ts +0 -99
- package/src/components/agent-picker/index.ts +0 -1
- package/src/components/ai-driver/ai-driver.ts +0 -166
- package/src/components/ai-driver/index.ts +0 -1
- package/src/components/chat-bubble/chat-bubble.styles.ts +0 -194
- package/src/components/chat-bubble/chat-bubble.template.ts +0 -67
- package/src/components/chat-bubble/chat-bubble.ts +0 -436
- package/src/components/chat-bubble/index.ts +0 -1
- package/src/components/chat-driver/align-event-globals.ts +0 -23
- package/src/components/chat-driver/chat-driver.compact.test.ts +0 -131
- package/src/components/chat-driver/chat-driver.invocation-scope.test.ts +0 -285
- package/src/components/chat-driver/chat-driver.test.ts +0 -3679
- package/src/components/chat-driver/chat-driver.thinking-policy.test.ts +0 -185
- package/src/components/chat-driver/chat-driver.trace-capture.test.ts +0 -251
- package/src/components/chat-driver/chat-driver.ts +0 -3925
- package/src/components/chat-driver/chat-driver.turn-usage.test.ts +0 -362
- package/src/components/chat-driver/index.ts +0 -1
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts +0 -7
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts +0 -6
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts +0 -144
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts +0 -243
- package/src/components/chat-interaction-wrapper/index.ts +0 -3
- package/src/components/chat-markdown/chat-markdown.ts +0 -89
- package/src/components/chat-markdown/index.ts +0 -1
- package/src/components/flowing-waves-indicator.ts +0 -286
- package/src/components/halo-overlay.ts +0 -184
- package/src/components/orchestrating-driver/index.ts +0 -1
- package/src/components/orchestrating-driver/orchestrating-driver.budget.test.ts +0 -438
- package/src/components/orchestrating-driver/orchestrating-driver.cost.test.ts +0 -107
- package/src/components/orchestrating-driver/orchestrating-driver.pin.test.ts +0 -96
- package/src/components/orchestrating-driver/orchestrating-driver.ts +0 -813
- package/src/components/plasma-orb-indicator.ts +0 -281
- package/src/components/popout-manager/index.ts +0 -1
- package/src/components/popout-manager/popout-manager.ts +0 -212
- package/src/components/settings-modal/index.ts +0 -2
- package/src/components/settings-modal/settings-modal.styles.test.ts +0 -94
- package/src/components/settings-modal/settings-modal.styles.ts +0 -853
- package/src/components/settings-modal/settings-modal.template.test.ts +0 -124
- package/src/components/settings-modal/settings-modal.template.ts +0 -943
- package/src/components/waves-indicator.ts +0 -241
- package/src/config/config.ts +0 -608
- package/src/config/define-stateful-agent.test.ts +0 -64
- package/src/config/define-stateful-agent.ts +0 -642
- package/src/config/fallback-agents.ts +0 -29
- package/src/config/index.ts +0 -2
- package/src/config/validate-providers.test.ts +0 -148
- package/src/config/validate-providers.ts +0 -58
- package/src/index.ts +0 -29
- package/src/main/blocked-state.test.ts +0 -1444
- package/src/main/budget-meter.test.ts +0 -477
- package/src/main/cost-session-banking.test.ts +0 -679
- package/src/main/file-attachments.test.ts +0 -215
- package/src/main/index.ts +0 -3
- package/src/main/interaction-cost.test.ts +0 -140
- package/src/main/main.styles.test.ts +0 -216
- package/src/main/main.styles.ts +0 -995
- package/src/main/main.template.ts +0 -1119
- package/src/main/main.ts +0 -5432
- package/src/main/main.types.ts +0 -225
- package/src/main/persistence-broken-sources.test.ts +0 -219
- package/src/main/popout-interaction-gate.test.ts +0 -87
- package/src/provider/ai-provider-switcher.ts +0 -40
- package/src/provider/assistant-app-settings.ts +0 -84
- package/src/state/ai-assistant-slice.test.ts +0 -336
- package/src/state/ai-assistant-slice.ts +0 -604
- package/src/state/debug-event-log.test.ts +0 -154
- package/src/state/debug-event-log.ts +0 -358
- package/src/state/driver-registry.test.ts +0 -70
- package/src/state/driver-registry.ts +0 -79
- package/src/state/interaction-context.test.ts +0 -90
- package/src/state/interaction-context.ts +0 -101
- package/src/state/persistence/build-timeline-entries.ts +0 -66
- package/src/state/persistence/diagnostics-cursors.test.ts +0 -53
- package/src/state/persistence/diagnostics-cursors.ts +0 -80
- package/src/state/persistence/diagnostics.test.ts +0 -346
- package/src/state/persistence/diagnostics.ts +0 -220
- package/src/state/persistence/index.ts +0 -10
- package/src/state/persistence/persister-registry.ts +0 -55
- package/src/state/persistence/session-persistence-provider.test.ts +0 -136
- package/src/state/persistence/session-persistence-provider.ts +0 -226
- package/src/state/persistence/session-persistence.integration.test.ts +0 -141
- package/src/state/persistence/session-persister.test.ts +0 -787
- package/src/state/persistence/session-persister.ts +0 -917
- package/src/state/persistence/session-snapshot.test.ts +0 -352
- package/src/state/persistence/session-snapshot.ts +0 -344
- package/src/state/persistence/stateful-restore.e2e.test.ts +0 -229
- package/src/state/session-store.ts +0 -56
- package/src/styles/ai-colours.ts +0 -30
- package/src/styles/menu.ts +0 -187
- package/src/styles/settings-section.ts +0 -40
- package/src/suggestions/chat-suggestions.ts +0 -186
- package/src/tags/index.ts +0 -1
- package/src/types/ai-chat-widget.ts +0 -67
- package/src/types/interaction-context.ts +0 -82
- package/src/utils/animated-panel-toggle.ts +0 -62
- package/src/utils/animation-exclusivity.test.ts +0 -72
- package/src/utils/animation-exclusivity.ts +0 -40
- package/src/utils/banked-usage-baselines.ts +0 -90
- package/src/utils/collect-session-models.test.ts +0 -82
- package/src/utils/collect-session-models.ts +0 -65
- package/src/utils/condense-history.test.ts +0 -746
- package/src/utils/condense-history.ts +0 -448
- package/src/utils/cost-session-history.test.ts +0 -226
- package/src/utils/cost-session-history.ts +0 -218
- package/src/utils/derive-cost-session-title.test.ts +0 -63
- package/src/utils/derive-cost-session-title.ts +0 -76
- package/src/utils/flatten-sub-agent-messages.test.ts +0 -163
- package/src/utils/flatten-sub-agent-messages.ts +0 -88
- package/src/utils/format-usd.ts +0 -26
- package/src/utils/history-transform.test.ts +0 -228
- package/src/utils/history-transform.ts +0 -163
- package/src/utils/index.ts +0 -3
- package/src/utils/logger.ts +0 -3
- package/src/utils/message-partition.test.ts +0 -129
- package/src/utils/message-partition.ts +0 -115
- package/src/utils/resolve-cost-history-config.test.ts +0 -38
- package/src/utils/resolve-cost-history-config.ts +0 -38
- package/src/utils/resolve-preference-baseline.test.ts +0 -84
- package/src/utils/resolve-preference-baseline.ts +0 -80
- package/src/utils/strip-agent-handlers.test.ts +0 -99
- package/src/utils/strip-agent-handlers.ts +0 -53
- package/src/utils/sum-costs.test.ts +0 -136
- package/src/utils/sum-costs.ts +0 -18
- package/src/utils/sum-tokens.test.ts +0 -93
- package/src/utils/sum-tokens.ts +0 -68
- package/src/utils/sum-usage.test.ts +0 -191
- package/src/utils/sum-usage.ts +0 -208
- package/src/utils/tool-fold.ts +0 -181
- package/src/utils/usage-rows.test.ts +0 -237
- package/src/utils/usage-rows.ts +0 -187
- package/src/utils/with-timeout.ts +0 -24
- package/tsconfig.json +0 -9
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { WebStorageSessionProvider } from './session-persistence-provider';
|
|
3
|
-
import { createPersistedSession } from './session-snapshot';
|
|
4
|
-
|
|
5
|
-
// GENC-1351 §5.2: the WebStorageSessionProvider against a fake Storage (no real
|
|
6
|
-
// sessionStorage in Node). Covers round-trip, absent/corrupt entries, clear,
|
|
7
|
-
// and keyPrefix isolation.
|
|
8
|
-
|
|
9
|
-
/** Minimal in-memory Storage matching the DOM `Storage` shape the provider uses. */
|
|
10
|
-
function fakeStorage(): Storage {
|
|
11
|
-
const m = new Map<string, string>();
|
|
12
|
-
return {
|
|
13
|
-
get length() {
|
|
14
|
-
return m.size;
|
|
15
|
-
},
|
|
16
|
-
clear: () => m.clear(),
|
|
17
|
-
getItem: (k: string) => (m.has(k) ? m.get(k)! : null),
|
|
18
|
-
key: (i: number) => [...m.keys()][i] ?? null,
|
|
19
|
-
removeItem: (k: string) => {
|
|
20
|
-
m.delete(k);
|
|
21
|
-
},
|
|
22
|
-
setItem: (k: string, v: string) => {
|
|
23
|
-
m.set(k, v);
|
|
24
|
-
},
|
|
25
|
-
} as Storage;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const snap = (sessionKey: string) =>
|
|
29
|
-
createPersistedSession({
|
|
30
|
-
sessionKey,
|
|
31
|
-
host: 'app.example',
|
|
32
|
-
messages: [],
|
|
33
|
-
pinnedAgentName: null,
|
|
34
|
-
flowOwnerAgentName: null,
|
|
35
|
-
sessionCostUsd: 0,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const Suite = createLogicSuite('web-storage-session-provider');
|
|
39
|
-
|
|
40
|
-
Suite('save → load round-trips a snapshot', async () => {
|
|
41
|
-
const provider = new WebStorageSessionProvider(fakeStorage());
|
|
42
|
-
const s = snap('s1');
|
|
43
|
-
await provider.save('s1', s);
|
|
44
|
-
const loaded = await provider.load('s1');
|
|
45
|
-
// Compare through JSON: createPersistedSession leaves optional keys undefined,
|
|
46
|
-
// which JSON drops — so normalise both sides the same way.
|
|
47
|
-
assert.equal(loaded, JSON.parse(JSON.stringify(s)));
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
Suite('load returns null for an unknown key', async () => {
|
|
51
|
-
const provider = new WebStorageSessionProvider(fakeStorage());
|
|
52
|
-
assert.is(await provider.load('missing'), null);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
Suite('load returns null (not throw) on a corrupt entry', async () => {
|
|
56
|
-
const storage = fakeStorage();
|
|
57
|
-
storage.setItem('genesis-ai-session:s1', '{ not valid json');
|
|
58
|
-
const provider = new WebStorageSessionProvider(storage);
|
|
59
|
-
assert.is(await provider.load('s1'), null);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
Suite('clear removes the snapshot', async () => {
|
|
63
|
-
const provider = new WebStorageSessionProvider(fakeStorage());
|
|
64
|
-
await provider.save('s1', snap('s1'));
|
|
65
|
-
await provider.clear('s1');
|
|
66
|
-
assert.is(await provider.load('s1'), null);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
Suite('keyPrefix isolates snapshots stored in the same Storage', async () => {
|
|
70
|
-
const storage = fakeStorage();
|
|
71
|
-
const a = new WebStorageSessionProvider(storage, { keyPrefix: 'appA' });
|
|
72
|
-
const b = new WebStorageSessionProvider(storage, { keyPrefix: 'appB' });
|
|
73
|
-
await a.save('k', snap('k'));
|
|
74
|
-
assert.is(await b.load('k'), null);
|
|
75
|
-
assert.ok(await a.load('k'));
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
Suite('saveDebounceMs defaults to 0 (web storage is sync) and is overridable', () => {
|
|
79
|
-
assert.is(new WebStorageSessionProvider(fakeStorage()).saveDebounceMs, 0);
|
|
80
|
-
assert.is(
|
|
81
|
-
new WebStorageSessionProvider(fakeStorage(), { saveDebounceMs: 1500 }).saveDebounceMs,
|
|
82
|
-
1500,
|
|
83
|
-
);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// GENC-1351: user preferences (remember-chat + display toggles) — persisted
|
|
87
|
-
// independently of the snapshot and of the enabled toggle.
|
|
88
|
-
Suite('loadPreferences is undefined until set, then round-trips a blob', async () => {
|
|
89
|
-
const provider = new WebStorageSessionProvider(fakeStorage());
|
|
90
|
-
assert.is(
|
|
91
|
-
await provider.loadPreferences('s1'),
|
|
92
|
-
undefined,
|
|
93
|
-
'never saved → undefined (use defaults)',
|
|
94
|
-
);
|
|
95
|
-
const prefs = {
|
|
96
|
-
enabled: false,
|
|
97
|
-
showToolCalls: true,
|
|
98
|
-
showThinkingSteps: true,
|
|
99
|
-
showNarration: false,
|
|
100
|
-
showAgentSwitchIndicator: true,
|
|
101
|
-
animations: ['dots'],
|
|
102
|
-
};
|
|
103
|
-
await provider.savePreferences('s1', prefs);
|
|
104
|
-
assert.equal(await provider.loadPreferences('s1'), prefs);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
Suite('loadPreferences returns undefined (not throw) on a corrupt entry', async () => {
|
|
108
|
-
const storage = fakeStorage();
|
|
109
|
-
storage.setItem('genesis-ai-session:prefs:s1', '{ not valid json');
|
|
110
|
-
const provider = new WebStorageSessionProvider(storage);
|
|
111
|
-
assert.is(await provider.loadPreferences('s1'), undefined);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
Suite('clear wipes the snapshot but keeps the preferences', async () => {
|
|
115
|
-
const provider = new WebStorageSessionProvider(fakeStorage());
|
|
116
|
-
await provider.save('s1', snap('s1'));
|
|
117
|
-
await provider.savePreferences('s1', { enabled: false, showToolCalls: true });
|
|
118
|
-
await provider.clear('s1');
|
|
119
|
-
assert.is(await provider.load('s1'), null, 'snapshot cleared');
|
|
120
|
-
assert.equal(
|
|
121
|
-
await provider.loadPreferences('s1'),
|
|
122
|
-
{ enabled: false, showToolCalls: true },
|
|
123
|
-
'preferences survive clear',
|
|
124
|
-
);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
Suite('preferences are keyPrefix-isolated too', async () => {
|
|
128
|
-
const storage = fakeStorage();
|
|
129
|
-
const a = new WebStorageSessionProvider(storage, { keyPrefix: 'appA' });
|
|
130
|
-
const b = new WebStorageSessionProvider(storage, { keyPrefix: 'appB' });
|
|
131
|
-
await a.savePreferences('k', { enabled: true });
|
|
132
|
-
assert.is(await b.loadPreferences('k'), undefined);
|
|
133
|
-
assert.equal(await a.loadPreferences('k'), { enabled: true });
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
Suite.run();
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import type { CostSessionRecord } from '../../utils/cost-session-history';
|
|
2
|
-
import type { DiagnosticEntry } from './diagnostics';
|
|
3
|
-
import type { PersistedSession } from './session-snapshot';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Storage backend for chat-session persistence. The library ships this
|
|
7
|
-
* interface only and wires nothing by default; the host app provides an
|
|
8
|
-
* implementation and opts in via `chatConfig` (GENC-1351 §5.2/D2). Every method
|
|
9
|
-
* is async so a server-backed provider satisfies the same contract as a
|
|
10
|
-
* browser-storage one.
|
|
11
|
-
*
|
|
12
|
-
* @public
|
|
13
|
-
*/
|
|
14
|
-
export interface SessionPersistenceProvider {
|
|
15
|
-
/**
|
|
16
|
-
* Minimum milliseconds between autosaves for this backend — a throttle the
|
|
17
|
-
* assistant applies to its "save on every transcript change" signal. `0`
|
|
18
|
-
* means save as soon as the current tick settles (coalescing only writes
|
|
19
|
-
* within the same tick), which is right for synchronous, cheap storage like
|
|
20
|
-
* `sessionStorage`/`localStorage`. A network/server backend should set a
|
|
21
|
-
* higher value (e.g. 1000–2000) so it doesn't hammer the wire mid tool-loop.
|
|
22
|
-
*
|
|
23
|
-
* **Required** — not optional — so every implementation makes a deliberate
|
|
24
|
-
* choice for its own write-cost profile rather than inheriting a guess.
|
|
25
|
-
*/
|
|
26
|
-
saveDebounceMs: number;
|
|
27
|
-
/** Load the snapshot for `sessionKey`, or `null` if none / unreadable. */
|
|
28
|
-
load(sessionKey: string): Promise<PersistedSession | null>;
|
|
29
|
-
/** Persist `snapshot` for `sessionKey`, overwriting any existing one. */
|
|
30
|
-
save(sessionKey: string, snapshot: PersistedSession): Promise<void>;
|
|
31
|
-
/** Remove any stored snapshot for `sessionKey`. */
|
|
32
|
-
clear(sessionKey: string): Promise<void>;
|
|
33
|
-
/**
|
|
34
|
-
* Load the user's remembered UI preferences for `sessionKey` (GENC-1351) — the
|
|
35
|
-
* "remember chat" toggle plus the cog-menu display switches. Stored
|
|
36
|
-
* **independently of the session snapshot** (survives `clear`) and **independently
|
|
37
|
-
* of the `enabled` toggle** — a user's display choices persist whether or not
|
|
38
|
-
* they remember the chat. `undefined` means none were ever saved → the host's
|
|
39
|
-
* configured `chatConfig.ui` defaults apply. Only the fields the user has changed
|
|
40
|
-
* are present; absent fields keep their config default. Optional: a provider that
|
|
41
|
-
* omits it simply doesn't remember preferences across reloads.
|
|
42
|
-
*/
|
|
43
|
-
loadPreferences?(sessionKey: string): Promise<SessionPreferences | undefined>;
|
|
44
|
-
/** Persist the user's UI preferences for `sessionKey` (kept across `clear`). */
|
|
45
|
-
savePreferences?(sessionKey: string, preferences: SessionPreferences): Promise<void>;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Scoped cost/build history — the per-project usage rows behind the Usage tab.
|
|
49
|
-
*
|
|
50
|
-
* ‼️ **Keyed by SCOPE, not `sessionKey`**, unlike every other method on this interface.
|
|
51
|
-
* A row holds one project's lifetime usage and the list spans projects, so keying it
|
|
52
|
-
* per session would defeat the model. The scope comes from
|
|
53
|
-
* `chatConfig.costHistory.scope`, falling back to the element `id`, then `'default'`.
|
|
54
|
-
*
|
|
55
|
-
* Optional: when a provider omits these, the assistant falls back to `localStorage`, so
|
|
56
|
-
* hosts that don't implement them are unaffected. Implementing them moves the ledger to
|
|
57
|
-
* the host's own backend, which is what stops it being stranded in one browser.
|
|
58
|
-
*
|
|
59
|
-
* Read/written **independently of the `enabled` toggle** (as with preferences and
|
|
60
|
-
* diagnostics): usage accounting is not conversation content, so a user turning off
|
|
61
|
-
* chat retention should not lose their cost ledger. Best-effort — a failed write must
|
|
62
|
-
* never affect the chat.
|
|
63
|
-
*
|
|
64
|
-
* `undefined` from `loadCostHistory` means "nothing stored", the same as `[]`.
|
|
65
|
-
*/
|
|
66
|
-
loadCostHistory?(scope: string): Promise<CostSessionRecord[] | undefined>;
|
|
67
|
-
/** Replace the stored row list for `scope`. See {@link SessionPersistenceProvider.loadCostHistory}. */
|
|
68
|
-
saveCostHistory?(scope: string, records: CostSessionRecord[]): Promise<void>;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Server-saved diagnostics (GENC-1351 §5.8) — a **forward-only append** stream
|
|
72
|
-
* that lets the downloadable debug log span the whole session lifetime, not just
|
|
73
|
-
* the current page load. Optional and **capacious-backends-only**: `WebStorage`
|
|
74
|
-
* omits it (the log dwarfs the restore core), so the feature is simply off there.
|
|
75
|
-
* The element emits *what happened* (new entries since the last flush); the
|
|
76
|
-
* provider owns *how it's filed* (one object per session / per run / per day —
|
|
77
|
-
* its call). Best-effort: a failed write must never affect the chat.
|
|
78
|
-
*
|
|
79
|
-
* Captured **independently of the `enabled` toggle** (like preferences): the
|
|
80
|
-
* forensic log is orthogonal to whether the *chat* is remembered, so it runs
|
|
81
|
-
* whenever a provider implements this — even with "remember chat" off.
|
|
82
|
-
*/
|
|
83
|
-
appendDiagnostics?(sessionKey: string, entries: DiagnosticEntry[]): Promise<void>;
|
|
84
|
-
/** Load the full diagnostics stream for `sessionKey` (all appends, any run), for download-reassembly. */
|
|
85
|
-
loadDiagnostics?(sessionKey: string): Promise<DiagnosticEntry[]>;
|
|
86
|
-
/** Hard-delete the diagnostics stream for `sessionKey`. Host/GDPR use — NOT wired to the UI Clear. */
|
|
87
|
-
clearDiagnostics?(sessionKey: string): Promise<void>;
|
|
88
|
-
/**
|
|
89
|
-
* Min ms between diagnostics appends — coarser than `saveDebounceMs` because
|
|
90
|
-
* the log is heavy. Defaults to a sensible coarse value if omitted.
|
|
91
|
-
*/
|
|
92
|
-
diagnosticsDebounceMs?: number;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* User-changeable settings that persist across reloads, independently of the chat
|
|
97
|
-
* snapshot and of the "remember chat" toggle (GENC-1351). All optional — an absent
|
|
98
|
-
* field keeps its `chatConfig.ui` default rather than being forced off.
|
|
99
|
-
*
|
|
100
|
-
* @public
|
|
101
|
-
*/
|
|
102
|
-
export interface SessionPreferences {
|
|
103
|
-
/** "Remember chat" — whether the session snapshot itself is persisted. */
|
|
104
|
-
enabled?: boolean;
|
|
105
|
-
showToolCalls?: boolean;
|
|
106
|
-
showThinkingSteps?: boolean;
|
|
107
|
-
showNarration?: boolean;
|
|
108
|
-
showAgentSwitchIndicator?: boolean;
|
|
109
|
-
/** Enabled loading-animation ids (`AiAssistantAnimation` values, as plain strings). */
|
|
110
|
-
animations?: string[];
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Host-supplied persistence configuration, passed to the assistant via its
|
|
115
|
-
* `persistence` property. Persistence stays inert unless `enabled` is true and
|
|
116
|
-
* a `provider` is set — so existing consumers are unaffected (GENC-1351 D2/D3).
|
|
117
|
-
*
|
|
118
|
-
* @public
|
|
119
|
-
*/
|
|
120
|
-
export interface SessionPersistenceConfig {
|
|
121
|
-
/** Master switch — persistence does nothing unless this is true. */
|
|
122
|
-
enabled?: boolean;
|
|
123
|
-
/** Storage backend. Required for persistence to take effect. */
|
|
124
|
-
provider?: SessionPersistenceProvider;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const DEFAULT_KEY_PREFIX = 'genesis-ai-session';
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* A {@link SessionPersistenceProvider} backed by a Web Storage area. Both
|
|
131
|
-
* `window.sessionStorage` and `window.localStorage` implement the `Storage`
|
|
132
|
-
* API, so one class serves both: point it at `sessionStorage` for
|
|
133
|
-
* refresh-survival that clears on tab close (the showcase default, O6), or at
|
|
134
|
-
* `localStorage` for durable local persistence.
|
|
135
|
-
*
|
|
136
|
-
* Opt-in: a host constructs it and passes it through `chatConfig`; the library
|
|
137
|
-
* never instantiates it on its own.
|
|
138
|
-
*
|
|
139
|
-
* @public
|
|
140
|
-
*/
|
|
141
|
-
export class WebStorageSessionProvider implements SessionPersistenceProvider {
|
|
142
|
-
private readonly storage: Storage;
|
|
143
|
-
private readonly keyPrefix: string;
|
|
144
|
-
/**
|
|
145
|
-
* Web storage is synchronous and cheap, so the default is `0` — save as
|
|
146
|
-
* soon as the tick settles, no throttle. Override via `options.saveDebounceMs`
|
|
147
|
-
* (e.g. if pointing at `localStorage` and you'd rather coalesce large writes).
|
|
148
|
-
*/
|
|
149
|
-
readonly saveDebounceMs: number;
|
|
150
|
-
|
|
151
|
-
constructor(storage: Storage, options: { keyPrefix?: string; saveDebounceMs?: number } = {}) {
|
|
152
|
-
this.storage = storage;
|
|
153
|
-
this.keyPrefix = options.keyPrefix ?? DEFAULT_KEY_PREFIX;
|
|
154
|
-
this.saveDebounceMs = options.saveDebounceMs ?? 0;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
private storageKey(sessionKey: string): string {
|
|
158
|
-
return `${this.keyPrefix}:${sessionKey}`;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** Preferences key, namespaced apart from the session key so `clear` leaves it. */
|
|
162
|
-
private preferencesKey(sessionKey: string): string {
|
|
163
|
-
return `${this.keyPrefix}:prefs:${sessionKey}`;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async load(sessionKey: string): Promise<PersistedSession | null> {
|
|
167
|
-
const raw = this.storage.getItem(this.storageKey(sessionKey));
|
|
168
|
-
if (!raw) return null;
|
|
169
|
-
try {
|
|
170
|
-
return JSON.parse(raw) as PersistedSession;
|
|
171
|
-
} catch {
|
|
172
|
-
// A corrupt or partially-written entry is treated as absent rather than
|
|
173
|
-
// throwing — a bad snapshot must never block the assistant from starting.
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async save(sessionKey: string, snapshot: PersistedSession): Promise<void> {
|
|
179
|
-
const serialized = JSON.stringify(snapshot);
|
|
180
|
-
try {
|
|
181
|
-
this.storage.setItem(this.storageKey(sessionKey), serialized);
|
|
182
|
-
} catch (e) {
|
|
183
|
-
if (isQuotaExceeded(e)) {
|
|
184
|
-
throw new Error(
|
|
185
|
-
`WebStorageSessionProvider: storage quota exceeded saving session "${sessionKey}" ` +
|
|
186
|
-
`(${serialized.length} chars). Compact the conversation or use a server-backed provider.`,
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
throw e;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async clear(sessionKey: string): Promise<void> {
|
|
194
|
-
this.storage.removeItem(this.storageKey(sessionKey));
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
async loadPreferences(sessionKey: string): Promise<SessionPreferences | undefined> {
|
|
198
|
-
const raw = this.storage.getItem(this.preferencesKey(sessionKey));
|
|
199
|
-
if (raw === null) return undefined;
|
|
200
|
-
try {
|
|
201
|
-
return JSON.parse(raw) as SessionPreferences;
|
|
202
|
-
} catch {
|
|
203
|
-
// A corrupt entry is treated as "no preferences" — never block startup.
|
|
204
|
-
return undefined;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async savePreferences(sessionKey: string, preferences: SessionPreferences): Promise<void> {
|
|
209
|
-
// Small blob — quota is a non-issue, so no QuotaExceeded guard.
|
|
210
|
-
this.storage.setItem(this.preferencesKey(sessionKey), JSON.stringify(preferences));
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Web Storage signals an exceeded quota with different `name`s across browsers
|
|
216
|
-
* (`QuotaExceededError` everywhere modern; `NS_ERROR_DOM_QUOTA_REACHED` on
|
|
217
|
-
* older Firefox). The legacy numeric `code`s (22 / 1014) are intentionally not
|
|
218
|
-
* checked — they add nothing for the browsers this app targets.
|
|
219
|
-
*/
|
|
220
|
-
function isQuotaExceeded(e: unknown): boolean {
|
|
221
|
-
return (
|
|
222
|
-
typeof DOMException !== 'undefined' &&
|
|
223
|
-
e instanceof DOMException &&
|
|
224
|
-
(e.name === 'QuotaExceededError' || e.name === 'NS_ERROR_DOM_QUOTA_REACHED')
|
|
225
|
-
);
|
|
226
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import type { ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
-
import { aiAssistantSlice, createDefaultSessionState } from '../ai-assistant-slice';
|
|
4
|
-
import { WebStorageSessionProvider } from './session-persistence-provider';
|
|
5
|
-
import {
|
|
6
|
-
createPersistedSession,
|
|
7
|
-
isRestorableSession,
|
|
8
|
-
toLoadSessionPayload,
|
|
9
|
-
trimToRestorableBoundary,
|
|
10
|
-
} from './session-snapshot';
|
|
11
|
-
|
|
12
|
-
// GENC-1351 Phase 1 integration: the page-reload cycle the element wires —
|
|
13
|
-
// save on idle → reload drops the in-memory store + provider → restore hydrates
|
|
14
|
-
// a fresh store. Exercises the REAL provider + schema + reducer composed; the
|
|
15
|
-
// custom-element glue (connectedCallback → maybeRestoreSession) is covered by a
|
|
16
|
-
// manual app test.
|
|
17
|
-
|
|
18
|
-
/** In-memory Storage that survives the simulated reload (like sessionStorage does). */
|
|
19
|
-
function fakeStorage(): Storage {
|
|
20
|
-
const m = new Map<string, string>();
|
|
21
|
-
return {
|
|
22
|
-
get length() {
|
|
23
|
-
return m.size;
|
|
24
|
-
},
|
|
25
|
-
clear: () => m.clear(),
|
|
26
|
-
getItem: (k: string) => (m.has(k) ? m.get(k)! : null),
|
|
27
|
-
key: (i: number) => [...m.keys()][i] ?? null,
|
|
28
|
-
removeItem: (k: string) => {
|
|
29
|
-
m.delete(k);
|
|
30
|
-
},
|
|
31
|
-
setItem: (k: string, v: string) => {
|
|
32
|
-
m.set(k, v);
|
|
33
|
-
},
|
|
34
|
-
} as Storage;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const asMsg = (o: Record<string, unknown>): ChatMessage => o as unknown as ChatMessage;
|
|
38
|
-
const reduce = aiAssistantSlice.reducer;
|
|
39
|
-
const { loadSession } = aiAssistantSlice.actions;
|
|
40
|
-
|
|
41
|
-
const Suite = createLogicSuite('session-persistence-integration');
|
|
42
|
-
|
|
43
|
-
Suite('save → reload → restore brings back messages, selection, counters', async () => {
|
|
44
|
-
const storage = fakeStorage(); // persists across the simulated reload
|
|
45
|
-
const host = 'app.example';
|
|
46
|
-
const key = 'assistant::Title';
|
|
47
|
-
|
|
48
|
-
// 1. A live session reaches idle with history + a pinned agent.
|
|
49
|
-
const messages = [
|
|
50
|
-
asMsg({ role: 'user', content: 'book a trade' }),
|
|
51
|
-
asMsg({ role: 'assistant', content: 'sure' }),
|
|
52
|
-
];
|
|
53
|
-
await new WebStorageSessionProvider(storage).save(
|
|
54
|
-
key,
|
|
55
|
-
createPersistedSession({
|
|
56
|
-
sessionKey: key,
|
|
57
|
-
host,
|
|
58
|
-
messages,
|
|
59
|
-
activeAgentName: 'Guided Booking',
|
|
60
|
-
pinnedAgentName: 'Guided Booking',
|
|
61
|
-
flowOwnerAgentName: null,
|
|
62
|
-
sessionCostUsd: 0.5,
|
|
63
|
-
contextTokens: 800,
|
|
64
|
-
contextLimit: 200000,
|
|
65
|
-
activeModel: 'claude-sonnet-4-6',
|
|
66
|
-
activeProviderName: 'anthropic',
|
|
67
|
-
}),
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
// 2. "Reload": the in-memory store + provider instance are gone; storage remains.
|
|
71
|
-
const freshStore = createDefaultSessionState();
|
|
72
|
-
const snapshot = await new WebStorageSessionProvider(storage).load(key);
|
|
73
|
-
assert.ok(snapshot, 'snapshot survives the reload');
|
|
74
|
-
assert.ok(isRestorableSession(snapshot, { host }), 'snapshot is restorable for this host');
|
|
75
|
-
|
|
76
|
-
// 3. Restore into the fresh store (what maybeRestoreSession does).
|
|
77
|
-
const restored = reduce(freshStore, loadSession(toLoadSessionPayload(snapshot!)));
|
|
78
|
-
assert.is(restored.messages.length, 2);
|
|
79
|
-
assert.is(restored.pinnedAgentName, 'Guided Booking');
|
|
80
|
-
// Cost comes from the restored transcript, NOT the snapshot's stored counter — so it
|
|
81
|
-
// is whatever the messages actually carry. Computed independently here rather than
|
|
82
|
-
// by calling the summing helper under test.
|
|
83
|
-
const expectedCost = restored.messages.reduce(
|
|
84
|
-
(sum, m) => sum + (m.cost ?? 0) + (m.externalCostUsd ?? 0),
|
|
85
|
-
0,
|
|
86
|
-
);
|
|
87
|
-
assert.is(restored.sessionUsage.costUsd, expectedCost);
|
|
88
|
-
assert.is(restored.contextTokens, 800);
|
|
89
|
-
assert.is(restored.contextLimit, 200000);
|
|
90
|
-
assert.is(restored.activeModel, 'claude-sonnet-4-6');
|
|
91
|
-
assert.is(restored.activeProviderName, 'anthropic');
|
|
92
|
-
assert.is(restored.state, 'idle');
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
Suite('a cross-host snapshot is rejected on restore', async () => {
|
|
96
|
-
const storage = fakeStorage();
|
|
97
|
-
const provider = new WebStorageSessionProvider(storage);
|
|
98
|
-
await provider.save(
|
|
99
|
-
'k',
|
|
100
|
-
createPersistedSession({
|
|
101
|
-
sessionKey: 'k',
|
|
102
|
-
host: 'app.example',
|
|
103
|
-
messages: [asMsg({ role: 'user', content: 'x' })],
|
|
104
|
-
pinnedAgentName: null,
|
|
105
|
-
flowOwnerAgentName: null,
|
|
106
|
-
sessionCostUsd: 0,
|
|
107
|
-
}),
|
|
108
|
-
);
|
|
109
|
-
const snapshot = await provider.load('k');
|
|
110
|
-
assert.ok(snapshot);
|
|
111
|
-
assert.ok(!isRestorableSession(snapshot, { host: 'evil.example' }));
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
Suite('a dangling interaction tail is scrubbed before restore', async () => {
|
|
115
|
-
const storage = fakeStorage();
|
|
116
|
-
const provider = new WebStorageSessionProvider(storage);
|
|
117
|
-
const messages = [
|
|
118
|
-
asMsg({ role: 'user', content: 'hi' }),
|
|
119
|
-
asMsg({
|
|
120
|
-
role: 'assistant',
|
|
121
|
-
content: '',
|
|
122
|
-
interaction: { interactionId: '1', componentName: 'x', data: {} },
|
|
123
|
-
}),
|
|
124
|
-
];
|
|
125
|
-
await provider.save(
|
|
126
|
-
'k',
|
|
127
|
-
createPersistedSession({
|
|
128
|
-
sessionKey: 'k',
|
|
129
|
-
host: 'h',
|
|
130
|
-
messages,
|
|
131
|
-
pinnedAgentName: null,
|
|
132
|
-
flowOwnerAgentName: null,
|
|
133
|
-
sessionCostUsd: 0,
|
|
134
|
-
}),
|
|
135
|
-
);
|
|
136
|
-
const snapshot = await provider.load('k');
|
|
137
|
-
const scrubbed = trimToRestorableBoundary(snapshot!.messages);
|
|
138
|
-
assert.is(scrubbed.length, 1, 'the unresolved interaction message is dropped');
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
Suite.run();
|