@genesislcap/ai-assistant 14.450.0 → 14.451.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 +85 -15
- package/dist/ai-assistant.d.ts +94 -3
- package/dist/dts/components/chat-driver/chat-driver.d.ts +7 -1
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +48 -2
- package/dist/dts/main/main.d.ts.map +1 -1
- package/dist/dts/state/debug-event-log.d.ts +82 -0
- package/dist/dts/state/debug-event-log.d.ts.map +1 -0
- package/dist/esm/components/chat-driver/chat-driver.js +83 -4
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +19 -3
- package/dist/esm/main/main.js +149 -14
- package/dist/esm/state/debug-event-log.js +118 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -16
- package/src/components/chat-driver/chat-driver.ts +84 -8
- package/src/components/orchestrating-driver/orchestrating-driver.ts +19 -2
- package/src/main/main.ts +152 -6
- package/src/state/debug-event-log.ts +194 -0
|
@@ -2,6 +2,7 @@ import { __awaiter } from "tslib";
|
|
|
2
2
|
import { MalformedFunctionCallError } from '@genesislcap/foundation-ai';
|
|
3
3
|
import { agenticActivityBus } from '../../channel/ai-activity-bus';
|
|
4
4
|
import { resolveChatProvider } from '../../config/validate-providers';
|
|
5
|
+
import { recordMetaEvent } from '../../state/debug-event-log';
|
|
5
6
|
import { applyHistoryCap } from '../../utils/history-transform';
|
|
6
7
|
import { logger } from '../../utils/logger';
|
|
7
8
|
import { TOOL_FOLD_SYMBOL } from '../../utils/tool-fold';
|
|
@@ -27,12 +28,17 @@ const HANDOFF_TOOL_RESULT_PLACEHOLDER = 'Handoff to another specialist — routi
|
|
|
27
28
|
* @beta
|
|
28
29
|
*/
|
|
29
30
|
export class ChatDriver extends EventTarget {
|
|
30
|
-
constructor(providerRegistry, toolHandlers = {}, toolDefinitions = [], systemPrompt, primerHistory, maxToolIterations = DEFAULT_MAX_TOOL_ITERATIONS, maxFoldOperations = DEFAULT_MAX_FOLD_OPERATIONS, maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS
|
|
31
|
+
constructor(providerRegistry, toolHandlers = {}, toolDefinitions = [], systemPrompt, primerHistory, maxToolIterations = DEFAULT_MAX_TOOL_ITERATIONS, maxFoldOperations = DEFAULT_MAX_FOLD_OPERATIONS, maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS,
|
|
32
|
+
/** Session identity used to file meta events onto the shared debug-log timeline. */
|
|
33
|
+
sessionKey = '') {
|
|
31
34
|
super();
|
|
32
35
|
this.providerRegistry = providerRegistry;
|
|
33
36
|
this.maxToolIterations = maxToolIterations;
|
|
37
|
+
this.sessionKey = sessionKey;
|
|
34
38
|
this.history = [];
|
|
35
39
|
this.busy = false;
|
|
40
|
+
/** Epoch ms when the current turn loop began — drives the `turn.end` duration. */
|
|
41
|
+
this.turnStartedAt = 0;
|
|
36
42
|
this.pendingInteractions = new Map();
|
|
37
43
|
/** Stack of fold frames — grows when a fold opens, shrinks when it closes. */
|
|
38
44
|
this.foldStack = [];
|
|
@@ -184,6 +190,10 @@ export class ChatDriver extends EventTarget {
|
|
|
184
190
|
this.lastResolvedProviderName = resolvedName;
|
|
185
191
|
if (resolvedName !== this.lastDispatchedProviderName) {
|
|
186
192
|
this.lastDispatchedProviderName = resolvedName;
|
|
193
|
+
recordMetaEvent(this.sessionKey, 'provider.selected', {
|
|
194
|
+
provider: resolvedName,
|
|
195
|
+
agent: this.activeAgentName,
|
|
196
|
+
});
|
|
187
197
|
this.dispatchEvent(new CustomEvent('provider-changed', { detail: { name: resolvedName } }));
|
|
188
198
|
}
|
|
189
199
|
return provider;
|
|
@@ -412,6 +422,11 @@ export class ChatDriver extends EventTarget {
|
|
|
412
422
|
reject,
|
|
413
423
|
overrideId: chatInputDuringExecution ? interactionId : undefined,
|
|
414
424
|
});
|
|
425
|
+
recordMetaEvent(this.sessionKey, 'interaction.requested', {
|
|
426
|
+
interactionId,
|
|
427
|
+
component: componentName,
|
|
428
|
+
agent: this.activeAgentName,
|
|
429
|
+
});
|
|
415
430
|
if (chatInputDuringExecution) {
|
|
416
431
|
this.dispatchEvent(new CustomEvent('interaction-start', {
|
|
417
432
|
detail: { interactionId, chatInputDuringExecution },
|
|
@@ -442,6 +457,7 @@ export class ChatDriver extends EventTarget {
|
|
|
442
457
|
if (interaction.overrideId) {
|
|
443
458
|
this.dispatchEvent(new CustomEvent('interaction-stop', { detail: { interactionId } }));
|
|
444
459
|
}
|
|
460
|
+
recordMetaEvent(this.sessionKey, 'interaction.resolved', { interactionId });
|
|
445
461
|
interaction.resolve(result);
|
|
446
462
|
this.pendingInteractions.delete(interactionId);
|
|
447
463
|
}
|
|
@@ -467,16 +483,32 @@ export class ChatDriver extends EventTarget {
|
|
|
467
483
|
this.subAgentCompletion = undefined;
|
|
468
484
|
this.agentReleaseRequested = false;
|
|
469
485
|
this.appendToHistory({ role: 'user', content: userInput, attachments });
|
|
486
|
+
this.turnStartedAt = Date.now();
|
|
487
|
+
recordMetaEvent(this.sessionKey, 'turn.start', {
|
|
488
|
+
phase: 'sendMessage',
|
|
489
|
+
agent: this.activeAgentName,
|
|
490
|
+
});
|
|
470
491
|
agenticActivityBus.publish('tool-loop-start', undefined);
|
|
471
492
|
try {
|
|
472
493
|
return yield this.runToolLoop(userInput, attachments);
|
|
473
494
|
}
|
|
474
495
|
catch (e) {
|
|
475
496
|
logger.error('ChatDriver error:', e);
|
|
497
|
+
recordMetaEvent(this.sessionKey, 'turn.error', {
|
|
498
|
+
phase: 'sendMessage',
|
|
499
|
+
agent: this.activeAgentName,
|
|
500
|
+
provider: this.lastResolvedProviderName,
|
|
501
|
+
message: e instanceof Error ? e.message : String(e),
|
|
502
|
+
});
|
|
476
503
|
this.appendToHistory({ role: 'assistant', content: 'Sorry, something went wrong.' });
|
|
477
504
|
return { reason: 'done' };
|
|
478
505
|
}
|
|
479
506
|
finally {
|
|
507
|
+
recordMetaEvent(this.sessionKey, 'turn.end', {
|
|
508
|
+
phase: 'sendMessage',
|
|
509
|
+
agent: this.activeAgentName,
|
|
510
|
+
durationMs: Date.now() - this.turnStartedAt,
|
|
511
|
+
});
|
|
480
512
|
this.busy = false;
|
|
481
513
|
agenticActivityBus.publish('tool-loop-end', undefined);
|
|
482
514
|
}
|
|
@@ -601,16 +633,32 @@ export class ChatDriver extends EventTarget {
|
|
|
601
633
|
return { reason: 'done' };
|
|
602
634
|
this.busy = true;
|
|
603
635
|
this.subAgentCompletion = undefined;
|
|
636
|
+
this.turnStartedAt = Date.now();
|
|
637
|
+
recordMetaEvent(this.sessionKey, 'turn.start', {
|
|
638
|
+
phase: 'continueFromHistory',
|
|
639
|
+
agent: this.activeAgentName,
|
|
640
|
+
});
|
|
604
641
|
agenticActivityBus.publish('tool-loop-start', undefined);
|
|
605
642
|
try {
|
|
606
643
|
return yield this.runToolLoop('', undefined, transientPrimer);
|
|
607
644
|
}
|
|
608
645
|
catch (e) {
|
|
609
646
|
logger.error('ChatDriver error:', e);
|
|
647
|
+
recordMetaEvent(this.sessionKey, 'turn.error', {
|
|
648
|
+
phase: 'continueFromHistory',
|
|
649
|
+
agent: this.activeAgentName,
|
|
650
|
+
provider: this.lastResolvedProviderName,
|
|
651
|
+
message: e instanceof Error ? e.message : String(e),
|
|
652
|
+
});
|
|
610
653
|
this.appendToHistory({ role: 'assistant', content: 'Sorry, something went wrong.' });
|
|
611
654
|
return { reason: 'done' };
|
|
612
655
|
}
|
|
613
656
|
finally {
|
|
657
|
+
recordMetaEvent(this.sessionKey, 'turn.end', {
|
|
658
|
+
phase: 'continueFromHistory',
|
|
659
|
+
agent: this.activeAgentName,
|
|
660
|
+
durationMs: Date.now() - this.turnStartedAt,
|
|
661
|
+
});
|
|
614
662
|
this.busy = false;
|
|
615
663
|
agenticActivityBus.publish('tool-loop-end', undefined);
|
|
616
664
|
}
|
|
@@ -854,6 +902,11 @@ export class ChatDriver extends EventTarget {
|
|
|
854
902
|
continue;
|
|
855
903
|
}
|
|
856
904
|
logger.error('ChatDriver: MALFORMED_FUNCTION_CALL, max retries reached');
|
|
905
|
+
recordMetaEvent(this.sessionKey, 'turn.error', {
|
|
906
|
+
reason: 'malformed-function-call',
|
|
907
|
+
agent: this.activeAgentName,
|
|
908
|
+
provider: this.lastResolvedProviderName,
|
|
909
|
+
});
|
|
857
910
|
this.appendToHistory({
|
|
858
911
|
role: 'assistant',
|
|
859
912
|
content: "I'm sorry, I wasn't able to complete that request. Please try rephrasing or breaking it into smaller steps.",
|
|
@@ -872,6 +925,11 @@ export class ChatDriver extends EventTarget {
|
|
|
872
925
|
continue;
|
|
873
926
|
}
|
|
874
927
|
logger.error('ChatDriver: empty model response after all retries');
|
|
928
|
+
recordMetaEvent(this.sessionKey, 'turn.error', {
|
|
929
|
+
reason: 'empty-response',
|
|
930
|
+
agent: this.activeAgentName,
|
|
931
|
+
provider: this.lastResolvedProviderName,
|
|
932
|
+
});
|
|
875
933
|
this.appendToHistory({
|
|
876
934
|
role: 'assistant',
|
|
877
935
|
content: 'Remote agent returned no response.',
|
|
@@ -976,6 +1034,11 @@ export class ChatDriver extends EventTarget {
|
|
|
976
1034
|
}
|
|
977
1035
|
catch (e) {
|
|
978
1036
|
logger.error(`ChatDriver tool "${tc.name}" failed:`, e);
|
|
1037
|
+
recordMetaEvent(this.sessionKey, 'tool.failed', {
|
|
1038
|
+
tool: tc.name,
|
|
1039
|
+
agent: this.activeAgentName,
|
|
1040
|
+
message: e instanceof Error ? e.message : String(e),
|
|
1041
|
+
});
|
|
979
1042
|
executedById.set(tc.id, {
|
|
980
1043
|
toolCallId: tc.id,
|
|
981
1044
|
content: `Tool error: ${e.message}`,
|
|
@@ -1042,6 +1105,11 @@ export class ChatDriver extends EventTarget {
|
|
|
1042
1105
|
}
|
|
1043
1106
|
if (hitUnknownToolLimit) {
|
|
1044
1107
|
logger.error(`ChatDriver: unknown-tool limit (${DEFAULT_MAX_UNKNOWN_TOOL_CALLS}) reached — stopping`);
|
|
1108
|
+
recordMetaEvent(this.sessionKey, 'turn.error', {
|
|
1109
|
+
reason: 'unknown-tool-limit',
|
|
1110
|
+
agent: this.activeAgentName,
|
|
1111
|
+
provider: this.lastResolvedProviderName,
|
|
1112
|
+
});
|
|
1045
1113
|
this.appendToHistory({
|
|
1046
1114
|
role: 'assistant',
|
|
1047
1115
|
content: "I'm sorry, I repeatedly tried to use tools that don't exist. Please check your agent configuration or try rephrasing your request.",
|
|
@@ -1061,6 +1129,11 @@ export class ChatDriver extends EventTarget {
|
|
|
1061
1129
|
}
|
|
1062
1130
|
if (iterations >= this.maxToolIterations) {
|
|
1063
1131
|
logger.warn('ChatDriver: reached max tool iterations, stopping');
|
|
1132
|
+
recordMetaEvent(this.sessionKey, 'turn.error', {
|
|
1133
|
+
reason: 'max-iterations',
|
|
1134
|
+
agent: this.activeAgentName,
|
|
1135
|
+
provider: this.lastResolvedProviderName,
|
|
1136
|
+
});
|
|
1064
1137
|
this.appendToHistory({
|
|
1065
1138
|
role: 'assistant',
|
|
1066
1139
|
content: "I've reached my limit for this response. You can ask me to continue and I'll pick up where I left off.",
|
|
@@ -1070,10 +1143,16 @@ export class ChatDriver extends EventTarget {
|
|
|
1070
1143
|
});
|
|
1071
1144
|
}
|
|
1072
1145
|
appendToHistory(message) {
|
|
1073
|
-
|
|
1074
|
-
|
|
1146
|
+
var _a;
|
|
1147
|
+
const tagged = Object.assign(Object.assign(Object.assign({}, message), {
|
|
1148
|
+
// Stamp on first append; preserve any caller-supplied timestamp.
|
|
1149
|
+
timestamp: (_a = message.timestamp) !== null && _a !== void 0 ? _a : new Date().toISOString() }), (this.activeAgentName
|
|
1150
|
+
? {
|
|
1151
|
+
agentName: this.activeAgentName,
|
|
1075
1152
|
// Display-only — falls back to agentName in renderers when unset.
|
|
1076
|
-
agentLabel: this.activeAgentLabel
|
|
1153
|
+
agentLabel: this.activeAgentLabel,
|
|
1154
|
+
}
|
|
1155
|
+
: {}));
|
|
1077
1156
|
this.history = [...this.history, tagged];
|
|
1078
1157
|
this.dispatchEvent(new CustomEvent('history-updated', {
|
|
1079
1158
|
detail: this.history,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { __awaiter } from "tslib";
|
|
2
2
|
import { validateStaticAgentProviders } from '../../config/validate-providers';
|
|
3
|
+
import { recordMetaEvent } from '../../state/debug-event-log';
|
|
3
4
|
import { transformHistoryForAgent } from '../../utils/history-transform';
|
|
4
5
|
import { logger } from '../../utils/logger';
|
|
5
6
|
import { ChatDriver, REQUEST_CONTINUATION_TOOL, } from '../chat-driver/chat-driver';
|
|
@@ -95,7 +96,7 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
95
96
|
const rawFallback = fallbacks[0];
|
|
96
97
|
this.fallback = rawFallback
|
|
97
98
|
? Object.assign(Object.assign({}, rawFallback), { systemPrompt: buildFallbackSystemPrompt(rawFallback, this.specialists) }) : undefined;
|
|
98
|
-
this.chatDriver = new ChatDriver(providerRegistry, {}, [], undefined, undefined, options.maxToolIterations, options.maxFoldOperations, options.maxTurnSnapshots);
|
|
99
|
+
this.chatDriver = new ChatDriver(providerRegistry, {}, [], undefined, undefined, options.maxToolIterations, options.maxFoldOperations, options.maxTurnSnapshots, this.sessionKey);
|
|
99
100
|
// Proxy events from the shared driver
|
|
100
101
|
this.chatDriver.addEventListener('history-updated', (e) => {
|
|
101
102
|
this.dispatchEvent(new CustomEvent('history-updated', { detail: e.detail }));
|
|
@@ -242,8 +243,17 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
242
243
|
}
|
|
243
244
|
applyAgent(agent) {
|
|
244
245
|
return __awaiter(this, void 0, void 0, function* () {
|
|
246
|
+
var _a;
|
|
245
247
|
const previousAgent = this.activeAgent;
|
|
246
248
|
const isSwitch = !previousAgent || previousAgent.name !== agent.name;
|
|
249
|
+
// Record the agent transition on the debug-log timeline. A `null` `from`
|
|
250
|
+
// marks the initial activation; a named `from` is a handoff between agents.
|
|
251
|
+
if (isSwitch) {
|
|
252
|
+
recordMetaEvent(this.sessionKey, 'agent.handoff', {
|
|
253
|
+
from: (_a = previousAgent === null || previousAgent === void 0 ? void 0 : previousAgent.name) !== null && _a !== void 0 ? _a : null,
|
|
254
|
+
to: agent.name,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
247
257
|
// Fire lifecycle hooks around the swap — outgoing first, then incoming.
|
|
248
258
|
// Both are awaited so a heavy `onActivate` (e.g. machine restore) completes
|
|
249
259
|
// before the agent's first turn runs.
|
|
@@ -311,7 +321,10 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
311
321
|
}
|
|
312
322
|
if (previousAgent && previousAgent.name !== agent.name) {
|
|
313
323
|
const rawHistory = this.chatDriver.getHistory();
|
|
314
|
-
this.chatDriver.loadHistory([
|
|
324
|
+
this.chatDriver.loadHistory([
|
|
325
|
+
...rawHistory,
|
|
326
|
+
{ role: 'system-event', content: agent.name, timestamp: new Date().toISOString() },
|
|
327
|
+
]);
|
|
315
328
|
}
|
|
316
329
|
this.chatDriver.setProviderHistoryTransform((h) => transformHistoryForAgent(h, agent.name));
|
|
317
330
|
this.chatDriver.applyAgent(agentToApply);
|
|
@@ -493,7 +506,10 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
493
506
|
}
|
|
494
507
|
appendInlineMessage(content) {
|
|
495
508
|
const history = this.chatDriver.getHistory();
|
|
496
|
-
this.chatDriver.loadHistory([
|
|
509
|
+
this.chatDriver.loadHistory([
|
|
510
|
+
...history,
|
|
511
|
+
{ role: 'assistant', content, timestamp: new Date().toISOString() },
|
|
512
|
+
]);
|
|
497
513
|
this.dispatchEvent(new CustomEvent('history-updated', { detail: this.chatDriver.getHistory() }));
|
|
498
514
|
}
|
|
499
515
|
}
|
package/dist/esm/main/main.js
CHANGED
|
@@ -34,7 +34,8 @@ import { AiChatInteractionWrapper } from '../components/chat-interaction-wrapper
|
|
|
34
34
|
import { AiChatMarkdown } from '../components/chat-markdown/chat-markdown';
|
|
35
35
|
import { AiHaloOverlay } from '../components/halo-overlay';
|
|
36
36
|
import { OrchestratingDriver } from '../components/orchestrating-driver/orchestrating-driver';
|
|
37
|
-
import {
|
|
37
|
+
import { recordMetaEvent, getMetaEvents, DEBUG_LOG_README, } from '../state/debug-event-log';
|
|
38
|
+
import { getOrCreateDriver, getDriver, deleteDriver } from '../state/driver-registry';
|
|
38
39
|
import { getSessionStore, hasSessionStore } from '../state/session-store';
|
|
39
40
|
import { AI_COLOUR_AMBER, AI_COLOUR_CYAN, AI_COLOUR_PINK, AI_COLOUR_VIOLET, } from '../styles/ai-colours';
|
|
40
41
|
import { ChatSuggestions } from '../suggestions/chat-suggestions';
|
|
@@ -130,6 +131,8 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
130
131
|
};
|
|
131
132
|
/** True when the user has intentionally scrolled away from the bottom — suppresses auto-scroll. */
|
|
132
133
|
this._userScrolledAway = false;
|
|
134
|
+
/** Whether the one-shot ≥80% `context.threshold-crossed` event has fired this session. */
|
|
135
|
+
this._contextThresholdCrossed = false;
|
|
133
136
|
/**
|
|
134
137
|
* Interaction widgets that grow mid-lifecycle (e.g. expanding a "More info"
|
|
135
138
|
* panel, appending an SSE status row, revealing generated code) bubble a
|
|
@@ -190,6 +193,11 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
190
193
|
var _a, _b;
|
|
191
194
|
const prev = (_a = this._sessionRef) === null || _a === void 0 ? void 0 : _a.store.aiAssistant.state;
|
|
192
195
|
(_b = this._sessionRef) === null || _b === void 0 ? void 0 : _b.actions.aiAssistant.setState(value);
|
|
196
|
+
// Only record a real transition against a live session — a missing
|
|
197
|
+
// _sessionRef means setState no-op'd, so no transition actually happened.
|
|
198
|
+
if (this._sessionRef && prev !== value) {
|
|
199
|
+
this.logMeta('state.changed', { from: prev, to: value });
|
|
200
|
+
}
|
|
193
201
|
this.syncShowHalo();
|
|
194
202
|
// When the agent finishes (loading → !loading) the input row reappears (or
|
|
195
203
|
// becomes enabled) — refocus it so the user can type immediately, but only
|
|
@@ -327,6 +335,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
327
335
|
// Suggestions are scoped to the active routing — resetting forces a fresh
|
|
328
336
|
// fetch when the user pins/unpins so the prompts match the new agent.
|
|
329
337
|
if (previous !== value) {
|
|
338
|
+
this.logMeta(value ? 'agent.pinned' : 'agent.unpinned', { agent: value !== null && value !== void 0 ? value : previous });
|
|
330
339
|
this.suggestionsState = { status: 'idle' };
|
|
331
340
|
this.fetchSuggestions();
|
|
332
341
|
}
|
|
@@ -576,6 +585,10 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
576
585
|
if (history.length)
|
|
577
586
|
this.driver.loadHistory([...history]);
|
|
578
587
|
this._driverAgentsKey = newKey;
|
|
588
|
+
this.logMeta('driver.created', {
|
|
589
|
+
reason: 'agents-changed',
|
|
590
|
+
driverKind: this.driver instanceof OrchestratingDriver ? 'orchestrating' : 'chat',
|
|
591
|
+
});
|
|
579
592
|
}
|
|
580
593
|
/** Returns a stable fingerprint for an agents array based on agent names and tool handler keys. */
|
|
581
594
|
getAgentsKey(agents) {
|
|
@@ -631,7 +644,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
631
644
|
}
|
|
632
645
|
}
|
|
633
646
|
createDriver() {
|
|
634
|
-
var _a, _b;
|
|
647
|
+
var _a, _b, _c;
|
|
635
648
|
this.warnUnreachableStatefulAgents();
|
|
636
649
|
const agent = (_a = this.chatConfig.agent) !== null && _a !== void 0 ? _a : {};
|
|
637
650
|
const { agents } = this;
|
|
@@ -659,7 +672,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
659
672
|
maxTurnSnapshots: agent.maxTurnSnapshots,
|
|
660
673
|
});
|
|
661
674
|
}
|
|
662
|
-
return new ChatDriver(this.providerRegistry, {}, [], undefined, undefined, agent.maxToolIterations, agent.maxFoldOperations, agent.maxTurnSnapshots);
|
|
675
|
+
return new ChatDriver(this.providerRegistry, {}, [], undefined, undefined, agent.maxToolIterations, agent.maxFoldOperations, agent.maxTurnSnapshots, (_c = this.getStateKey()) !== null && _c !== void 0 ? _c : '');
|
|
663
676
|
}
|
|
664
677
|
/**
|
|
665
678
|
* Attaches event listeners to the current driver. Stores a cleanup function
|
|
@@ -811,7 +824,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
811
824
|
this.driverCleanup = undefined;
|
|
812
825
|
}
|
|
813
826
|
connectedCallback() {
|
|
814
|
-
var _a, _b, _c, _d, _e, _f, _j;
|
|
827
|
+
var _a, _b, _c, _d, _e, _f, _j, _k, _l;
|
|
815
828
|
// Initialise the store reference BEFORE super.connectedCallback() so that
|
|
816
829
|
// the first FAST render has access to the store. The store Proxy calls
|
|
817
830
|
// Observable.track(observableStore, sliceName) whenever a slice is read,
|
|
@@ -835,6 +848,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
835
848
|
this.pinnedAgentName = defaultAgent;
|
|
836
849
|
}
|
|
837
850
|
}
|
|
851
|
+
const driverExisted = !!getDriver(key);
|
|
838
852
|
this.driver = getOrCreateDriver(key, () => this.createDriver());
|
|
839
853
|
this._driverAgentsKey = this.getAgentsKey(this.agents);
|
|
840
854
|
this.wireDriver();
|
|
@@ -843,9 +857,11 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
843
857
|
// panel, a collapse-mode element connects and wires to the same shared
|
|
844
858
|
// driver. Unwire this (hidden) instance on popout; re-wire on popin.
|
|
845
859
|
const unsubPopout = agenticActivityBus.subscribe('chat-popout', () => {
|
|
860
|
+
this.logMeta('driver.unwired', { reason: 'popout' });
|
|
846
861
|
this.unwireDriver();
|
|
847
862
|
});
|
|
848
863
|
const unsubPopin = agenticActivityBus.subscribe('chat-popin', () => {
|
|
864
|
+
this.logMeta('driver.wired', { reason: 'popin' });
|
|
849
865
|
this.wireDriver();
|
|
850
866
|
});
|
|
851
867
|
this.unsubBus = () => {
|
|
@@ -896,9 +912,16 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
896
912
|
if (this.state !== 'loading')
|
|
897
913
|
this.maybeAutoFocusChatInput();
|
|
898
914
|
logger.debug('FoundationAiAssistant connected');
|
|
915
|
+
this.logMeta('assistant.connected', {
|
|
916
|
+
store: isNewStore ? 'created' : 'restored',
|
|
917
|
+
restoredMessages: this.messages.length,
|
|
918
|
+
driver: driverExisted ? 'reused' : 'created',
|
|
919
|
+
driverKind: this.driver instanceof OrchestratingDriver ? 'orchestrating' : 'chat',
|
|
920
|
+
driverBusy: (_l = (_k = this.driver) === null || _k === void 0 ? void 0 : _k.isBusy()) !== null && _l !== void 0 ? _l : false,
|
|
921
|
+
});
|
|
899
922
|
}
|
|
900
923
|
disconnectedCallback() {
|
|
901
|
-
var _a, _b;
|
|
924
|
+
var _a, _b, _c, _d;
|
|
902
925
|
super.disconnectedCallback();
|
|
903
926
|
this.stopLoadingTimer();
|
|
904
927
|
this.state = 'idle';
|
|
@@ -920,6 +943,8 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
920
943
|
// the panel re-mounts open.
|
|
921
944
|
this._agentPickerToggle.finalize();
|
|
922
945
|
this._settingsToggle.finalize();
|
|
946
|
+
// Capture before clearing — `wasBusy` reads the driver, which is dropped below.
|
|
947
|
+
this.logMeta('assistant.disconnected', { wasBusy: (_d = (_c = this.driver) === null || _c === void 0 ? void 0 : _c.isBusy()) !== null && _d !== void 0 ? _d : false });
|
|
923
948
|
// Clear local references only — driver and store stay in their registries.
|
|
924
949
|
this.driver = undefined;
|
|
925
950
|
this._sessionRef = undefined;
|
|
@@ -1008,6 +1033,31 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1008
1033
|
if (runningCost !== this.sessionCostUsd) {
|
|
1009
1034
|
this.sessionCostUsd = runningCost;
|
|
1010
1035
|
}
|
|
1036
|
+
// Record a context.updated meta event when the token count changes (≈once
|
|
1037
|
+
// per LLM call, as a new usage-bearing message arrives), plus a one-shot
|
|
1038
|
+
// threshold-crossed event the first time usage passes 80%.
|
|
1039
|
+
if (this.contextTokens != null && this.contextTokens !== this._lastLoggedContextTokens) {
|
|
1040
|
+
this._lastLoggedContextTokens = this.contextTokens;
|
|
1041
|
+
const usagePercent = this.contextLimit != null && this.contextLimit > 0
|
|
1042
|
+
? Math.round((this.contextTokens / this.contextLimit) * 100)
|
|
1043
|
+
: undefined;
|
|
1044
|
+
this.logMeta('context.updated', {
|
|
1045
|
+
tokens: this.contextTokens,
|
|
1046
|
+
limit: this.contextLimit,
|
|
1047
|
+
usagePercent,
|
|
1048
|
+
costUsd: this.sessionCostUsd,
|
|
1049
|
+
});
|
|
1050
|
+
if (usagePercent != null &&
|
|
1051
|
+
usagePercent >= FoundationAiAssistant_1.CONTEXT_USAGE_WARN_PERCENT &&
|
|
1052
|
+
!this._contextThresholdCrossed) {
|
|
1053
|
+
this._contextThresholdCrossed = true;
|
|
1054
|
+
this.logMeta('context.threshold-crossed', {
|
|
1055
|
+
usagePercent,
|
|
1056
|
+
tokens: this.contextTokens,
|
|
1057
|
+
limit: this.contextLimit,
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1011
1061
|
// Publish halo-start whenever a new toolCalls message arrives.
|
|
1012
1062
|
if (this.showHalo !== 'no' && ((_a = this.enabledAnimations) === null || _a === void 0 ? void 0 : _a.includes('halo'))) {
|
|
1013
1063
|
const last = this.messages[this.messages.length - 1];
|
|
@@ -1067,9 +1117,11 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1067
1117
|
/** Called when the user clicks the popout button. */
|
|
1068
1118
|
handlePopout() {
|
|
1069
1119
|
if (this.popoutMode === 'expand') {
|
|
1120
|
+
this.logMeta('assistant.popout');
|
|
1070
1121
|
agenticActivityBus.publish('chat-popout', undefined);
|
|
1071
1122
|
}
|
|
1072
1123
|
else if (this.popoutMode === 'collapse') {
|
|
1124
|
+
this.logMeta('assistant.popin');
|
|
1073
1125
|
agenticActivityBus.publish('chat-popin', undefined);
|
|
1074
1126
|
}
|
|
1075
1127
|
}
|
|
@@ -1078,10 +1130,30 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1078
1130
|
const parts = [this.id, this.headerTitle].filter(Boolean);
|
|
1079
1131
|
return parts.length ? parts.join('::') : undefined;
|
|
1080
1132
|
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Record a meta/lifecycle event onto this session's debug-log timeline
|
|
1135
|
+
* (exported via {@link FoundationAiAssistant.getDebugLog}). Every event
|
|
1136
|
+
* auto-carries `placement` so a session emitted from both the bubble and the
|
|
1137
|
+
* layout panel stays disambiguated. Keyed by `stateKey` so the timeline is
|
|
1138
|
+
* shared across pop-in/pop-out and survives driver rebuilds.
|
|
1139
|
+
*/
|
|
1140
|
+
logMeta(type, detail) {
|
|
1141
|
+
const key = this.getStateKey();
|
|
1142
|
+
if (!key)
|
|
1143
|
+
return;
|
|
1144
|
+
const placement = this.popoutMode === 'expand'
|
|
1145
|
+
? 'bubble'
|
|
1146
|
+
: this.popoutMode === 'collapse'
|
|
1147
|
+
? 'panel'
|
|
1148
|
+
: 'standalone';
|
|
1149
|
+
recordMetaEvent(key, type, Object.assign({ placement }, detail));
|
|
1150
|
+
}
|
|
1081
1151
|
toggleSettings() {
|
|
1152
|
+
this.logMeta('panel.toggled', { panel: 'settings', open: !this.settingsOpen });
|
|
1082
1153
|
this._settingsToggle.toggle();
|
|
1083
1154
|
}
|
|
1084
1155
|
toggleAgentPicker() {
|
|
1156
|
+
this.logMeta('panel.toggled', { panel: 'agent-picker', open: !this.agentPickerOpen });
|
|
1085
1157
|
this._agentPickerToggle.toggle();
|
|
1086
1158
|
}
|
|
1087
1159
|
/**
|
|
@@ -1213,12 +1285,66 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1213
1285
|
const contextUsagePercent = this.contextTokens != null && this.contextLimit != null && this.contextLimit > 0
|
|
1214
1286
|
? Math.round((this.contextTokens / this.contextLimit) * 100)
|
|
1215
1287
|
: undefined;
|
|
1288
|
+
const stateKey = this.getStateKey();
|
|
1289
|
+
// Collapse repeated system prompts across consecutive turns. A stateful
|
|
1290
|
+
// agent's prompt changes between turns, but a stable agent repeats the same
|
|
1291
|
+
// (often multi-KB) prompt every turn — so replace a turn's `systemPrompt`
|
|
1292
|
+
// with a sentinel when it's byte-identical to the previous turn's. The
|
|
1293
|
+
// prompt is still shown in full whenever it changes, so prompt evolution
|
|
1294
|
+
// stays visible.
|
|
1295
|
+
let lastFullPrompt;
|
|
1296
|
+
let lastFullIndex = -1;
|
|
1297
|
+
const turns = ((_e = (_d = (_c = this.driver) === null || _c === void 0 ? void 0 : _c.getTurnSnapshots) === null || _d === void 0 ? void 0 : _d.call(_c)) !== null && _e !== void 0 ? _e : []).map((t) => {
|
|
1298
|
+
let { systemPrompt } = t;
|
|
1299
|
+
if (systemPrompt != null && systemPrompt === lastFullPrompt) {
|
|
1300
|
+
systemPrompt = `<repeated — identical to turn ${lastFullIndex}>`;
|
|
1301
|
+
}
|
|
1302
|
+
else if (systemPrompt != null) {
|
|
1303
|
+
lastFullPrompt = systemPrompt;
|
|
1304
|
+
lastFullIndex = t.turnIndex;
|
|
1305
|
+
}
|
|
1306
|
+
return Object.assign(Object.assign({ kind: 'turn' }, t), { systemPrompt });
|
|
1307
|
+
});
|
|
1308
|
+
// Single chronological timeline. The conversation messages, per-LLM-call
|
|
1309
|
+
// turn snapshots, and lifecycle/structural meta events are stored separately
|
|
1310
|
+
// in memory (history on the driver, turn buffer on the driver, meta buffer
|
|
1311
|
+
// keyed by stateKey) but interleaved here so the exported log reads
|
|
1312
|
+
// top-to-bottom as one sequence — no cross-referencing parallel arrays.
|
|
1313
|
+
// Every entry is tagged `kind`. All three streams stamp ISO timestamps in
|
|
1314
|
+
// the same format, so a string compare orders them chronologically; ties
|
|
1315
|
+
// break by `kind` (event → turn → message: the cause, then the call it
|
|
1316
|
+
// triggered, then the output) and are otherwise stable within a stream.
|
|
1317
|
+
const KIND_RANK = {
|
|
1318
|
+
event: 0,
|
|
1319
|
+
turn: 1,
|
|
1320
|
+
message: 2,
|
|
1321
|
+
};
|
|
1322
|
+
const messages = (_k = (_j = (_f = this.driver) === null || _f === void 0 ? void 0 : _f.getRawHistory) === null || _j === void 0 ? void 0 : _j.call(_f)) !== null && _k !== void 0 ? _k : this.messages;
|
|
1323
|
+
const timeline = [
|
|
1324
|
+
...messages.map((m) => (Object.assign({ kind: 'message' }, m))),
|
|
1325
|
+
...turns,
|
|
1326
|
+
...(stateKey ? getMetaEvents(stateKey) : []).map((e) => (Object.assign({ kind: 'event' }, e))),
|
|
1327
|
+
].sort((a, b) => {
|
|
1328
|
+
var _a, _b;
|
|
1329
|
+
const ta = (_a = a.timestamp) !== null && _a !== void 0 ? _a : '';
|
|
1330
|
+
const tb = (_b = b.timestamp) !== null && _b !== void 0 ? _b : '';
|
|
1331
|
+
if (ta < tb)
|
|
1332
|
+
return -1;
|
|
1333
|
+
if (ta > tb)
|
|
1334
|
+
return 1;
|
|
1335
|
+
return KIND_RANK[a.kind] - KIND_RANK[b.kind];
|
|
1336
|
+
});
|
|
1216
1337
|
return {
|
|
1217
|
-
|
|
1338
|
+
// First key so it's at the top of the file — tells whoever opens the JSON
|
|
1339
|
+
// (often an AI agent) how to read the rest. See debug-event-log.ts.
|
|
1340
|
+
readme: DEBUG_LOG_README,
|
|
1341
|
+
// The whole session as one readable sequence. The conversation transcript
|
|
1342
|
+
// lives here as `kind: 'message'` entries rather than in a parallel block.
|
|
1343
|
+
timeline,
|
|
1218
1344
|
meta: {
|
|
1219
1345
|
timestamp,
|
|
1220
1346
|
host: window.location.host,
|
|
1221
|
-
agentSummary: (
|
|
1347
|
+
agentSummary: (_l = this.agents) === null || _l === void 0 ? void 0 : _l.map((a) => {
|
|
1222
1348
|
var _a;
|
|
1223
1349
|
return (Object.assign(Object.assign({}, a), { toolDefinitions: Array.isArray(a.toolDefinitions)
|
|
1224
1350
|
? typeof a.toolHandlers === 'function'
|
|
@@ -1230,10 +1356,10 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1230
1356
|
? '<dynamic — resolved per turn>'
|
|
1231
1357
|
: [], toolHandlers: undefined, onActivate: undefined, onDeactivate: undefined, getDebugSnapshot: undefined }));
|
|
1232
1358
|
}),
|
|
1233
|
-
activeSystemPrompt: typeof ((
|
|
1359
|
+
activeSystemPrompt: typeof ((_m = this.activeAgent) === null || _m === void 0 ? void 0 : _m.systemPrompt) === 'function'
|
|
1234
1360
|
? '<dynamic — resolved per turn>'
|
|
1235
|
-
: (
|
|
1236
|
-
activePrimerHistory: (
|
|
1361
|
+
: (_o = this.activeAgent) === null || _o === void 0 ? void 0 : _o.systemPrompt,
|
|
1362
|
+
activePrimerHistory: (_p = this.activeAgent) === null || _p === void 0 ? void 0 : _p.primerHistory,
|
|
1237
1363
|
activeFoldStack: this.driver instanceof ChatDriver ? this.driver.getActiveFoldNames() : undefined,
|
|
1238
1364
|
// Context window + cost snapshot. `sessionCostUsd` is the chat-scoped
|
|
1239
1365
|
// total (per-message `cost` summed); the transport's lifetime cost is
|
|
@@ -1250,9 +1376,6 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1250
1376
|
// Snapshot captured fresh at log-export time — reflects state NOW, which
|
|
1251
1377
|
// may have transitioned since the last LLM call.
|
|
1252
1378
|
activeDebugSnapshot,
|
|
1253
|
-
// Per-LLM-call timeline — pairs each turn with the prompt + tool surface
|
|
1254
|
-
// + agent state that drove it. Capped to the most recent N entries.
|
|
1255
|
-
turnSnapshots: (_p = (_o = (_m = this.driver) === null || _m === void 0 ? void 0 : _m.getTurnSnapshots) === null || _o === void 0 ? void 0 : _o.call(_m)) !== null && _p !== void 0 ? _p : [],
|
|
1256
1379
|
debug: (_q = this.debugStateFactory) === null || _q === void 0 ? void 0 : _q.call(this),
|
|
1257
1380
|
},
|
|
1258
1381
|
};
|
|
@@ -1325,6 +1448,10 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1325
1448
|
}
|
|
1326
1449
|
catch (readError) {
|
|
1327
1450
|
logger.error('Failed to read file:', readError);
|
|
1451
|
+
this.logMeta('file.read-failed', {
|
|
1452
|
+
file: file.name,
|
|
1453
|
+
message: readError instanceof Error ? readError.message : String(readError),
|
|
1454
|
+
});
|
|
1328
1455
|
return { ok: false, message: `Failed to read "${file.name}".` };
|
|
1329
1456
|
}
|
|
1330
1457
|
})));
|
|
@@ -1345,8 +1472,13 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1345
1472
|
const files = Array.from((_a = input.files) !== null && _a !== void 0 ? _a : []);
|
|
1346
1473
|
input.value = '';
|
|
1347
1474
|
const { attachments, errors } = yield this.processFiles(files);
|
|
1348
|
-
if (attachments.length)
|
|
1475
|
+
if (attachments.length) {
|
|
1349
1476
|
this.attachments = [...this.attachments, ...attachments];
|
|
1477
|
+
this.logMeta('attachment.added', {
|
|
1478
|
+
count: attachments.length,
|
|
1479
|
+
names: attachments.map((a) => a.name),
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1350
1482
|
if (errors.length)
|
|
1351
1483
|
this.attachmentErrors = [...this.attachmentErrors, ...errors];
|
|
1352
1484
|
});
|
|
@@ -1474,6 +1606,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1474
1606
|
return;
|
|
1475
1607
|
this.suggestionsState = { status: 'error', message: e.message };
|
|
1476
1608
|
logger.error('Failed to fetch suggestions:', e);
|
|
1609
|
+
this.logMeta('suggestions.failed', { message: e.message });
|
|
1477
1610
|
}
|
|
1478
1611
|
});
|
|
1479
1612
|
}
|
|
@@ -1544,6 +1677,8 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1544
1677
|
}
|
|
1545
1678
|
};
|
|
1546
1679
|
FoundationAiAssistant.SCROLL_BOTTOM_THRESHOLD_PX = 50;
|
|
1680
|
+
/** Context-usage percentage that fires the one-shot `context.threshold-crossed` event. */
|
|
1681
|
+
FoundationAiAssistant.CONTEXT_USAGE_WARN_PERCENT = 80;
|
|
1547
1682
|
FoundationAiAssistant.DEFAULT_LOADING_DELAY_S = 5;
|
|
1548
1683
|
FoundationAiAssistant.DEFAULT_SUGGESTION_COUNT = 3;
|
|
1549
1684
|
FoundationAiAssistant.MS_PER_SECOND = 1000;
|