@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,54 +0,0 @@
|
|
|
1
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { FoundationAiAssistant } from './main';
|
|
3
|
-
// Hold a reference so the custom-element registration isn't tree-shaken.
|
|
4
|
-
FoundationAiAssistant;
|
|
5
|
-
// GENC-1351 §5.11 (PR review): the single-element popout refactor removed the
|
|
6
|
-
// `claimSideEffect` first-writer latch, so the only thing preventing a pending
|
|
7
|
-
// interaction widget from being re-instantiated (and re-firing its side effect) on a
|
|
8
|
-
// dock/undock move is that the popout control is disabled while an interaction is
|
|
9
|
-
// pending — the template binds `?disabled=${x.hasActivePendingInteraction}`
|
|
10
|
-
// (`main.template.ts`). This suite pins the GATE LOGIC (`hasActivePendingInteraction`)
|
|
11
|
-
// that binding depends on, so a change to when the gate opens/closes is caught.
|
|
12
|
-
//
|
|
13
|
-
// Note: this deliberately does NOT mount the assistant. Mounting runs `connectedCallback`,
|
|
14
|
-
// which subscribes to `agenticActivityBus` — opening a cross-tab `BroadcastChannel` in a browser
|
|
15
|
-
// env and leaving the runner's event loop open. A getter-level test needs no mount and stays fast
|
|
16
|
-
// + reliable. `document.createElement` only upgrades the element (constructor) — it does not
|
|
17
|
-
// connect — so nothing subscribes, and the bus opens its channel lazily on first use, so none is
|
|
18
|
-
// ever created here (no teardown needed).
|
|
19
|
-
const Suite = createLogicSuite('FoundationAiAssistant popout interaction gate');
|
|
20
|
-
/** A fresh (unconnected) element with a fake session store wired in. */
|
|
21
|
-
function elementWith(state, messages) {
|
|
22
|
-
const el = document.createElement('foundation-ai-assistant');
|
|
23
|
-
el._sessionRef = {
|
|
24
|
-
store: { aiAssistant: { state, messages } },
|
|
25
|
-
};
|
|
26
|
-
return el;
|
|
27
|
-
}
|
|
28
|
-
const withInteraction = (resolved) => [
|
|
29
|
-
{ role: 'user', content: 'hi' },
|
|
30
|
-
{
|
|
31
|
-
role: 'assistant',
|
|
32
|
-
content: resolved ? 'done' : '',
|
|
33
|
-
interaction: { interactionId: '1', componentName: 'x', data: {}, resolved },
|
|
34
|
-
},
|
|
35
|
-
];
|
|
36
|
-
Suite('is true only while a turn is running AND a pending interaction is the last message', () => {
|
|
37
|
-
// Gated: a turn is running and the last message is an unresolved interaction. This is
|
|
38
|
-
// the state during which the popout/dock control MUST be disabled.
|
|
39
|
-
assert.is(elementWith('loading', withInteraction(false)).hasActivePendingInteraction, true, 'busy + unresolved interaction → gated');
|
|
40
|
-
// `cancelling` counts as busy too (a stop was requested but the turn is winding down).
|
|
41
|
-
assert.is(elementWith('cancelling', withInteraction(false)).hasActivePendingInteraction, true, 'cancelling + unresolved interaction → gated');
|
|
42
|
-
});
|
|
43
|
-
Suite('is false when idle, when the interaction is resolved, or when there is none', () => {
|
|
44
|
-
// Not busy → never gated, even with an unresolved interaction present.
|
|
45
|
-
assert.is(elementWith('idle', withInteraction(false)).hasActivePendingInteraction, false, 'idle → not gated');
|
|
46
|
-
// Busy but the interaction has resolved → the widget is answered, nothing to strand.
|
|
47
|
-
assert.is(elementWith('loading', withInteraction({ value: 'ok' })).hasActivePendingInteraction, false, 'resolved interaction → not gated');
|
|
48
|
-
// Busy but the last message is not an interaction at all.
|
|
49
|
-
assert.is(elementWith('loading', [{ role: 'assistant', content: 'thinking' }])
|
|
50
|
-
.hasActivePendingInteraction, false, 'no interaction → not gated');
|
|
51
|
-
// No session / empty transcript.
|
|
52
|
-
assert.is(elementWith('loading', []).hasActivePendingInteraction, false, 'empty transcript → not gated');
|
|
53
|
-
});
|
|
54
|
-
Suite.run();
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { aiAssistantSlice, createDefaultSessionState, } from './ai-assistant-slice';
|
|
3
|
-
// GENC-1351 §5.6 / Phase 1: the new bulk loadSession + resetSession reducers.
|
|
4
|
-
const { loadSession, resetSession, setBlocked, setBlockedReason, setVendorBlocked, setInputValue, setMessages, setState, setSessionMenuOpen, setShowToolCalls, setShowThinkingSteps, setShowNarration, setShowAgentSwitchIndicator, setEnabledAnimations, } = aiAssistantSlice.actions;
|
|
5
|
-
const reduce = aiAssistantSlice.reducer;
|
|
6
|
-
const Suite = createLogicSuite('ai-assistant-slice-persistence');
|
|
7
|
-
Suite('loadSession hydrates the restorable fields and forces idle', () => {
|
|
8
|
-
let start = createDefaultSessionState();
|
|
9
|
-
start = reduce(start, setState('loading')); // prove it overrides any prior state
|
|
10
|
-
const payload = {
|
|
11
|
-
// Usage rides on the message: `loadSession` derives the session totals from the
|
|
12
|
-
// restored transcript rather than from a stored counter, so the cost has to be in
|
|
13
|
-
// the messages for the assertion below to see it.
|
|
14
|
-
messages: [
|
|
15
|
-
{ role: 'user', content: 'hi', cost: 1.25, inputTokens: 100, outputTokens: 20 },
|
|
16
|
-
],
|
|
17
|
-
pinnedAgentName: 'Guided Booking',
|
|
18
|
-
flowOwnerAgentName: 'Guided Booking',
|
|
19
|
-
contextTokens: 1200,
|
|
20
|
-
contextLimit: 200000,
|
|
21
|
-
activeModel: 'claude-sonnet-4-6',
|
|
22
|
-
activeProviderName: 'anthropic',
|
|
23
|
-
};
|
|
24
|
-
const next = reduce(start, loadSession(payload));
|
|
25
|
-
assert.is(next.messages.length, 1);
|
|
26
|
-
assert.is(next.pinnedAgentName, 'Guided Booking');
|
|
27
|
-
assert.is(next.flowOwnerAgentName, 'Guided Booking');
|
|
28
|
-
assert.is(next.sessionUsage.costUsd, 1.25);
|
|
29
|
-
// Derived from the same walk: 100 prompt tokens with no cache split reported, so all
|
|
30
|
-
// of it is uncached input, plus the 20 generated.
|
|
31
|
-
assert.is(next.sessionUsage.uncachedInputTokens, 100);
|
|
32
|
-
assert.is(next.sessionUsage.outputTokens, 20);
|
|
33
|
-
assert.is(next.contextTokens, 1200);
|
|
34
|
-
assert.is(next.contextLimit, 200000);
|
|
35
|
-
assert.is(next.activeModel, 'claude-sonnet-4-6');
|
|
36
|
-
assert.is(next.activeProviderName, 'anthropic');
|
|
37
|
-
assert.is(next.state, 'idle');
|
|
38
|
-
});
|
|
39
|
-
Suite('resetSession wipes back to a fresh default', () => {
|
|
40
|
-
let state = createDefaultSessionState();
|
|
41
|
-
state = reduce(state, setMessages([{ role: 'user', content: 'x' }]));
|
|
42
|
-
state = reduce(state, loadSession({
|
|
43
|
-
messages: [],
|
|
44
|
-
pinnedAgentName: 'A',
|
|
45
|
-
flowOwnerAgentName: 'A',
|
|
46
|
-
contextTokens: 5,
|
|
47
|
-
contextLimit: 10,
|
|
48
|
-
activeModel: 'm',
|
|
49
|
-
activeProviderName: 'p',
|
|
50
|
-
}));
|
|
51
|
-
const cleared = reduce(state, resetSession());
|
|
52
|
-
assert.is(cleared.messages.length, 0);
|
|
53
|
-
assert.is(cleared.sessionUsage.costUsd, 0);
|
|
54
|
-
assert.is(cleared.pinnedAgentName, null);
|
|
55
|
-
assert.is(cleared.flowOwnerAgentName, null);
|
|
56
|
-
assert.is(cleared.contextTokens, undefined);
|
|
57
|
-
assert.is(cleared.state, 'idle');
|
|
58
|
-
});
|
|
59
|
-
Suite('resetSession preserves the user display preferences (they survive Clear)', () => {
|
|
60
|
-
// Display prefs are settings, not conversation state — Clear/New-chat must keep them
|
|
61
|
-
// (GENC-1351 #10). Turn several on, plus an animation, then reset.
|
|
62
|
-
let state = createDefaultSessionState();
|
|
63
|
-
state = reduce(state, setShowToolCalls(true));
|
|
64
|
-
state = reduce(state, setShowThinkingSteps(true));
|
|
65
|
-
state = reduce(state, setShowNarration(true));
|
|
66
|
-
state = reduce(state, setShowAgentSwitchIndicator(true));
|
|
67
|
-
state = reduce(state, setEnabledAnimations(['halo']));
|
|
68
|
-
state = reduce(state, setMessages([{ role: 'user', content: 'x' }]));
|
|
69
|
-
const cleared = reduce(state, resetSession());
|
|
70
|
-
// Conversation wiped…
|
|
71
|
-
assert.is(cleared.messages.length, 0, 'conversation cleared');
|
|
72
|
-
// …but the display prefs kept.
|
|
73
|
-
assert.is(cleared.showToolCalls, true);
|
|
74
|
-
assert.is(cleared.showThinkingSteps, true);
|
|
75
|
-
assert.is(cleared.showNarration, true);
|
|
76
|
-
assert.is(cleared.showAgentSwitchIndicator, true);
|
|
77
|
-
assert.equal(cleared.enabledAnimations, ['halo']);
|
|
78
|
-
});
|
|
79
|
-
Suite('createDefaultSessionState returns independent collections', () => {
|
|
80
|
-
// No aliasing between instances — resetSession must hand back a clean slate.
|
|
81
|
-
assert.ok(createDefaultSessionState().messages !== createDefaultSessionState().messages);
|
|
82
|
-
});
|
|
83
|
-
// GENC-1351 Phase 3: the lifecycle (session) menu open/close flag.
|
|
84
|
-
Suite('setSessionMenuOpen toggles the lifecycle-menu flag; resetSession clears it', () => {
|
|
85
|
-
let state = createDefaultSessionState();
|
|
86
|
-
assert.is(state.sessionMenuOpen, false);
|
|
87
|
-
state = reduce(state, setSessionMenuOpen(true));
|
|
88
|
-
assert.is(state.sessionMenuOpen, true);
|
|
89
|
-
const cleared = reduce(state, resetSession());
|
|
90
|
-
assert.is(cleared.sessionMenuOpen, false);
|
|
91
|
-
});
|
|
92
|
-
// GENC-1464: the backend-blocked latch (exhausted AI budget).
|
|
93
|
-
Suite('setBlocked latches the flag and its reason', () => {
|
|
94
|
-
let state = createDefaultSessionState();
|
|
95
|
-
assert.is(state.blocked, false);
|
|
96
|
-
assert.is(state.blockedReason, null);
|
|
97
|
-
state = reduce(state, setBlocked({ blocked: true }));
|
|
98
|
-
assert.is(state.blocked, true);
|
|
99
|
-
assert.is(state.blockedReason, null, 'no reason given → null, so the element uses its default');
|
|
100
|
-
state = reduce(state, setBlocked({ blocked: true, reason: 'Budget exhausted — contact billing' }));
|
|
101
|
-
assert.is(state.blockedReason, 'Budget exhausted — contact billing');
|
|
102
|
-
});
|
|
103
|
-
Suite('unblocking always clears the reason', () => {
|
|
104
|
-
// A stale explanation must never outlive the condition it described.
|
|
105
|
-
let state = reduce(createDefaultSessionState(), setBlocked({ blocked: true, reason: 'gone' }));
|
|
106
|
-
state = reduce(state, setBlocked({ blocked: false }));
|
|
107
|
-
assert.is(state.blocked, false);
|
|
108
|
-
assert.is(state.blockedReason, null);
|
|
109
|
-
});
|
|
110
|
-
Suite('resetSession preserves the block — "New chat" does not refill the budget', () => {
|
|
111
|
-
// Deliberately unlike sessionMenuOpen/compacting above: this is a backend
|
|
112
|
-
// condition, not conversation state, so Clear must not hand the user a
|
|
113
|
-
// working-looking composer that fails on the next send.
|
|
114
|
-
let state = reduce(createDefaultSessionState(), setBlocked({ blocked: true, reason: 'no funds' }));
|
|
115
|
-
state = reduce(state, setMessages([{ role: 'user', content: 'x' }]));
|
|
116
|
-
const cleared = reduce(state, resetSession());
|
|
117
|
-
assert.is(cleared.messages.length, 0, 'the transcript is still cleared');
|
|
118
|
-
assert.is(cleared.blocked, true, 'the block survives Clear');
|
|
119
|
-
assert.is(cleared.blockedReason, 'no funds', 'and so does its reason');
|
|
120
|
-
});
|
|
121
|
-
Suite('re-latching without a reason preserves the host explanation', () => {
|
|
122
|
-
// The race this fixes is deterministic, not theoretical: `tool-loop-end` is
|
|
123
|
-
// published from the driver's `finally`, i.e. BEFORE `sendMessage()` resolves.
|
|
124
|
-
// So a bus-subscribed host (what the migration guide's Option C recommends)
|
|
125
|
-
// writes its detailed copy first, and the element's own latch — which may have
|
|
126
|
-
// no figures to offer — lands a moment later. Treating an OMITTED reason as
|
|
127
|
-
// "clear it" replaced the host's copy with generic copy every single time.
|
|
128
|
-
let state = reduce(createDefaultSessionState(), setBlocked({ blocked: true, reason: 'contact ops@acme.com' }));
|
|
129
|
-
state = reduce(state, setBlocked({ blocked: true }));
|
|
130
|
-
assert.is(state.blocked, true);
|
|
131
|
-
assert.is(state.blockedReason, 'contact ops@acme.com', 'the host copy survives the re-latch');
|
|
132
|
-
});
|
|
133
|
-
Suite('reason:null clears the explanation while staying blocked', () => {
|
|
134
|
-
// The explicit escape hatch the omitted case gives up: `null` still clears.
|
|
135
|
-
let state = reduce(createDefaultSessionState(), setBlocked({ blocked: true, reason: 'stale copy' }));
|
|
136
|
-
state = reduce(state, setBlocked({ blocked: true, reason: null }));
|
|
137
|
-
assert.is(state.blocked, true);
|
|
138
|
-
assert.is(state.blockedReason, null, 'the banner falls back to the default copy');
|
|
139
|
-
});
|
|
140
|
-
Suite('resetSession still resets everything NOT on the preserved list', () => {
|
|
141
|
-
// Pins the boundary from the other side, so the preserved list cannot quietly
|
|
142
|
-
// grow: these are conversation/UI state and Clear must wipe them.
|
|
143
|
-
let state = reduce(createDefaultSessionState(), setBlocked({ blocked: true, reason: 'r' }));
|
|
144
|
-
state = reduce(state, setSessionMenuOpen(true));
|
|
145
|
-
state = reduce(state, setInputValue('half-typed message'));
|
|
146
|
-
const cleared = reduce(state, resetSession());
|
|
147
|
-
assert.is(cleared.inputValue, '', 'the composer draft is reset');
|
|
148
|
-
assert.is(cleared.sessionMenuOpen, false, 'the panel flag is reset');
|
|
149
|
-
assert.is(cleared.blocked, true, '…while the block is still preserved');
|
|
150
|
-
});
|
|
151
|
-
// ── Per-vendor latches (GENC-1464 per-vendor budgets) ──────────────────────────
|
|
152
|
-
Suite('setVendorBlocked walls one vendor and keeps the wall order', () => {
|
|
153
|
-
let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: 'anthropic copy' }));
|
|
154
|
-
assert.equal(state.blockedVendors, ['anthropic']);
|
|
155
|
-
assert.equal(state.blockedVendorReasons, { anthropic: 'anthropic copy' });
|
|
156
|
-
assert.is(state.blocked, false, 'the vendor-agnostic flag is untouched');
|
|
157
|
-
state = reduce(state, setVendorBlocked({ vendor: 'gemini', blocked: true }));
|
|
158
|
-
assert.equal(state.blockedVendors, ['anthropic', 'gemini'], 'order is the order they were walled');
|
|
159
|
-
});
|
|
160
|
-
Suite('walling an already-walled vendor is idempotent', () => {
|
|
161
|
-
let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'gemini', blocked: true, reason: 'first' }));
|
|
162
|
-
state = reduce(state, setVendorBlocked({ vendor: 'gemini', blocked: true, reason: 'second' }));
|
|
163
|
-
assert.equal(state.blockedVendors, ['gemini'], 'no duplicate entry');
|
|
164
|
-
assert.is(state.blockedVendorReasons.gemini, 'second');
|
|
165
|
-
});
|
|
166
|
-
Suite('the per-vendor reason honours the same three cases as setBlocked', () => {
|
|
167
|
-
// The same two-writer race (driver latch landing after a bus-subscribed host)
|
|
168
|
-
// applies per vendor, so "omitted keeps" has to hold here too.
|
|
169
|
-
let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: 'host copy' }));
|
|
170
|
-
state = reduce(state, setVendorBlocked({ vendor: 'anthropic', blocked: true }));
|
|
171
|
-
assert.is(state.blockedVendorReasons.anthropic, 'host copy', 'omitted keeps');
|
|
172
|
-
state = reduce(state, setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: null }));
|
|
173
|
-
assert.is(state.blockedVendorReasons.anthropic, undefined, 'null clears');
|
|
174
|
-
});
|
|
175
|
-
Suite('releasing a vendor drops it and its copy', () => {
|
|
176
|
-
let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: 'r' }));
|
|
177
|
-
state = reduce(state, setVendorBlocked({ vendor: 'gemini', blocked: true }));
|
|
178
|
-
state = reduce(state, setVendorBlocked({ vendor: 'anthropic', blocked: false }));
|
|
179
|
-
assert.equal(state.blockedVendors, ['gemini']);
|
|
180
|
-
assert.is(state.blockedVendorReasons.anthropic, undefined);
|
|
181
|
-
});
|
|
182
|
-
Suite('setBlocked(false) also clears every per-vendor latch', () => {
|
|
183
|
-
// THE back-compat rule. A host asserting "the wall is gone" after its own
|
|
184
|
-
// pre-flight (migration-GENC-1464 Option B) must not be overruled by a driver
|
|
185
|
-
// latch it cannot see — without this, per-vendor would silently make the
|
|
186
|
-
// documented unblock stop working.
|
|
187
|
-
let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: 'r' }));
|
|
188
|
-
state = reduce(state, setBlocked({ blocked: true, reason: 'global' }));
|
|
189
|
-
state = reduce(state, setBlocked({ blocked: false }));
|
|
190
|
-
assert.is(state.blocked, false);
|
|
191
|
-
assert.is(state.blockedReason, null);
|
|
192
|
-
assert.equal(state.blockedVendors, []);
|
|
193
|
-
assert.equal(state.blockedVendorReasons, {});
|
|
194
|
-
});
|
|
195
|
-
Suite('setBlockedReason writes the copy without disturbing any latch', () => {
|
|
196
|
-
// It exists precisely so the element's `blockedReason` setter cannot route
|
|
197
|
-
// through `setBlocked(false, …)` — which would now clear the vendor latches.
|
|
198
|
-
let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'gemini', blocked: true }));
|
|
199
|
-
state = reduce(state, setBlockedReason('host copy'));
|
|
200
|
-
assert.is(state.blockedReason, 'host copy');
|
|
201
|
-
assert.is(state.blocked, false, 'the vendor-agnostic flag is untouched');
|
|
202
|
-
assert.equal(state.blockedVendors, ['gemini'], 'and so are the per-vendor latches');
|
|
203
|
-
});
|
|
204
|
-
Suite('resetSession preserves the per-vendor latches too', () => {
|
|
205
|
-
let state = reduce(createDefaultSessionState(), setVendorBlocked({ vendor: 'anthropic', blocked: true, reason: 'no funds' }));
|
|
206
|
-
state = reduce(state, setMessages([{ role: 'user', content: 'hi' }]));
|
|
207
|
-
const cleared = reduce(state, resetSession());
|
|
208
|
-
assert.equal(cleared.messages, [], 'the conversation is wiped');
|
|
209
|
-
assert.equal(cleared.blockedVendors, ['anthropic'], 'Clear does not refill a vendor budget');
|
|
210
|
-
assert.equal(cleared.blockedVendorReasons, { anthropic: 'no funds' });
|
|
211
|
-
});
|
|
212
|
-
// `resetSession` preserves its keys through `Object.assign`, whose source parameter
|
|
213
|
-
// is structurally permissive: a typo like `blockedResaon` would compile clean,
|
|
214
|
-
// silently stop preserving the real key, AND leak a junk key into serialised state.
|
|
215
|
-
// The reducer therefore annotates its literal `satisfies
|
|
216
|
-
// Partial<AiAssistantSessionState>`, which is a COMPILE-time guarantee — so the
|
|
217
|
-
// durable proof is a type fixture, not a runtime assertion.
|
|
218
|
-
//
|
|
219
|
-
// This mirrors the reducer's clause on the same typo. If `satisfies` ever stopped
|
|
220
|
-
// rejecting excess properties (a TS behaviour change, or someone widening the
|
|
221
|
-
// annotated type), the `@ts-expect-error` below becomes unused and the BUILD fails
|
|
222
|
-
// — which is exactly the signal wanted. The runtime suites above are its
|
|
223
|
-
// behavioural companions: they pin the preserved list from both sides.
|
|
224
|
-
const _typoIsRejectedAtCompileTime = {
|
|
225
|
-
// @ts-expect-error — an excess/misspelled key must be rejected by `satisfies`
|
|
226
|
-
blockedResaon: null,
|
|
227
|
-
};
|
|
228
|
-
void _typoIsRejectedAtCompileTime;
|
|
229
|
-
Suite.run();
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { clearMetaEventRegistry, clearSession, DEBUG_LOG_README, getMetaEvents, mergeMetaEvents, recordMetaEvent, } from './debug-event-log';
|
|
3
|
-
const event = (overrides = {}) => (Object.assign({ index: 0, timestamp: '2026-06-19T16:00:00.000Z', type: 'turn.retry', importance: 'normal' }, overrides));
|
|
4
|
-
const suite = createLogicSuite('debug-event-log merge/clear');
|
|
5
|
-
suite('mergeMetaEvents is a no-op for an empty batch', () => {
|
|
6
|
-
clearMetaEventRegistry();
|
|
7
|
-
mergeMetaEvents('k', []);
|
|
8
|
-
assert.is(getMetaEvents('k').length, 0);
|
|
9
|
-
});
|
|
10
|
-
suite('mergeMetaEvents preserves each event timestamp and re-indexes monotonically', () => {
|
|
11
|
-
clearMetaEventRegistry();
|
|
12
|
-
mergeMetaEvents('k', [
|
|
13
|
-
event({ index: 7, timestamp: '2026-06-19T16:00:01.000Z', detail: { a: 1 } }),
|
|
14
|
-
event({ index: 99, timestamp: '2026-06-19T16:00:02.000Z', detail: { b: 2 } }),
|
|
15
|
-
]);
|
|
16
|
-
const out = getMetaEvents('k');
|
|
17
|
-
assert.is(out.length, 2);
|
|
18
|
-
// original timestamps kept (not re-stamped) ...
|
|
19
|
-
assert.is(out[0].timestamp, '2026-06-19T16:00:01.000Z');
|
|
20
|
-
assert.is(out[1].timestamp, '2026-06-19T16:00:02.000Z');
|
|
21
|
-
// ... indices re-assigned from the target's counter, not carried from source.
|
|
22
|
-
assert.is(out[0].index, 0);
|
|
23
|
-
assert.is(out[1].index, 1);
|
|
24
|
-
assert.equal(out[0].detail, { a: 1 });
|
|
25
|
-
});
|
|
26
|
-
suite('mergeMetaEvents appends after existing events with a continuing index', () => {
|
|
27
|
-
clearMetaEventRegistry();
|
|
28
|
-
recordMetaEvent('k', 'turn.start'); // index 0, real timestamp
|
|
29
|
-
mergeMetaEvents('k', [event({ type: 'turn.error', importance: 'high' })]);
|
|
30
|
-
const out = getMetaEvents('k');
|
|
31
|
-
assert.is(out.length, 2);
|
|
32
|
-
assert.is(out[0].type, 'turn.start');
|
|
33
|
-
assert.is(out[1].type, 'turn.error');
|
|
34
|
-
assert.is(out[1].index, 1); // continues the monotonic counter
|
|
35
|
-
});
|
|
36
|
-
suite('mergeMetaEvents into a fresh key creates the bucket', () => {
|
|
37
|
-
clearMetaEventRegistry();
|
|
38
|
-
mergeMetaEvents('brand-new', [event()]);
|
|
39
|
-
assert.is(getMetaEvents('brand-new').length, 1);
|
|
40
|
-
});
|
|
41
|
-
suite('mergeMetaEvents evicts oldest non-high events past the cap but keeps every high', () => {
|
|
42
|
-
clearMetaEventRegistry();
|
|
43
|
-
const highStamp = '2026-06-19T16:30:00.000Z';
|
|
44
|
-
// One `high` failure first, then a flood of `normal` events well past the cap.
|
|
45
|
-
const batch = [
|
|
46
|
-
event({ type: 'turn.error', importance: 'high', timestamp: highStamp }),
|
|
47
|
-
];
|
|
48
|
-
for (let i = 0; i < 2000; i += 1) {
|
|
49
|
-
batch.push(event({ type: 'turn.retry', importance: 'normal' }));
|
|
50
|
-
}
|
|
51
|
-
mergeMetaEvents('k', batch);
|
|
52
|
-
const out = getMetaEvents('k');
|
|
53
|
-
// Eviction ran (bounded below what we merged) ...
|
|
54
|
-
assert.ok(out.length < 2001);
|
|
55
|
-
// ... and the lone high-importance failure was never dropped.
|
|
56
|
-
assert.is(out.filter((e) => e.importance === 'high').length, 1);
|
|
57
|
-
assert.ok(out.some((e) => e.timestamp === highStamp));
|
|
58
|
-
});
|
|
59
|
-
suite('clearSession drops one key without touching others', () => {
|
|
60
|
-
clearMetaEventRegistry();
|
|
61
|
-
recordMetaEvent('child', 'turn.retry');
|
|
62
|
-
recordMetaEvent('parent', 'turn.start');
|
|
63
|
-
clearSession('child');
|
|
64
|
-
assert.is(getMetaEvents('child').length, 0);
|
|
65
|
-
assert.is(getMetaEvents('parent').length, 1);
|
|
66
|
-
});
|
|
67
|
-
// ── README ↔ taxonomy sync (GENC-1464) ─────────────────────────────────────────
|
|
68
|
-
// DEBUG_LOG_README is emitted as the FIRST key of the exported debug log
|
|
69
|
-
// specifically so an agent reading the JSON does not have to reverse-engineer it.
|
|
70
|
-
// Its reason enumerations therefore read as exhaustive and will be trusted — but
|
|
71
|
-
// they had already drifted twice (`refusal` was missing from both) before anyone
|
|
72
|
-
// noticed, because "the doc lives next to the catalogue" is not enforcement.
|
|
73
|
-
//
|
|
74
|
-
// The `Record<..., true>` below is the enforcement: adding a union member without
|
|
75
|
-
// listing it here is a COMPILE error, and listing it without documenting it is a
|
|
76
|
-
// test failure. So the next added reason fails in the one place the drift keeps
|
|
77
|
-
// happening.
|
|
78
|
-
const ALL_TURN_FAILURE_REASONS = Object.keys({
|
|
79
|
-
exception: true,
|
|
80
|
-
'malformed-function-call': true,
|
|
81
|
-
'empty-response': true,
|
|
82
|
-
'unknown-tool-limit': true,
|
|
83
|
-
'max-iterations': true,
|
|
84
|
-
'response-truncated': true,
|
|
85
|
-
refusal: true,
|
|
86
|
-
'budget-exhausted': true,
|
|
87
|
-
'provider-refused': true,
|
|
88
|
-
});
|
|
89
|
-
const ALL_SUB_AGENT_FAILURE_REASONS = Object.keys({
|
|
90
|
-
max_iterations: true,
|
|
91
|
-
malformed_tool_call: true,
|
|
92
|
-
empty_response: true,
|
|
93
|
-
unknown_tool_limit: true,
|
|
94
|
-
timeout: true,
|
|
95
|
-
response_truncated: true,
|
|
96
|
-
refusal: true,
|
|
97
|
-
budget_exhausted: true,
|
|
98
|
-
provider_refused: true,
|
|
99
|
-
});
|
|
100
|
-
const README_TEXT = DEBUG_LOG_README.join('\n');
|
|
101
|
-
suite('DEBUG_LOG_README enumerates every TurnFailureReason', () => {
|
|
102
|
-
for (const reason of ALL_TURN_FAILURE_REASONS) {
|
|
103
|
-
assert.ok(README_TEXT.includes(reason), `turn.error reason '${reason}' is missing from DEBUG_LOG_README`);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
suite('DEBUG_LOG_README enumerates every SubAgentFailureReason', () => {
|
|
107
|
-
for (const reason of ALL_SUB_AGENT_FAILURE_REASONS) {
|
|
108
|
-
assert.ok(README_TEXT.includes(reason), `subagent.failed reason '${reason}' is missing from DEBUG_LOG_README`);
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
suite('DEBUG_LOG_README documents the budget diagnostics that ride turn.error', () => {
|
|
112
|
-
// `recordTurnError('budget-exhausted', …)` attaches these; an agent triaging a
|
|
113
|
-
// support ticket needs to know to look for them.
|
|
114
|
-
assert.ok(README_TEXT.includes('budgetUsd'));
|
|
115
|
-
assert.ok(README_TEXT.includes('spentUsd'));
|
|
116
|
-
});
|
|
117
|
-
suite.run();
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { clearDriverRegistry, deleteDriver, getDriver, getDriverAgentsKey, getOrCreateDriver, } from './driver-registry';
|
|
3
|
-
// Minimal stub — the registry only stores and returns the reference; it never
|
|
4
|
-
// invokes driver methods, so an opaque object is sufficient.
|
|
5
|
-
const stub = (id) => ({ id });
|
|
6
|
-
const suite = createLogicSuite('driver-registry agents-key tracking');
|
|
7
|
-
suite('getOrCreateDriver creates once and records the agents key', () => {
|
|
8
|
-
clearDriverRegistry();
|
|
9
|
-
const d = stub('a');
|
|
10
|
-
const created = getOrCreateDriver('k', 'agentsKeyA', () => d);
|
|
11
|
-
assert.is(created, d);
|
|
12
|
-
assert.is(getDriver('k'), d);
|
|
13
|
-
assert.is(getDriverAgentsKey('k'), 'agentsKeyA');
|
|
14
|
-
});
|
|
15
|
-
suite('reuse returns the existing driver and keeps its original agents key', () => {
|
|
16
|
-
clearDriverRegistry();
|
|
17
|
-
const first = stub('first');
|
|
18
|
-
getOrCreateDriver('k', 'agentsKeyA', () => first);
|
|
19
|
-
// A second component sharing the key, even with a different desired agents
|
|
20
|
-
// fingerprint, must NOT replace the driver or overwrite the recorded key.
|
|
21
|
-
let factoryCalled = false;
|
|
22
|
-
const reused = getOrCreateDriver('k', 'agentsKeyB', () => {
|
|
23
|
-
factoryCalled = true;
|
|
24
|
-
return stub('second');
|
|
25
|
-
});
|
|
26
|
-
assert.is(reused, first);
|
|
27
|
-
assert.is(factoryCalled, false);
|
|
28
|
-
assert.is(getDriverAgentsKey('k'), 'agentsKeyA');
|
|
29
|
-
});
|
|
30
|
-
suite('deleteDriver clears both the driver and its recorded agents key', () => {
|
|
31
|
-
clearDriverRegistry();
|
|
32
|
-
getOrCreateDriver('k', 'agentsKeyA', () => stub('a'));
|
|
33
|
-
deleteDriver('k');
|
|
34
|
-
assert.is(getDriver('k'), undefined);
|
|
35
|
-
assert.is(getDriverAgentsKey('k'), undefined);
|
|
36
|
-
});
|
|
37
|
-
suite('a rebuild under the same key records the new agents key (the adopt signal)', () => {
|
|
38
|
-
clearDriverRegistry();
|
|
39
|
-
// Initial driver, built for the old agent set.
|
|
40
|
-
getOrCreateDriver('k', 'old', () => stub('d-old'));
|
|
41
|
-
// Rebuild: the host deletes then recreates with the new fingerprint.
|
|
42
|
-
deleteDriver('k');
|
|
43
|
-
const rebuilt = stub('d-new');
|
|
44
|
-
getOrCreateDriver('k', 'new', () => rebuilt);
|
|
45
|
-
// GENC-1388: a sibling sharing the key can now detect that the registry driver
|
|
46
|
-
// already matches its desired agents and adopt `rebuilt`, rather than
|
|
47
|
-
// recreating (which would orphan a driver mid-turn).
|
|
48
|
-
assert.is(getDriver('k'), rebuilt);
|
|
49
|
-
assert.is(getDriverAgentsKey('k'), 'new');
|
|
50
|
-
});
|
|
51
|
-
suite('getDriverAgentsKey is undefined for an unknown key', () => {
|
|
52
|
-
clearDriverRegistry();
|
|
53
|
-
assert.is(getDriverAgentsKey('missing'), undefined);
|
|
54
|
-
});
|
|
55
|
-
suite.run();
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { createInteractionContext } from './interaction-context';
|
|
3
|
-
const suite = createLogicSuite('interaction-context factory');
|
|
4
|
-
suite('once runs the factory a single time and memoises the result', () => {
|
|
5
|
-
const { context } = createInteractionContext('id-1');
|
|
6
|
-
let calls = 0;
|
|
7
|
-
const first = context.once('ws', () => {
|
|
8
|
-
calls += 1;
|
|
9
|
-
return { socket: true };
|
|
10
|
-
});
|
|
11
|
-
const second = context.once('ws', () => {
|
|
12
|
-
calls += 1;
|
|
13
|
-
return { socket: false };
|
|
14
|
-
});
|
|
15
|
-
assert.is(calls, 1, 'factory ran once');
|
|
16
|
-
assert.is(second, first, 'the memoised value is returned on the second call');
|
|
17
|
-
});
|
|
18
|
-
suite('subscribe/notify fans out to listeners; unsubscribe stops them', () => {
|
|
19
|
-
const { context } = createInteractionContext('id-2');
|
|
20
|
-
let a = 0;
|
|
21
|
-
let b = 0;
|
|
22
|
-
const unsubA = context.subscribe(() => (a += 1));
|
|
23
|
-
context.subscribe(() => (b += 1));
|
|
24
|
-
context.notify();
|
|
25
|
-
assert.is(a, 1);
|
|
26
|
-
assert.is(b, 1);
|
|
27
|
-
unsubA();
|
|
28
|
-
context.notify();
|
|
29
|
-
assert.is(a, 1, 'unsubscribed listener no longer fires');
|
|
30
|
-
assert.is(b, 2, 'remaining listener still fires');
|
|
31
|
-
});
|
|
32
|
-
suite('a throwing subscriber does not stop the others', () => {
|
|
33
|
-
const { context } = createInteractionContext('id-3');
|
|
34
|
-
let reached = false;
|
|
35
|
-
context.subscribe(() => {
|
|
36
|
-
throw new Error('boom');
|
|
37
|
-
});
|
|
38
|
-
context.subscribe(() => (reached = true));
|
|
39
|
-
context.notify();
|
|
40
|
-
assert.ok(reached, 'later subscriber still ran after an earlier one threw');
|
|
41
|
-
});
|
|
42
|
-
suite('dispose runs every onDispose callback once, then is idempotent', () => {
|
|
43
|
-
const { context, dispose } = createInteractionContext('id-4');
|
|
44
|
-
let count = 0;
|
|
45
|
-
context.onDispose(() => (count += 1));
|
|
46
|
-
context.onDispose(() => (count += 1));
|
|
47
|
-
dispose();
|
|
48
|
-
assert.is(count, 2, 'both disposers ran');
|
|
49
|
-
dispose();
|
|
50
|
-
assert.is(count, 2, 'a second dispose is a no-op');
|
|
51
|
-
});
|
|
52
|
-
suite('onDispose registered after dispose runs immediately', () => {
|
|
53
|
-
const { context, dispose } = createInteractionContext('id-5');
|
|
54
|
-
dispose();
|
|
55
|
-
let ran = false;
|
|
56
|
-
context.onDispose(() => (ran = true));
|
|
57
|
-
assert.ok(ran, 'late onDispose runs immediately so no resource leaks');
|
|
58
|
-
});
|
|
59
|
-
suite('notify after dispose is a no-op (listeners cleared)', () => {
|
|
60
|
-
const { context, dispose } = createInteractionContext('id-6');
|
|
61
|
-
let fired = 0;
|
|
62
|
-
context.subscribe(() => (fired += 1));
|
|
63
|
-
dispose();
|
|
64
|
-
context.notify();
|
|
65
|
-
assert.is(fired, 0, 'no listeners fire after dispose');
|
|
66
|
-
});
|
|
67
|
-
suite('once after dispose returns null without running the factory', () => {
|
|
68
|
-
const { context, dispose } = createInteractionContext('id-7');
|
|
69
|
-
dispose();
|
|
70
|
-
let calls = 0;
|
|
71
|
-
const result = context.once('late', () => {
|
|
72
|
-
calls += 1;
|
|
73
|
-
return { socket: true };
|
|
74
|
-
});
|
|
75
|
-
assert.is(calls, 0, 'the factory never ran on an ended interaction (no leaked resource)');
|
|
76
|
-
assert.is(result, null, 'once returns null after dispose');
|
|
77
|
-
});
|
|
78
|
-
suite.run();
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { clearDiagnosticsCursorRegistry, deleteDiagnosticsCursorsFor, getDiagnosticsCursors, resetDiagnosticsCursorsFor, } from './diagnostics-cursors';
|
|
3
|
-
// GENC-1351 §5.8/§5.12 (#9): the forward-capture cursors, and the crucial difference
|
|
4
|
-
// between the Clear reset (keep the event high-water) and the in-place-switch delete
|
|
5
|
-
// (fresh cursors, because the meta-event buffer is torn down and its indices restart).
|
|
6
|
-
const KEY = 'session-1';
|
|
7
|
-
const Suite = createLogicSuite('diagnostics-cursors');
|
|
8
|
-
Suite.before.each(() => {
|
|
9
|
-
clearDiagnosticsCursorRegistry();
|
|
10
|
-
});
|
|
11
|
-
Suite('resetDiagnosticsCursorsFor KEEPS lastEventIndex (Clear: meta buffer keeps climbing)', () => {
|
|
12
|
-
const c = getDiagnosticsCursors(KEY);
|
|
13
|
-
c.lastEventIndex = 40;
|
|
14
|
-
c.emittedMsgKeys.add('m');
|
|
15
|
-
c.restoredMsgKeys.add('r');
|
|
16
|
-
resetDiagnosticsCursorsFor(KEY);
|
|
17
|
-
const after = getDiagnosticsCursors(KEY);
|
|
18
|
-
assert.is(after.lastEventIndex, 40, 'event high-water kept — Clear does not reset the meta buffer');
|
|
19
|
-
assert.is(after.emittedMsgKeys.size, 0, 'per-conversation message cursors restarted');
|
|
20
|
-
assert.is(after.restoredMsgKeys.size, 0, 'restore watermark restarted');
|
|
21
|
-
});
|
|
22
|
-
Suite('deleteDiagnosticsCursorsFor RESETS lastEventIndex (switch: meta buffer restarts at 0)', () => {
|
|
23
|
-
const c = getDiagnosticsCursors(KEY);
|
|
24
|
-
c.lastEventIndex = 40;
|
|
25
|
-
deleteDiagnosticsCursorsFor(KEY);
|
|
26
|
-
// A later return re-creates fresh cursors on first access.
|
|
27
|
-
const after = getDiagnosticsCursors(KEY);
|
|
28
|
-
assert.is(after.lastEventIndex, -1, 'fresh cursor — a returning session whose events restart at 0 is NOT skipped');
|
|
29
|
-
});
|
|
30
|
-
Suite.run();
|