@genesislcap/ai-assistant 14.461.2 → 14.462.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 +55 -1
- package/dist/ai-assistant.d.ts +52 -0
- package/dist/dts/channel/ai-activity-channel.d.ts +20 -0
- package/dist/dts/channel/ai-activity-channel.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +28 -0
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +4 -0
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/esm/components/chat-driver/chat-driver.js +87 -1
- package/dist/esm/components/chat-driver/chat-driver.test.js +216 -0
- package/dist/esm/main/main.js +23 -6
- package/dist/esm/state/debug-event-log.js +1 -1
- package/package.json +16 -16
- package/src/channel/ai-activity-channel.ts +20 -0
- package/src/components/chat-driver/chat-driver.test.ts +290 -0
- package/src/components/chat-driver/chat-driver.ts +102 -1
- package/src/main/main.ts +20 -1
- package/src/state/debug-event-log.ts +1 -1
|
@@ -803,6 +803,45 @@ interactionPresentation('presentation is absent when the option is omitted', ()
|
|
|
803
803
|
}));
|
|
804
804
|
interactionPresentation.run();
|
|
805
805
|
// ---------------------------------------------------------------------------
|
|
806
|
+
// interaction activity-bus signals (GENC-1346) — the driver brackets a parked
|
|
807
|
+
// widget interaction with `interaction-requested` / `interaction-resolved`, so
|
|
808
|
+
// turn-aware UI can distinguish "actively computing" from "parked awaiting the
|
|
809
|
+
// user". No tool-loop event fires at a park boundary, so these are the signal.
|
|
810
|
+
// ---------------------------------------------------------------------------
|
|
811
|
+
const interactionBus = createLogicSuite('ChatDriver interaction activity-bus signals');
|
|
812
|
+
interactionBus.after(() => {
|
|
813
|
+
agenticActivityBus.close();
|
|
814
|
+
});
|
|
815
|
+
interactionBus('brackets a park with interaction-requested then -resolved', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
816
|
+
const events = [];
|
|
817
|
+
const unsubs = [
|
|
818
|
+
agenticActivityBus.subscribe('interaction-requested', () => events.push('requested')),
|
|
819
|
+
agenticActivityBus.subscribe('interaction-resolved', () => events.push('resolved')),
|
|
820
|
+
];
|
|
821
|
+
const driver = makeDriver(agent({ name: 'a' }), scriptedProvider([]));
|
|
822
|
+
const pending = driver.requestInteraction('w', {});
|
|
823
|
+
assert.equal(events, ['requested'], 'parking fires interaction-requested');
|
|
824
|
+
const id = driver.getHistory().at(-1).interaction.interactionId;
|
|
825
|
+
driver.resolveInteraction(id, { status: 'approved' });
|
|
826
|
+
yield pending;
|
|
827
|
+
assert.equal(events, ['requested', 'resolved'], 'resolving fires interaction-resolved');
|
|
828
|
+
unsubs.forEach((u) => u());
|
|
829
|
+
driver.dispose();
|
|
830
|
+
}));
|
|
831
|
+
interactionBus('a timed-out interaction still fires interaction-resolved', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
832
|
+
const events = [];
|
|
833
|
+
const unsub = agenticActivityBus.subscribe('interaction-resolved', () => events.push('resolved'));
|
|
834
|
+
const driver = makeDriver(agent({ name: 'a' }), scriptedProvider([]));
|
|
835
|
+
// Never resolved by a user — the timeout path runs the same teardown, so it
|
|
836
|
+
// must signal the bus too (else the button would stay enabled after a timeout).
|
|
837
|
+
const pending = driver.requestInteraction('w', {}, { timeoutMs: 1 });
|
|
838
|
+
yield pending;
|
|
839
|
+
assert.equal(events, ['resolved'], 'the timeout resolution path also signals the bus');
|
|
840
|
+
unsub();
|
|
841
|
+
driver.dispose();
|
|
842
|
+
}));
|
|
843
|
+
interactionBus.run();
|
|
844
|
+
// ---------------------------------------------------------------------------
|
|
806
845
|
// interaction timeout — requestInteraction({ timeoutMs }) resolves with a
|
|
807
846
|
// status:'timeout' result (never rejects) and closes the widget read-only.
|
|
808
847
|
// ---------------------------------------------------------------------------
|
|
@@ -863,3 +902,180 @@ interactionCost('leaves externalCostUsd unset for a missing, zero, or negative c
|
|
|
863
902
|
assert.not.ok(driver.getHistory().some((m) => m.externalCostUsd != null));
|
|
864
903
|
}));
|
|
865
904
|
interactionCost.run();
|
|
905
|
+
/**
|
|
906
|
+
* A single-name (`'high'`) observable registry. `get`/`default` always return
|
|
907
|
+
* the current provider, so swapping it mid-session models a same-name vendor
|
|
908
|
+
* switch (the tier name stays `'high'`, the provider underneath changes).
|
|
909
|
+
*/
|
|
910
|
+
const makeObservableRegistry = (initial) => {
|
|
911
|
+
let current = initial;
|
|
912
|
+
const listeners = new Set();
|
|
913
|
+
return {
|
|
914
|
+
get: () => current,
|
|
915
|
+
default: () => current,
|
|
916
|
+
defaultName: () => 'high',
|
|
917
|
+
names: () => ['high'],
|
|
918
|
+
getStatus: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
|
|
919
|
+
listStatuses: () => __awaiter(void 0, void 0, void 0, function* () { return []; }),
|
|
920
|
+
subscribe(listener) {
|
|
921
|
+
listeners.add(listener);
|
|
922
|
+
return () => {
|
|
923
|
+
listeners.delete(listener);
|
|
924
|
+
};
|
|
925
|
+
},
|
|
926
|
+
swap(provider) {
|
|
927
|
+
current = provider;
|
|
928
|
+
for (const l of Array.from(listeners))
|
|
929
|
+
l();
|
|
930
|
+
},
|
|
931
|
+
listenerCount: () => listeners.size,
|
|
932
|
+
};
|
|
933
|
+
};
|
|
934
|
+
const makeDriverWithRegistry = (config, registry) => {
|
|
935
|
+
const driver = new ChatDriver(registry, {}, [], undefined, undefined, 50, 5, undefined, '');
|
|
936
|
+
driver.applyAgent(config);
|
|
937
|
+
return driver;
|
|
938
|
+
};
|
|
939
|
+
const observable = createLogicSuite('ChatDriver observable provider registry');
|
|
940
|
+
observable.after(() => {
|
|
941
|
+
agenticActivityBus.close();
|
|
942
|
+
});
|
|
943
|
+
observable('a registry change clears the resolved-provider cache so the next turn uses the new provider', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
944
|
+
const providerA = scriptedProvider([{ role: 'assistant', content: 'A' }]);
|
|
945
|
+
const providerB = scriptedProvider([{ role: 'assistant', content: 'B' }]);
|
|
946
|
+
const registry = makeObservableRegistry(providerA);
|
|
947
|
+
// A static provider name means lookups go through `resolvedProviderCache` —
|
|
948
|
+
// the cache that must self-invalidate on a registry change.
|
|
949
|
+
const driver = makeDriverWithRegistry(agent({ name: 'tiered', provider: 'high' }), registry);
|
|
950
|
+
yield driver.sendMessage('first');
|
|
951
|
+
assert.is(providerA.advertisedPerCall.length, 1, 'turn 1 resolves the original provider');
|
|
952
|
+
assert.is(providerB.advertisedPerCall.length, 0);
|
|
953
|
+
registry.swap(providerB); // notify → cache cleared
|
|
954
|
+
yield driver.sendMessage('second');
|
|
955
|
+
assert.is(providerB.advertisedPerCall.length, 1, 'turn 2 resolves the swapped-in provider');
|
|
956
|
+
assert.is(providerA.advertisedPerCall.length, 1, 'the stale provider is not reused');
|
|
957
|
+
driver.dispose();
|
|
958
|
+
}));
|
|
959
|
+
observable('re-emits provider-changed on a same-name swap', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
960
|
+
const providerA = scriptedProvider([{ role: 'assistant', content: 'A' }]);
|
|
961
|
+
const providerB = scriptedProvider([{ role: 'assistant', content: 'B' }]);
|
|
962
|
+
const registry = makeObservableRegistry(providerA);
|
|
963
|
+
const driver = makeDriverWithRegistry(agent({ name: 'tiered', provider: 'high' }), registry);
|
|
964
|
+
const names = [];
|
|
965
|
+
driver.addEventListener('provider-changed', (e) => {
|
|
966
|
+
names.push(e.detail.name);
|
|
967
|
+
});
|
|
968
|
+
yield driver.sendMessage('first');
|
|
969
|
+
registry.swap(providerB);
|
|
970
|
+
yield driver.sendMessage('second');
|
|
971
|
+
// The resolved name ('high') never changes, but the swap resets the
|
|
972
|
+
// last-dispatched name so the cog can refresh — two events, not one.
|
|
973
|
+
assert.equal(names, ['high', 'high']);
|
|
974
|
+
driver.dispose();
|
|
975
|
+
}));
|
|
976
|
+
observable('a non-observable registry is a no-op — turn runs, dispose does not throw', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
977
|
+
const provider = scriptedProvider([{ role: 'assistant', content: 'hi' }]);
|
|
978
|
+
const driver = makeDriverWithRegistry(agent({ name: 'plain' }), makeRegistry(provider));
|
|
979
|
+
yield driver.sendMessage('go');
|
|
980
|
+
assert.is(provider.advertisedPerCall.length, 1);
|
|
981
|
+
driver.dispose(); // no subscription was wired — must still be safe
|
|
982
|
+
}));
|
|
983
|
+
observable('a child sub-agent driver unsubscribes on completion — no listener leak', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
984
|
+
const provider = scriptedProvider([callsTool('delegate', 'd1'), callsTool('finish', 'f1')]);
|
|
985
|
+
const registry = makeObservableRegistry(provider);
|
|
986
|
+
const parent = delegatingParent(completingWorker({ ok: true }), () => { });
|
|
987
|
+
const driver = makeDriverWithRegistry(parent, registry);
|
|
988
|
+
assert.is(registry.listenerCount(), 1, 'the parent driver subscribed on construction');
|
|
989
|
+
yield driver.sendMessage('go');
|
|
990
|
+
// The child subscribed during the run; if it didn't clean up on its (normal)
|
|
991
|
+
// completion the registry would now hold two listeners.
|
|
992
|
+
assert.is(registry.listenerCount(), 1, 'the completed child unsubscribed');
|
|
993
|
+
driver.dispose();
|
|
994
|
+
assert.is(registry.listenerCount(), 0, 'the parent unsubscribed on dispose');
|
|
995
|
+
}));
|
|
996
|
+
observable.run();
|
|
997
|
+
// ---------------------------------------------------------------------------
|
|
998
|
+
// per-message model attribution (GENC-1346)
|
|
999
|
+
//
|
|
1000
|
+
// Each model-produced assistant message carries `model` (the concrete model id
|
|
1001
|
+
// the active provider's getStatus reports) and `providerName` (the registry slot
|
|
1002
|
+
// it resolved under), so the exported debug log shows which model produced each
|
|
1003
|
+
// message — and, since tool calls ride on the assistant message, each tool call.
|
|
1004
|
+
// ---------------------------------------------------------------------------
|
|
1005
|
+
/** A provider that replays scripted replies and reports `model` via getStatus
|
|
1006
|
+
* (omitted entirely when `model` is undefined, to model a provider with no
|
|
1007
|
+
* status). */
|
|
1008
|
+
const modelProvider = (model, responses) => {
|
|
1009
|
+
const queue = [...responses];
|
|
1010
|
+
const provider = {
|
|
1011
|
+
chat: () => __awaiter(void 0, void 0, void 0, function* () { var _a; return (_a = queue.shift()) !== null && _a !== void 0 ? _a : { role: 'assistant', content: 'done' }; }),
|
|
1012
|
+
};
|
|
1013
|
+
if (model !== undefined) {
|
|
1014
|
+
provider.getStatus = () => __awaiter(void 0, void 0, void 0, function* () { return ({ provider: 'gemini', model }); });
|
|
1015
|
+
}
|
|
1016
|
+
return provider;
|
|
1017
|
+
};
|
|
1018
|
+
const modelAttr = createLogicSuite('ChatDriver per-message model attribution');
|
|
1019
|
+
modelAttr.after(() => {
|
|
1020
|
+
agenticActivityBus.close();
|
|
1021
|
+
});
|
|
1022
|
+
modelAttr('stamps the resolved model and registry name onto an assistant reply', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
1023
|
+
const provider = modelProvider('gemini-2.5-flash-lite', [
|
|
1024
|
+
{ role: 'assistant', content: 'hi there' },
|
|
1025
|
+
]);
|
|
1026
|
+
const driver = makeDriver(agent({ name: 'plain' }), provider);
|
|
1027
|
+
yield driver.sendMessage('hello');
|
|
1028
|
+
const reply = driver.getHistory().find((m) => m.role === 'assistant');
|
|
1029
|
+
assert.ok(reply, 'assistant reply present');
|
|
1030
|
+
assert.is(reply.model, 'gemini-2.5-flash-lite', 'model id read from provider getStatus');
|
|
1031
|
+
// makeRegistry registers a single provider under the name 'test'.
|
|
1032
|
+
assert.is(reply.providerName, 'test', 'registry slot the turn resolved under');
|
|
1033
|
+
driver.dispose();
|
|
1034
|
+
}));
|
|
1035
|
+
modelAttr('attributes a tool-calling assistant message (and so its tool calls) to the model', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
1036
|
+
const provider = modelProvider('claude-haiku-4-5-20251001', [
|
|
1037
|
+
callsTool('noop', 't1'),
|
|
1038
|
+
{ role: 'assistant', content: 'finished' },
|
|
1039
|
+
]);
|
|
1040
|
+
const config = agent({
|
|
1041
|
+
name: 'withTool',
|
|
1042
|
+
toolDefinitions: [def('noop')],
|
|
1043
|
+
toolHandlers: { noop: () => __awaiter(void 0, void 0, void 0, function* () { return 'ok'; }) },
|
|
1044
|
+
});
|
|
1045
|
+
const driver = makeDriver(config, provider);
|
|
1046
|
+
yield driver.sendMessage('go');
|
|
1047
|
+
const toolCallMsg = driver.getHistory().find((m) => { var _a, _b; return ((_b = (_a = m.toolCalls) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0; });
|
|
1048
|
+
assert.ok(toolCallMsg, 'an assistant message with tool calls is present');
|
|
1049
|
+
assert.is(toolCallMsg.model, 'claude-haiku-4-5-20251001');
|
|
1050
|
+
assert.is(toolCallMsg.providerName, 'test');
|
|
1051
|
+
driver.dispose();
|
|
1052
|
+
}));
|
|
1053
|
+
modelAttr('picks up a model swapped behind a stable name', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
1054
|
+
var _a, _b, _c;
|
|
1055
|
+
const before = modelProvider('gemini-2.5-flash-lite', [{ role: 'assistant', content: 'A' }]);
|
|
1056
|
+
const after = modelProvider('gemini-2.5-pro', [{ role: 'assistant', content: 'B' }]);
|
|
1057
|
+
const registry = makeObservableRegistry(before);
|
|
1058
|
+
const driver = makeDriverWithRegistry(agent({ name: 'tiered', provider: 'high' }), registry);
|
|
1059
|
+
yield driver.sendMessage('first');
|
|
1060
|
+
registry.swap(after); // notify → model cache cleared, next turn re-resolves
|
|
1061
|
+
yield driver.sendMessage('second');
|
|
1062
|
+
const replies = driver.getHistory().filter((m) => m.role === 'assistant');
|
|
1063
|
+
assert.is((_a = replies.at(-2)) === null || _a === void 0 ? void 0 : _a.model, 'gemini-2.5-flash-lite', 'first turn keeps the original model');
|
|
1064
|
+
assert.is((_b = replies.at(-1)) === null || _b === void 0 ? void 0 : _b.model, 'gemini-2.5-pro', 'after the swap the new model is stamped');
|
|
1065
|
+
// The tier name ('high') never changed across the swap — only the model behind it.
|
|
1066
|
+
assert.is((_c = replies.at(-1)) === null || _c === void 0 ? void 0 : _c.providerName, 'high');
|
|
1067
|
+
driver.dispose();
|
|
1068
|
+
}));
|
|
1069
|
+
modelAttr('omits the model key entirely when the provider reports no status', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
1070
|
+
const provider = modelProvider(undefined, [{ role: 'assistant', content: 'no status here' }]);
|
|
1071
|
+
const driver = makeDriver(agent({ name: 'plain' }), provider);
|
|
1072
|
+
yield driver.sendMessage('hi');
|
|
1073
|
+
const reply = driver.getHistory().find((m) => m.role === 'assistant');
|
|
1074
|
+
assert.ok(reply, 'assistant reply present');
|
|
1075
|
+
// No getStatus → no model. The key is left off, not set to undefined, so the
|
|
1076
|
+
// exported log carries no dead `model` line for this message.
|
|
1077
|
+
assert.not.ok('model' in reply, 'model key is absent, not present-as-undefined');
|
|
1078
|
+
assert.is(reply.providerName, 'test', 'provider name is still recorded');
|
|
1079
|
+
driver.dispose();
|
|
1080
|
+
}));
|
|
1081
|
+
modelAttr.run();
|
package/dist/esm/main/main.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// =============================================================================
|
|
23
23
|
var FoundationAiAssistant_1;
|
|
24
24
|
import { __awaiter, __decorate, __rest } from "tslib";
|
|
25
|
-
import { AIProviderRegistry } from '@genesislcap/foundation-ai';
|
|
25
|
+
import { AIProviderRegistry, isObservableAIProviderRegistry } from '@genesislcap/foundation-ai';
|
|
26
26
|
import { avoidTreeShaking } from '@genesislcap/foundation-utils';
|
|
27
27
|
import { customElement, html, GenesisElement, observable, volatile, attr, } from '@genesislcap/web-core';
|
|
28
28
|
import DOMPurify from 'dompurify';
|
|
@@ -900,7 +900,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
900
900
|
this.driverCleanup = undefined;
|
|
901
901
|
}
|
|
902
902
|
connectedCallback() {
|
|
903
|
-
var _a, _b, _c, _d, _e, _f, _j, _k, _l;
|
|
903
|
+
var _a, _b, _c, _d, _e, _f, _j, _k, _l, _m;
|
|
904
904
|
// Initialise the store reference BEFORE super.connectedCallback() so that
|
|
905
905
|
// the first FAST render has access to the store. The store Proxy calls
|
|
906
906
|
// Observable.track(observableStore, sliceName) whenever a slice is read,
|
|
@@ -969,6 +969,21 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
969
969
|
this.fetchSuggestions();
|
|
970
970
|
void this.resolveContextLimit();
|
|
971
971
|
void this.loadProviderStatuses();
|
|
972
|
+
// When the host registered an observable registry (runtime provider
|
|
973
|
+
// switching), refresh the displayed model/limit and the provider list the
|
|
974
|
+
// instant its contents change — so the header reflects the new provider
|
|
975
|
+
// immediately on switch, not only when the next turn re-emits
|
|
976
|
+
// `provider-changed`. Feature-detected: a no-op for immutable registries.
|
|
977
|
+
// Re-subscribed per connect (docking/popout remounts); balanced in
|
|
978
|
+
// disconnectedCallback.
|
|
979
|
+
(_k = this.unsubProviderRegistry) === null || _k === void 0 ? void 0 : _k.call(this);
|
|
980
|
+
this.unsubProviderRegistry = undefined;
|
|
981
|
+
if (isObservableAIProviderRegistry(this.providerRegistry)) {
|
|
982
|
+
this.unsubProviderRegistry = this.providerRegistry.subscribe(() => {
|
|
983
|
+
void this.resolveContextLimit();
|
|
984
|
+
void this.loadProviderStatuses();
|
|
985
|
+
});
|
|
986
|
+
}
|
|
972
987
|
if (this.messagesEl) {
|
|
973
988
|
this._scrollListener = () => {
|
|
974
989
|
this._userScrolledAway =
|
|
@@ -992,18 +1007,20 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
992
1007
|
restoredMessages: this.messages.length,
|
|
993
1008
|
driver: driverExisted ? 'reused' : 'created',
|
|
994
1009
|
driverKind: this.driver instanceof OrchestratingDriver ? 'orchestrating' : 'chat',
|
|
995
|
-
driverBusy: (
|
|
1010
|
+
driverBusy: (_m = (_l = this.driver) === null || _l === void 0 ? void 0 : _l.isBusy()) !== null && _m !== void 0 ? _m : false,
|
|
996
1011
|
});
|
|
997
1012
|
}
|
|
998
1013
|
disconnectedCallback() {
|
|
999
|
-
var _a, _b, _c, _d;
|
|
1014
|
+
var _a, _b, _c, _d, _e;
|
|
1000
1015
|
super.disconnectedCallback();
|
|
1001
1016
|
this.stopLoadingTimer();
|
|
1002
1017
|
this.state = 'idle';
|
|
1003
1018
|
this.unwireDriver();
|
|
1004
1019
|
(_a = this.unsubBus) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
1005
1020
|
this.unsubBus = undefined;
|
|
1006
|
-
(_b = this.
|
|
1021
|
+
(_b = this.unsubProviderRegistry) === null || _b === void 0 ? void 0 : _b.call(this);
|
|
1022
|
+
this.unsubProviderRegistry = undefined;
|
|
1023
|
+
(_c = this._executionCompletionUnsub) === null || _c === void 0 ? void 0 : _c.call(this);
|
|
1007
1024
|
this._executionCompletionUnsub = undefined;
|
|
1008
1025
|
if (this.messagesEl && this._scrollListener) {
|
|
1009
1026
|
this.messagesEl.removeEventListener('scroll', this._scrollListener);
|
|
@@ -1019,7 +1036,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1019
1036
|
this._agentPickerToggle.finalize();
|
|
1020
1037
|
this._settingsToggle.finalize();
|
|
1021
1038
|
// Capture before clearing — `wasBusy` reads the driver, which is dropped below.
|
|
1022
|
-
this.logMeta('assistant.disconnected', { wasBusy: (
|
|
1039
|
+
this.logMeta('assistant.disconnected', { wasBusy: (_e = (_d = this.driver) === null || _d === void 0 ? void 0 : _d.isBusy()) !== null && _e !== void 0 ? _e : false });
|
|
1023
1040
|
// Clear local references only — driver and store stay in their registries.
|
|
1024
1041
|
this.driver = undefined;
|
|
1025
1042
|
this._sessionRef = undefined;
|
|
@@ -138,7 +138,7 @@ export const DEBUG_LOG_README = [
|
|
|
138
138
|
'This is an exported debug log for the Genesis AI assistant. Read it top-to-bottom.',
|
|
139
139
|
'`timeline` is the entire session as one array, already sorted chronologically by `timestamp` (ISO 8601). Every entry has a `kind`.',
|
|
140
140
|
'Timestamps are millisecond-resolution; entries that share the same millisecond are ordered by a fixed kind rank (event, then turn, then message), which is a heuristic and may not reflect exact causal order within that millisecond — e.g. a user message and the turn it triggered, or a final assistant message and its turn.end event, can appear in either order depending on whether they landed in the same millisecond. Read the logical structure of a turn rather than over-interpreting the micro-ordering of co-timestamped entries of different kinds.',
|
|
141
|
-
"kind:'message' — the conversation. `role` is user/assistant/tool/system-event/synthetic-user; `agentName` says which agent produced it; `toolCalls`/`toolResult`/`interaction` carry tool and widget activity; `inputTokens`/`outputTokens`/`cost` are per-message LLM usage, and `externalCostUsd` is any non-LLM cost a widget reported for its own external service calls (folded into the session cost total alongside `cost`). A 'synthetic-user' message is a display-only echo of an interaction outcome (e.g. the answer a widget reported): it renders on the user's side of the chat and `agentName` is the agent that created it, but it is never sent to the LLM — so it has no matching 'turn' and the model learns the outcome only from the corresponding tool result.",
|
|
141
|
+
"kind:'message' — the conversation. `role` is user/assistant/tool/system-event/synthetic-user; `agentName` says which agent produced it; `toolCalls`/`toolResult`/`interaction` carry tool and widget activity; `inputTokens`/`outputTokens`/`cost` are per-message LLM usage, and `externalCostUsd` is any non-LLM cost a widget reported for its own external service calls (folded into the session cost total alongside `cost`). On model-produced assistant messages, `model` is the concrete model id that generated it (e.g. 'gemini-2.5-flash-lite') and `providerName` is the registry slot it resolved under (e.g. a tier name like 'high'/'low', or the default); together they attribute the message — and any tool calls it carries — to an exact model even across a mid-session vendor/tier switch, where one slot name can map to different models before and after the switch. Both are undefined on any entry that is NOT an LLM response: non-assistant roles (user/tool/system-event) and 'synthetic-user' echoes; assistant interaction/widget entries (empty content carrying an `interaction` — a rendered widget, not a model turn); driver-authored assistant fallbacks (the timeout, repeated-malformed-call, and empty-response apology messages); and messages restored from a session persisted before these fields existed. One partial case: on a genuine model turn whose provider exposes no `getStatus` (or reports no model), `providerName` is still set but `model` alone is undefined. A 'synthetic-user' message is a display-only echo of an interaction outcome (e.g. the answer a widget reported): it renders on the user's side of the chat and `agentName` is the agent that created it, but it is never sent to the LLM — so it has no matching 'turn' and the model learns the outcome only from the corresponding tool result.",
|
|
142
142
|
"kind:'turn' — one LLM call. `turnIndex` is a string: a top-level turn is the bare counter ('0', '1', …); a sub-agent's turns are numbered under the parent turn that activated them ('3-1', '3-2', …, and a nested sub-agent contributes '3-2-1', …), and `agentName` names the agent that ran the turn. `systemPrompt` and `toolNames` are what the model saw. A systemPrompt of '<repeated — identical to turn N>' was byte-identical to turn N and de-duplicated; the full prompt is shown whenever it changes (often because a stateful agent advanced), so prompt evolution is visible.",
|
|
143
143
|
"kind:'turn'.`agentSnapshot` — the active agent's own view of its internal state, captured at that turn. An agent opts into this by exposing a `getDebugSnapshot()` that returns JSON-serializable per-state info; stateful/flow agents wire it automatically, so you can watch a flow advance turn-by-turn (e.g. current step, cursor, collected fields, pending changes). Absent for agents that don't expose one.",
|
|
144
144
|
"kind:'event' — a meta/lifecycle event. `type` names it (see below); `detail` carries structured data. `detail.placement` is the emitting UI instance: 'bubble' (collapsed), 'panel' (popped-out), or 'standalone'.",
|
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.462.0",
|
|
5
5
|
"license": "SEE LICENSE IN license.txt",
|
|
6
6
|
"main": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/ai-assistant.d.ts",
|
|
@@ -64,24 +64,24 @@
|
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
66
|
"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.
|
|
67
|
+
"@genesislcap/foundation-testing": "14.462.0",
|
|
68
|
+
"@genesislcap/genx": "14.462.0",
|
|
69
|
+
"@genesislcap/rollup-builder": "14.462.0",
|
|
70
|
+
"@genesislcap/ts-builder": "14.462.0",
|
|
71
|
+
"@genesislcap/uvu-playwright-builder": "14.462.0",
|
|
72
|
+
"@genesislcap/vite-builder": "14.462.0",
|
|
73
|
+
"@genesislcap/webpack-builder": "14.462.0",
|
|
74
74
|
"@types/dompurify": "^3.0.5",
|
|
75
75
|
"@types/marked": "^5.0.2"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@genesislcap/foundation-ai": "14.
|
|
79
|
-
"@genesislcap/foundation-logger": "14.
|
|
80
|
-
"@genesislcap/foundation-redux": "14.
|
|
81
|
-
"@genesislcap/foundation-ui": "14.
|
|
82
|
-
"@genesislcap/foundation-utils": "14.
|
|
83
|
-
"@genesislcap/rapid-design-system": "14.
|
|
84
|
-
"@genesislcap/web-core": "14.
|
|
78
|
+
"@genesislcap/foundation-ai": "14.462.0",
|
|
79
|
+
"@genesislcap/foundation-logger": "14.462.0",
|
|
80
|
+
"@genesislcap/foundation-redux": "14.462.0",
|
|
81
|
+
"@genesislcap/foundation-ui": "14.462.0",
|
|
82
|
+
"@genesislcap/foundation-utils": "14.462.0",
|
|
83
|
+
"@genesislcap/rapid-design-system": "14.462.0",
|
|
84
|
+
"@genesislcap/web-core": "14.462.0",
|
|
85
85
|
"dompurify": "^3.3.1",
|
|
86
86
|
"marked": "^17.0.3"
|
|
87
87
|
},
|
|
@@ -93,5 +93,5 @@
|
|
|
93
93
|
"publishConfig": {
|
|
94
94
|
"access": "public"
|
|
95
95
|
},
|
|
96
|
-
"gitHead": "
|
|
96
|
+
"gitHead": "d332227a536531b1b807ae33f65ce4639f0991ba"
|
|
97
97
|
}
|
|
@@ -61,4 +61,24 @@ export interface AgenticActivityEvents {
|
|
|
61
61
|
* (telemetry flushes, post-turn workspace transitions, deferred state recomputation).
|
|
62
62
|
*/
|
|
63
63
|
'tool-loop-end': undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Fired when a tool handler hands a widget to the user mid-loop and parks awaiting it
|
|
66
|
+
* (`requestInteraction`) — the turn is suspended between provider calls, with no request
|
|
67
|
+
* in flight. Pairs with {@link AgenticActivityEvents.'interaction-resolved'}.
|
|
68
|
+
*
|
|
69
|
+
* This is the inverse boundary to {@link AgenticActivityEvents.'tool-loop-start'}: the
|
|
70
|
+
* loop is running (still inside `tool-loop-start`/`tool-loop-end`) but is momentarily idle,
|
|
71
|
+
* waiting on the user. Use it for hooks that are unsafe *while computing* but safe *while
|
|
72
|
+
* parked* — e.g. enabling a control that must not change mid-request but is fine to change
|
|
73
|
+
* during a long journey step. Tab-local (not forwarded cross-tab): the widget and its
|
|
74
|
+
* waiting state belong to the tab that opened it.
|
|
75
|
+
*/
|
|
76
|
+
'interaction-requested': undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Fired when a parked widget interaction ends — by user completion, timeout, or
|
|
79
|
+
* cancellation — just before the suspended tool handler resumes and the loop continues.
|
|
80
|
+
* Pairs with {@link AgenticActivityEvents.'interaction-requested'}. After this the turn is
|
|
81
|
+
* computing again (until `tool-loop-end`). Tab-local.
|
|
82
|
+
*/
|
|
83
|
+
'interaction-resolved': undefined;
|
|
64
84
|
}
|