@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,228 +0,0 @@
|
|
|
1
|
-
import type { ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
-
import {
|
|
4
|
-
buildCompactionSummaryPrompt,
|
|
5
|
-
compactionDividerLabel,
|
|
6
|
-
COMPACT_KEEP_RECENT_MESSAGES,
|
|
7
|
-
COMPACT_MIN_MESSAGES_TO_COMPACT,
|
|
8
|
-
findCompactionCut,
|
|
9
|
-
normalizeForProvider,
|
|
10
|
-
transformHistoryForAgent,
|
|
11
|
-
} from './history-transform';
|
|
12
|
-
|
|
13
|
-
const asMsg = (o: Record<string, unknown>): ChatMessage => o as unknown as ChatMessage;
|
|
14
|
-
const user = (content: string) => asMsg({ role: 'user', content });
|
|
15
|
-
const assistant = (content: string) => asMsg({ role: 'assistant', content });
|
|
16
|
-
|
|
17
|
-
// GENC-1351 §5.7 — the pure compaction helpers (no DOM/provider deps).
|
|
18
|
-
const Suite = createLogicSuite('history-transform-compaction');
|
|
19
|
-
|
|
20
|
-
// ── findCompactionCut ─────────────────────────────────────────────────────────
|
|
21
|
-
|
|
22
|
-
Suite('findCompactionCut returns null for a transcript too short to be worth compacting', () => {
|
|
23
|
-
assert.is(findCompactionCut([]), null);
|
|
24
|
-
// One below the floor (keep-recent + min-to-compact) → nothing to do.
|
|
25
|
-
const justUnder = Array.from(
|
|
26
|
-
{ length: COMPACT_KEEP_RECENT_MESSAGES + COMPACT_MIN_MESSAGES_TO_COMPACT - 1 },
|
|
27
|
-
(_, i) => (i % 2 === 0 ? user(`u${i}`) : assistant(`a${i}`)),
|
|
28
|
-
);
|
|
29
|
-
assert.is(findCompactionCut(justUnder), null);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
Suite('findCompactionCut cuts at a user-message boundary, keeping the recent tail', () => {
|
|
33
|
-
// 10 messages alternating user/assistant; user at even indices.
|
|
34
|
-
const h = Array.from({ length: 10 }, (_, i) =>
|
|
35
|
-
i % 2 === 0 ? user(`u${i}`) : assistant(`a${i}`),
|
|
36
|
-
);
|
|
37
|
-
const cut = findCompactionCut(h);
|
|
38
|
-
// target = 10 - 4 = 6; history[6] is a user message → cut there.
|
|
39
|
-
assert.is(cut, 6);
|
|
40
|
-
assert.is(h[cut!].role, 'user', 'tail must start on a user boundary');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
Suite('findCompactionCut walks back to the nearest earlier user boundary', () => {
|
|
44
|
-
// user only at indices 0,1,3,5,7,9 — index 6 is assistant, so it walks back to 5.
|
|
45
|
-
const h = [
|
|
46
|
-
user('u0'),
|
|
47
|
-
user('u1'),
|
|
48
|
-
assistant('a2'),
|
|
49
|
-
user('u3'),
|
|
50
|
-
assistant('a4'),
|
|
51
|
-
user('u5'),
|
|
52
|
-
assistant('a6'),
|
|
53
|
-
user('u7'),
|
|
54
|
-
assistant('a8'),
|
|
55
|
-
user('u9'),
|
|
56
|
-
];
|
|
57
|
-
// target = 6 (assistant) → walk back to 5 (user).
|
|
58
|
-
assert.is(findCompactionCut(h), 5);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
Suite('findCompactionCut returns null when the only user turn is the first message', () => {
|
|
62
|
-
// No user boundary after index 0 → nothing can be summarized behind a boundary.
|
|
63
|
-
const h = [user('u0'), ...Array.from({ length: 9 }, (_, i) => assistant(`a${i}`))];
|
|
64
|
-
assert.is(findCompactionCut(h), null);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
Suite(
|
|
68
|
-
'findCompactionCut includes an existing head summary in the region to compact (rolling)',
|
|
69
|
-
() => {
|
|
70
|
-
const h = [
|
|
71
|
-
asMsg({ role: 'compacted-summary', content: 'prior', compaction: { compactedCount: 3 } }),
|
|
72
|
-
user('u1'),
|
|
73
|
-
assistant('a2'),
|
|
74
|
-
user('u3'),
|
|
75
|
-
assistant('a4'),
|
|
76
|
-
user('u5'),
|
|
77
|
-
assistant('a6'),
|
|
78
|
-
user('u7'),
|
|
79
|
-
assistant('a8'),
|
|
80
|
-
user('u9'),
|
|
81
|
-
];
|
|
82
|
-
const cut = findCompactionCut(h);
|
|
83
|
-
assert.ok(
|
|
84
|
-
cut != null && cut >= 1,
|
|
85
|
-
'cut should leave the head summary inside the compacted region',
|
|
86
|
-
);
|
|
87
|
-
assert.is(h.slice(0, cut!)[0].role, 'compacted-summary', 'prior summary is folded back in');
|
|
88
|
-
},
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
// ── buildCompactionSummaryPrompt ──────────────────────────────────────────────
|
|
92
|
-
|
|
93
|
-
Suite('buildCompactionSummaryPrompt wraps the transcript and labels roles', () => {
|
|
94
|
-
const { systemPrompt, userMessage } = buildCompactionSummaryPrompt([
|
|
95
|
-
user('book a trade'),
|
|
96
|
-
assistant('sure, which counterparty?'),
|
|
97
|
-
]);
|
|
98
|
-
assert.ok(/summar/i.test(systemPrompt), 'system prompt frames a summarization task');
|
|
99
|
-
assert.ok(userMessage.includes('<conversation_history>'));
|
|
100
|
-
assert.ok(userMessage.includes('</conversation_history>'));
|
|
101
|
-
assert.ok(userMessage.includes('User: book a trade'));
|
|
102
|
-
assert.ok(userMessage.includes('Assistant: sure, which counterparty?'));
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
Suite('buildCompactionSummaryPrompt renders tool activity and a prior summary', () => {
|
|
106
|
-
const { userMessage } = buildCompactionSummaryPrompt([
|
|
107
|
-
asMsg({
|
|
108
|
-
role: 'compacted-summary',
|
|
109
|
-
content: 'earlier stuff',
|
|
110
|
-
compaction: { compactedCount: 2 },
|
|
111
|
-
}),
|
|
112
|
-
asMsg({
|
|
113
|
-
role: 'assistant',
|
|
114
|
-
content: '',
|
|
115
|
-
toolCalls: [{ id: 't1', name: 'searchTrades', args: {} }],
|
|
116
|
-
}),
|
|
117
|
-
asMsg({ role: 'tool', content: '', toolResult: { toolCallId: 't1', content: '3 hits' } }),
|
|
118
|
-
]);
|
|
119
|
-
assert.ok(userMessage.includes('[Earlier summary] earlier stuff'));
|
|
120
|
-
assert.ok(userMessage.includes('searchTrades'), 'tool call name is preserved for continuity');
|
|
121
|
-
assert.ok(userMessage.includes('Tool result: 3 hits'));
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
Suite(
|
|
125
|
-
'buildCompactionSummaryPrompt folds in reasoning + narration, labelled Thinking / Working',
|
|
126
|
-
() => {
|
|
127
|
-
const { userMessage } = buildCompactionSummaryPrompt([
|
|
128
|
-
asMsg({
|
|
129
|
-
role: 'assistant',
|
|
130
|
-
content: 'weigh the counterparty options',
|
|
131
|
-
category: 'reasoning',
|
|
132
|
-
}),
|
|
133
|
-
asMsg({ role: 'assistant', content: 'searching the trade blotter', category: 'narration' }),
|
|
134
|
-
]);
|
|
135
|
-
assert.ok(userMessage.includes('Thinking: weigh the counterparty options'));
|
|
136
|
-
assert.ok(userMessage.includes('Working: searching the trade blotter'));
|
|
137
|
-
},
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
// ── normalizeForProvider (the no-leak guarantee) ──────────────────────────────
|
|
141
|
-
|
|
142
|
-
Suite(
|
|
143
|
-
'normalizeForProvider translates compacted-summary into a <conversation_summary> user msg',
|
|
144
|
-
() => {
|
|
145
|
-
const out = normalizeForProvider([
|
|
146
|
-
asMsg({ role: 'compacted-summary', content: 'the gist', compaction: { compactedCount: 9 } }),
|
|
147
|
-
user('and now?'),
|
|
148
|
-
]);
|
|
149
|
-
assert.is(out[0].role, 'user', 'the raw display-only role is gone');
|
|
150
|
-
assert.ok(out[0].content.includes('<conversation_summary>'));
|
|
151
|
-
assert.ok(out[0].content.includes('the gist'));
|
|
152
|
-
assert.is(out[0].compaction, undefined, 'compaction metadata is dropped from the sent message');
|
|
153
|
-
},
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
Suite('normalizeForProvider never emits a compacted-summary role (no leak to a transport)', () => {
|
|
157
|
-
const out = normalizeForProvider([
|
|
158
|
-
asMsg({ role: 'compacted-summary', content: 's1', compaction: { compactedCount: 1 } }),
|
|
159
|
-
user('u'),
|
|
160
|
-
assistant('a'),
|
|
161
|
-
asMsg({ role: 'system-event', content: 'Switched agent' }),
|
|
162
|
-
]);
|
|
163
|
-
assert.ok(
|
|
164
|
-
out.every((m) => m.role !== 'compacted-summary'),
|
|
165
|
-
'no compacted-summary role survives normalization',
|
|
166
|
-
);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
Suite('normalizeForProvider passes non-summary messages through untouched (same ref)', () => {
|
|
170
|
-
const u = user('hello');
|
|
171
|
-
const a = assistant('hi');
|
|
172
|
-
const out = normalizeForProvider([u, a]);
|
|
173
|
-
assert.is(out[0], u);
|
|
174
|
-
assert.is(out[1], a);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
// ── compactionDividerLabel ────────────────────────────────────────────────────
|
|
178
|
-
|
|
179
|
-
Suite('compactionDividerLabel pluralizes and falls back to 0', () => {
|
|
180
|
-
assert.is(
|
|
181
|
-
compactionDividerLabel(
|
|
182
|
-
asMsg({ role: 'compacted-summary', content: '', compaction: { compactedCount: 1 } }),
|
|
183
|
-
),
|
|
184
|
-
'Compacted 1 earlier message',
|
|
185
|
-
);
|
|
186
|
-
assert.is(
|
|
187
|
-
compactionDividerLabel(
|
|
188
|
-
asMsg({ role: 'compacted-summary', content: '', compaction: { compactedCount: 12 } }),
|
|
189
|
-
),
|
|
190
|
-
'Compacted 12 earlier messages',
|
|
191
|
-
);
|
|
192
|
-
assert.is(
|
|
193
|
-
compactionDividerLabel(asMsg({ role: 'compacted-summary', content: '' })),
|
|
194
|
-
'Compacted 0 earlier messages',
|
|
195
|
-
);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
// ── masking another agent's tool payload (GENC-1508) ─────────────────────────
|
|
199
|
-
|
|
200
|
-
Suite('transformHistoryForAgent drops tool-result IMAGES, not just the text', () => {
|
|
201
|
-
const history: ChatMessage[] = [
|
|
202
|
-
asMsg({
|
|
203
|
-
role: 'tool',
|
|
204
|
-
content: '',
|
|
205
|
-
agentName: 'other-agent',
|
|
206
|
-
toolResult: {
|
|
207
|
-
toolCallId: 'r1',
|
|
208
|
-
content: 'rendered the mockup',
|
|
209
|
-
attachments: [
|
|
210
|
-
{ kind: 'image', name: 'mockup.png', mimeType: 'image/png', data: 'a'.repeat(2000) },
|
|
211
|
-
],
|
|
212
|
-
},
|
|
213
|
-
}),
|
|
214
|
-
];
|
|
215
|
-
const out = transformHistoryForAgent(history, 'this-agent');
|
|
216
|
-
assert.ok(
|
|
217
|
-
out[0].toolResult!.content.includes('omitted'),
|
|
218
|
-
'the text is stubbed, as it always was',
|
|
219
|
-
);
|
|
220
|
-
assert.is(
|
|
221
|
-
out[0].toolResult!.attachments,
|
|
222
|
-
undefined,
|
|
223
|
-
"masking exists to keep another agent's payload out of context — an image IS the payload, " +
|
|
224
|
-
'and stubbing the prose while shipping the render defeats the mask where it matters most',
|
|
225
|
-
);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
Suite.run();
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import type { ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Masks the tool-specific payload of a single message — clears tool call args
|
|
5
|
-
* and replaces tool result content with a placeholder. Used when passing history
|
|
6
|
-
* to an agent that should not see another agent's implementation detail.
|
|
7
|
-
*/
|
|
8
|
-
function maskToolPayload(msg: ChatMessage): ChatMessage {
|
|
9
|
-
if (msg.toolCalls?.length) {
|
|
10
|
-
return { ...msg, toolCalls: msg.toolCalls.map((tc) => ({ ...tc, args: {} })) };
|
|
11
|
-
}
|
|
12
|
-
if (msg.toolResult) {
|
|
13
|
-
// `attachments` is dropped, not spread through. Masking exists to keep another agent's
|
|
14
|
-
// payload out of this one's context, and an image is the largest payload there is — spreading
|
|
15
|
-
// it would stub the prose while still shipping the render, defeating the mask for exactly the
|
|
16
|
-
// case it matters most. `applyHistoryCap` masks everything past the cap for sub-agent context,
|
|
17
|
-
// so without this every older image rides every sub-agent turn under an "omitted" label.
|
|
18
|
-
const { attachments: _masked, ...restOfResult } = msg.toolResult;
|
|
19
|
-
return {
|
|
20
|
-
...msg,
|
|
21
|
-
toolResult: { ...restOfResult, content: "[other agent's tool result omitted]" },
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
return msg;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Prepares history for the LLM only: masks tool call args and results from other
|
|
29
|
-
* agents so the active specialist is not confused by tools it does not have.
|
|
30
|
-
* Canonical history in `ChatDriver` stays unmasked for UI and logging.
|
|
31
|
-
*/
|
|
32
|
-
export function transformHistoryForAgent(history: ChatMessage[], agentName: string): ChatMessage[] {
|
|
33
|
-
return history.map((msg) => {
|
|
34
|
-
if (!msg.agentName || msg.agentName === agentName) return msg;
|
|
35
|
-
return maskToolPayload(msg);
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Applies a history cap for sub-agent context: the last `cap` messages are passed
|
|
41
|
-
* verbatim; older messages have their tool payloads masked.
|
|
42
|
-
*/
|
|
43
|
-
export function applyHistoryCap(history: readonly ChatMessage[], cap: number): ChatMessage[] {
|
|
44
|
-
const cutoff = history.length - cap;
|
|
45
|
-
return history.map((msg, i) => (i < cutoff ? maskToolPayload(msg) : msg));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ───────────────────────────── Compaction (GENC-1351 §5.7) ─────────────────────────────
|
|
49
|
-
|
|
50
|
-
/** Recent messages kept verbatim when compacting; everything earlier is summarized. */
|
|
51
|
-
export const COMPACT_KEEP_RECENT_MESSAGES = 4;
|
|
52
|
-
|
|
53
|
-
/** Don't bother compacting unless at least this many messages would be summarized. */
|
|
54
|
-
export const COMPACT_MIN_MESSAGES_TO_COMPACT = 4;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Index at which the verbatim tail begins — everything before it gets summarized.
|
|
58
|
-
* The tail starts on a clean turn boundary (a `user` message): a user message
|
|
59
|
-
* never appears between a `tool_use` and its `tool_result`, so cutting there
|
|
60
|
-
* guarantees the summarized region is tool-pair-balanced (compaction only runs
|
|
61
|
-
* when idle, so history is turn-complete). Returns `null` when there is nothing
|
|
62
|
-
* worth compacting (too short, or no earlier user boundary) — e.g. a freshly
|
|
63
|
-
* restored session whose only user turn is the current one.
|
|
64
|
-
*/
|
|
65
|
-
export function findCompactionCut(history: readonly ChatMessage[]): number | null {
|
|
66
|
-
if (history.length < COMPACT_KEEP_RECENT_MESSAGES + COMPACT_MIN_MESSAGES_TO_COMPACT) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
const target = history.length - COMPACT_KEEP_RECENT_MESSAGES;
|
|
70
|
-
for (let i = target; i > 0; i -= 1) {
|
|
71
|
-
if (history[i].role === 'user') {
|
|
72
|
-
return i >= COMPACT_MIN_MESSAGES_TO_COMPACT ? i : null;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** One-line rendering of a message for the summarizer transcript. */
|
|
79
|
-
function renderMessageForSummary(m: ChatMessage): string {
|
|
80
|
-
switch (m.role) {
|
|
81
|
-
case 'compacted-summary':
|
|
82
|
-
return `[Earlier summary] ${m.content}`;
|
|
83
|
-
case 'user':
|
|
84
|
-
return m.content ? `User: ${m.content}` : '';
|
|
85
|
-
case 'assistant': {
|
|
86
|
-
// Reasoning ("Thinking") and narration ("Working") are display-only assistant splits
|
|
87
|
-
// (GENC-1410) with no tool calls of their own — fold them into the summary too, labelled
|
|
88
|
-
// the same as the UI so the summarizer knows they were internal, not a final answer.
|
|
89
|
-
if (m.category === 'reasoning') {
|
|
90
|
-
return m.content ? `Thinking: ${m.content}` : '';
|
|
91
|
-
}
|
|
92
|
-
if (m.category === 'narration') {
|
|
93
|
-
return m.content ? `Working: ${m.content}` : '';
|
|
94
|
-
}
|
|
95
|
-
if (m.toolCalls?.length) {
|
|
96
|
-
const calls = m.toolCalls.map((t) => t.name).join(', ');
|
|
97
|
-
return m.content
|
|
98
|
-
? `Assistant: ${m.content} (called: ${calls})`
|
|
99
|
-
: `Assistant called: ${calls}`;
|
|
100
|
-
}
|
|
101
|
-
return m.content ? `Assistant: ${m.content}` : '';
|
|
102
|
-
}
|
|
103
|
-
case 'tool':
|
|
104
|
-
return m.toolResult ? `Tool result: ${m.toolResult.content}` : '';
|
|
105
|
-
default:
|
|
106
|
-
return ''; // system-event / synthetic-user — not part of the model transcript
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Build the one-shot summarizer prompt for a run of older messages (used with
|
|
112
|
-
* `AIProvider.prompt`). Mirrors the `<conversation_history>` block convention
|
|
113
|
-
* that suggestions use. Any existing `compacted-summary` at the head is folded
|
|
114
|
-
* in (rendered as prior-summary context), so rolling re-compaction produces one
|
|
115
|
-
* combined summary.
|
|
116
|
-
*/
|
|
117
|
-
export function buildCompactionSummaryPrompt(toCompact: readonly ChatMessage[]): {
|
|
118
|
-
systemPrompt: string;
|
|
119
|
-
userMessage: string;
|
|
120
|
-
} {
|
|
121
|
-
const transcript = toCompact.map(renderMessageForSummary).filter(Boolean).join('\n');
|
|
122
|
-
const systemPrompt =
|
|
123
|
-
'You compress a conversation transcript into a concise summary that lets an AI ' +
|
|
124
|
-
'assistant continue the conversation seamlessly. Preserve key facts, decisions, ' +
|
|
125
|
-
'what the user wants, entity names and IDs, and any unresolved threads. Write ' +
|
|
126
|
-
'plain prose. Never roleplay or continue the conversation.';
|
|
127
|
-
const userMessage =
|
|
128
|
-
`<conversation_history>\n${transcript}\n</conversation_history>\n\n` +
|
|
129
|
-
'Summarize the conversation above so it can be dropped from context while ' +
|
|
130
|
-
'preserving everything needed to continue. Output only the summary prose.';
|
|
131
|
-
return { systemPrompt, userMessage };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Normalize history for a provider request (GENC-1351 §5.7): translate each
|
|
136
|
-
* `compacted-summary` message into a `user` message that wraps its summary in a
|
|
137
|
-
* `<conversation_summary>` block, so the raw display-only role never reaches a
|
|
138
|
-
* transport. Other roles pass through untouched. Pure — returns a new array.
|
|
139
|
-
*
|
|
140
|
-
* Applied at the request-build chokepoint *before* per-agent masking, so the
|
|
141
|
-
* guarantee holds for every provider/transport regardless of any optional
|
|
142
|
-
* per-agent history transform.
|
|
143
|
-
*/
|
|
144
|
-
export function normalizeForProvider(history: readonly ChatMessage[]): ChatMessage[] {
|
|
145
|
-
return history.map((m) => {
|
|
146
|
-
if (m.role !== 'compacted-summary') return m;
|
|
147
|
-
return {
|
|
148
|
-
...m,
|
|
149
|
-
role: 'user',
|
|
150
|
-
content:
|
|
151
|
-
`<conversation_summary>\n${m.content}\n</conversation_summary>\n\n` +
|
|
152
|
-
'The above summarizes earlier parts of this conversation that were compacted. ' +
|
|
153
|
-
'Continue naturally from it.',
|
|
154
|
-
compaction: undefined,
|
|
155
|
-
};
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/** Human-facing divider label for a `compacted-summary` message. */
|
|
160
|
-
export function compactionDividerLabel(m: ChatMessage): string {
|
|
161
|
-
const n = m.compaction?.compactedCount ?? 0;
|
|
162
|
-
return `Compacted ${n} earlier message${n === 1 ? '' : 's'}`;
|
|
163
|
-
}
|
package/src/utils/index.ts
DELETED
package/src/utils/logger.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import type { ChatInteraction, ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
3
|
-
import {
|
|
4
|
-
filterVisibleMessages,
|
|
5
|
-
type MessageFilterState,
|
|
6
|
-
SESSION_RESTORED_LABEL,
|
|
7
|
-
SESSION_RESTORE_FAILED_LABEL,
|
|
8
|
-
trailingInteractionRow,
|
|
9
|
-
} from './message-partition';
|
|
10
|
-
|
|
11
|
-
const msg = (overrides: Partial<ChatMessage> = {}): ChatMessage => ({
|
|
12
|
-
role: 'assistant',
|
|
13
|
-
content: '',
|
|
14
|
-
...overrides,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const interaction = (interactionId: string, resolved = false): ChatInteraction => ({
|
|
18
|
-
interactionId,
|
|
19
|
-
componentName: 'test-widget',
|
|
20
|
-
data: {},
|
|
21
|
-
...(resolved ? { resolved: { status: 'approved' } } : {}),
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const ALL_ON: MessageFilterState = {
|
|
25
|
-
showToolCalls: true,
|
|
26
|
-
showThinkingSteps: true,
|
|
27
|
-
showNarration: true,
|
|
28
|
-
showAgentSwitchIndicator: true,
|
|
29
|
-
showRestoreNotices: true,
|
|
30
|
-
};
|
|
31
|
-
const ALL_OFF: MessageFilterState = {
|
|
32
|
-
showToolCalls: false,
|
|
33
|
-
showThinkingSteps: false,
|
|
34
|
-
showNarration: false,
|
|
35
|
-
showAgentSwitchIndicator: false,
|
|
36
|
-
showRestoreNotices: false,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const trailing = createLogicSuite('trailingInteractionRow');
|
|
40
|
-
|
|
41
|
-
trailing('is empty when there are no messages', () => {
|
|
42
|
-
assert.equal(trailingInteractionRow([]), []);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
trailing('is empty when the last message has no interaction', () => {
|
|
46
|
-
assert.equal(trailingInteractionRow([msg({ content: 'hi' })]), []);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
trailing('returns the last message when it carries an interaction (pending)', () => {
|
|
50
|
-
const pending = msg({ interaction: interaction('id-a') });
|
|
51
|
-
assert.equal(trailingInteractionRow([msg({ content: 'q' }), pending]), [pending]);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
trailing('still returns the trailing interaction once it is resolved', () => {
|
|
55
|
-
// Keeps it in the pinned slot so resolving forwards in place without a rebuild.
|
|
56
|
-
const resolved = msg({ interaction: interaction('id-a', true) });
|
|
57
|
-
assert.equal(trailingInteractionRow([resolved]), [resolved]);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
trailing('returns nothing once a newer non-interaction message arrives', () => {
|
|
61
|
-
// The interaction is no longer last, so it rejoins the scrolling list.
|
|
62
|
-
const earlier = msg({ interaction: interaction('id-a', true) });
|
|
63
|
-
assert.equal(trailingInteractionRow([earlier, msg({ content: 'next' })]), []);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
trailing('returns the same message reference (lets FAST short-circuit rebind)', () => {
|
|
67
|
-
const pending = msg({ interaction: interaction('id-a') });
|
|
68
|
-
assert.is(trailingInteractionRow([pending])[0], pending);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const filter = createLogicSuite('filterVisibleMessages');
|
|
72
|
-
|
|
73
|
-
filter('hides tool result messages regardless of toggles', () => {
|
|
74
|
-
const msgs = [msg({ role: 'tool', toolResult: { toolCallId: 't1', content: 'r' } })];
|
|
75
|
-
assert.equal(filterVisibleMessages(msgs, ALL_ON), []);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
filter('hides tool-call messages when showToolCalls is off, shows them when on', () => {
|
|
79
|
-
const toolCall = msg({ toolCalls: [{ id: 't1', name: 'do', args: {} } as any] });
|
|
80
|
-
assert.equal(filterVisibleMessages([toolCall], ALL_OFF), []);
|
|
81
|
-
assert.equal(filterVisibleMessages([toolCall], ALL_ON), [toolCall]);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
filter('hides thinking messages when showThinkingSteps is off', () => {
|
|
85
|
-
const thinking = msg({ thinking: true, content: 'hmm' });
|
|
86
|
-
assert.equal(filterVisibleMessages([thinking], ALL_OFF), []);
|
|
87
|
-
assert.equal(filterVisibleMessages([thinking], ALL_ON), [thinking]);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
filter('hides system-event rows unless the agent-switch toggle is on', () => {
|
|
91
|
-
const event = msg({ role: 'system-event', content: 'switched' });
|
|
92
|
-
assert.equal(filterVisibleMessages([event], ALL_OFF), []);
|
|
93
|
-
assert.equal(filterVisibleMessages([event], ALL_ON), [event]);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
filter('drops empty assistant messages', () => {
|
|
97
|
-
assert.equal(filterVisibleMessages([msg({ content: ' ' })], ALL_ON), []);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
filter('keeps user and non-empty assistant messages with everything off', () => {
|
|
101
|
-
const user = msg({ role: 'user', content: 'hello' });
|
|
102
|
-
const reply = msg({ content: 'hi there' });
|
|
103
|
-
assert.equal(filterVisibleMessages([user, reply], ALL_OFF), [user, reply]);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// GENC-1351 §6: restore notices are gated by their own (persistence) toggle, NOT
|
|
107
|
-
// by showAgentSwitchIndicator — the two unrelated switches must not cross-control.
|
|
108
|
-
filter(
|
|
109
|
-
'restore notices follow showRestoreNotices; agent-switch rows follow showAgentSwitchIndicator',
|
|
110
|
-
() => {
|
|
111
|
-
const restored = msg({ role: 'system-event', content: SESSION_RESTORED_LABEL });
|
|
112
|
-
const failed = msg({ role: 'system-event', content: SESSION_RESTORE_FAILED_LABEL });
|
|
113
|
-
const agentSwitch = msg({ role: 'system-event', content: 'Guided Booking' });
|
|
114
|
-
const rows = [restored, failed, agentSwitch];
|
|
115
|
-
|
|
116
|
-
// Persistence on, agent-switch off → only the restore notices show.
|
|
117
|
-
assert.equal(filterVisibleMessages(rows, { ...ALL_OFF, showRestoreNotices: true }), [
|
|
118
|
-
restored,
|
|
119
|
-
failed,
|
|
120
|
-
]);
|
|
121
|
-
// Agent-switch on, persistence off → only the agent-switch indicator shows.
|
|
122
|
-
assert.equal(filterVisibleMessages(rows, { ...ALL_OFF, showAgentSwitchIndicator: true }), [
|
|
123
|
-
agentSwitch,
|
|
124
|
-
]);
|
|
125
|
-
},
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
trailing.run();
|
|
129
|
-
filter.run();
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import type { ChatMessage } from '@genesislcap/foundation-ai';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Display-only session-restore notices (GENC-1351 §6): the "Restored…" divider on
|
|
5
|
-
* a successful reload restore, and the "Couldn't restore…" divider when a saved
|
|
6
|
-
* session existed but couldn't be brought back (timeout / error / unusable
|
|
7
|
-
* snapshot). Both are `system-event` rows, stripped from the persisted snapshot so
|
|
8
|
-
* they never become permanent history — and, unlike agent-switch indicators, they
|
|
9
|
-
* belong to the persistence ("remember chat") feature, so `filterVisibleMessages`
|
|
10
|
-
* gates them by `showRestoreNotices` rather than `showAgentSwitchIndicator`.
|
|
11
|
-
*
|
|
12
|
-
* @internal
|
|
13
|
-
*/
|
|
14
|
-
export const SESSION_RESTORED_LABEL = 'Restored previous conversation';
|
|
15
|
-
/** @internal */
|
|
16
|
-
export const SESSION_RESTORE_FAILED_LABEL = "Couldn't restore the previous conversation";
|
|
17
|
-
/** @internal */
|
|
18
|
-
export const isSessionRestoredMarker = (m: ChatMessage): boolean =>
|
|
19
|
-
m.role === 'system-event' && m.content === SESSION_RESTORED_LABEL;
|
|
20
|
-
/** @internal */
|
|
21
|
-
export const isSessionRestoreFailedMarker = (m: ChatMessage): boolean =>
|
|
22
|
-
m.role === 'system-event' && m.content === SESSION_RESTORE_FAILED_LABEL;
|
|
23
|
-
/** Either restore notice — both display-only, stripped before persist. @internal */
|
|
24
|
-
export const isSessionNoticeMarker = (m: ChatMessage): boolean =>
|
|
25
|
-
isSessionRestoredMarker(m) || isSessionRestoreFailedMarker(m);
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Toggle state that drives which messages appear in the scrolling list. Mirrors
|
|
29
|
-
* the cog-menu switches; `showAgentSwitchIndicator` is pre-resolved by the
|
|
30
|
-
* caller (it falls back to `showToolCalls` when the host hasn't configured an
|
|
31
|
-
* explicit agent-switch toggle).
|
|
32
|
-
*/
|
|
33
|
-
export interface MessageFilterState {
|
|
34
|
-
showToolCalls: boolean;
|
|
35
|
-
showThinkingSteps: boolean;
|
|
36
|
-
showNarration: boolean;
|
|
37
|
-
showAgentSwitchIndicator: boolean;
|
|
38
|
-
/**
|
|
39
|
-
* Whether session-restore notices show — driven by the persistence ("remember
|
|
40
|
-
* chat") toggle, since restore is that feature's concern (GENC-1351 §6). Kept
|
|
41
|
-
* separate from `showAgentSwitchIndicator` so those two unrelated switches don't
|
|
42
|
-
* cross-control each other.
|
|
43
|
-
*/
|
|
44
|
-
showRestoreNotices: boolean;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* The trailing interaction to render in a pinned slot, separate from the
|
|
49
|
-
* filtered list. Empty unless the most recent message carries an interaction.
|
|
50
|
-
*
|
|
51
|
-
* Pinning the trailing interaction keeps its widget out of the list's
|
|
52
|
-
* reconciliation: FAST's `repeat` has no key function and reconciles by array
|
|
53
|
-
* index, so re-filtering the list (a cog-menu toggle) would otherwise rebind an
|
|
54
|
-
* existing row to a different message and re-create the live widget — discarding
|
|
55
|
-
* any in-progress input. The slot's binding reads only `messages` and returns
|
|
56
|
-
* the same message object across toggles, so the widget's DOM is preserved.
|
|
57
|
-
*
|
|
58
|
-
* It is the *trailing* message (resolved or not), not "unresolved only", so that
|
|
59
|
-
* answering an interaction leaves it in the slot — `resolved` is forwarded in
|
|
60
|
-
* place with no re-render. It rejoins the list naturally once a newer message
|
|
61
|
-
* arrives and it is no longer last.
|
|
62
|
-
*/
|
|
63
|
-
export function trailingInteractionRow(messages: ChatMessage[]): ChatMessage[] {
|
|
64
|
-
const last = messages[messages.length - 1];
|
|
65
|
-
return last?.interaction ? [last] : [];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Filters messages for the scrolling list per the current cog-menu toggles.
|
|
70
|
-
*
|
|
71
|
-
* The trailing interaction (see `trailingInteractionRow`) must be removed
|
|
72
|
-
* before calling, as it is rendered separately.
|
|
73
|
-
*/
|
|
74
|
-
export function filterVisibleMessages(
|
|
75
|
-
messages: ChatMessage[],
|
|
76
|
-
toggles: MessageFilterState,
|
|
77
|
-
): ChatMessage[] {
|
|
78
|
-
return messages.filter((m) => {
|
|
79
|
-
// Compaction dividers are always shown — a real conversation landmark, not
|
|
80
|
-
// optional UI noise (GENC-1351 §5.7).
|
|
81
|
-
if (m.role === 'compacted-summary') {
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
if (m.role === 'system-event') {
|
|
85
|
-
// Restore notices belong to persistence ("remember chat"), agent-switch
|
|
86
|
-
// indicators to their own toggle — gate each by its own switch.
|
|
87
|
-
return isSessionNoticeMarker(m)
|
|
88
|
-
? toggles.showRestoreNotices
|
|
89
|
-
: toggles.showAgentSwitchIndicator;
|
|
90
|
-
}
|
|
91
|
-
// Tool result messages are never shown to the user.
|
|
92
|
-
if (m.role === 'tool') {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
// Reasoning (chain-of-thought summary) follows the thinking toggle.
|
|
96
|
-
if (m.category === 'reasoning') {
|
|
97
|
-
return toggles.showThinkingSteps;
|
|
98
|
-
}
|
|
99
|
-
// Narration (interstitial prose alongside a tool call) follows its own toggle.
|
|
100
|
-
if (m.category === 'narration') {
|
|
101
|
-
return toggles.showNarration;
|
|
102
|
-
}
|
|
103
|
-
// Legacy: sessions persisted before the reasoning/narration split tagged reasoning-ish
|
|
104
|
-
// content with `thinking`; keep honouring the flag so their history still hides it.
|
|
105
|
-
if (m.thinking && !toggles.showThinkingSteps) {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
// Drop empty assistant messages that slipped through.
|
|
109
|
-
if (m.role === 'assistant' && !m.content?.trim() && !m.toolCalls?.length && !m.interaction) {
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
112
|
-
const isToolRelated = !!(m.toolCalls?.length || m.toolResult);
|
|
113
|
-
return !isToolRelated || toggles.showToolCalls;
|
|
114
|
-
});
|
|
115
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { resolveCostHistoryConfig } from './resolve-cost-history-config';
|
|
3
|
-
|
|
4
|
-
const Suite = createLogicSuite('resolveCostHistoryConfig');
|
|
5
|
-
|
|
6
|
-
Suite('defaults to disabled with cost UI visible', () => {
|
|
7
|
-
const resolved = resolveCostHistoryConfig({});
|
|
8
|
-
assert.is(resolved.enabled, false);
|
|
9
|
-
assert.is(resolved.showCost, true);
|
|
10
|
-
assert.is(resolved.showTokens, true);
|
|
11
|
-
assert.is(resolved.badgeLabel, 'Currently building');
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
Suite('enables persistence only when costHistory.enabled is true', () => {
|
|
15
|
-
assert.is(resolveCostHistoryConfig({ costHistory: {} }).enabled, false);
|
|
16
|
-
assert.is(resolveCostHistoryConfig({ costHistory: { enabled: true } }).enabled, true);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
Suite('reads costHistory fields', () => {
|
|
20
|
-
const resolved = resolveCostHistoryConfig({
|
|
21
|
-
costHistory: {
|
|
22
|
-
enabled: true,
|
|
23
|
-
title: 'My project',
|
|
24
|
-
scope: 'app-scope',
|
|
25
|
-
showCost: false,
|
|
26
|
-
showTokens: false,
|
|
27
|
-
badgeLabel: 'Building',
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
assert.is(resolved.title, 'My project');
|
|
32
|
-
assert.is(resolved.scope, 'app-scope');
|
|
33
|
-
assert.is(resolved.showCost, false);
|
|
34
|
-
assert.is(resolved.showTokens, false);
|
|
35
|
-
assert.is(resolved.badgeLabel, 'Building');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
Suite.run();
|