@genesislcap/ai-assistant 14.495.0 → 14.496.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-assistant.api.json +576 -211
- package/dist/ai-assistant.d.ts +128 -5
- package/dist/chat-driver.cjs +5608 -0
- package/dist/chat-driver.cjs.map +7 -0
- package/dist/chat-driver.mjs +5566 -0
- package/dist/chat-driver.mjs.map +7 -0
- package/dist/custom-elements.json +306 -2
- package/dist/dts/channel/ai-activity-bus.d.ts +36 -0
- package/dist/dts/channel/ai-activity-bus.d.ts.map +1 -1
- package/dist/dts/chat-driver-node.d.ts +28 -0
- package/dist/dts/chat-driver-node.d.ts.map +1 -0
- package/dist/dts/components/chat-driver/chat-driver.d.ts +49 -5
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +3 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/config/config.d.ts +37 -2
- package/dist/dts/config/config.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/esm/channel/ai-activity-bus.js +48 -12
- package/dist/esm/chat-driver-node.js +33 -0
- package/dist/esm/components/chat-driver/chat-driver.js +28 -14
- package/dist/esm/components/chat-driver/chat-driver.test.js +32 -40
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +7 -1
- package/dist/esm/main/main.js +8 -1
- package/dist/esm/main/popout-interaction-gate.test.js +6 -10
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +29 -19
- package/scripts/build-chat-driver-node.mjs +42 -0
- package/src/channel/ai-activity-bus.ts +64 -10
- package/src/chat-driver-node.ts +54 -0
- package/src/components/chat-driver/chat-driver.test.ts +36 -62
- package/src/components/chat-driver/chat-driver.ts +96 -28
- package/src/components/orchestrating-driver/orchestrating-driver.ts +10 -11
- package/src/config/config.ts +40 -1
- package/src/main/main.ts +8 -11
- package/src/main/popout-interaction-gate.test.ts +6 -11
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
2
|
-
import { agenticActivityBus } from '../channel/ai-activity-bus';
|
|
3
2
|
import { FoundationAiAssistant } from './main';
|
|
4
3
|
// Hold a reference so the custom-element registration isn't tree-shaken.
|
|
5
4
|
FoundationAiAssistant;
|
|
@@ -11,16 +10,13 @@ FoundationAiAssistant;
|
|
|
11
10
|
// (`main.template.ts`). This suite pins the GATE LOGIC (`hasActivePendingInteraction`)
|
|
12
11
|
// that binding depends on, so a change to when the gate opens/closes is caught.
|
|
13
12
|
//
|
|
14
|
-
// Note: this deliberately does NOT mount the assistant. Mounting runs
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
13
|
+
// Note: this deliberately does NOT mount the assistant. Mounting runs `connectedCallback`,
|
|
14
|
+
// which subscribes to `agenticActivityBus` — opening a cross-tab `BroadcastChannel` in a browser
|
|
15
|
+
// env and leaving the runner's event loop open. A getter-level test needs no mount and stays fast
|
|
16
|
+
// + reliable. `document.createElement` only upgrades the element (constructor) — it does not
|
|
17
|
+
// connect — so nothing subscribes, and the bus opens its channel lazily on first use, so none is
|
|
18
|
+
// ever created here (no teardown needed).
|
|
20
19
|
const Suite = createLogicSuite('FoundationAiAssistant popout interaction gate');
|
|
21
|
-
Suite.after(() => {
|
|
22
|
-
agenticActivityBus.close();
|
|
23
|
-
});
|
|
24
20
|
/** A fresh (unconnected) element with a fake session store wired in. */
|
|
25
21
|
function elementWith(state, messages) {
|
|
26
22
|
const el = document.createElement('foundation-ai-assistant');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/flowing-waves-indicator.ts","../src/components/halo-overlay.ts","../src/components/plasma-orb-indicator.ts","../src/components/waves-indicator.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/align-event-globals.ts","../src/components/chat-driver/chat-driver.compact.test.ts","../src/components/chat-driver/chat-driver.test.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.pin.test.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/components/settings-modal/index.ts","../src/components/settings-modal/settings-modal.styles.ts","../src/components/settings-modal/settings-modal.template.ts","../src/config/config.ts","../src/config/define-stateful-agent.test.ts","../src/config/define-stateful-agent.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/config/validate-providers.test.ts","../src/config/validate-providers.ts","../src/main/index.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/main/popout-interaction-gate.test.ts","../src/provider/ai-provider-switcher.ts","../src/provider/assistant-app-settings.ts","../src/state/ai-assistant-slice.test.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.test.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.test.ts","../src/state/driver-registry.ts","../src/state/interaction-context.test.ts","../src/state/interaction-context.ts","../src/state/session-store.ts","../src/state/persistence/diagnostics-cursors.test.ts","../src/state/persistence/diagnostics-cursors.ts","../src/state/persistence/diagnostics.test.ts","../src/state/persistence/diagnostics.ts","../src/state/persistence/index.ts","../src/state/persistence/persister-registry.ts","../src/state/persistence/session-persistence-provider.test.ts","../src/state/persistence/session-persistence-provider.ts","../src/state/persistence/session-persistence.integration.test.ts","../src/state/persistence/session-persister.test.ts","../src/state/persistence/session-persister.ts","../src/state/persistence/session-snapshot.test.ts","../src/state/persistence/session-snapshot.ts","../src/state/persistence/stateful-restore.e2e.test.ts","../src/styles/ai-colours.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/types/interaction-context.ts","../src/utils/animated-panel-toggle.ts","../src/utils/animation-exclusivity.test.ts","../src/utils/animation-exclusivity.ts","../src/utils/collect-session-models.test.ts","../src/utils/collect-session-models.ts","../src/utils/condense-history.test.ts","../src/utils/condense-history.ts","../src/utils/cost-session-history.test.ts","../src/utils/cost-session-history.ts","../src/utils/derive-cost-session-title.test.ts","../src/utils/derive-cost-session-title.ts","../src/utils/flatten-sub-agent-messages.test.ts","../src/utils/flatten-sub-agent-messages.ts","../src/utils/history-transform.test.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/message-partition.test.ts","../src/utils/message-partition.ts","../src/utils/resolve-cost-history-config.test.ts","../src/utils/resolve-cost-history-config.ts","../src/utils/resolve-preference-baseline.test.ts","../src/utils/resolve-preference-baseline.ts","../src/utils/strip-agent-handlers.test.ts","../src/utils/strip-agent-handlers.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/sum-tokens.test.ts","../src/utils/sum-tokens.ts","../src/utils/tool-fold.ts","../src/utils/with-timeout.ts"],"version":"5.9.2"}
|
|
1
|
+
{"root":["../src/chat-driver-node.ts","../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/flowing-waves-indicator.ts","../src/components/halo-overlay.ts","../src/components/plasma-orb-indicator.ts","../src/components/waves-indicator.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/align-event-globals.ts","../src/components/chat-driver/chat-driver.compact.test.ts","../src/components/chat-driver/chat-driver.test.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.test.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.pin.test.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/components/settings-modal/index.ts","../src/components/settings-modal/settings-modal.styles.ts","../src/components/settings-modal/settings-modal.template.ts","../src/config/config.ts","../src/config/define-stateful-agent.test.ts","../src/config/define-stateful-agent.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/config/validate-providers.test.ts","../src/config/validate-providers.ts","../src/main/index.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/main/popout-interaction-gate.test.ts","../src/provider/ai-provider-switcher.ts","../src/provider/assistant-app-settings.ts","../src/state/ai-assistant-slice.test.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.test.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.test.ts","../src/state/driver-registry.ts","../src/state/interaction-context.test.ts","../src/state/interaction-context.ts","../src/state/session-store.ts","../src/state/persistence/diagnostics-cursors.test.ts","../src/state/persistence/diagnostics-cursors.ts","../src/state/persistence/diagnostics.test.ts","../src/state/persistence/diagnostics.ts","../src/state/persistence/index.ts","../src/state/persistence/persister-registry.ts","../src/state/persistence/session-persistence-provider.test.ts","../src/state/persistence/session-persistence-provider.ts","../src/state/persistence/session-persistence.integration.test.ts","../src/state/persistence/session-persister.test.ts","../src/state/persistence/session-persister.ts","../src/state/persistence/session-snapshot.test.ts","../src/state/persistence/session-snapshot.ts","../src/state/persistence/stateful-restore.e2e.test.ts","../src/styles/ai-colours.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/types/interaction-context.ts","../src/utils/animated-panel-toggle.ts","../src/utils/animation-exclusivity.test.ts","../src/utils/animation-exclusivity.ts","../src/utils/collect-session-models.test.ts","../src/utils/collect-session-models.ts","../src/utils/condense-history.test.ts","../src/utils/condense-history.ts","../src/utils/cost-session-history.test.ts","../src/utils/cost-session-history.ts","../src/utils/derive-cost-session-title.test.ts","../src/utils/derive-cost-session-title.ts","../src/utils/flatten-sub-agent-messages.test.ts","../src/utils/flatten-sub-agent-messages.ts","../src/utils/history-transform.test.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/message-partition.test.ts","../src/utils/message-partition.ts","../src/utils/resolve-cost-history-config.test.ts","../src/utils/resolve-cost-history-config.ts","../src/utils/resolve-preference-baseline.test.ts","../src/utils/resolve-preference-baseline.ts","../src/utils/strip-agent-handlers.test.ts","../src/utils/strip-agent-handlers.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/sum-tokens.test.ts","../src/utils/sum-tokens.ts","../src/utils/tool-fold.ts","../src/utils/with-timeout.ts"],"version":"5.9.2"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genesislcap/ai-assistant",
|
|
3
3
|
"description": "Genesis AI Assistant micro-frontend",
|
|
4
|
-
"version": "14.
|
|
4
|
+
"version": "14.496.0",
|
|
5
5
|
"license": "SEE LICENSE IN license.txt",
|
|
6
6
|
"main": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/ai-assistant.d.ts",
|
|
@@ -27,6 +27,11 @@
|
|
|
27
27
|
"types": "./dist/dts/config/index.d.ts",
|
|
28
28
|
"default": "./dist/esm/config/index.js"
|
|
29
29
|
},
|
|
30
|
+
"./chat-driver": {
|
|
31
|
+
"types": "./dist/dts/chat-driver-node.d.ts",
|
|
32
|
+
"import": "./dist/chat-driver.mjs",
|
|
33
|
+
"require": "./dist/chat-driver.cjs"
|
|
34
|
+
},
|
|
30
35
|
"./package.json": "./package.json",
|
|
31
36
|
"./react": {
|
|
32
37
|
"types": "./dist/dts/react.d.ts",
|
|
@@ -38,11 +43,15 @@
|
|
|
38
43
|
"*": {
|
|
39
44
|
"config": [
|
|
40
45
|
"./dist/dts/config/index.d.ts"
|
|
46
|
+
],
|
|
47
|
+
"chat-driver": [
|
|
48
|
+
"./dist/dts/chat-driver-node.d.ts"
|
|
41
49
|
]
|
|
42
50
|
}
|
|
43
51
|
},
|
|
44
52
|
"scripts": {
|
|
45
|
-
"build": "genx build -b ts",
|
|
53
|
+
"build": "genx build -b ts && node ./scripts/build-chat-driver-node.mjs",
|
|
54
|
+
"build:chat-driver": "node ./scripts/build-chat-driver-node.mjs",
|
|
46
55
|
"build:webpack": "genx build",
|
|
47
56
|
"build:webpack:stats": "genx analyze",
|
|
48
57
|
"circular": "npx -y madge --extensions ts --circular ./src",
|
|
@@ -64,25 +73,26 @@
|
|
|
64
73
|
}
|
|
65
74
|
},
|
|
66
75
|
"devDependencies": {
|
|
67
|
-
"@genesislcap/foundation-testing": "14.
|
|
68
|
-
"@genesislcap/genx": "14.
|
|
69
|
-
"@genesislcap/rollup-builder": "14.
|
|
70
|
-
"@genesislcap/ts-builder": "14.
|
|
71
|
-
"@genesislcap/uvu-playwright-builder": "14.
|
|
72
|
-
"@genesislcap/vite-builder": "14.
|
|
73
|
-
"@genesislcap/webpack-builder": "14.
|
|
76
|
+
"@genesislcap/foundation-testing": "14.496.0",
|
|
77
|
+
"@genesislcap/genx": "14.496.0",
|
|
78
|
+
"@genesislcap/rollup-builder": "14.496.0",
|
|
79
|
+
"@genesislcap/ts-builder": "14.496.0",
|
|
80
|
+
"@genesislcap/uvu-playwright-builder": "14.496.0",
|
|
81
|
+
"@genesislcap/vite-builder": "14.496.0",
|
|
82
|
+
"@genesislcap/webpack-builder": "14.496.0",
|
|
74
83
|
"@types/dompurify": "^3.0.5",
|
|
75
|
-
"@types/marked": "^5.0.2"
|
|
84
|
+
"@types/marked": "^5.0.2",
|
|
85
|
+
"esbuild": "0.25.12"
|
|
76
86
|
},
|
|
77
87
|
"dependencies": {
|
|
78
|
-
"@genesislcap/foundation-ai": "14.
|
|
79
|
-
"@genesislcap/foundation-logger": "14.
|
|
80
|
-
"@genesislcap/foundation-notifications": "14.
|
|
81
|
-
"@genesislcap/foundation-redux": "14.
|
|
82
|
-
"@genesislcap/foundation-ui": "14.
|
|
83
|
-
"@genesislcap/foundation-utils": "14.
|
|
84
|
-
"@genesislcap/rapid-design-system": "14.
|
|
85
|
-
"@genesislcap/web-core": "14.
|
|
88
|
+
"@genesislcap/foundation-ai": "14.496.0",
|
|
89
|
+
"@genesislcap/foundation-logger": "14.496.0",
|
|
90
|
+
"@genesislcap/foundation-notifications": "14.496.0",
|
|
91
|
+
"@genesislcap/foundation-redux": "14.496.0",
|
|
92
|
+
"@genesislcap/foundation-ui": "14.496.0",
|
|
93
|
+
"@genesislcap/foundation-utils": "14.496.0",
|
|
94
|
+
"@genesislcap/rapid-design-system": "14.496.0",
|
|
95
|
+
"@genesislcap/web-core": "14.496.0",
|
|
86
96
|
"dompurify": "^3.3.1",
|
|
87
97
|
"marked": "^17.0.3"
|
|
88
98
|
},
|
|
@@ -95,5 +105,5 @@
|
|
|
95
105
|
"access": "public"
|
|
96
106
|
},
|
|
97
107
|
"customElements": "dist/custom-elements.json",
|
|
98
|
-
"gitHead": "
|
|
108
|
+
"gitHead": "2ae3ec60cab8440c6beb1da84e2d47239b11e9b5"
|
|
99
109
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundles the Node-safe `./chat-driver` subpath into self-contained, Node-loadable dual output
|
|
3
|
+
* (`dist/chat-driver.mjs` + `dist/chat-driver.cjs`).
|
|
4
|
+
*
|
|
5
|
+
* Why a bundler and not just tsc: `genx build -b ts` emits bundler-target ESM (extensionless
|
|
6
|
+
* relative imports + a bare `tslib` import) that Node's own loader can't resolve. This step bundles
|
|
7
|
+
* `src/chat-driver-node.ts` and its closure (including `@genesislcap/foundation-ai`, `tslib`, and
|
|
8
|
+
* the logger) inline, so a plain Node/CommonJS host can `require`/`import` the driver stack with no
|
|
9
|
+
* bundler and no jsdom. The FAST/DOM layer is never in this entry's closure, and `foundation-ai`'s
|
|
10
|
+
* DI module (its only `@microsoft/fast-foundation` importer) tree-shakes out.
|
|
11
|
+
*
|
|
12
|
+
* Runs after `genx build -b ts` (which produces the matching `dist/dts/chat-driver-node.d.ts`).
|
|
13
|
+
*/
|
|
14
|
+
import { dirname, resolve } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import * as esbuild from 'esbuild';
|
|
17
|
+
|
|
18
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
19
|
+
const entry = resolve(packageRoot, 'src/chat-driver-node.ts');
|
|
20
|
+
|
|
21
|
+
/** @type {import('esbuild').BuildOptions} */
|
|
22
|
+
const shared = {
|
|
23
|
+
entryPoints: [entry],
|
|
24
|
+
bundle: true,
|
|
25
|
+
platform: 'node',
|
|
26
|
+
target: 'node22',
|
|
27
|
+
// Self-contained: bundle third-party deps (foundation-ai, tslib, logger) inline so the artifact
|
|
28
|
+
// is Node-loadable standalone. Node built-ins are external automatically under platform: 'node'.
|
|
29
|
+
sourcemap: 'linked',
|
|
30
|
+
logLevel: 'info',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
await esbuild.build({
|
|
34
|
+
...shared,
|
|
35
|
+
format: 'esm',
|
|
36
|
+
outfile: resolve(packageRoot, 'dist/chat-driver.mjs'),
|
|
37
|
+
});
|
|
38
|
+
await esbuild.build({
|
|
39
|
+
...shared,
|
|
40
|
+
format: 'cjs',
|
|
41
|
+
outfile: resolve(packageRoot, 'dist/chat-driver.cjs'),
|
|
42
|
+
});
|
|
@@ -10,10 +10,37 @@ export interface AgenticActivityBusConfig {
|
|
|
10
10
|
crossTabEvents?: (keyof AgenticActivityEvents)[];
|
|
11
11
|
/** BroadcastChannel name. Only used when crossTabEvents is non-empty. */
|
|
12
12
|
channelName?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Lazy alternative to {@link AgenticActivityBusConfig.channelName}: called the first
|
|
15
|
+
* time a cross-tab channel is actually needed, in a browser only. Deferring the name
|
|
16
|
+
* computation keeps side effects (e.g. `sessionStorage` reads) out of module-eval and
|
|
17
|
+
* off the non-browser path. Ignored if `channelName` is also set.
|
|
18
|
+
*/
|
|
19
|
+
channelNameFactory?: () => string;
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
type AnyHandler = (detail: any) => void;
|
|
16
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Minimal activity-bus surface the drivers depend on. `ChatDriver`/`OrchestratingDriver`
|
|
26
|
+
* only ever *publish* — accepting this narrow interface (rather than importing the module
|
|
27
|
+
* singleton) lets a headless/server or unit-test host omit the bus entirely, so no live
|
|
28
|
+
* `BroadcastChannel` is ever opened on that path. See {@link NOOP_ACTIVITY_BUS}.
|
|
29
|
+
*
|
|
30
|
+
* @beta
|
|
31
|
+
*/
|
|
32
|
+
export interface ActivityBus {
|
|
33
|
+
publish<K extends keyof AgenticActivityEvents>(type: K, detail: AgenticActivityEvents[K]): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* No-op {@link ActivityBus}. The default when no bus is injected (Node, tests, headless
|
|
38
|
+
* server use) — publishing goes nowhere and nothing is left open to tear down.
|
|
39
|
+
*
|
|
40
|
+
* @beta
|
|
41
|
+
*/
|
|
42
|
+
export const NOOP_ACTIVITY_BUS: ActivityBus = { publish: () => {} };
|
|
43
|
+
|
|
17
44
|
/**
|
|
18
45
|
* Typed in-process event bus for AI assistant activity events.
|
|
19
46
|
*
|
|
@@ -31,7 +58,9 @@ type AnyHandler = (detail: any) => void;
|
|
|
31
58
|
export class AgenticActivityBus {
|
|
32
59
|
private readonly subscribers = new Map<string, Set<AnyHandler>>();
|
|
33
60
|
private readonly crossTabEvents: Set<string>;
|
|
61
|
+
private readonly channelNameFactory?: () => string;
|
|
34
62
|
private channel?: BroadcastChannel;
|
|
63
|
+
private channelInitialised = false;
|
|
35
64
|
|
|
36
65
|
/**
|
|
37
66
|
* @param config - Optional configuration. Omit to create an in-memory-only bus.
|
|
@@ -39,13 +68,28 @@ export class AgenticActivityBus {
|
|
|
39
68
|
*/
|
|
40
69
|
constructor(config?: AgenticActivityBusConfig) {
|
|
41
70
|
this.crossTabEvents = new Set(config?.crossTabEvents ?? []);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
71
|
+
this.channelNameFactory =
|
|
72
|
+
config?.channelName != null ? () => config.channelName! : config?.channelNameFactory;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Lazily open the cross-tab `BroadcastChannel` on first use. Deferred (rather than
|
|
77
|
+
* created in the constructor) so that importing this module, or constructing the bus
|
|
78
|
+
* off-browser (Node, tests, headless server), never opens a live channel or reads
|
|
79
|
+
* `sessionStorage` — both would keep a Node event loop alive and hang the process.
|
|
80
|
+
* Cross-tab forwarding is inherently browser-only, so channel creation is gated on a
|
|
81
|
+
* browser environment; elsewhere the bus stays purely in-process.
|
|
82
|
+
*/
|
|
83
|
+
private ensureChannel(): void {
|
|
84
|
+
if (this.channelInitialised) return;
|
|
85
|
+
this.channelInitialised = true;
|
|
86
|
+
const inBrowser = typeof window !== 'undefined' && typeof BroadcastChannel !== 'undefined';
|
|
87
|
+
if (this.crossTabEvents.size === 0 || !this.channelNameFactory || !inBrowser) return;
|
|
88
|
+
this.channel = new BroadcastChannel(this.channelNameFactory());
|
|
89
|
+
this.channel.onmessage = (ev) => {
|
|
90
|
+
const { type, detail } = (ev.data ?? {}) as { type?: string; detail?: unknown };
|
|
91
|
+
if (type) this.notifyLocal(type, detail);
|
|
92
|
+
};
|
|
49
93
|
}
|
|
50
94
|
|
|
51
95
|
/**
|
|
@@ -58,6 +102,9 @@ export class AgenticActivityBus {
|
|
|
58
102
|
type: K,
|
|
59
103
|
handler: (detail: AgenticActivityEvents[K]) => void,
|
|
60
104
|
): () => void {
|
|
105
|
+
// Open the cross-tab channel (browser only) so this subscriber receives events
|
|
106
|
+
// forwarded from other tabs; no-op off-browser.
|
|
107
|
+
this.ensureChannel();
|
|
61
108
|
let handlers = this.subscribers.get(type as string);
|
|
62
109
|
if (!handlers) {
|
|
63
110
|
handlers = new Set();
|
|
@@ -76,8 +123,9 @@ export class AgenticActivityBus {
|
|
|
76
123
|
*/
|
|
77
124
|
publish<K extends keyof AgenticActivityEvents>(type: K, detail: AgenticActivityEvents[K]): void {
|
|
78
125
|
this.notifyLocal(type as string, detail);
|
|
79
|
-
if (this.crossTabEvents.has(type as string)
|
|
80
|
-
this.
|
|
126
|
+
if (this.crossTabEvents.has(type as string)) {
|
|
127
|
+
this.ensureChannel();
|
|
128
|
+
this.channel?.postMessage({ type, detail });
|
|
81
129
|
}
|
|
82
130
|
}
|
|
83
131
|
|
|
@@ -124,6 +172,9 @@ export class AgenticActivityBus {
|
|
|
124
172
|
* instances on the same origin.
|
|
125
173
|
*/
|
|
126
174
|
function getTabScopedChannelName(base: string): string {
|
|
175
|
+
// Belt-and-braces: this only runs in a browser (see `ensureChannel`), but guard anyway
|
|
176
|
+
// so a direct call off-browser never throws a `sessionStorage is not defined` ReferenceError.
|
|
177
|
+
if (typeof sessionStorage === 'undefined') return `${base}-nostorage`;
|
|
127
178
|
const key = `${base}-tab-id`;
|
|
128
179
|
let id = sessionStorage.getItem(key);
|
|
129
180
|
if (!id) {
|
|
@@ -151,5 +202,8 @@ function getTabScopedChannelName(base: string): string {
|
|
|
151
202
|
*/
|
|
152
203
|
export const agenticActivityBus = new AgenticActivityBus({
|
|
153
204
|
crossTabEvents: ['halo-start', 'halo-stop', 'tool-loop-start', 'tool-loop-end'],
|
|
154
|
-
|
|
205
|
+
// Lazy: the tab-scoped name (and its `sessionStorage` read) is computed on first
|
|
206
|
+
// cross-tab use in a browser, not at module-eval — so importing this module is
|
|
207
|
+
// side-effect-free and Node/test-safe.
|
|
208
|
+
channelNameFactory: () => getTabScopedChannelName('foundation-ai-activity'),
|
|
155
209
|
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-safe headless entry point for the AI assistant driver stack (`@genesislcap/ai-assistant/chat-driver`).
|
|
3
|
+
*
|
|
4
|
+
* Re-exports ONLY the framework-agnostic pieces — {@link ChatDriver}, `OrchestratingDriver`, the
|
|
5
|
+
* agent-config surface (`defineAgent`, `defineStatefulAgent`, and the config/input types), and the
|
|
6
|
+
* activity-bus types/`NOOP_ACTIVITY_BUS` — WITHOUT the DOM/FAST component layer that the package's
|
|
7
|
+
* main entry (`.`) pulls in via `main/main`. This lets a Node / CommonJS host (server, unit tests,
|
|
8
|
+
* benches) construct and drive `ChatDriver` over `foundation-ai` providers with no jsdom and no
|
|
9
|
+
* custom-element registration.
|
|
10
|
+
*
|
|
11
|
+
* Every module re-exported here is DOM-clean at import time (no `window`/`document`/`sessionStorage`
|
|
12
|
+
* eval-time access, no `@genesislcap/web-core`). The activity bus is imported for its types and the
|
|
13
|
+
* no-op default; its cross-tab `BroadcastChannel` is created lazily and only in a browser, so
|
|
14
|
+
* pulling this entry into Node opens nothing.
|
|
15
|
+
*
|
|
16
|
+
* @packageDocumentation
|
|
17
|
+
* @beta
|
|
18
|
+
*/
|
|
19
|
+
export * from './components/chat-driver';
|
|
20
|
+
export * from './components/orchestrating-driver';
|
|
21
|
+
export * from './channel/ai-activity-channel';
|
|
22
|
+
export * from './channel/ai-activity-bus';
|
|
23
|
+
export * from './config/config';
|
|
24
|
+
export * from './config/define-stateful-agent';
|
|
25
|
+
export * from './config/fallback-agents';
|
|
26
|
+
|
|
27
|
+
// Provider-construction surface, re-exported from `@genesislcap/foundation-ai` so a headless
|
|
28
|
+
// consumer builds its registry from the SAME bundled foundation-ai instance the driver uses. That
|
|
29
|
+
// shared identity matters: `ChatDriver`'s malformed/truncated handling does `instanceof` on the
|
|
30
|
+
// transports' error classes, so a second foundation-ai copy (a separate dep) would silently break
|
|
31
|
+
// those checks. NAMED re-exports only — `export *` would drag in the FAST-DI module
|
|
32
|
+
// (`@microsoft/fast-foundation` → `fast-element` → `document` at eval); by naming just these,
|
|
33
|
+
// esbuild tree-shakes the DI module out and the bundle stays Node-loadable.
|
|
34
|
+
export {
|
|
35
|
+
AnthropicProvider,
|
|
36
|
+
AnthropicTransport,
|
|
37
|
+
GeminiProvider,
|
|
38
|
+
GeminiTransport,
|
|
39
|
+
MutableAIProviderRegistry,
|
|
40
|
+
isObservableAIProviderRegistry,
|
|
41
|
+
} from '@genesislcap/foundation-ai';
|
|
42
|
+
export type {
|
|
43
|
+
AIProvider,
|
|
44
|
+
AIProviderRegistry,
|
|
45
|
+
AnthropicModelId,
|
|
46
|
+
CachePolicy,
|
|
47
|
+
ChatFallback,
|
|
48
|
+
ChatMessage,
|
|
49
|
+
ChatRequestOptions,
|
|
50
|
+
ChatToolChoice,
|
|
51
|
+
ChatToolDefinition,
|
|
52
|
+
ChatToolHandlers,
|
|
53
|
+
GeminiModelId,
|
|
54
|
+
} from '@genesislcap/foundation-ai';
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
ResponseTruncatedError,
|
|
18
18
|
} from '@genesislcap/foundation-ai';
|
|
19
19
|
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
|
|
20
|
-
import {
|
|
20
|
+
import { type ActivityBus, AgenticActivityBus } from '../../channel/ai-activity-bus';
|
|
21
21
|
import type { AgentConfig } from '../../config/config';
|
|
22
22
|
import { clearMetaEventRegistry, getMetaEvents } from '../../state/debug-event-log';
|
|
23
23
|
import { sumCosts } from '../../utils/sum-costs';
|
|
@@ -120,18 +120,18 @@ const callsTool = (name: string, id: string): ChatMessage => ({
|
|
|
120
120
|
const agent = (overrides: Partial<AgentConfig> & { name: string }): AgentConfig =>
|
|
121
121
|
({ description: 'test agent', ...overrides }) as AgentConfig;
|
|
122
122
|
|
|
123
|
-
const makeDriver = (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
50,
|
|
131
|
-
5,
|
|
132
|
-
undefined,
|
|
123
|
+
const makeDriver = (
|
|
124
|
+
config: AgentConfig,
|
|
125
|
+
provider: AIProvider,
|
|
126
|
+
sessionKey = '',
|
|
127
|
+
activityBus?: ActivityBus,
|
|
128
|
+
): ChatDriver => {
|
|
129
|
+
const driver = new ChatDriver(makeRegistry(provider), {
|
|
130
|
+
maxToolIterations: 50,
|
|
131
|
+
maxFoldOperations: 5,
|
|
133
132
|
sessionKey,
|
|
134
|
-
|
|
133
|
+
activityBus,
|
|
134
|
+
});
|
|
135
135
|
driver.applyAgent(config);
|
|
136
136
|
return driver;
|
|
137
137
|
};
|
|
@@ -159,13 +159,6 @@ const unresolvedEvents = (sessionKey: string): Array<Record<string, unknown>> =>
|
|
|
159
159
|
|
|
160
160
|
const stale = createLogicSuite('ChatDriver stale-tool detection');
|
|
161
161
|
|
|
162
|
-
// The driver imports the `agenticActivityBus` singleton, which opens a
|
|
163
|
-
// BroadcastChannel at module load. An open channel keeps the test page alive
|
|
164
|
-
// and hangs the runner, so close it once the suite finishes.
|
|
165
|
-
stale.after(() => {
|
|
166
|
-
agenticActivityBus.close();
|
|
167
|
-
});
|
|
168
|
-
|
|
169
162
|
stale('guides the model when it calls a tool that an earlier state exposed', async () => {
|
|
170
163
|
// State A exposes tool_a; calling it advances to state B, which exposes only
|
|
171
164
|
// tool_b. A factory-form agent narrows the tool set per turn, mirroring how
|
|
@@ -363,10 +356,6 @@ stale.run();
|
|
|
363
356
|
|
|
364
357
|
const hook = createLogicSuite('ChatDriver onUnresolvedTool hook');
|
|
365
358
|
|
|
366
|
-
hook.after(() => {
|
|
367
|
-
agenticActivityBus.close();
|
|
368
|
-
});
|
|
369
|
-
|
|
370
359
|
hook('replaces the default for a hallucinated tool when the hook returns a string', async () => {
|
|
371
360
|
const config = agent({
|
|
372
361
|
name: 'Hooked',
|
|
@@ -722,12 +711,6 @@ cancel.run();
|
|
|
722
711
|
|
|
723
712
|
const subagent = createLogicSuite('ChatDriver sub-agents');
|
|
724
713
|
|
|
725
|
-
subagent.after(() => {
|
|
726
|
-
// Safe to call again even if `stale` already closed it — close() is
|
|
727
|
-
// idempotent and cross-tab publishes are guarded by `&& this.channel`.
|
|
728
|
-
agenticActivityBus.close();
|
|
729
|
-
});
|
|
730
|
-
|
|
731
714
|
/** A sub-agent named `worker` that finishes by calling `completeSubAgent`. */
|
|
732
715
|
const completingWorker = (result: unknown): AgentConfig =>
|
|
733
716
|
agent({
|
|
@@ -1536,17 +1519,18 @@ interactionContextLifecycle.run();
|
|
|
1536
1519
|
|
|
1537
1520
|
const interactionBus = createLogicSuite('ChatDriver interaction activity-bus signals');
|
|
1538
1521
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1522
|
+
// These two tests need to observe what the driver publishes, so they inject a local
|
|
1523
|
+
// in-memory bus (no `crossTabEvents` → no BroadcastChannel is ever opened, nothing to close)
|
|
1524
|
+
// rather than the shared singleton. Every other suite lets the driver default to the no-op bus.
|
|
1542
1525
|
|
|
1543
1526
|
interactionBus('brackets a park with interaction-requested then -resolved', async () => {
|
|
1544
1527
|
const events: string[] = [];
|
|
1528
|
+
const bus = new AgenticActivityBus();
|
|
1545
1529
|
const unsubs = [
|
|
1546
|
-
|
|
1547
|
-
|
|
1530
|
+
bus.subscribe('interaction-requested', () => events.push('requested')),
|
|
1531
|
+
bus.subscribe('interaction-resolved', () => events.push('resolved')),
|
|
1548
1532
|
];
|
|
1549
|
-
const driver = makeDriver(agent({ name: 'a' }), scriptedProvider([]));
|
|
1533
|
+
const driver = makeDriver(agent({ name: 'a' }), scriptedProvider([]), '', bus);
|
|
1550
1534
|
|
|
1551
1535
|
const pending = driver.requestInteraction('w', {});
|
|
1552
1536
|
assert.equal(events, ['requested'], 'parking fires interaction-requested');
|
|
@@ -1562,8 +1546,9 @@ interactionBus('brackets a park with interaction-requested then -resolved', asyn
|
|
|
1562
1546
|
|
|
1563
1547
|
interactionBus('a timed-out interaction still fires interaction-resolved', async () => {
|
|
1564
1548
|
const events: string[] = [];
|
|
1565
|
-
const
|
|
1566
|
-
const
|
|
1549
|
+
const bus = new AgenticActivityBus();
|
|
1550
|
+
const unsub = bus.subscribe('interaction-resolved', () => events.push('resolved'));
|
|
1551
|
+
const driver = makeDriver(agent({ name: 'a' }), scriptedProvider([]), '', bus);
|
|
1567
1552
|
|
|
1568
1553
|
// Never resolved by a user — the timeout path runs the same teardown, so it
|
|
1569
1554
|
// must signal the bus too (else the button would stay enabled after a timeout).
|
|
@@ -1719,17 +1704,13 @@ const makeObservableRegistry = (initial: AIProvider): ObservableTestRegistry =>
|
|
|
1719
1704
|
};
|
|
1720
1705
|
|
|
1721
1706
|
const makeDriverWithRegistry = (config: AgentConfig, registry: AIProviderRegistry): ChatDriver => {
|
|
1722
|
-
const driver = new ChatDriver(registry, {
|
|
1707
|
+
const driver = new ChatDriver(registry, { maxToolIterations: 50, maxFoldOperations: 5 });
|
|
1723
1708
|
driver.applyAgent(config);
|
|
1724
1709
|
return driver;
|
|
1725
1710
|
};
|
|
1726
1711
|
|
|
1727
1712
|
const observable = createLogicSuite('ChatDriver observable provider registry');
|
|
1728
1713
|
|
|
1729
|
-
observable.after(() => {
|
|
1730
|
-
agenticActivityBus.close();
|
|
1731
|
-
});
|
|
1732
|
-
|
|
1733
1714
|
observable(
|
|
1734
1715
|
'a registry change clears the resolved-provider cache so the next turn uses the new provider',
|
|
1735
1716
|
async () => {
|
|
@@ -1828,10 +1809,6 @@ const modelProvider = (model: string | undefined, responses: ChatMessage[]): AIP
|
|
|
1828
1809
|
|
|
1829
1810
|
const modelAttr = createLogicSuite('ChatDriver per-message model attribution');
|
|
1830
1811
|
|
|
1831
|
-
modelAttr.after(() => {
|
|
1832
|
-
agenticActivityBus.close();
|
|
1833
|
-
});
|
|
1834
|
-
|
|
1835
1812
|
modelAttr('stamps the resolved model and registry name onto an assistant reply', async () => {
|
|
1836
1813
|
const provider = modelProvider('gemini-2.5-flash-lite', [
|
|
1837
1814
|
{ role: 'assistant', content: 'hi there' },
|
|
@@ -1918,9 +1895,6 @@ modelAttr.run();
|
|
|
1918
1895
|
// ---------------------------------------------------------------------------
|
|
1919
1896
|
|
|
1920
1897
|
const condense = createLogicSuite('ChatDriver condenseWhen');
|
|
1921
|
-
condense.after(() => {
|
|
1922
|
-
agenticActivityBus.close();
|
|
1923
|
-
});
|
|
1924
1898
|
|
|
1925
1899
|
const bigBody = 'A'.repeat(2000);
|
|
1926
1900
|
|
|
@@ -2193,8 +2167,12 @@ condense.run();
|
|
|
2193
2167
|
|
|
2194
2168
|
const outcome = createLogicSuite('ChatDriver turn-outcome surfacing');
|
|
2195
2169
|
|
|
2170
|
+
// This branch routes activity events to an INJECTED bus (default NOOP), not the module
|
|
2171
|
+
// singleton, so these tests inject a local bus and observe the same seam the driver publishes to.
|
|
2172
|
+
const outcomeBus = new AgenticActivityBus();
|
|
2173
|
+
|
|
2196
2174
|
outcome.after(() => {
|
|
2197
|
-
|
|
2175
|
+
outcomeBus.close();
|
|
2198
2176
|
});
|
|
2199
2177
|
|
|
2200
2178
|
/**
|
|
@@ -2209,7 +2187,7 @@ const captureLoopEnd = (): {
|
|
|
2209
2187
|
} => {
|
|
2210
2188
|
let last: { failureReason?: TurnFailureReason } | undefined;
|
|
2211
2189
|
let seen = false;
|
|
2212
|
-
const stop =
|
|
2190
|
+
const stop = outcomeBus.subscribe('tool-loop-end', (d) => {
|
|
2213
2191
|
last = d;
|
|
2214
2192
|
seen = true;
|
|
2215
2193
|
});
|
|
@@ -2223,17 +2201,12 @@ const makeCappedDriver = (
|
|
|
2223
2201
|
maxIterations: number,
|
|
2224
2202
|
sessionKey = '',
|
|
2225
2203
|
): ChatDriver => {
|
|
2226
|
-
const driver = new ChatDriver(
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
[],
|
|
2230
|
-
undefined,
|
|
2231
|
-
undefined,
|
|
2232
|
-
maxIterations,
|
|
2233
|
-
5,
|
|
2234
|
-
undefined,
|
|
2204
|
+
const driver = new ChatDriver(makeRegistry(provider), {
|
|
2205
|
+
maxToolIterations: maxIterations,
|
|
2206
|
+
maxFoldOperations: 5,
|
|
2235
2207
|
sessionKey,
|
|
2236
|
-
|
|
2208
|
+
activityBus: outcomeBus,
|
|
2209
|
+
});
|
|
2237
2210
|
driver.applyAgent(config);
|
|
2238
2211
|
return driver;
|
|
2239
2212
|
};
|
|
@@ -2250,7 +2223,7 @@ const assertSurfacesReason = async (
|
|
|
2250
2223
|
c,
|
|
2251
2224
|
p,
|
|
2252
2225
|
k,
|
|
2253
|
-
) => makeDriver(c, p, k),
|
|
2226
|
+
) => makeDriver(c, p, k, outcomeBus),
|
|
2254
2227
|
): Promise<void> => {
|
|
2255
2228
|
clearMetaEventRegistry();
|
|
2256
2229
|
const sessionKey = `outcome-${label}`;
|
|
@@ -2364,6 +2337,7 @@ outcome('a clean turn leaves the legacy shape byte-unchanged (no failureReason)'
|
|
|
2364
2337
|
config,
|
|
2365
2338
|
scriptedProvider([{ role: 'assistant', content: 'hi' }]),
|
|
2366
2339
|
'outcome-ok',
|
|
2340
|
+
outcomeBus,
|
|
2367
2341
|
);
|
|
2368
2342
|
const cap = captureLoopEnd();
|
|
2369
2343
|
|