@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,104 +0,0 @@
|
|
|
1
|
-
import { __awaiter } from "tslib";
|
|
2
|
-
import { assert, createComponentSuite } from '@genesislcap/foundation-testing';
|
|
3
|
-
import { DOM } from '@genesislcap/web-core';
|
|
4
|
-
import { AiChatInteractionWrapper } from './chat-interaction-wrapper';
|
|
5
|
-
// Hold a reference so the custom-element registration isn't tree-shaken.
|
|
6
|
-
AiChatInteractionWrapper;
|
|
7
|
-
/**
|
|
8
|
-
* Minimal stand-in for a real interaction widget. The wrapper instantiates the
|
|
9
|
-
* element named by `componentName` and assigns `interactionId`/`interaction` onto
|
|
10
|
-
* it (GENC-1390); the tests read `interaction` back to verify which interaction the
|
|
11
|
-
* rendered widget belongs to and its pending/resolved state.
|
|
12
|
-
*/
|
|
13
|
-
const FAKE_WIDGET = 'test-interaction-widget';
|
|
14
|
-
if (!customElements.get(FAKE_WIDGET)) {
|
|
15
|
-
customElements.define(FAKE_WIDGET, class extends HTMLElement {
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Renders a first interaction into a freshly-mounted wrapper.
|
|
20
|
-
*
|
|
21
|
-
* `componentName` is assigned last on purpose. The suite hands us an
|
|
22
|
-
* already-connected element, so the only render trigger left is
|
|
23
|
-
* `componentNameChanged`; setting it last means that render sees `data` and
|
|
24
|
-
* `interactionId` already in place — reproducing production, where the initial
|
|
25
|
-
* widget renders in `connectedCallback` with every bound property present. This
|
|
26
|
-
* keeps the *first* render correct on both the patched and unpatched code so
|
|
27
|
-
* that the id-change rebind is the only behaviour each test isolates.
|
|
28
|
-
*/
|
|
29
|
-
function renderFirstInteraction(element) {
|
|
30
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
31
|
-
yield DOM.nextUpdate();
|
|
32
|
-
element.data = { label: 'interaction-a' };
|
|
33
|
-
element.interactionId = 'id-a';
|
|
34
|
-
element.componentName = FAKE_WIDGET;
|
|
35
|
-
yield DOM.nextUpdate();
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
const Suite = createComponentSuite('AiChatInteractionWrapper', 'ai-chat-interaction-wrapper');
|
|
39
|
-
/**
|
|
40
|
-
* Regression for the cog-menu toggle misalignment: FAST's `repeat` reconciles
|
|
41
|
-
* the message list by array index (it has no key function), so toggling "Tool
|
|
42
|
-
* calls"/"Thinking" re-filters the visible messages and rebinds an existing
|
|
43
|
-
* row's wrapper to a *different* interaction. When both interactions use the
|
|
44
|
-
* same component, `componentNameChanged` doesn't fire — so the wrapper must
|
|
45
|
-
* re-render on `interactionId` change, or the previous interaction's widget
|
|
46
|
-
* stays mounted under the new message.
|
|
47
|
-
*/
|
|
48
|
-
Suite('re-renders the hosted widget when interactionId changes with the same componentName', (_a) => __awaiter(void 0, [_a], void 0, function* ({ element }) {
|
|
49
|
-
var _b, _c;
|
|
50
|
-
yield renderFirstInteraction(element);
|
|
51
|
-
const firstWidget = element.container.firstElementChild;
|
|
52
|
-
assert.ok(firstWidget, 'a widget is rendered for the first interaction');
|
|
53
|
-
assert.equal((_b = firstWidget.interaction) === null || _b === void 0 ? void 0 : _b.data, { label: 'interaction-a' });
|
|
54
|
-
// Rebind the reused wrapper to a different interaction that happens to use
|
|
55
|
-
// the same component — `data` then `interactionId`, mirroring the template's
|
|
56
|
-
// property-binding order. `componentName` is unchanged, so the only thing
|
|
57
|
-
// that can drive a re-render is the interactionId hook.
|
|
58
|
-
element.data = { label: 'interaction-b' };
|
|
59
|
-
element.interactionId = 'id-b';
|
|
60
|
-
yield DOM.nextUpdate();
|
|
61
|
-
const secondWidget = element.container.firstElementChild;
|
|
62
|
-
assert.is.not(secondWidget, firstWidget, 'the stale widget is replaced, not left mounted');
|
|
63
|
-
assert.equal((_c = secondWidget.interaction) === null || _c === void 0 ? void 0 : _c.data, { label: 'interaction-b' }, 'the rendered widget reflects the new interaction, not the stale one');
|
|
64
|
-
}));
|
|
65
|
-
/**
|
|
66
|
-
* Resolving an interaction in place (the user answers it) must forward the
|
|
67
|
-
* result without tearing down and rebuilding the widget — only a genuine change
|
|
68
|
-
* of interaction identity should rebuild.
|
|
69
|
-
*/
|
|
70
|
-
Suite('forwards resolved in place without rebuilding the widget', (_a) => __awaiter(void 0, [_a], void 0, function* ({ element }) {
|
|
71
|
-
var _b, _c, _d;
|
|
72
|
-
yield renderFirstInteraction(element);
|
|
73
|
-
const widget = element.container.firstElementChild;
|
|
74
|
-
assert.is((_b = widget.interaction) === null || _b === void 0 ? void 0 : _b.status, 'pending', 'the widget starts pending');
|
|
75
|
-
element.resolved = { status: 'approved' };
|
|
76
|
-
yield DOM.nextUpdate();
|
|
77
|
-
assert.is(element.container.firstElementChild, widget, 'resolving in place must not rebuild the widget');
|
|
78
|
-
assert.is((_c = widget.interaction) === null || _c === void 0 ? void 0 : _c.status, 'resolved', 'the widget transitions to resolved in place');
|
|
79
|
-
assert.equal(((_d = widget.interaction) === null || _d === void 0 ? void 0 : _d.status) === 'resolved' ? widget.interaction.result : undefined, { status: 'approved' }, 'the resolved result is forwarded to the widget');
|
|
80
|
-
}));
|
|
81
|
-
/**
|
|
82
|
-
* When no live `InteractionContext` exists for the interaction — it has resolved and the driver
|
|
83
|
-
* dropped it (the `resolved` binding can lag a render behind during a layout rebuild), or the widget
|
|
84
|
-
* is rendered outside the normal host flow (as here: no host on the root) — the wrapper must hand the
|
|
85
|
-
* widget an INERT context whose `once` refuses, never a live throwaway. A live throwaway re-ran the
|
|
86
|
-
* widget's open-once side effect on the remount following a resolve, re-opening a just-closed side
|
|
87
|
-
* panel and looping the layout rebuild (GENC-1390).
|
|
88
|
-
*/
|
|
89
|
-
Suite('falls back to an inert context (once refuses) when there is no live context', (_a) => __awaiter(void 0, [_a], void 0, function* ({ element }) {
|
|
90
|
-
var _b, _c;
|
|
91
|
-
yield renderFirstInteraction(element);
|
|
92
|
-
const widget = element.container.firstElementChild;
|
|
93
|
-
assert.is((_b = widget.interaction) === null || _b === void 0 ? void 0 : _b.status, 'pending', 'the widget is pending');
|
|
94
|
-
const ctx = ((_c = widget.interaction) === null || _c === void 0 ? void 0 : _c.status) === 'pending' ? widget.interaction.interactionContext : undefined;
|
|
95
|
-
assert.ok(ctx, 'a context is still provided so the widget does not crash on a missing one');
|
|
96
|
-
let ran = 0;
|
|
97
|
-
const result = ctx.once('open-review-panel', () => {
|
|
98
|
-
ran += 1;
|
|
99
|
-
return 'opened';
|
|
100
|
-
});
|
|
101
|
-
assert.is(ran, 0, 'the inert fallback context must not run a once factory');
|
|
102
|
-
assert.is(result, null, 'once returns null on the inert fallback context');
|
|
103
|
-
}));
|
|
104
|
-
Suite.run();
|
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
import { __awaiter } from "tslib";
|
|
2
|
-
import { BudgetExhaustedError } from '@genesislcap/foundation-ai';
|
|
3
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
4
|
-
import { AgenticActivityBus } from '../../channel/ai-activity-bus';
|
|
5
|
-
// Side-effect import — MUST precede `./orchestrating-driver` so the driver
|
|
6
|
-
// subclasses jsdom's EventTarget (CustomEvent dispatch then works in node).
|
|
7
|
-
import '../chat-driver/align-event-globals';
|
|
8
|
-
import { OrchestratingDriver } from './orchestrating-driver';
|
|
9
|
-
// GENC-1464 — the orchestrated turn's OUTCOME.
|
|
10
|
-
//
|
|
11
|
-
// `createDriver` returns an `OrchestratingDriver` for every agents-configured
|
|
12
|
-
// host, and its `sendMessage` used to end with a hardcoded `return { reason:
|
|
13
|
-
// 'done' }`. That made every typed failure unreachable for those hosts —
|
|
14
|
-
// including `'budget-exhausted'`, which the assistant element latches its blocked
|
|
15
|
-
// state off. A 402 therefore produced an apology over a still-enabled composer
|
|
16
|
-
// for exactly the configurations this workstream exists to fix.
|
|
17
|
-
//
|
|
18
|
-
// The classifier compounds it: `classify()` is a SECOND retry ladder stacked on
|
|
19
|
-
// the transport's, so an untyped catch re-issued the 402 `classifierRetries + 1`
|
|
20
|
-
// times and then ran a fallback turn into the same wall.
|
|
21
|
-
const makeRegistry = (provider) => ({
|
|
22
|
-
get: () => provider,
|
|
23
|
-
default: () => provider,
|
|
24
|
-
defaultName: () => 'test',
|
|
25
|
-
names: () => ['test'],
|
|
26
|
-
getStatus: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
|
|
27
|
-
listStatuses: () => __awaiter(void 0, void 0, void 0, function* () { return []; }),
|
|
28
|
-
});
|
|
29
|
-
const agent = (overrides) => (Object.assign({ description: 'test agent' }, overrides));
|
|
30
|
-
/** A provider that ends every turn immediately with a plain-text reply (no tools). */
|
|
31
|
-
const okProvider = () => ({
|
|
32
|
-
chat: () => __awaiter(void 0, void 0, void 0, function* () { return ({ role: 'assistant', content: 'ok' }); }),
|
|
33
|
-
});
|
|
34
|
-
/** A provider that counts calls and always refuses for budget. */
|
|
35
|
-
const walledProvider = (vendorLabel = 'Anthropic') => {
|
|
36
|
-
let n = 0;
|
|
37
|
-
return {
|
|
38
|
-
calls: () => n,
|
|
39
|
-
provider: {
|
|
40
|
-
chat: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
41
|
-
n += 1;
|
|
42
|
-
throw new BudgetExhaustedError(vendorLabel, 25, 25.4);
|
|
43
|
-
}),
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
};
|
|
47
|
-
const failureReasonOf = (r) => r.reason === 'done' ? r.failureReason : undefined;
|
|
48
|
-
const Suite = createLogicSuite('orchestrating-driver budget wall');
|
|
49
|
-
// ── 1. The showstopper: a walled AGENT turn must reach the caller ───────────
|
|
50
|
-
Suite('a budget wall on the agent turn surfaces as the orchestrated result', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
51
|
-
// ONE specialist and no fallback, so `classify` short-circuits without calling
|
|
52
|
-
// the provider — the wall is hit by the AGENT turn, inside a real tool loop.
|
|
53
|
-
// Against the old hardcoded return this deep-equal fails.
|
|
54
|
-
const { provider } = walledProvider();
|
|
55
|
-
const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'Solo' })], {
|
|
56
|
-
sessionKey: 'budget-1',
|
|
57
|
-
});
|
|
58
|
-
const result = yield driver.sendMessage('go');
|
|
59
|
-
assert.equal(result, {
|
|
60
|
-
reason: 'done',
|
|
61
|
-
failureReason: 'budget-exhausted',
|
|
62
|
-
budget: { budgetUsd: 25, spentUsd: 25.4, vendorLabel: 'Anthropic', vendor: 'anthropic' },
|
|
63
|
-
});
|
|
64
|
-
}));
|
|
65
|
-
Suite("the vendor is the refusing transport's, not a hardcoded default", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
66
|
-
// The label rides the contract; `vendor` is its normalised key. Without a
|
|
67
|
-
// second vendor exercised here, a constant would pass every other assertion in
|
|
68
|
-
// this file — and per-vendor blocking would wall the wrong vendor forever.
|
|
69
|
-
const { provider } = walledProvider('Gemini');
|
|
70
|
-
const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'Solo' })], {
|
|
71
|
-
sessionKey: 'budget-gemini',
|
|
72
|
-
});
|
|
73
|
-
const result = yield driver.sendMessage('go');
|
|
74
|
-
assert.equal(result.reason === 'done' ? result.budget : undefined, {
|
|
75
|
-
budgetUsd: 25,
|
|
76
|
-
spentUsd: 25.4,
|
|
77
|
-
vendorLabel: 'Gemini',
|
|
78
|
-
vendor: 'gemini',
|
|
79
|
-
});
|
|
80
|
-
}));
|
|
81
|
-
Suite('an unrecognised vendor label carries no typed vendor at all', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
82
|
-
// Attribution degrades to "unattributable" rather than to a wrong vendor —
|
|
83
|
-
// which makes the element fall back to its vendor-agnostic block. Safe.
|
|
84
|
-
const { provider } = walledProvider('Acme AI');
|
|
85
|
-
const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'Solo' })], {
|
|
86
|
-
sessionKey: 'budget-unknown-vendor',
|
|
87
|
-
});
|
|
88
|
-
const result = yield driver.sendMessage('go');
|
|
89
|
-
assert.equal(result.reason === 'done' ? result.budget : undefined, {
|
|
90
|
-
budgetUsd: 25,
|
|
91
|
-
spentUsd: 25.4,
|
|
92
|
-
vendorLabel: 'Acme AI',
|
|
93
|
-
});
|
|
94
|
-
}));
|
|
95
|
-
Suite('the wall also reaches the tool-loop-end bus detail through the orchestrator', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
96
|
-
var _a;
|
|
97
|
-
const bus = new AgenticActivityBus();
|
|
98
|
-
const seen = [];
|
|
99
|
-
const stop = bus.subscribe('tool-loop-end', (d) => seen.push(d));
|
|
100
|
-
const { provider } = walledProvider();
|
|
101
|
-
const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'Solo' })], {
|
|
102
|
-
sessionKey: 'budget-2',
|
|
103
|
-
activityBus: bus,
|
|
104
|
-
});
|
|
105
|
-
yield driver.sendMessage('go');
|
|
106
|
-
assert.is(seen.length, 1, 'exactly one tool-loop-end for the one agent turn');
|
|
107
|
-
assert.is((_a = seen[0]) === null || _a === void 0 ? void 0 : _a.failureReason, 'budget-exhausted');
|
|
108
|
-
stop();
|
|
109
|
-
bus.close();
|
|
110
|
-
}));
|
|
111
|
-
Suite('the bus detail carries the proxy figures, not just the failure reason', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
112
|
-
var _a, _b, _c;
|
|
113
|
-
// The element latches off THIS event for an in-loop wall — the publish sits in
|
|
114
|
-
// sendMessage's `finally`, so it lands before the return-value seam and the
|
|
115
|
-
// idempotent latch makes the later one a no-op. If the figures rode only the
|
|
116
|
-
// return value they would be unreachable in the common case and the banner would
|
|
117
|
-
// show the generic copy in exactly the path they were added for.
|
|
118
|
-
const bus = new AgenticActivityBus();
|
|
119
|
-
const seen = [];
|
|
120
|
-
const stop = bus.subscribe('tool-loop-end', (d) => seen.push(d));
|
|
121
|
-
const { provider } = walledProvider();
|
|
122
|
-
const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'Solo' })], {
|
|
123
|
-
sessionKey: 'budget-figures',
|
|
124
|
-
activityBus: bus,
|
|
125
|
-
});
|
|
126
|
-
const result = yield driver.sendMessage('go');
|
|
127
|
-
assert.equal((_a = seen[0]) === null || _a === void 0 ? void 0 : _a.budget, { budgetUsd: 25, spentUsd: 25.4, vendorLabel: 'Anthropic', vendor: 'anthropic' }, 'the event carries the same figures as the return value');
|
|
128
|
-
assert.is((_b = seen[0]) === null || _b === void 0 ? void 0 : _b.vendor, 'anthropic', 'and the top-level vendor agrees with the refusing transport, not with a stale last-resolved provider');
|
|
129
|
-
assert.equal(result.reason === 'done' ? result.budget : undefined, (_c = seen[0]) === null || _c === void 0 ? void 0 : _c.budget, 'event and return value agree — neither is the sole carrier');
|
|
130
|
-
stop();
|
|
131
|
-
bus.close();
|
|
132
|
-
}));
|
|
133
|
-
Suite('a clean turn publishes no budget key at all', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
134
|
-
const bus = new AgenticActivityBus();
|
|
135
|
-
const seen = [];
|
|
136
|
-
const stop = bus.subscribe('tool-loop-end', (d) => seen.push(d));
|
|
137
|
-
const provider = {
|
|
138
|
-
chat: () => __awaiter(void 0, void 0, void 0, function* () { return ({ role: 'assistant', content: 'ok' }); }),
|
|
139
|
-
};
|
|
140
|
-
const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'Solo' })], {
|
|
141
|
-
sessionKey: 'budget-clean',
|
|
142
|
-
activityBus: bus,
|
|
143
|
-
});
|
|
144
|
-
yield driver.sendMessage('go');
|
|
145
|
-
assert.ok(seen.every((d) => d === undefined || !('budget' in d)), 'no budget key on a non-budget turn — the historical detail shape is unchanged');
|
|
146
|
-
stop();
|
|
147
|
-
bus.close();
|
|
148
|
-
}));
|
|
149
|
-
// ── 2. No regression on the happy / handoff paths ───────────────────────────
|
|
150
|
-
Suite('a clean turn still returns the bare legacy shape', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
151
|
-
const driver = new OrchestratingDriver(makeRegistry(okProvider()), [agent({ name: 'Solo' })], {
|
|
152
|
-
sessionKey: 'budget-3',
|
|
153
|
-
});
|
|
154
|
-
const result = yield driver.sendMessage('go');
|
|
155
|
-
assert.equal(result, { reason: 'done' }, 'no failureReason on a clean turn');
|
|
156
|
-
}));
|
|
157
|
-
Suite('a handoff-capped turn never leaks agent-handoff to the caller', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
158
|
-
// `'agent-handoff'` is an internal routing protocol. The two breaks that hold a
|
|
159
|
-
// non-'done' inner result both carry it, so the collapse rule in `sendMessage`
|
|
160
|
-
// is what keeps "report the turn that ended the loop" from changing the
|
|
161
|
-
// discriminant a host matches on.
|
|
162
|
-
let n = 0;
|
|
163
|
-
const nextId = () => {
|
|
164
|
-
n += 1;
|
|
165
|
-
return n;
|
|
166
|
-
};
|
|
167
|
-
const handoffProvider = {
|
|
168
|
-
chat: (_h, _u, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
169
|
-
var _a;
|
|
170
|
-
// The classifier call carries the routing tool; answer it with index 0.
|
|
171
|
-
if ((_a = options === null || options === void 0 ? void 0 : options.tools) === null || _a === void 0 ? void 0 : _a.some((t) => t.name === 'select_agent')) {
|
|
172
|
-
return {
|
|
173
|
-
role: 'assistant',
|
|
174
|
-
content: '',
|
|
175
|
-
toolCalls: [{ id: `c-${nextId()}`, name: 'select_agent', args: { agent_index: 0 } }],
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
// Every agent turn asks to hand off, so the handoff cap is what ends it.
|
|
179
|
-
return {
|
|
180
|
-
role: 'assistant',
|
|
181
|
-
content: '',
|
|
182
|
-
toolCalls: [
|
|
183
|
-
{
|
|
184
|
-
id: `h-${nextId()}`,
|
|
185
|
-
name: 'request_continuation',
|
|
186
|
-
args: { summary: 's', remaining_task: 'more' },
|
|
187
|
-
},
|
|
188
|
-
],
|
|
189
|
-
};
|
|
190
|
-
}),
|
|
191
|
-
};
|
|
192
|
-
const driver = new OrchestratingDriver(makeRegistry(handoffProvider), [agent({ name: 'A' }), agent({ name: 'B' })], { sessionKey: 'budget-4', maxHandoffs: 1 });
|
|
193
|
-
const result = yield driver.sendMessage('go');
|
|
194
|
-
assert.is(result.reason, 'done', 'the internal handoff protocol never escapes');
|
|
195
|
-
}));
|
|
196
|
-
Suite('a wall on a post-handoff classify does not duplicate the user message', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
197
|
-
// `classify()` runs at TWO seams. Before the first agent turn the user's
|
|
198
|
-
// message is unappended (only the optimistic `history-updated` echo exists),
|
|
199
|
-
// so the wall-catch must append it. At the END of a handoff iteration
|
|
200
|
-
// `chatDriver.sendMessage` has already appended it — passing it again
|
|
201
|
-
// produced ['user','assistant','tool','user','assistant'] with 'go' twice,
|
|
202
|
-
// persisting into the snapshot and into the history sent to the model once
|
|
203
|
-
// the budget was raised. The single-specialist narrowness guard below cannot
|
|
204
|
-
// catch this: with one specialist, classify short-circuits and the handoff
|
|
205
|
-
// seam never runs.
|
|
206
|
-
let n = 0;
|
|
207
|
-
const provider = {
|
|
208
|
-
chat: (_h, _u, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
209
|
-
var _a;
|
|
210
|
-
n += 1;
|
|
211
|
-
if ((_a = options === null || options === void 0 ? void 0 : options.tools) === null || _a === void 0 ? void 0 : _a.some((t) => t.name === 'select_agent')) {
|
|
212
|
-
// First classify routes; the post-handoff classify hits the wall.
|
|
213
|
-
if (n > 2)
|
|
214
|
-
throw new BudgetExhaustedError('Anthropic', 25, 25.4);
|
|
215
|
-
return {
|
|
216
|
-
role: 'assistant',
|
|
217
|
-
content: '',
|
|
218
|
-
toolCalls: [{ id: `c-${n}`, name: 'select_agent', args: { agent_index: 0 } }],
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
// The agent turn asks to hand off, forcing the second classify.
|
|
222
|
-
return {
|
|
223
|
-
role: 'assistant',
|
|
224
|
-
content: '',
|
|
225
|
-
toolCalls: [
|
|
226
|
-
{
|
|
227
|
-
id: `h-${n}`,
|
|
228
|
-
name: 'request_continuation',
|
|
229
|
-
args: { summary: 's', remaining_task: 'more' },
|
|
230
|
-
},
|
|
231
|
-
],
|
|
232
|
-
};
|
|
233
|
-
}),
|
|
234
|
-
};
|
|
235
|
-
const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'A' }), agent({ name: 'B' })], { sessionKey: 'budget-4b' });
|
|
236
|
-
const result = yield driver.sendMessage('go');
|
|
237
|
-
assert.is(failureReasonOf(result), 'budget-exhausted');
|
|
238
|
-
const history = driver.getRawHistory();
|
|
239
|
-
const userMessages = history.filter((m) => m.role === 'user' && m.content === 'go');
|
|
240
|
-
assert.is(userMessages.length, 1, "the user's message appears exactly once");
|
|
241
|
-
}));
|
|
242
|
-
// ── 3. The classifier's own retry ladder ───────────────────────────────────
|
|
243
|
-
Suite('a wall during classification costs exactly one provider call', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
244
|
-
// TWO specialists and no fallback, so `classify` really runs. Before the narrow
|
|
245
|
-
// rethrow this was FOUR calls: `classifierRetries + 1` classifier attempts, then
|
|
246
|
-
// a doomed agent turn.
|
|
247
|
-
const { calls, provider } = walledProvider();
|
|
248
|
-
const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'A' }), agent({ name: 'B' })], { sessionKey: 'budget-5' });
|
|
249
|
-
const result = yield driver.sendMessage('go');
|
|
250
|
-
assert.is(calls(), 1, 'no ladder clears a budget wall — do not climb it');
|
|
251
|
-
assert.is(failureReasonOf(result), 'budget-exhausted');
|
|
252
|
-
}));
|
|
253
|
-
Suite('a classify-time wall lands on the transcript, without a routing apology', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
254
|
-
// The old ordering emitted "I'm not sure how to help with that…" from the
|
|
255
|
-
// no-fallback exit and THEN routed anyway, so the user read a routing excuse
|
|
256
|
-
// the classifier never earned, immediately above the real error.
|
|
257
|
-
const { provider } = walledProvider();
|
|
258
|
-
const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'A' }), agent({ name: 'B' })], { sessionKey: 'budget-6' });
|
|
259
|
-
yield driver.sendMessage('go');
|
|
260
|
-
const history = driver.getRawHistory();
|
|
261
|
-
const text = history.map((m) => m.content).join('\n');
|
|
262
|
-
assert.ok(text.includes('AI usage limit'), 'the real reason is what the user reads');
|
|
263
|
-
assert.not.ok(text.includes("I'm not sure how to help with that"));
|
|
264
|
-
// The user's own message must survive. `runOrchestratedTurn` dispatches it
|
|
265
|
-
// optimistically and leaves the real append to `chatDriver.sendMessage`, which
|
|
266
|
-
// a classify-time wall never reaches — so the driver's history held only the
|
|
267
|
-
// assistant bubble and the next render dropped what the user had typed.
|
|
268
|
-
assert.equal(history.map((m) => m.role), ['user', 'assistant'], 'the question stays above the answer');
|
|
269
|
-
assert.is(history[0].content, 'go');
|
|
270
|
-
}));
|
|
271
|
-
Suite('an in-loop wall does not double-append the user message', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
272
|
-
// Narrowness guard for the append above: `reportBudgetExhausted` also serves
|
|
273
|
-
// the in-loop path, where `sendMessage` has ALREADY appended the user message.
|
|
274
|
-
// Only the classify seam passes one, so this path must be unchanged. ONE
|
|
275
|
-
// specialist and no fallback, so `classify` short-circuits and the wall is hit
|
|
276
|
-
// inside a real tool loop.
|
|
277
|
-
const { provider } = walledProvider();
|
|
278
|
-
const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'Solo' })], {
|
|
279
|
-
sessionKey: 'budget-6b',
|
|
280
|
-
});
|
|
281
|
-
yield driver.sendMessage('go');
|
|
282
|
-
const history = driver.getRawHistory();
|
|
283
|
-
assert.is(history.filter((m) => m.role === 'user').length, 1, 'one user message, not two');
|
|
284
|
-
}));
|
|
285
|
-
Suite('a wall during classification never starts the fallback turn', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
286
|
-
const { calls, provider } = walledProvider();
|
|
287
|
-
const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'A' }), agent({ name: 'B' }), agent({ name: 'Catch-all', fallback: true })], { sessionKey: 'budget-7' });
|
|
288
|
-
const result = yield driver.sendMessage('go');
|
|
289
|
-
assert.is(calls(), 1, 'the fallback agent is not run into the same wall');
|
|
290
|
-
assert.is(failureReasonOf(result), 'budget-exhausted');
|
|
291
|
-
}));
|
|
292
|
-
Suite('a plain classifier failure still retries and falls back (narrowness guard)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
293
|
-
// Proves the rethrow did not make every classify failure terminal: only the
|
|
294
|
-
// budget wall short-circuits, because only the budget wall is unclearable.
|
|
295
|
-
let classifierCalls = 0;
|
|
296
|
-
const provider = {
|
|
297
|
-
chat: (_h, _u, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
298
|
-
var _a;
|
|
299
|
-
if ((_a = options === null || options === void 0 ? void 0 : options.tools) === null || _a === void 0 ? void 0 : _a.some((t) => t.name === 'select_agent')) {
|
|
300
|
-
classifierCalls += 1;
|
|
301
|
-
throw new Error('classifier is having a moment');
|
|
302
|
-
}
|
|
303
|
-
return { role: 'assistant', content: 'fallback handled it' };
|
|
304
|
-
}),
|
|
305
|
-
};
|
|
306
|
-
const driver = new OrchestratingDriver(makeRegistry(provider), [agent({ name: 'A' }), agent({ name: 'B' }), agent({ name: 'Catch-all', fallback: true })], { sessionKey: 'budget-8' });
|
|
307
|
-
const result = yield driver.sendMessage('go');
|
|
308
|
-
assert.is(classifierCalls, 3, 'classifierRetries (2) + the first attempt');
|
|
309
|
-
assert.is(result.reason, 'done');
|
|
310
|
-
assert.is(failureReasonOf(result), undefined, 'the fallback turn succeeded');
|
|
311
|
-
}));
|
|
312
|
-
Suite.run();
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { __awaiter } from "tslib";
|
|
2
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
-
// Side-effect import — MUST precede `./orchestrating-driver` so the driver
|
|
4
|
-
// subclasses jsdom's EventTarget (CustomEvent dispatch then works in node).
|
|
5
|
-
import '../chat-driver/align-event-globals';
|
|
6
|
-
import { OrchestratingDriver } from './orchestrating-driver';
|
|
7
|
-
// GENC-1508 — `recordExternalCost` is OPTIONAL on `AiDriver`, so a missing delegation here
|
|
8
|
-
// is not a compile error: the host's `driver?.recordExternalCost?.(…)` simply evaluates to
|
|
9
|
-
// undefined and every post-resolve charge from every widget disappears — silently, and only
|
|
10
|
-
// in orchestrated setups. That is the same invisible-when-broken failure the method exists to
|
|
11
|
-
// fix, so the delegation gets a test rather than trust.
|
|
12
|
-
const makeRegistry = (provider) => ({
|
|
13
|
-
get: () => provider,
|
|
14
|
-
default: () => provider,
|
|
15
|
-
defaultName: () => 'test',
|
|
16
|
-
names: () => ['test'],
|
|
17
|
-
getStatus: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
|
|
18
|
-
listStatuses: () => __awaiter(void 0, void 0, void 0, function* () { return []; }),
|
|
19
|
-
});
|
|
20
|
-
const noToolProvider = () => ({
|
|
21
|
-
chat: (_history, _userMessage, _options) => __awaiter(void 0, void 0, void 0, function* () { return ({ role: 'assistant', content: 'ok' }); }),
|
|
22
|
-
});
|
|
23
|
-
const agent = (overrides) => (Object.assign({ description: 'test agent' }, overrides));
|
|
24
|
-
const makeDriver = () => new OrchestratingDriver(makeRegistry(noToolProvider()), [agent({ name: 'Genesis Assistant' })], {
|
|
25
|
-
sessionKey: 'k',
|
|
26
|
-
});
|
|
27
|
-
/** The orchestrator does not expose `requestInteraction`, so the interaction is ARRANGED on the
|
|
28
|
-
* wrapped driver. Every assertion below goes through the orchestrator's own public surface. */
|
|
29
|
-
const wrapped = (driver) => driver.chatDriver;
|
|
30
|
-
const Suite = createLogicSuite('orchestrating-driver external cost');
|
|
31
|
-
Suite('post-resolve spend delegates to the wrapped driver', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
32
|
-
const driver = makeDriver();
|
|
33
|
-
const inner = wrapped(driver);
|
|
34
|
-
const pending = inner.requestInteraction('planning-question', { question: 'Pick one' });
|
|
35
|
-
const id = driver.getRawHistory().at(-1).interaction.interactionId;
|
|
36
|
-
driver.resolveInteraction(id, { status: 'approved', costUsd: 0.5 });
|
|
37
|
-
yield pending;
|
|
38
|
-
assert.is(driver.recordExternalCost(id, 0.25), true);
|
|
39
|
-
const msg = driver.getRawHistory().find((m) => { var _a; return ((_a = m.interaction) === null || _a === void 0 ? void 0 : _a.interactionId) === id; });
|
|
40
|
-
assert.is(msg === null || msg === void 0 ? void 0 : msg.externalCostUsd, 0.75);
|
|
41
|
-
}));
|
|
42
|
-
Suite('an unknown interaction is reported as unrecorded, not thrown', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
43
|
-
const driver = makeDriver();
|
|
44
|
-
assert.is(driver.recordExternalCost('never-existed', 0.25), false);
|
|
45
|
-
}));
|
|
46
|
-
Suite('the announcement reaches the HOST, which only ever holds this wrapper', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
47
|
-
// The delegation test above passes with the forward missing: the money still lands on the
|
|
48
|
-
// message. What breaks is quieter — `createDriver` hands the host an OrchestratingDriver for any
|
|
49
|
-
// non-empty `agents`, so an event the inner driver raises and the constructor's forward list
|
|
50
|
-
// omits is one the host never sees. The totals then never refresh and the save is never
|
|
51
|
-
// scheduled, and because this flow's interaction is the last thing in the session, "stale until
|
|
52
|
-
// the next history change" means permanently. Asserted on the WRAPPER for that reason.
|
|
53
|
-
const driver = makeDriver();
|
|
54
|
-
const inner = wrapped(driver);
|
|
55
|
-
const pending = inner.requestInteraction('planning-question', { question: 'Pick one' });
|
|
56
|
-
const id = driver.getRawHistory().at(-1).interaction.interactionId;
|
|
57
|
-
driver.resolveInteraction(id, { status: 'approved' });
|
|
58
|
-
yield pending;
|
|
59
|
-
let seen = 0;
|
|
60
|
-
let detail;
|
|
61
|
-
driver.addEventListener('external-cost-recorded', (e) => {
|
|
62
|
-
seen += 1;
|
|
63
|
-
detail = e.detail;
|
|
64
|
-
});
|
|
65
|
-
// …and never on `history-updated`, which would rebuild the reporting widget.
|
|
66
|
-
let historyUpdated = 0;
|
|
67
|
-
driver.addEventListener('history-updated', () => {
|
|
68
|
-
historyUpdated += 1;
|
|
69
|
-
});
|
|
70
|
-
driver.recordExternalCost(id, 0.25);
|
|
71
|
-
assert.is(seen, 1);
|
|
72
|
-
assert.is(detail === null || detail === void 0 ? void 0 : detail.costUsd, 0.25);
|
|
73
|
-
assert.is(detail === null || detail === void 0 ? void 0 : detail.interactionId, id);
|
|
74
|
-
assert.is(historyUpdated, 0);
|
|
75
|
-
}));
|
|
76
|
-
Suite.run();
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { __awaiter } from "tslib";
|
|
2
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
-
// Side-effect import — MUST precede `./orchestrating-driver` so the driver
|
|
4
|
-
// subclasses jsdom's EventTarget (CustomEvent dispatch then works in node).
|
|
5
|
-
import '../chat-driver/align-event-globals';
|
|
6
|
-
import { OrchestratingDriver } from './orchestrating-driver';
|
|
7
|
-
// GENC-1351 — a set routing pin must be honoured on the next turn. This is the
|
|
8
|
-
// invariant that broke on restore (found via the §5.8 diagnostics log): the pin
|
|
9
|
-
// was hydrated into the store but never pushed to the driver, so the first
|
|
10
|
-
// post-restore message fell through to the classifier and re-routed away from the
|
|
11
|
-
// pinned agent. `Guided Booking` here is `excludeFromClassifier`, so the classifier
|
|
12
|
-
// can NEVER pick it — a turn landing on it proves the pin (not classification)
|
|
13
|
-
// routed it. The element-side fix (restore → `driver.setPinnedAgent`) is what makes
|
|
14
|
-
// the pin reach the driver; this locks the driver half of the contract.
|
|
15
|
-
const makeRegistry = (provider) => ({
|
|
16
|
-
get: () => provider,
|
|
17
|
-
default: () => provider,
|
|
18
|
-
defaultName: () => 'test',
|
|
19
|
-
names: () => ['test'],
|
|
20
|
-
getStatus: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
|
|
21
|
-
listStatuses: () => __awaiter(void 0, void 0, void 0, function* () { return []; }),
|
|
22
|
-
});
|
|
23
|
-
/** A provider that ends every turn immediately with a plain-text reply (no tools). */
|
|
24
|
-
const noToolProvider = () => ({
|
|
25
|
-
chat: (_history, _userMessage, _options) => __awaiter(void 0, void 0, void 0, function* () { return ({ role: 'assistant', content: 'ok' }); }),
|
|
26
|
-
});
|
|
27
|
-
const agent = (overrides) => (Object.assign({ description: 'test agent' }, overrides));
|
|
28
|
-
const makeDriver = () => new OrchestratingDriver(makeRegistry(noToolProvider()), [
|
|
29
|
-
agent({ name: 'Genesis Assistant' }), // classifier-eligible specialist
|
|
30
|
-
agent({ name: 'Guided Booking', excludeFromClassifier: true }), // pin-only
|
|
31
|
-
], { sessionKey: 'k' });
|
|
32
|
-
const Suite = createLogicSuite('orchestrating-driver-pin');
|
|
33
|
-
Suite('a set pin routes the next turn to the pinned agent (bypassing the classifier)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
-
var _a;
|
|
35
|
-
const driver = makeDriver();
|
|
36
|
-
driver.setPinnedAgent('Guided Booking');
|
|
37
|
-
yield driver.sendMessage('hi');
|
|
38
|
-
assert.is((_a = driver.getActiveAgent()) === null || _a === void 0 ? void 0 : _a.name, 'Guided Booking', 'pinned agent ran; it is excludeFromClassifier, so only the pin could have routed it');
|
|
39
|
-
}));
|
|
40
|
-
Suite('the pin is sticky across turns', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
41
|
-
var _a;
|
|
42
|
-
const driver = makeDriver();
|
|
43
|
-
driver.setPinnedAgent('Guided Booking');
|
|
44
|
-
yield driver.sendMessage('one');
|
|
45
|
-
yield driver.sendMessage('two');
|
|
46
|
-
assert.is((_a = driver.getActiveAgent()) === null || _a === void 0 ? void 0 : _a.name, 'Guided Booking', 'still pinned on the second turn');
|
|
47
|
-
}));
|
|
48
|
-
// GENC-1351 — the flow-owner lock is the *unpinned* routing path. A classifier-routed
|
|
49
|
-
// stateful flow (flow-owned but never user-pinned) must resume on restore without
|
|
50
|
-
// re-classifying: the persister re-applies BOTH `setPinnedAgent` and `setFlowOwner` on
|
|
51
|
-
// restore, and `resolvePinnedAgent` reads `flowOwnerAgentName ?? pinnedAgentName`. Here
|
|
52
|
-
// `Guided Booking` is `excludeFromClassifier` AND left unpinned, so a turn landing on it
|
|
53
|
-
// proves the flow-owner lock (not the pin, not classification) routed it.
|
|
54
|
-
Suite('a set flow-owner lock routes the next turn to that agent (bypassing the classifier)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
55
|
-
var _a;
|
|
56
|
-
const driver = makeDriver();
|
|
57
|
-
driver.setFlowOwner('Guided Booking'); // flow-owned but NOT pinned
|
|
58
|
-
yield driver.sendMessage('hi');
|
|
59
|
-
assert.is((_a = driver.getActiveAgent()) === null || _a === void 0 ? void 0 : _a.name, 'Guided Booking', 'flow-owner routed it; it is excludeFromClassifier and unpinned, so only the flow-owner lock could have');
|
|
60
|
-
}));
|
|
61
|
-
Suite.run();
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
var _a;
|
|
2
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
-
import { settingsModalStyles } from './settings-modal.styles';
|
|
4
|
-
// GENC-1464 budget meter — the styling invariants a reviewer would otherwise
|
|
5
|
-
// have to catch by eye, following main.styles.test.ts (see there for why CSS
|
|
6
|
-
// gets asserted from the generated text at all, and for the scoping lesson:
|
|
7
|
-
// these assertions are about the BUDGET rules only, not a global style policy).
|
|
8
|
-
//
|
|
9
|
-
// 1. Design-system tokens are consumed UNFALLBACKED — the #2411/#2436 review
|
|
10
|
-
// rule. Every token the meter reads ships a DS default, so a
|
|
11
|
-
// `var(--token, fallback)` is dead code at best and a silently-broken
|
|
12
|
-
// token name at worst.
|
|
13
|
-
// 2. The meter's row anatomy is SHARED with the context indicator via grouped
|
|
14
|
-
// selectors, not copied. The two indicators are siblings by design;
|
|
15
|
-
// copied declarations would let them drift apart one edit at a time, which
|
|
16
|
-
// is exactly what the grouping was chosen to make impossible.
|
|
17
|
-
const suite = createLogicSuite('settings-modal.styles budget-meter rules');
|
|
18
|
-
/**
|
|
19
|
-
* The generated stylesheet text, flattened recursively — `css` composes nested
|
|
20
|
-
* `ElementStyles`, and a shallow read can return almost nothing, making every
|
|
21
|
-
* negative assertion below pass vacuously. The first test guards that.
|
|
22
|
-
*/
|
|
23
|
-
const flatten = (value) => {
|
|
24
|
-
if (typeof value === 'string')
|
|
25
|
-
return value;
|
|
26
|
-
if (Array.isArray(value))
|
|
27
|
-
return value.map(flatten).join('\n');
|
|
28
|
-
const nested = value === null || value === void 0 ? void 0 : value.styles;
|
|
29
|
-
return nested ? flatten(nested) : '';
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* Comments stripped before matching: the assertions below are about
|
|
33
|
-
* DECLARATIONS, and the sheet's own prose legitimately mentions the very
|
|
34
|
-
* pattern (`var(--x, y)`) the negative assertion hunts for.
|
|
35
|
-
*/
|
|
36
|
-
const cssText = flatten(settingsModalStyles).replace(/\/\*[\s\S]*?\*\//g, '');
|
|
37
|
-
/** Every declaration block whose selector mentions `.settings-budget-`, joined. */
|
|
38
|
-
const budgetRules = ((_a = cssText.match(/[^{}]*\.settings-budget-[^{}]*\{[^}]*\}/g)) !== null && _a !== void 0 ? _a : []).join('\n');
|
|
39
|
-
suite('the budget rules were extracted as text (guards the assertions below)', () => {
|
|
40
|
-
assert.ok(cssText.includes('.settings-budget-usage'), 'the meter container rule is in the sheet');
|
|
41
|
-
assert.ok(budgetRules.length > 0, 'the extraction found the budget rules');
|
|
42
|
-
});
|
|
43
|
-
suite('the meter consumes design-system tokens without dead fallbacks', () => {
|
|
44
|
-
// One structural probe rather than a per-token list: ANY `var(--x,` inside a
|
|
45
|
-
// budget rule is a fallback, and the meter reads only tokens that ship DS
|
|
46
|
-
// defaults (--design-unit, --neutral-foreground-*), so there is no legitimate
|
|
47
|
-
// fallback for this scope. Scoped to the budget rules on purpose — see
|
|
48
|
-
// main.styles.test.ts for why a whole-sheet grep is the wrong test.
|
|
49
|
-
assert.not.ok(/var\(--[a-z-]+\s*,/.test(budgetRules), 'no var(--token, fallback) in the meter');
|
|
50
|
-
assert.ok(budgetRules.includes('var(--design-unit)'), 'and it does consume tokens');
|
|
51
|
-
});
|
|
52
|
-
suite('every meter row sits on one shared rhythm', () => {
|
|
53
|
-
// The 4/2/2 fix. `.settings-modal-section-body` gaps at 4 units and the
|
|
54
|
-
// budget meter at 2, so an unwrapped context indicator sat 4 units above the
|
|
55
|
-
// FIRST vendor row while the vendor rows sat 2 apart — three visually
|
|
56
|
-
// identical meter rows, unevenly spaced. Both meters now live in
|
|
57
|
-
// `.settings-usage-meters`, which must carry the SAME gap as the meter it
|
|
58
|
-
// wraps: asserted as ONE grouped rule, so the two cannot drift to different
|
|
59
|
-
// rhythms. (settings-modal.template.test.ts pins the other half — that the
|
|
60
|
-
// wrapper is conditional, so a host with no meters pays no stray gap for it.)
|
|
61
|
-
const shared = /\.settings-usage-meters\s*,\s*\.settings-budget-usage\s*\{([^}]*)\}/.exec(cssText);
|
|
62
|
-
assert.ok(shared, '.settings-usage-meters is grouped with .settings-budget-usage');
|
|
63
|
-
assert.ok(shared[1].includes('flex-direction: column'), 'the shared container is a column');
|
|
64
|
-
assert.ok(shared[1].includes('gap: calc(var(--design-unit) * 2px)'), 'gapped at the meter rhythm (2 units), not the section body 4');
|
|
65
|
-
});
|
|
66
|
-
suite("the meter shares the context indicator's row anatomy by grouped selector", () => {
|
|
67
|
-
// The invariant is the SHARING, not the shape: each budget class must sit in
|
|
68
|
-
// the same selector list as its context sibling, so restyling one restyles
|
|
69
|
-
// both and the two indicators cannot drift apart.
|
|
70
|
-
const sharedPairs = [
|
|
71
|
-
['.settings-context-header', '.settings-budget-header'],
|
|
72
|
-
['.settings-context-label', '.settings-budget-label'],
|
|
73
|
-
['.settings-context-tokens', '.settings-budget-figures'],
|
|
74
|
-
];
|
|
75
|
-
for (const [contextClass, budgetClass] of sharedPairs) {
|
|
76
|
-
const grouped = new RegExp(`${contextClass.replace(/\./g, '\\.')}\\s*,\\s*${budgetClass.replace(/\./g, '\\.')}\\s*\\{`);
|
|
77
|
-
assert.ok(grouped.test(cssText), `${budgetClass} is grouped with ${contextClass}`);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
suite.run();
|