@genesislcap/ai-assistant 15.58.2 → 15.60.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 +10096 -6986
- package/dist/ai-assistant.d.ts +562 -101
- package/dist/chat-driver.cjs +624 -207
- package/dist/chat-driver.cjs.map +4 -4
- package/dist/chat-driver.mjs +624 -207
- package/dist/chat-driver.mjs.map +4 -4
- package/dist/custom-elements.json +5426 -3570
- package/dist/dts/components/agent-session/agent-session.d.ts +19 -3
- package/dist/dts/components/agent-session/agent-session.d.ts.map +1 -1
- package/dist/dts/components/ai-driver/ai-driver.d.ts +120 -10
- package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
- package/dist/dts/components/ai-driver/base-driver-decorator.d.ts +103 -0
- package/dist/dts/components/ai-driver/base-driver-decorator.d.ts.map +1 -0
- package/dist/dts/components/ai-driver/index.d.ts +3 -1
- package/dist/dts/components/ai-driver/index.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts +84 -8
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/chat-driver/interaction-controller.d.ts +37 -3
- package/dist/dts/components/chat-driver/interaction-controller.d.ts.map +1 -1
- package/dist/dts/components/message-feedback/index.d.ts +2 -0
- package/dist/dts/components/message-feedback/index.d.ts.map +1 -0
- package/dist/dts/components/message-feedback/message-feedback.d.ts +61 -0
- package/dist/dts/components/message-feedback/message-feedback.d.ts.map +1 -0
- package/dist/dts/components/message-feedback/message-feedback.styles.d.ts +2 -0
- package/dist/dts/components/message-feedback/message-feedback.styles.d.ts.map +1 -0
- package/dist/dts/components/message-feedback/message-feedback.template.d.ts +4 -0
- package/dist/dts/components/message-feedback/message-feedback.template.d.ts.map +1 -0
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +30 -93
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/index.d.ts +4 -1
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +21 -5
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/main/main.styles.d.ts.map +1 -1
- package/dist/dts/main/main.template.d.ts.map +1 -1
- package/dist/dts/react.d.ts +10 -0
- package/dist/dts/state/debug-event-log.d.ts +1 -1
- package/dist/dts/state/debug-event-log.d.ts.map +1 -1
- package/dist/dts/state/interaction-context.d.ts +7 -1
- package/dist/dts/state/interaction-context.d.ts.map +1 -1
- package/dist/dts/state/persistence/session-persister.d.ts.map +1 -1
- package/dist/dts/suggestions/generate-suggestions.d.ts.map +1 -1
- package/dist/dts/types/handler-capability.d.ts +28 -0
- package/dist/dts/types/handler-capability.d.ts.map +1 -0
- package/dist/dts/types/interaction-context.d.ts +16 -0
- package/dist/dts/types/interaction-context.d.ts.map +1 -1
- package/dist/dts/types/interaction-interceptor.d.ts +109 -0
- package/dist/dts/types/interaction-interceptor.d.ts.map +1 -0
- package/dist/dts/utils/delegated-message.d.ts +52 -0
- package/dist/dts/utils/delegated-message.d.ts.map +1 -0
- package/dist/dts/utils/history-transform.d.ts.map +1 -1
- package/dist/esm/components/agent-session/agent-session.js +17 -0
- package/dist/esm/components/ai-driver/ai-driver.js +21 -1
- package/dist/esm/components/ai-driver/base-driver-decorator.js +211 -0
- package/dist/esm/components/ai-driver/index.js +2 -1
- package/dist/esm/components/chat-driver/chat-driver.js +195 -14
- package/dist/esm/components/chat-driver/interaction-controller.js +168 -14
- package/dist/esm/components/message-feedback/index.js +1 -0
- package/dist/esm/components/message-feedback/message-feedback.js +241 -0
- package/dist/esm/components/message-feedback/message-feedback.styles.js +94 -0
- package/dist/esm/components/message-feedback/message-feedback.template.js +80 -0
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +72 -196
- package/dist/esm/index.js +1 -0
- package/dist/esm/main/main.js +123 -78
- package/dist/esm/main/main.styles.js +27 -0
- package/dist/esm/main/main.template.js +13 -0
- package/dist/esm/state/debug-event-log.js +7 -2
- package/dist/esm/state/interaction-context.js +11 -0
- package/dist/esm/state/persistence/session-persister.js +5 -6
- package/dist/esm/suggestions/generate-suggestions.js +8 -1
- package/dist/esm/types/handler-capability.js +1 -0
- package/dist/esm/types/interaction-interceptor.js +1 -0
- package/dist/esm/utils/delegated-message.js +25 -0
- package/dist/esm/utils/history-transform.js +12 -1
- package/dist/react.cjs +7 -0
- package/dist/react.mjs +6 -0
- package/package.json +17 -17
- package/sandbox/README.md +1 -1
- package/sandbox/sandbox.ts +35 -0
package/dist/esm/main/main.js
CHANGED
|
@@ -36,6 +36,7 @@ import { AiChatInteractionWrapper } from '../components/chat-interaction-wrapper
|
|
|
36
36
|
import { AiChatMarkdown } from '../components/chat-markdown/chat-markdown';
|
|
37
37
|
import { AiFlowingWavesIndicator } from '../components/flowing-waves-indicator';
|
|
38
38
|
import { AiHaloOverlay } from '../components/halo-overlay';
|
|
39
|
+
import { isRateableReply, MessageFeedback, replyKey, } from '../components/message-feedback/message-feedback';
|
|
39
40
|
import { asOrchestrator, OrchestratingDriver, } from '../components/orchestrating-driver/orchestrating-driver';
|
|
40
41
|
import { AiPlasmaOrbIndicator } from '../components/plasma-orb-indicator';
|
|
41
42
|
import { SettingsModalController, } from '../components/settings-modal';
|
|
@@ -165,7 +166,7 @@ const TIMESTAMP_CLOCK_INTERVAL_MS = 30000;
|
|
|
165
166
|
*/
|
|
166
167
|
export { budgetPercentOf, formatBlockedReason } from './budget-controller';
|
|
167
168
|
// Register supporting components when the main component module is imported.
|
|
168
|
-
avoidTreeShaking(AiChatMarkdown, AiChatInteractionWrapper, AiHaloOverlay, AiWavesIndicator, AiFlowingWavesIndicator, AiPlasmaOrbIndicator, AiChatBubble, AiActivityHalo, ChatSuggestions, AgentPicker);
|
|
169
|
+
avoidTreeShaking(AiChatMarkdown, AiChatInteractionWrapper, AiHaloOverlay, AiWavesIndicator, AiFlowingWavesIndicator, AiPlasmaOrbIndicator, AiChatBubble, AiActivityHalo, ChatSuggestions, AgentPicker, MessageFeedback);
|
|
169
170
|
/**
|
|
170
171
|
* Foundation AI Assistant component.
|
|
171
172
|
*
|
|
@@ -1206,7 +1207,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1206
1207
|
var _a, _b, _c, _d;
|
|
1207
1208
|
const previous = (_b = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.pinnedAgentName) !== null && _b !== void 0 ? _b : null;
|
|
1208
1209
|
(_c = this._sessionRef) === null || _c === void 0 ? void 0 : _c.actions.aiAssistant.setPinnedAgentName(value);
|
|
1209
|
-
(_d = this.
|
|
1210
|
+
(_d = this.driver) === null || _d === void 0 ? void 0 : _d.setPinnedAgent(value);
|
|
1210
1211
|
// Suggestions are scoped to the active routing — resetting forces a fresh
|
|
1211
1212
|
// fetch when the user pins/unpins so the prompts match the new agent.
|
|
1212
1213
|
if (previous !== value) {
|
|
@@ -1231,7 +1232,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1231
1232
|
set flowOwnerAgentName(value) {
|
|
1232
1233
|
var _a, _b;
|
|
1233
1234
|
(_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.actions.aiAssistant.setFlowOwnerAgentName(value);
|
|
1234
|
-
(_b = this.
|
|
1235
|
+
(_b = this.driver) === null || _b === void 0 ? void 0 : _b.setFlowOwner(value);
|
|
1235
1236
|
}
|
|
1236
1237
|
get liveSubAgentTrace() {
|
|
1237
1238
|
var _a, _b;
|
|
@@ -1576,12 +1577,12 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1576
1577
|
this.settings.costHistorySlotted = value;
|
|
1577
1578
|
}
|
|
1578
1579
|
/**
|
|
1579
|
-
*
|
|
1580
|
-
*
|
|
1581
|
-
*
|
|
1580
|
+
* Which kind of driver this element runs, for the `driver.created` / `assistant.connected` log
|
|
1581
|
+
* lines only. Looks through any decorators (GENC-1660): behaviour goes through `AiDriver`
|
|
1582
|
+
* members, which every layer forwards, so this is the one place the concrete class matters.
|
|
1582
1583
|
*/
|
|
1583
|
-
get
|
|
1584
|
-
return asOrchestrator(this.driver);
|
|
1584
|
+
get driverKind() {
|
|
1585
|
+
return asOrchestrator(this.driver) ? 'orchestrating' : 'chat';
|
|
1585
1586
|
}
|
|
1586
1587
|
/**
|
|
1587
1588
|
* A get/set pair over one of this element's own properties, for a controller
|
|
@@ -1751,7 +1752,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1751
1752
|
this.driver.loadHistory([...history]);
|
|
1752
1753
|
this.logMeta('driver.created', {
|
|
1753
1754
|
reason: 'agents-changed',
|
|
1754
|
-
driverKind: this.
|
|
1755
|
+
driverKind: this.driverKind,
|
|
1755
1756
|
});
|
|
1756
1757
|
}
|
|
1757
1758
|
/**
|
|
@@ -1835,14 +1836,13 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1835
1836
|
* times — unwires any existing listeners first.
|
|
1836
1837
|
*/
|
|
1837
1838
|
wireDriver() {
|
|
1839
|
+
var _a;
|
|
1838
1840
|
const { driver } = this;
|
|
1839
1841
|
if (!driver)
|
|
1840
1842
|
return;
|
|
1841
1843
|
// Captured, not re-read: the cleanup closures below must remove their listeners
|
|
1842
1844
|
// from the object they were added to, even if `this.driver` has been reassigned
|
|
1843
|
-
// by the time `unwireDriver` runs.
|
|
1844
|
-
// and reading both from `this` here means they cannot disagree.
|
|
1845
|
-
const orchestrator = this.orchestrator;
|
|
1845
|
+
// by the time `unwireDriver` runs.
|
|
1846
1846
|
// Idempotent — unwire previous listeners on this component before re-wiring.
|
|
1847
1847
|
this.unwireDriver();
|
|
1848
1848
|
const onHistoryUpdated = (e) => {
|
|
@@ -1963,72 +1963,75 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1963
1963
|
() => driver.removeEventListener('interaction-stop', onInteractionStop),
|
|
1964
1964
|
() => driver.removeEventListener('provider-changed', onProviderChanged),
|
|
1965
1965
|
];
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
(
|
|
1966
|
+
// Wired on the driver this element holds, whatever it is, rather than on an orchestrator found
|
|
1967
|
+
// by narrowing (GENC-1660). Every layer forwards these members and events, so this works for a
|
|
1968
|
+
// bare `ChatDriver` — which now raises the lifecycle events itself (GENC-1485) — and for any
|
|
1969
|
+
// decorator stack, where a narrowed orchestrator would be one layer the host should not reach.
|
|
1970
|
+
//
|
|
1971
|
+
// Restore the user pin and flow-owner lock from the session store onto
|
|
1972
|
+
// the freshly built driver, so pop-in/pop-out and agents-array
|
|
1973
|
+
// reassignment preserve both.
|
|
1974
|
+
driver.setPinnedAgent(this.pinnedAgentName);
|
|
1975
|
+
driver.setFlowOwner(this.flowOwnerAgentName);
|
|
1976
|
+
(_a = driver.setContextGuard) === null || _a === void 0 ? void 0 : _a.call(driver, this.contextGateCtrl.guardPolicy);
|
|
1977
|
+
const onOrchStart = () => {
|
|
1978
|
+
this.showHalo = 'orchestrating';
|
|
1979
|
+
};
|
|
1980
|
+
const onOrchStop = () => {
|
|
1981
|
+
if (this.showHalo === 'orchestrating')
|
|
1982
|
+
this.showHalo = 'agent';
|
|
1983
|
+
};
|
|
1984
|
+
const onAgentChanged = (e) => {
|
|
1985
|
+
this.activeAgent = e.detail;
|
|
1986
|
+
};
|
|
1987
|
+
// User-initiated pin changes flow into the driver via the
|
|
1988
|
+
// `pinnedAgentName` setter, which already calls setPinnedAgent — no
|
|
1989
|
+
// event-driven mirror needed in this direction. The driver does still
|
|
1990
|
+
// emit `'pinned-changed'` if other code ever calls `setPinnedAgent`
|
|
1991
|
+
// directly, so keep the mirror for safety; guard against the redundant
|
|
1992
|
+
// round-trip caused by our own setter call.
|
|
1993
|
+
const onPinnedChanged = (e) => {
|
|
1994
|
+
var _a, _b;
|
|
1995
|
+
const name = e.detail;
|
|
1996
|
+
if (((_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.pinnedAgentName) !== name) {
|
|
1997
|
+
(_b = this._sessionRef) === null || _b === void 0 ? void 0 : _b.actions.aiAssistant.setPinnedAgentName(name);
|
|
1998
|
+
}
|
|
1999
|
+
};
|
|
2000
|
+
// Orchestrator-initiated flow-owner changes (auto-lock on stateful
|
|
2001
|
+
// activation, auto-clear on release). Mirror to Redux so the UI freeze
|
|
2002
|
+
// state and `pinLocked` survive component remount.
|
|
2003
|
+
const onFlowOwnerChanged = (e) => {
|
|
2004
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2005
|
+
const name = e.detail;
|
|
2006
|
+
if (((_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.flowOwnerAgentName) !== name) {
|
|
2007
|
+
(_b = this._sessionRef) === null || _b === void 0 ? void 0 : _b.actions.aiAssistant.setFlowOwnerAgentName(name);
|
|
2008
|
+
}
|
|
2009
|
+
// Capture the prompt that started this flow so a non-resumable tombstone can
|
|
2010
|
+
// hand it back on return (GENC-1351 §5.9). On release, clear it. Slice-backed so
|
|
2011
|
+
// both element instances agree.
|
|
2012
|
+
if (name) {
|
|
2013
|
+
// Prefer the message currently being sent (`send()` stashes it in
|
|
2014
|
+
// `_pendingSendPrompt`) over the driver's raw history: `flow-owner-changed` fires
|
|
2015
|
+
// while routing THIS turn — BEFORE `chatDriver.sendMessage` appends the triggering
|
|
2016
|
+
// message to raw history — so a raw-history read here captures the PREVIOUS user
|
|
2017
|
+
// turn (the bug behind an interrupted flow re-priming the wrong prompt). Fall back
|
|
2018
|
+
// to raw history for a flow that locks outside a user send.
|
|
2019
|
+
const history = (_e = (_d = (_c = this.driver) === null || _c === void 0 ? void 0 : _c.getRawHistory) === null || _d === void 0 ? void 0 : _d.call(_c)) !== null && _e !== void 0 ? _e : [];
|
|
2020
|
+
const prompt = this._pendingSendPrompt || ((_f = history[lastUserMessageIndex(history)]) === null || _f === void 0 ? void 0 : _f.content);
|
|
2021
|
+
if (prompt) {
|
|
2022
|
+
(_g = this._sessionRef) === null || _g === void 0 ? void 0 : _g.actions.aiAssistant.setFlowActivationPrompt(prompt);
|
|
2023
2023
|
}
|
|
2024
|
-
}
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2024
|
+
}
|
|
2025
|
+
else {
|
|
2026
|
+
(_h = this._sessionRef) === null || _h === void 0 ? void 0 : _h.actions.aiAssistant.setFlowActivationPrompt(null);
|
|
2027
|
+
}
|
|
2028
|
+
};
|
|
2029
|
+
driver.addEventListener('orchestrating-start', onOrchStart);
|
|
2030
|
+
driver.addEventListener('orchestrating-stop', onOrchStop);
|
|
2031
|
+
driver.addEventListener('agent-changed', onAgentChanged);
|
|
2032
|
+
driver.addEventListener('pinned-changed', onPinnedChanged);
|
|
2033
|
+
driver.addEventListener('flow-owner-changed', onFlowOwnerChanged);
|
|
2034
|
+
cleanups.push(() => driver.removeEventListener('orchestrating-start', onOrchStart), () => driver.removeEventListener('orchestrating-stop', onOrchStop), () => driver.removeEventListener('agent-changed', onAgentChanged), () => driver.removeEventListener('pinned-changed', onPinnedChanged), () => driver.removeEventListener('flow-owner-changed', onFlowOwnerChanged));
|
|
2032
2035
|
this.driverCleanup = () => {
|
|
2033
2036
|
for (const fn of cleanups)
|
|
2034
2037
|
fn();
|
|
@@ -2157,7 +2160,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
2157
2160
|
store: isNewStore ? 'created' : 'restored',
|
|
2158
2161
|
restoredMessages: this.messages.length,
|
|
2159
2162
|
driver: driverExisted ? 'reused' : 'created',
|
|
2160
|
-
driverKind: this.
|
|
2163
|
+
driverKind: this.driverKind,
|
|
2161
2164
|
driverBusy: (_f = (_e = this.driver) === null || _e === void 0 ? void 0 : _e.isBusy()) !== null && _f !== void 0 ? _f : false,
|
|
2162
2165
|
});
|
|
2163
2166
|
}
|
|
@@ -3679,6 +3682,48 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3679
3682
|
const stem = this.debugLogAgentName.toLowerCase().replace(/\s+/g, '-');
|
|
3680
3683
|
return `${stem}-${new Date().toISOString().replace(/:/g, '-')}.json`;
|
|
3681
3684
|
}
|
|
3685
|
+
/**
|
|
3686
|
+
* The most recent rateable reply, whose thumbs stay visible without a hover.
|
|
3687
|
+
*
|
|
3688
|
+
* @internal
|
|
3689
|
+
*/
|
|
3690
|
+
get latestReply() {
|
|
3691
|
+
for (let i = this.messages.length - 1; i >= 0; i -= 1) {
|
|
3692
|
+
if (isRateableReply(this.messages[i]))
|
|
3693
|
+
return this.messages[i];
|
|
3694
|
+
}
|
|
3695
|
+
return undefined;
|
|
3696
|
+
}
|
|
3697
|
+
/** @internal */
|
|
3698
|
+
get messageFeedbackDescription() {
|
|
3699
|
+
var _a, _b, _c;
|
|
3700
|
+
return ((_c = (_b = (_a = this.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.messageFeedback) === null || _b === void 0 ? void 0 : _b.description) !== null && _c !== void 0 ? _c : 'Sends your rating and note on this reply.');
|
|
3701
|
+
}
|
|
3702
|
+
/**
|
|
3703
|
+
* Hand a rated reply to the host's `ui.messageFeedback.send`, with a getter for the same
|
|
3704
|
+
* lifetime log the agent-log Send button submits — built only if the host asks for it.
|
|
3705
|
+
* Rejects when the host does, or does not settle in time.
|
|
3706
|
+
*
|
|
3707
|
+
* @internal
|
|
3708
|
+
*/
|
|
3709
|
+
submitMessageFeedback(reply, rating, note) {
|
|
3710
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
3711
|
+
var _a, _b, _c;
|
|
3712
|
+
const config = (_a = this.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.messageFeedback;
|
|
3713
|
+
if (!config)
|
|
3714
|
+
throw new Error('ui.messageFeedback is not configured');
|
|
3715
|
+
yield settleWithin(config.send({
|
|
3716
|
+
rating,
|
|
3717
|
+
reply,
|
|
3718
|
+
replyKey: replyKey(reply),
|
|
3719
|
+
message: note,
|
|
3720
|
+
filename: this.debugLogFilename(),
|
|
3721
|
+
agentName: (_b = reply.agentName) !== null && _b !== void 0 ? _b : this.debugLogAgentName,
|
|
3722
|
+
host: window.location.host,
|
|
3723
|
+
getLogJson: () => __awaiter(this, void 0, void 0, function* () { return JSON.stringify(yield this.getLifetimeDebugLog(), null, 2); }),
|
|
3724
|
+
}), (_c = config.timeoutMs) !== null && _c !== void 0 ? _c : DEBUG_LOG_SUBMIT_TIMEOUT_MS, 'messageFeedback.send');
|
|
3725
|
+
});
|
|
3726
|
+
}
|
|
3682
3727
|
/**
|
|
3683
3728
|
* Back to idle: nothing in flight, no flash, no open compose box, no note. Called on
|
|
3684
3729
|
* disconnect and on a session switch / Clear (a note typed against one conversation
|
|
@@ -459,6 +459,33 @@ const baseStyles = css `
|
|
|
459
459
|
}
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
+
/* Revealed like the 'hover' timestamps, with space reserved so the bubble never reflows.
|
|
463
|
+
Always shown on the latest reply, once rated, and while the note panel is open. */
|
|
464
|
+
.message-feedback {
|
|
465
|
+
opacity: 0%;
|
|
466
|
+
transition: opacity 0.12s ease-out;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.message-row:hover .message-feedback,
|
|
470
|
+
.message-row:focus-within .message-feedback,
|
|
471
|
+
.message-feedback[latest],
|
|
472
|
+
.message-feedback[open],
|
|
473
|
+
.message-feedback[rated] {
|
|
474
|
+
opacity: 100%;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
@media (hover: none) {
|
|
478
|
+
.message-feedback {
|
|
479
|
+
opacity: 100%;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
@media (prefers-reduced-motion: reduce) {
|
|
484
|
+
.message-feedback {
|
|
485
|
+
transition: none;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
462
489
|
.payload {
|
|
463
490
|
font-family: monospace;
|
|
464
491
|
white-space: pre-wrap;
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { isChatToolCallUnknown } from '@genesislcap/foundation-ai';
|
|
18
18
|
import { classNames, html, ref, repeat, slotted, when } from '@genesislcap/web-core';
|
|
19
19
|
import { MARKDOWN_EXPORTPARTS } from '../components/chat-markdown/chat-markdown';
|
|
20
|
+
import { isRateableReply, MESSAGE_FEEDBACK_EXPORTPARTS, } from '../components/message-feedback/message-feedback';
|
|
20
21
|
import { SettingsModalTemplate } from '../components/settings-modal';
|
|
21
22
|
import { SUGGESTIONS_EXPORTPARTS } from '../suggestions/chat-suggestions';
|
|
22
23
|
import { formatAbsoluteTimestamp, formatMessageTimestamp, messageTimestampDateTime, } from '../utils/format-message-timestamp';
|
|
@@ -651,6 +652,18 @@ ${(tc) => { var _a; return (((_a = tc.foldPath) === null || _a === void 0 ? void
|
|
|
651
652
|
`)}
|
|
652
653
|
${when((m) => m.interaction, interactionWrapperTemplate)}
|
|
653
654
|
</div>
|
|
655
|
+
${when((m, c) => { var _a; return isRateableReply(m) && ((_a = c.parent.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.messageFeedback) != null; }, html `
|
|
656
|
+
<foundation-ai-message-feedback
|
|
657
|
+
class="message-feedback"
|
|
658
|
+
part="message-feedback"
|
|
659
|
+
exportparts="${MESSAGE_FEEDBACK_EXPORTPARTS}"
|
|
660
|
+
?latest="${(m, c) => m === c.parent.latestReply}"
|
|
661
|
+
:designSystemPrefix="${(_, c) => c.parent.designSystemPrefix}"
|
|
662
|
+
:description="${(_, c) => c.parent.messageFeedbackDescription}"
|
|
663
|
+
:submit="${(_, c) => c.parent.submitMessageFeedback.bind(c.parent)}"
|
|
664
|
+
:message="${(m) => m}"
|
|
665
|
+
></foundation-ai-message-feedback>
|
|
666
|
+
`)}
|
|
654
667
|
</div>
|
|
655
668
|
</div>
|
|
656
669
|
`)}
|
|
@@ -76,6 +76,10 @@ export const META_EVENT_IMPORTANCE = {
|
|
|
76
76
|
'interaction.outcome': 'normal',
|
|
77
77
|
// An outcome that could not be recorded: the widget goes on showing only the answer.
|
|
78
78
|
'interaction.outcome-dropped': 'high',
|
|
79
|
+
// A claimant handing a pending interaction to the person, or taking it back.
|
|
80
|
+
'interaction.held': 'normal',
|
|
81
|
+
// A claim on a `userOnly` interaction, refused. Expected when an interceptor claims broadly.
|
|
82
|
+
'interaction.claim-refused': 'low',
|
|
79
83
|
'external-diagnostics.folded': 'normal',
|
|
80
84
|
'session.cleared': 'normal',
|
|
81
85
|
'session.switched': 'low',
|
|
@@ -222,7 +226,8 @@ export const DEBUG_LOG_README = [
|
|
|
222
226
|
"What 'session' means for those figures: the CURRENT conversation under this session key (a host-supplied per-project id, or the element id + header title when the host supplies none) — which is not necessarily everything in `timeline`. It is re-derived from the live transcript, so it spans page loads only when the chat is being remembered, and a Clear resets it to zero. `timeline` is append-only regardless: it keeps every page load and the pre-Clear conversation, with the session.cleared event as the boundary. So on a long-lived log, read these totals as the latest conversation's and the earlier sections as history — not as a total of the whole file.",
|
|
223
227
|
'`timeline` is the entire session as one array, already sorted chronologically by `timestamp` (ISO 8601). Every entry has a `kind`.',
|
|
224
228
|
'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.',
|
|
225
|
-
"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, where `inputTokens` is the WHOLE prompt for that request and `cacheReadTokens`/`cacheWriteTokens` BREAK IT DOWN rather than add to it — uncached input is `inputTokens` minus those two, and adding the cache fields to `inputTokens` counts the prompt twice. The cache fields are absent on providers that report no cache split (Gemini reports reads only, since implicit caching bills no write) and on messages persisted before they existed, so read them as 0 when missing. Each bucket bills at a different rate — cache reads a fraction of uncached input, cache writes a premium, output highest — so a large token count at a small cost means the prompt was mostly cache hits. `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.",
|
|
229
|
+
"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, where `inputTokens` is the WHOLE prompt for that request and `cacheReadTokens`/`cacheWriteTokens` BREAK IT DOWN rather than add to it — uncached input is `inputTokens` minus those two, and adding the cache fields to `inputTokens` counts the prompt twice. The cache fields are absent on providers that report no cache split (Gemini reports reads only, since implicit caching bills no write) and on messages persisted before they existed, so read them as 0 when missing. Each bucket bills at a different rate — cache reads a fraction of uncached input, cache writes a premium, output highest — so a large token count at a small cost means the prompt was mostly cache hits. `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. An echo of an answer an interceptor (such as an autopilot) gave is NOT synthetic-user: see the delegated-messages paragraph.",
|
|
230
|
+
"Delegated messages (GENC-1660): a message with a `delegate` field was written or sent ON THE USER'S BEHALF by a driver decorator, and `delegate` names it (e.g. autopilot). The platform stamps it; nothing else can. It renders as the assistant whatever its role. A delegated `user` message was NOT typed by the user: it is an answer given on their behalf, and the model read it with `delegateFraming` in brackets in front of the content (the stored `content` is the bare text). A delegated `assistant` message is display-only (`excludeFromProvider`): the decorator's narration, or the echo of a widget answer it gave, which takes the place of a synthetic-user echo. To tell what the person decided from what was decided for them, read `interaction.answeredBy` and these fields, never the role alone.",
|
|
226
231
|
"Sub-agent messages appear inline. When a tool delegates to a sub-agent (via `requestSubAgent`), the sub-agent's whole conversation — its own assistant/tool messages, each with their own `content`/`thinking`/`toolCalls`/`toolResult` and per-message `model`/`providerName`/`inputTokens`/`outputTokens`/`cacheReadTokens`/`cacheWriteTokens`/`cost` — is hoisted into the timeline as ordinary kind:'message' entries, interleaved by timestamp right after the tool call that spawned them (so you read the delegation top-to-bottom). A hoisted entry is marked: `subAgentDepth` is its delegation depth (1 for a sub-agent, 2 for a sub-agent's sub-agent, …), `subAgentOf` is the id of the parent tool call that spawned it (correlates it back even when two sub-agents run in one parent turn), `subAgentName` is the sub-agent's own name, and `agentName` is rewritten to a `\"<parent> › <sub-agent>\"` breadcrumb (composing when nested, e.g. `\"UI Builder › Planner › Grounding\"`). The sub-agent's per-LLM-call snapshots also surface as kind:'turn' entries with an N-M `turnIndex`, and subagent.started/completed (or subagent.failed) events bracket the run. Per-message `cost` on hoisted entries is already part of the session total (it is summed from the un-flattened history), so summing the top-level timeline does NOT double-count.",
|
|
227
232
|
"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.",
|
|
228
233
|
"kind:'turn'.`model`/`providerName`/`provider` — which model ran that call: the concrete model id, the registry slot it resolved under (a tier name like 'high'/'low', or the default), and the vendor. Recorded per CALL, so an agent whose `provider` selector varies by state — a flow that plans on a high tier and executes on a low one — has every step attributed, including calls that produced no message. `model` is the SERVING model where the provider reports one, so a turn answered by a server-side fallback names the model that answered rather than the one requested; it always matches the `model` on the message that call produced. Absent when the provider exposes no `getStatus` and the transport stamped nothing.",
|
|
@@ -230,7 +235,7 @@ export const DEBUG_LOG_README = [
|
|
|
230
235
|
"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.",
|
|
231
236
|
"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'.",
|
|
232
237
|
"Each 'event' also has an `importance`: 'high' (failures/limits — turn.error, tool.failed, subagent.failed, file.read-failed, suggestions.failed, context.threshold-crossed), 'normal' (session flow — connects, turns, retries, handoffs, agent/provider changes, interactions, sub-agent start/complete), or 'low' (skippable UI/bookkeeping noise — panel.toggled, attachment.added, driver.wired/unwired, context.updated, context.condensed). To skim, ignore importance:'low'; to triage a failure, filter to importance:'high' then read the nearby messages and turns. A 'high' turn.error is often preceded by one or more 'normal' turn.retry events for the same reason — read them together to see how many attempts were made before bailing. 'message' and 'turn' entries carry no importance — they are the substance, always read them.",
|
|
233
|
-
'Event types: assistant.connected/disconnected (mount + placement + whether the session was created or restored), assistant.popout/popin (window placement), driver.created/wired/unwired (which driver is live and why it stops/starts responding across a popout), state.changed (idle↔loading), turn.start/turn.end (turn boundary; turn.end carries durationMs), turn.retry (a recoverable in-turn retry — detail.reason plus attempt/maxAttempts; for malformed calls also finishMessage; for empty responses also the provider finishReason + thoughtsTokens + parts breakdown), turn.error (a turn failed or hit a guardrail — detail.reason is one of exception/malformed-function-call/empty-response/unknown-tool-limit/max-iterations/response-truncated/refusal/budget-exhausted/provider-refused/context-exhausted/ai-service-error, plus reason-specific diagnostics: attempts (for empty-response also finishReason + thoughtsTokens + a parts breakdown, distinguishing a thinking-only STOP from a truly empty turn), finishMessage, for response-truncated the model + maxTokens + outputTokens + tools, unknownTools (split into staleTools — real earlier this activation but retired by the current state or hidden behind an open exclusive fold — and hallucinatedTools — never advertised) + availableTools, iterations + limit, for budget-exhausted the budgetUsd + spentUsd figures reported by the proxy plus the resolved vendor, for provider-refused the kind (spend|auth) plus the upstream status and error type — the vendor refusing the ACCOUNT rather than us refusing to spend, so the remedy is a top-up or a key rotation rather than a raised cap, and the kind is the only place that distinction survives because the user-facing copy is deliberately cause-free, for context-exhausted the estimated contextTokens against the guard that stopped the turn plus the iterations reached — the driver ending a runaway tool loop at the edge of the context window rather than issuing a request the provider would reject, so the transcript survives intact and can still be compacted, for ai-service-error the code (one of AI_SERVICE_ERROR_CODES, e.g. NO_API_KEY) and the status the AI service answered with — its own text goes to the browser console only, or name + message for exceptions), tool.failed (a tool threw), tool.unresolved (the model called a tool that could not be dispatched — detail.kind is folded/fold-hidden/stale/unknown, plus tool + agent and, for the counted kinds, the consecutive streak; the recurring lead-up to an unknown-tool-limit turn.error), subagent.started/completed/failed (the lifecycle of a `requestSubAgent` delegation — detail.agent names the sub-agent; these bracket the sub-agent turns that appear as kind:turn entries with an N-M `turnIndex`; subagent.failed also carries detail.reason, one of max_iterations/malformed_tool_call/empty_response/unknown_tool_limit/timeout/response_truncated/refusal/budget_exhausted/provider_refused/context_exhausted/ai_service_error; budget_exhausted and provider_refused are terminal for the PARENT turn too, and so is ai_service_error when the refusal is about the service rather than the request and came from the vendor the parent calls — the parent stops rather than calling the model again into the same wall; after context_exhausted the parent goes on, but its own next call is likely to stop on the same guard), agent.handoff (routing; from=null is the initial activation), agent.pinned/unpinned (forced routing), routing.decided (ONE PER ROUTING DECISION — see its own paragraph below), provider.selected (the resolved provider for the upcoming turns — detail.provider is the registry SLOT/tier name, detail.model the concrete model behind it and detail.vendor its vendor; emitted only when the slot CHANGES, so read the per-turn `model` for the model of any given call rather than assuming the nearest event still applies), interaction.requested/resolved (blocking user widgets — explain quiet gaps; note that when a sub-agent opens a widget, detail.agent — and the agentName on the interaction message — is the HOST agent that owns the widget, NOT the sub-agent that asked, because widgets render and resolve on the host driver), context.updated/threshold-crossed (token + cost), context.condensed (a stale tool payload was collapsed out of the model-bound history by a `condenseWhen` declaration on the tool — detail.tool + toolCallId, target args|response, trigger (superseded:<key> or age:<n>), stubLen, and an estimated tokensSaved; stored history and this log keep the FULL payload, so the model-visible slice at any point is the full history minus the condensations recorded up to then), panel.toggled, attachment.added, file.read-failed, suggestions.failed.',
|
|
238
|
+
'Event types: assistant.connected/disconnected (mount + placement + whether the session was created or restored), assistant.popout/popin (window placement), driver.created/wired/unwired (which driver is live and why it stops/starts responding across a popout), state.changed (idle↔loading), turn.start/turn.end (turn boundary; turn.end carries durationMs), turn.retry (a recoverable in-turn retry — detail.reason plus attempt/maxAttempts; for malformed calls also finishMessage; for empty responses also the provider finishReason + thoughtsTokens + parts breakdown), turn.error (a turn failed or hit a guardrail — detail.reason is one of exception/malformed-function-call/empty-response/unknown-tool-limit/max-iterations/response-truncated/refusal/budget-exhausted/provider-refused/context-exhausted/ai-service-error, plus reason-specific diagnostics: attempts (for empty-response also finishReason + thoughtsTokens + a parts breakdown, distinguishing a thinking-only STOP from a truly empty turn), finishMessage, for response-truncated the model + maxTokens + outputTokens + tools, unknownTools (split into staleTools — real earlier this activation but retired by the current state or hidden behind an open exclusive fold — and hallucinatedTools — never advertised) + availableTools, iterations + limit, for budget-exhausted the budgetUsd + spentUsd figures reported by the proxy plus the resolved vendor, for provider-refused the kind (spend|auth) plus the upstream status and error type — the vendor refusing the ACCOUNT rather than us refusing to spend, so the remedy is a top-up or a key rotation rather than a raised cap, and the kind is the only place that distinction survives because the user-facing copy is deliberately cause-free, for context-exhausted the estimated contextTokens against the guard that stopped the turn plus the iterations reached — the driver ending a runaway tool loop at the edge of the context window rather than issuing a request the provider would reject, so the transcript survives intact and can still be compacted, for ai-service-error the code (one of AI_SERVICE_ERROR_CODES, e.g. NO_API_KEY) and the status the AI service answered with — its own text goes to the browser console only, or name + message for exceptions), tool.failed (a tool threw), tool.unresolved (the model called a tool that could not be dispatched — detail.kind is folded/fold-hidden/stale/unknown, plus tool + agent and, for the counted kinds, the consecutive streak; the recurring lead-up to an unknown-tool-limit turn.error), subagent.started/completed/failed (the lifecycle of a `requestSubAgent` delegation — detail.agent names the sub-agent; these bracket the sub-agent turns that appear as kind:turn entries with an N-M `turnIndex`; subagent.failed also carries detail.reason, one of max_iterations/malformed_tool_call/empty_response/unknown_tool_limit/timeout/response_truncated/refusal/budget_exhausted/provider_refused/context_exhausted/ai_service_error; budget_exhausted and provider_refused are terminal for the PARENT turn too, and so is ai_service_error when the refusal is about the service rather than the request and came from the vendor the parent calls — the parent stops rather than calling the model again into the same wall; after context_exhausted the parent goes on, but its own next call is likely to stop on the same guard), agent.handoff (routing; from=null is the initial activation), agent.pinned/unpinned (forced routing), routing.decided (ONE PER ROUTING DECISION — see its own paragraph below), provider.selected (the resolved provider for the upcoming turns — detail.provider is the registry SLOT/tier name, detail.model the concrete model behind it and detail.vendor its vendor; emitted only when the slot CHANGES, so read the per-turn `model` for the model of any given call rather than assuming the nearest event still applies), interaction.requested/resolved (blocking user widgets — explain quiet gaps; note that when a sub-agent opens a widget, detail.agent — and the agentName on the interaction message — is the HOST agent that owns the widget, NOT the sub-agent that asked, because widgets render and resolve on the host driver; detail.ownerPath names the agents that actually asked, outermost first, the last being the one whose tool requested it, and detail.heldBy says who held it when it opened: user, or the id of the interceptor that claimed it, such as autopilot; on interaction.resolved, detail.answeredBy is who answered — the claimant id when it settled through its own hold, user for any other answer (a click counts as the person even on a widget a claimant held) — absent when it closed unanswered (a timeout or a stop); the same value is stamped on the interaction message as answeredBy, and when it is not user the tool result that interaction returned carries a decidedOnUsersBehalf record, so the model was told the choice was not made by the person), interaction.held (a claimant moved a pending interaction to the person or back to itself — detail.heldBy is the new holder, detail.claimant the interceptor; a widget held by anything but user was not answered by the person), interaction.claim-refused (a claim on a userOnly interaction, which stays with the person — detail.claimant + reason), context.updated/threshold-crossed (token + cost), context.condensed (a stale tool payload was collapsed out of the model-bound history by a `condenseWhen` declaration on the tool — detail.tool + toolCallId, target args|response, trigger (superseded:<key> or age:<n>), stubLen, and an estimated tokensSaved; stored history and this log keep the FULL payload, so the model-visible slice at any point is the full history minus the condensations recorded up to then), panel.toggled, attachment.added, file.read-failed, suggestions.failed.',
|
|
234
239
|
'Sub-agent meta events: a sub-agent\'s own turn.retry/turn.error/tool.failed/tool.unresolved events are merged into this same timeline, tagged with `detail.subAgent` — a `"<parent> › <sub-agent>"` breadcrumb that composes when nested (e.g. `"UI Builder › Planner › Grounding"`) — and interleaved by their original timestamps within the subagent.started→completed/failed bracket. These are the per-attempt/per-failure signals that do NOT appear among the sub-agent\'s (hoisted) messages: a malformed/empty attempt that gets retried produces no message, and the stale-vs-hallucinated split and streak counts live only on the event. A sub-agent\'s high-volume, message-derivable events (turn.start/turn.end, provider.selected, context.updated) are intentionally NOT merged — read its hoisted messages for model/tokens/cost and turn-by-turn activity, and the bracketing subagent.* events for the run\'s span.',
|
|
235
240
|
"`meta` holds context captured at export time: agentSummary (full agent configs), context (active model, contextTokens/contextLimit/contextUsagePercent for the last call, and the session totals — sessionCostUsd, sessionTokensConsumed, and the four-bucket sessionUsage lifted to the top of this log), activeDebugSnapshot (the active agent's `getDebugSnapshot()` taken fresh at export — reflects state NOW, which may have advanced beyond the last turn's agentSnapshot), debug (optional host-supplied debug state), host, and the export timestamp.",
|
|
236
241
|
'Note the two different scopes in `meta.context`: `contextTokens` is the prompt size of the LAST call (against `contextLimit`, the model context window), while `sessionUsage`/`sessionTokensConsumed` are cumulative BILLED throughput. Every turn resends the conversation, so the cumulative figure counts each turn’s prompt again in the next turn’s and is expected to dwarf the context size — that is not double-counting.',
|
|
@@ -12,8 +12,12 @@ export function createInteractionContext(interactionId) {
|
|
|
12
12
|
const listeners = new Set();
|
|
13
13
|
const disposers = new Set();
|
|
14
14
|
let disposed = false;
|
|
15
|
+
let heldBy = 'user';
|
|
15
16
|
const context = {
|
|
16
17
|
interactionId,
|
|
18
|
+
get heldBy() {
|
|
19
|
+
return heldBy;
|
|
20
|
+
},
|
|
17
21
|
state,
|
|
18
22
|
once(key, factory) {
|
|
19
23
|
if (onceCache.has(key))
|
|
@@ -78,5 +82,12 @@ export function createInteractionContext(interactionId) {
|
|
|
78
82
|
// themselves are closed via their `onDispose`, but drop the references too.
|
|
79
83
|
onceCache.clear();
|
|
80
84
|
},
|
|
85
|
+
setHeldBy(holder) {
|
|
86
|
+
if (disposed || holder === heldBy)
|
|
87
|
+
return false;
|
|
88
|
+
heldBy = holder;
|
|
89
|
+
context.notify();
|
|
90
|
+
return true;
|
|
91
|
+
},
|
|
81
92
|
};
|
|
82
93
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { __awaiter } from "tslib";
|
|
2
|
-
import { asOrchestrator } from '../../components/orchestrating-driver/orchestrating-driver';
|
|
3
2
|
import { flattenSubAgentMessages } from '../../utils/flatten-sub-agent-messages';
|
|
4
3
|
import { logger } from '../../utils/logger';
|
|
5
4
|
import { isSessionNoticeMarker, SESSION_RESTORE_FAILED_LABEL, SESSION_RESTORED_LABEL, SESSION_RESUMED_LABEL, } from '../../utils/message-partition';
|
|
@@ -699,11 +698,11 @@ export class SessionPersister {
|
|
|
699
698
|
// (flow-owned but unpinned) — `resolvePinnedAgent` reads `flowOwnerAgentName ??
|
|
700
699
|
// pinnedAgentName`, so restoring both resumes the flow without re-classifying, and the
|
|
701
700
|
// driver's routing agrees with the restored (picker-locked) store state.
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
701
|
+
//
|
|
702
|
+
// On the driver itself, not a narrowed orchestrator (GENC-1660): both members are on
|
|
703
|
+
// `AiDriver` and every decorator forwards them, so this reaches the routing whatever wraps it.
|
|
704
|
+
driver === null || driver === void 0 ? void 0 : driver.setPinnedAgent((_g = snapshot.pinnedAgentName) !== null && _g !== void 0 ? _g : null);
|
|
705
|
+
driver === null || driver === void 0 ? void 0 : driver.setFlowOwner((_h = snapshot.flowOwnerAgentName) !== null && _h !== void 0 ? _h : null);
|
|
707
706
|
if (flowOwner) {
|
|
708
707
|
this.logMeta('persistence.resume-planned', {
|
|
709
708
|
flowOwner,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { __awaiter } from "tslib";
|
|
2
2
|
import { reachesProvider } from '../utils/context-tokens';
|
|
3
|
+
import { frameDelegatedContent } from '../utils/delegated-message';
|
|
3
4
|
import { logger } from '../utils/logger';
|
|
4
5
|
/** Turns of transcript given to the model when suggesting follow-ups. */
|
|
5
6
|
const HISTORY_WINDOW = 8;
|
|
@@ -75,7 +76,13 @@ export function buildSuggestionsPrompt(ctx, history, prompt, count, allAgentInfo
|
|
|
75
76
|
!!((_b = m.content) === null || _b === void 0 ? void 0 : _b.trim());
|
|
76
77
|
})
|
|
77
78
|
.slice(-HISTORY_WINDOW)
|
|
78
|
-
|
|
79
|
+
// A delegated user message is read framed, as every other model call reads it (GENC-1660).
|
|
80
|
+
.map((m) => {
|
|
81
|
+
var _a;
|
|
82
|
+
return m.role === 'user'
|
|
83
|
+
? `User: ${m.delegate ? frameDelegatedContent((_a = m.content) !== null && _a !== void 0 ? _a : '', m.delegateFraming) : m.content}`
|
|
84
|
+
: `Assistant: ${m.content}`;
|
|
85
|
+
})
|
|
79
86
|
.join('\n');
|
|
80
87
|
return (`<conversation_history>\n${conversationContext}\n</conversation_history>\n\n` +
|
|
81
88
|
`The conversation above has ended. Do not continue it or respond as the assistant. ` +
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether `message` was written or sent on the user's behalf. A delegated message renders as the
|
|
3
|
+
* assistant, whatever its role.
|
|
4
|
+
*
|
|
5
|
+
* @beta
|
|
6
|
+
*/
|
|
7
|
+
export const isDelegated = (message) => !!message.delegate;
|
|
8
|
+
/**
|
|
9
|
+
* How a delegated `user` message is framed for the model when its sender gives no phrase. Names no
|
|
10
|
+
* decorator, so the model has none to repeat.
|
|
11
|
+
*
|
|
12
|
+
* @beta
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_DELEGATE_FRAMING = "Sent on the user's behalf.";
|
|
15
|
+
/**
|
|
16
|
+
* A delegated `user` message's content as the model reads it: its framing in brackets, then the
|
|
17
|
+
* text. The one place this string is built, so the first model call (which takes the message as
|
|
18
|
+
* its input) and every later call (which reads it from history) send the same bytes and the cache
|
|
19
|
+
* prefix holds.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export function frameDelegatedContent(content, framing) {
|
|
24
|
+
return `(${framing !== null && framing !== void 0 ? framing : DEFAULT_DELEGATE_FRAMING})\n\n${content}`;
|
|
25
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { __rest } from "tslib";
|
|
2
2
|
import { COMPACTION_SUMMARY_TOKEN_ESTIMATE, estimateMessageTokens, projectCompaction, reachesProvider, } from './context-tokens';
|
|
3
|
+
import { frameDelegatedContent } from './delegated-message';
|
|
3
4
|
/**
|
|
4
5
|
* Masks the tool-specific payload of a single message — clears tool call args
|
|
5
6
|
* and replaces tool result content with a placeholder. Used when passing history
|
|
@@ -204,7 +205,11 @@ function renderMessageForSummary(m) {
|
|
|
204
205
|
case 'compacted-summary':
|
|
205
206
|
return `[Earlier summary] ${m.content}`;
|
|
206
207
|
case 'user':
|
|
207
|
-
|
|
208
|
+
// A delegated message keeps its framing here too, or the summary would put words in the
|
|
209
|
+
// user's mouth that outlive the message itself (GENC-1660).
|
|
210
|
+
if (!m.content)
|
|
211
|
+
return '';
|
|
212
|
+
return `User: ${m.delegate ? frameDelegatedContent(m.content, m.delegateFraming) : m.content}`;
|
|
208
213
|
case 'assistant': {
|
|
209
214
|
// Reasoning ("Thinking") and narration ("Working") are display-only assistant splits
|
|
210
215
|
// (GENC-1410) with no tool calls of their own — fold them into the summary too, labelled
|
|
@@ -265,6 +270,12 @@ export function buildCompactionSummaryPrompt(toCompact) {
|
|
|
265
270
|
*/
|
|
266
271
|
export function normalizeForProvider(history) {
|
|
267
272
|
return history.map((m) => {
|
|
273
|
+
var _a;
|
|
274
|
+
// A user message sent on the user's behalf (GENC-1660) is framed, so the model never reads
|
|
275
|
+
// it as something the person typed.
|
|
276
|
+
if (m.role === 'user' && m.delegate) {
|
|
277
|
+
return Object.assign(Object.assign({}, m), { content: frameDelegatedContent((_a = m.content) !== null && _a !== void 0 ? _a : '', m.delegateFraming) });
|
|
278
|
+
}
|
|
268
279
|
if (m.role !== 'compacted-summary')
|
|
269
280
|
return m;
|
|
270
281
|
return Object.assign(Object.assign({}, m), { role: 'user', content: `<conversation_summary>\n${m.content}\n</conversation_summary>\n\n` +
|
package/dist/react.cjs
CHANGED
|
@@ -13,6 +13,7 @@ const { AiChatInteractionWrapper: AiChatInteractionWrapperWC } = require('./esm/
|
|
|
13
13
|
const { AiChatMarkdown: AiChatMarkdownWC } = require('./esm/components/chat-markdown/chat-markdown.js');
|
|
14
14
|
const { AiFlowingWavesIndicator: AiFlowingWavesIndicatorWC } = require('./esm/components/flowing-waves-indicator.js');
|
|
15
15
|
const { AiHaloOverlay: AiHaloOverlayWC } = require('./esm/components/halo-overlay.js');
|
|
16
|
+
const { MessageFeedback: MessageFeedbackWC } = require('./esm/components/message-feedback/message-feedback.js');
|
|
16
17
|
const { AiPlasmaOrbIndicator: AiPlasmaOrbIndicatorWC } = require('./esm/components/plasma-orb-indicator.js');
|
|
17
18
|
const { FoundationAiPopoutManager: FoundationAiPopoutManagerWC } = require('./esm/components/popout-manager/popout-manager.js');
|
|
18
19
|
const { AiWavesIndicator: AiWavesIndicatorWC } = require('./esm/components/waves-indicator.js');
|
|
@@ -140,6 +141,11 @@ const AiChatMarkdown = React.forwardRef(function AiChatMarkdown(props, ref) {
|
|
|
140
141
|
return React.createElement(customElements.getName(AiChatMarkdownWC) ?? 'ai-chat-markdown', { ...rest, ref }, children);
|
|
141
142
|
});
|
|
142
143
|
|
|
144
|
+
const MessageFeedback = React.forwardRef(function MessageFeedback(props, ref) {
|
|
145
|
+
const { children, ...rest } = props;
|
|
146
|
+
return React.createElement(customElements.getName(MessageFeedbackWC) ?? 'foundation-ai-message-feedback', { ...rest, ref }, children);
|
|
147
|
+
});
|
|
148
|
+
|
|
143
149
|
const FoundationAiPopoutManager = React.forwardRef(function FoundationAiPopoutManager(props, ref) {
|
|
144
150
|
const { children, ...rest } = props;
|
|
145
151
|
return React.createElement(customElements.getName(FoundationAiPopoutManagerWC) ?? 'foundation-ai-popout-manager', { ...rest, ref }, children);
|
|
@@ -174,6 +180,7 @@ module.exports = {
|
|
|
174
180
|
AiChatBubble,
|
|
175
181
|
AiChatInteractionWrapper,
|
|
176
182
|
AiChatMarkdown,
|
|
183
|
+
MessageFeedback,
|
|
177
184
|
FoundationAiPopoutManager,
|
|
178
185
|
GenesisWriteReview,
|
|
179
186
|
};
|
package/dist/react.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import { AiChatInteractionWrapper as AiChatInteractionWrapperWC } from './esm/co
|
|
|
11
11
|
import { AiChatMarkdown as AiChatMarkdownWC } from './esm/components/chat-markdown/chat-markdown.js';
|
|
12
12
|
import { AiFlowingWavesIndicator as AiFlowingWavesIndicatorWC } from './esm/components/flowing-waves-indicator.js';
|
|
13
13
|
import { AiHaloOverlay as AiHaloOverlayWC } from './esm/components/halo-overlay.js';
|
|
14
|
+
import { MessageFeedback as MessageFeedbackWC } from './esm/components/message-feedback/message-feedback.js';
|
|
14
15
|
import { AiPlasmaOrbIndicator as AiPlasmaOrbIndicatorWC } from './esm/components/plasma-orb-indicator.js';
|
|
15
16
|
import { FoundationAiPopoutManager as FoundationAiPopoutManagerWC } from './esm/components/popout-manager/popout-manager.js';
|
|
16
17
|
import { AiWavesIndicator as AiWavesIndicatorWC } from './esm/components/waves-indicator.js';
|
|
@@ -138,6 +139,11 @@ export const AiChatMarkdown = React.forwardRef(function AiChatMarkdown(props, re
|
|
|
138
139
|
return React.createElement(customElements.getName(AiChatMarkdownWC) ?? 'ai-chat-markdown', { ...rest, ref }, children);
|
|
139
140
|
});
|
|
140
141
|
|
|
142
|
+
export const MessageFeedback = React.forwardRef(function MessageFeedback(props, ref) {
|
|
143
|
+
const { children, ...rest } = props;
|
|
144
|
+
return React.createElement(customElements.getName(MessageFeedbackWC) ?? 'foundation-ai-message-feedback', { ...rest, ref }, children);
|
|
145
|
+
});
|
|
146
|
+
|
|
141
147
|
export const FoundationAiPopoutManager = React.forwardRef(function FoundationAiPopoutManager(props, ref) {
|
|
142
148
|
const { children, ...rest } = props;
|
|
143
149
|
return React.createElement(customElements.getName(FoundationAiPopoutManagerWC) ?? 'foundation-ai-popout-manager', { ...rest, ref }, children);
|