@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
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module-level append-only timeline of meta/lifecycle events, keyed by session
|
|
3
|
+
* identity (the same `stateKey` space used by the driver registry and the
|
|
4
|
+
* session store).
|
|
5
|
+
*
|
|
6
|
+
* Why a module registry rather than the redux store or the driver:
|
|
7
|
+
* - **Survives the pop-in/pop-out element churn.** A single session can be
|
|
8
|
+
* emitted from up to two element instances (an `expand` bubble + a `collapse`
|
|
9
|
+
* panel) sharing one driver; keying by `stateKey` unifies them into one
|
|
10
|
+
* timeline.
|
|
11
|
+
* - **Survives driver rebuilds.** The driver is torn down and recreated when the
|
|
12
|
+
* host swaps the agents array (see `agentsChanged` / `deleteDriver`), so a
|
|
13
|
+
* driver-owned buffer would lose the timeline mid-session. The `stateKey`
|
|
14
|
+
* outlives the driver.
|
|
15
|
+
* - **Stays out of redux reactivity.** These appends never drive the UI, so
|
|
16
|
+
* there's no reason to pay immutable-reducer cost or flood the Redux DevTools
|
|
17
|
+
* action log with debug noise.
|
|
18
|
+
*
|
|
19
|
+
* Surfaced under `getDebugLog().meta.events`. Ring-buffered so a long-lived
|
|
20
|
+
* session can't grow the timeline unbounded.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Importance is intrinsic to the event *type*, not the instance, so it's
|
|
26
|
+
* defined once here and stamped automatically by {@link recordMetaEvent} — call
|
|
27
|
+
* sites never pass it. The `Record` is exhaustive: adding a `MetaEventType`
|
|
28
|
+
* without a level here is a compile error.
|
|
29
|
+
*
|
|
30
|
+
* - `high` — failures and hard limits you almost always want to see.
|
|
31
|
+
* - `normal` — meaningful session flow you read to follow what happened.
|
|
32
|
+
* - `low` — frequent UI / bookkeeping noise, usually safe to skip.
|
|
33
|
+
*/
|
|
34
|
+
export const META_EVENT_IMPORTANCE = {
|
|
35
|
+
'turn.error': 'high',
|
|
36
|
+
'tool.failed': 'high',
|
|
37
|
+
'file.read-failed': 'high',
|
|
38
|
+
'suggestions.failed': 'high',
|
|
39
|
+
'context.threshold-crossed': 'high',
|
|
40
|
+
'assistant.connected': 'normal',
|
|
41
|
+
'assistant.disconnected': 'normal',
|
|
42
|
+
'assistant.popout': 'normal',
|
|
43
|
+
'assistant.popin': 'normal',
|
|
44
|
+
'driver.created': 'normal',
|
|
45
|
+
'state.changed': 'normal',
|
|
46
|
+
'turn.start': 'normal',
|
|
47
|
+
'turn.end': 'normal',
|
|
48
|
+
'agent.handoff': 'normal',
|
|
49
|
+
'agent.pinned': 'normal',
|
|
50
|
+
'agent.unpinned': 'normal',
|
|
51
|
+
'provider.selected': 'normal',
|
|
52
|
+
'interaction.requested': 'normal',
|
|
53
|
+
'interaction.resolved': 'normal',
|
|
54
|
+
'driver.wired': 'low',
|
|
55
|
+
'driver.unwired': 'low',
|
|
56
|
+
'context.updated': 'low',
|
|
57
|
+
'panel.toggled': 'low',
|
|
58
|
+
'attachment.added': 'low',
|
|
59
|
+
};
|
|
60
|
+
/** Default ring-buffer cap. ~5× the turn-snapshot cap — entries are cheap. */
|
|
61
|
+
const DEFAULT_MAX_META_EVENTS = 200;
|
|
62
|
+
const registry = new Map();
|
|
63
|
+
/**
|
|
64
|
+
* Append a meta event to the timeline for `key`. Evicts the oldest entry once
|
|
65
|
+
* the buffer exceeds {@link DEFAULT_MAX_META_EVENTS}. An empty `key` is bucketed
|
|
66
|
+
* under `''`, matching how drivers/stores handle an absent session identity, so
|
|
67
|
+
* callers never need to guard.
|
|
68
|
+
*/
|
|
69
|
+
export function recordMetaEvent(key, type, detail) {
|
|
70
|
+
let buffer = registry.get(key);
|
|
71
|
+
if (!buffer) {
|
|
72
|
+
buffer = { events: [], next: 0 };
|
|
73
|
+
registry.set(key, buffer);
|
|
74
|
+
}
|
|
75
|
+
buffer.events.push({
|
|
76
|
+
index: buffer.next,
|
|
77
|
+
timestamp: new Date().toISOString(),
|
|
78
|
+
type,
|
|
79
|
+
importance: META_EVENT_IMPORTANCE[type],
|
|
80
|
+
detail,
|
|
81
|
+
});
|
|
82
|
+
buffer.next += 1;
|
|
83
|
+
if (buffer.events.length > DEFAULT_MAX_META_EVENTS) {
|
|
84
|
+
buffer.events.shift();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Returns the meta-event timeline for `key`, or an empty array if none recorded. */
|
|
88
|
+
export function getMetaEvents(key) {
|
|
89
|
+
var _a, _b;
|
|
90
|
+
return (_b = (_a = registry.get(key)) === null || _a === void 0 ? void 0 : _a.events) !== null && _b !== void 0 ? _b : [];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Human/agent-facing guide emitted as the first key of the exported debug log,
|
|
94
|
+
* so whoever opens the JSON (often an AI agent) knows how to read it without
|
|
95
|
+
* reverse-engineering the shape. Kept here next to the event catalogue it
|
|
96
|
+
* describes so the two stay in sync.
|
|
97
|
+
*/
|
|
98
|
+
export const DEBUG_LOG_README = [
|
|
99
|
+
'This is an exported debug log for the Genesis AI assistant. Read it top-to-bottom.',
|
|
100
|
+
'`timeline` is the entire session as one array, already sorted chronologically by `timestamp` (ISO 8601). Every entry has a `kind`.',
|
|
101
|
+
"kind:'message' — the conversation. `role` is user/assistant/tool/system-event; `agentName` says which agent produced it; `toolCalls`/`toolResult`/`interaction` carry tool and widget activity; `inputTokens`/`outputTokens`/`cost` are per-message usage.",
|
|
102
|
+
"kind:'turn' — one LLM call. `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.",
|
|
103
|
+
"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.",
|
|
104
|
+
"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'.",
|
|
105
|
+
"Each 'event' also has an `importance`: 'high' (failures/limits — turn.error, tool.failed, file.read-failed, suggestions.failed, context.threshold-crossed), 'normal' (session flow — connects, turns, handoffs, agent/provider changes, interactions), or 'low' (skippable UI/bookkeeping noise — panel.toggled, attachment.added, driver.wired/unwired, context.updated). To skim, ignore importance:'low'; to triage a failure, filter to importance:'high' then read the nearby messages and turns. 'message' and 'turn' entries carry no importance — they are the substance, always read them.",
|
|
106
|
+
'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.error (a turn failed or hit a guardrail — see detail.reason), tool.failed (a tool threw), agent.handoff (routing; from=null is the initial activation), agent.pinned/unpinned (forced routing), provider.selected (model/provider for the upcoming turns), interaction.requested/resolved (blocking user widgets — explain quiet gaps), context.updated/threshold-crossed (token + cost), panel.toggled, attachment.added, file.read-failed, suggestions.failed.',
|
|
107
|
+
"`meta` holds context captured at export time: agentSummary (full agent configs), context (active model, token usage, session cost), 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.",
|
|
108
|
+
'To debug a failure: find the last turn.error or tool.failed, then read upward for the user message, the turn(s), and the agent/provider/state events that led into it.',
|
|
109
|
+
];
|
|
110
|
+
/**
|
|
111
|
+
* Removes all entries. Exposed for test isolation only — not part of the
|
|
112
|
+
* public API.
|
|
113
|
+
*
|
|
114
|
+
* @internal
|
|
115
|
+
*/
|
|
116
|
+
export function clearMetaEventRegistry() {
|
|
117
|
+
registry.clear();
|
|
118
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/halo-overlay.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/config/config.ts","../src/config/define-stateful-agent.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/config/validate-providers.test.ts","../src/config/validate-providers.ts","../src/main/index.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/state/ai-assistant-slice.ts","../src/state/driver-registry.ts","../src/state/session-store.ts","../src/styles/ai-colours.ts","../src/styles/index.ts","../src/styles/styles.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/utils/animated-panel-toggle.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/tool-fold.ts"],"version":"5.9.2"}
|
|
1
|
+
{"root":["../src/index.ts","../src/channel/ai-activity-bus.ts","../src/channel/ai-activity-channel.ts","../src/components/halo-overlay.ts","../src/components/activity-halo/activity-halo.ts","../src/components/agent-picker/agent-picker.constants.ts","../src/components/agent-picker/agent-picker.styles.ts","../src/components/agent-picker/agent-picker.template.ts","../src/components/agent-picker/agent-picker.ts","../src/components/agent-picker/index.ts","../src/components/ai-driver/ai-driver.ts","../src/components/ai-driver/index.ts","../src/components/chat-bubble/chat-bubble.styles.ts","../src/components/chat-bubble/chat-bubble.template.ts","../src/components/chat-bubble/chat-bubble.ts","../src/components/chat-bubble/index.ts","../src/components/chat-driver/chat-driver.ts","../src/components/chat-driver/index.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.styles.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.template.ts","../src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts","../src/components/chat-interaction-wrapper/index.ts","../src/components/chat-markdown/chat-markdown.ts","../src/components/chat-markdown/index.ts","../src/components/orchestrating-driver/index.ts","../src/components/orchestrating-driver/orchestrating-driver.ts","../src/components/popout-manager/index.ts","../src/components/popout-manager/popout-manager.ts","../src/config/config.ts","../src/config/define-stateful-agent.ts","../src/config/fallback-agents.ts","../src/config/index.ts","../src/config/validate-providers.test.ts","../src/config/validate-providers.ts","../src/main/index.ts","../src/main/main.styles.ts","../src/main/main.template.ts","../src/main/main.ts","../src/main/main.types.ts","../src/state/ai-assistant-slice.ts","../src/state/debug-event-log.ts","../src/state/driver-registry.ts","../src/state/session-store.ts","../src/styles/ai-colours.ts","../src/styles/index.ts","../src/styles/styles.ts","../src/suggestions/chat-suggestions.ts","../src/tags/index.ts","../src/types/ai-chat-widget.ts","../src/utils/animated-panel-toggle.ts","../src/utils/history-transform.ts","../src/utils/index.ts","../src/utils/logger.ts","../src/utils/sum-costs.test.ts","../src/utils/sum-costs.ts","../src/utils/tool-fold.ts"],"version":"5.9.2"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genesislcap/ai-assistant",
|
|
3
3
|
"description": "Genesis AI Assistant micro-frontend",
|
|
4
|
-
"version": "14.
|
|
4
|
+
"version": "14.451.0",
|
|
5
5
|
"license": "SEE LICENSE IN license.txt",
|
|
6
6
|
"main": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/ai-assistant.d.ts",
|
|
@@ -64,24 +64,24 @@
|
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@genesislcap/foundation-testing": "14.
|
|
68
|
-
"@genesislcap/genx": "14.
|
|
69
|
-
"@genesislcap/rollup-builder": "14.
|
|
70
|
-
"@genesislcap/ts-builder": "14.
|
|
71
|
-
"@genesislcap/uvu-playwright-builder": "14.
|
|
72
|
-
"@genesislcap/vite-builder": "14.
|
|
73
|
-
"@genesislcap/webpack-builder": "14.
|
|
67
|
+
"@genesislcap/foundation-testing": "14.451.0",
|
|
68
|
+
"@genesislcap/genx": "14.451.0",
|
|
69
|
+
"@genesislcap/rollup-builder": "14.451.0",
|
|
70
|
+
"@genesislcap/ts-builder": "14.451.0",
|
|
71
|
+
"@genesislcap/uvu-playwright-builder": "14.451.0",
|
|
72
|
+
"@genesislcap/vite-builder": "14.451.0",
|
|
73
|
+
"@genesislcap/webpack-builder": "14.451.0",
|
|
74
74
|
"@types/dompurify": "^3.0.5",
|
|
75
75
|
"@types/marked": "^5.0.2"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@genesislcap/foundation-ai": "14.
|
|
79
|
-
"@genesislcap/foundation-logger": "14.
|
|
80
|
-
"@genesislcap/foundation-redux": "14.
|
|
81
|
-
"@genesislcap/foundation-ui": "14.
|
|
82
|
-
"@genesislcap/foundation-utils": "14.
|
|
83
|
-
"@genesislcap/rapid-design-system": "14.
|
|
84
|
-
"@genesislcap/web-core": "14.
|
|
78
|
+
"@genesislcap/foundation-ai": "14.451.0",
|
|
79
|
+
"@genesislcap/foundation-logger": "14.451.0",
|
|
80
|
+
"@genesislcap/foundation-redux": "14.451.0",
|
|
81
|
+
"@genesislcap/foundation-ui": "14.451.0",
|
|
82
|
+
"@genesislcap/foundation-utils": "14.451.0",
|
|
83
|
+
"@genesislcap/rapid-design-system": "14.451.0",
|
|
84
|
+
"@genesislcap/web-core": "14.451.0",
|
|
85
85
|
"dompurify": "^3.3.1",
|
|
86
86
|
"marked": "^17.0.3"
|
|
87
87
|
},
|
|
@@ -93,5 +93,5 @@
|
|
|
93
93
|
"publishConfig": {
|
|
94
94
|
"access": "public"
|
|
95
95
|
},
|
|
96
|
-
"gitHead": "
|
|
96
|
+
"gitHead": "0e6d8695edfcba1901f3f42f12e9387bee747c60"
|
|
97
97
|
}
|
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
ToolHandlersInput,
|
|
23
23
|
} from '../../config/config';
|
|
24
24
|
import { resolveChatProvider } from '../../config/validate-providers';
|
|
25
|
+
import { recordMetaEvent } from '../../state/debug-event-log';
|
|
25
26
|
import { applyHistoryCap } from '../../utils/history-transform';
|
|
26
27
|
import { logger } from '../../utils/logger';
|
|
27
28
|
import { TOOL_FOLD_SYMBOL, type ToolFold } from '../../utils/tool-fold';
|
|
@@ -99,6 +100,8 @@ interface FoldStackFrame {
|
|
|
99
100
|
export class ChatDriver extends EventTarget implements AiDriver {
|
|
100
101
|
private history: ChatMessage[] = [];
|
|
101
102
|
private busy = false;
|
|
103
|
+
/** Epoch ms when the current turn loop began — drives the `turn.end` duration. */
|
|
104
|
+
private turnStartedAt = 0;
|
|
102
105
|
private pendingInteractions = new Map<
|
|
103
106
|
string,
|
|
104
107
|
{
|
|
@@ -233,6 +236,8 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
233
236
|
private readonly maxToolIterations: number = DEFAULT_MAX_TOOL_ITERATIONS,
|
|
234
237
|
maxFoldOperations: number = DEFAULT_MAX_FOLD_OPERATIONS,
|
|
235
238
|
maxTurnSnapshots: number = DEFAULT_MAX_TURN_SNAPSHOTS,
|
|
239
|
+
/** Session identity used to file meta events onto the shared debug-log timeline. */
|
|
240
|
+
private readonly sessionKey: string = '',
|
|
236
241
|
) {
|
|
237
242
|
super();
|
|
238
243
|
if (typeof toolHandlers === 'function') {
|
|
@@ -348,6 +353,10 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
348
353
|
this.lastResolvedProviderName = resolvedName;
|
|
349
354
|
if (resolvedName !== this.lastDispatchedProviderName) {
|
|
350
355
|
this.lastDispatchedProviderName = resolvedName;
|
|
356
|
+
recordMetaEvent(this.sessionKey, 'provider.selected', {
|
|
357
|
+
provider: resolvedName,
|
|
358
|
+
agent: this.activeAgentName,
|
|
359
|
+
});
|
|
351
360
|
this.dispatchEvent(
|
|
352
361
|
new CustomEvent<{ name: string }>('provider-changed', { detail: { name: resolvedName } }),
|
|
353
362
|
);
|
|
@@ -610,6 +619,11 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
610
619
|
reject,
|
|
611
620
|
overrideId: chatInputDuringExecution ? interactionId : undefined,
|
|
612
621
|
});
|
|
622
|
+
recordMetaEvent(this.sessionKey, 'interaction.requested', {
|
|
623
|
+
interactionId,
|
|
624
|
+
component: componentName,
|
|
625
|
+
agent: this.activeAgentName,
|
|
626
|
+
});
|
|
613
627
|
if (chatInputDuringExecution) {
|
|
614
628
|
this.dispatchEvent(
|
|
615
629
|
new CustomEvent('interaction-start', {
|
|
@@ -647,6 +661,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
647
661
|
if (interaction.overrideId) {
|
|
648
662
|
this.dispatchEvent(new CustomEvent('interaction-stop', { detail: { interactionId } }));
|
|
649
663
|
}
|
|
664
|
+
recordMetaEvent(this.sessionKey, 'interaction.resolved', { interactionId });
|
|
650
665
|
interaction.resolve(result);
|
|
651
666
|
this.pendingInteractions.delete(interactionId);
|
|
652
667
|
} else {
|
|
@@ -674,15 +689,31 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
674
689
|
this.subAgentCompletion = undefined;
|
|
675
690
|
this.agentReleaseRequested = false;
|
|
676
691
|
this.appendToHistory({ role: 'user', content: userInput, attachments });
|
|
692
|
+
this.turnStartedAt = Date.now();
|
|
693
|
+
recordMetaEvent(this.sessionKey, 'turn.start', {
|
|
694
|
+
phase: 'sendMessage',
|
|
695
|
+
agent: this.activeAgentName,
|
|
696
|
+
});
|
|
677
697
|
agenticActivityBus.publish('tool-loop-start', undefined);
|
|
678
698
|
|
|
679
699
|
try {
|
|
680
700
|
return await this.runToolLoop(userInput, attachments);
|
|
681
701
|
} catch (e) {
|
|
682
702
|
logger.error('ChatDriver error:', e);
|
|
703
|
+
recordMetaEvent(this.sessionKey, 'turn.error', {
|
|
704
|
+
phase: 'sendMessage',
|
|
705
|
+
agent: this.activeAgentName,
|
|
706
|
+
provider: this.lastResolvedProviderName,
|
|
707
|
+
message: e instanceof Error ? e.message : String(e),
|
|
708
|
+
});
|
|
683
709
|
this.appendToHistory({ role: 'assistant', content: 'Sorry, something went wrong.' });
|
|
684
710
|
return { reason: 'done' };
|
|
685
711
|
} finally {
|
|
712
|
+
recordMetaEvent(this.sessionKey, 'turn.end', {
|
|
713
|
+
phase: 'sendMessage',
|
|
714
|
+
agent: this.activeAgentName,
|
|
715
|
+
durationMs: Date.now() - this.turnStartedAt,
|
|
716
|
+
});
|
|
686
717
|
this.busy = false;
|
|
687
718
|
agenticActivityBus.publish('tool-loop-end', undefined);
|
|
688
719
|
}
|
|
@@ -844,14 +875,30 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
844
875
|
|
|
845
876
|
this.busy = true;
|
|
846
877
|
this.subAgentCompletion = undefined;
|
|
878
|
+
this.turnStartedAt = Date.now();
|
|
879
|
+
recordMetaEvent(this.sessionKey, 'turn.start', {
|
|
880
|
+
phase: 'continueFromHistory',
|
|
881
|
+
agent: this.activeAgentName,
|
|
882
|
+
});
|
|
847
883
|
agenticActivityBus.publish('tool-loop-start', undefined);
|
|
848
884
|
try {
|
|
849
885
|
return await this.runToolLoop('', undefined, transientPrimer);
|
|
850
886
|
} catch (e) {
|
|
851
887
|
logger.error('ChatDriver error:', e);
|
|
888
|
+
recordMetaEvent(this.sessionKey, 'turn.error', {
|
|
889
|
+
phase: 'continueFromHistory',
|
|
890
|
+
agent: this.activeAgentName,
|
|
891
|
+
provider: this.lastResolvedProviderName,
|
|
892
|
+
message: e instanceof Error ? e.message : String(e),
|
|
893
|
+
});
|
|
852
894
|
this.appendToHistory({ role: 'assistant', content: 'Sorry, something went wrong.' });
|
|
853
895
|
return { reason: 'done' };
|
|
854
896
|
} finally {
|
|
897
|
+
recordMetaEvent(this.sessionKey, 'turn.end', {
|
|
898
|
+
phase: 'continueFromHistory',
|
|
899
|
+
agent: this.activeAgentName,
|
|
900
|
+
durationMs: Date.now() - this.turnStartedAt,
|
|
901
|
+
});
|
|
855
902
|
this.busy = false;
|
|
856
903
|
agenticActivityBus.publish('tool-loop-end', undefined);
|
|
857
904
|
}
|
|
@@ -1140,6 +1187,11 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1140
1187
|
continue;
|
|
1141
1188
|
}
|
|
1142
1189
|
logger.error('ChatDriver: MALFORMED_FUNCTION_CALL, max retries reached');
|
|
1190
|
+
recordMetaEvent(this.sessionKey, 'turn.error', {
|
|
1191
|
+
reason: 'malformed-function-call',
|
|
1192
|
+
agent: this.activeAgentName,
|
|
1193
|
+
provider: this.lastResolvedProviderName,
|
|
1194
|
+
});
|
|
1143
1195
|
this.appendToHistory({
|
|
1144
1196
|
role: 'assistant',
|
|
1145
1197
|
content:
|
|
@@ -1163,6 +1215,11 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1163
1215
|
continue;
|
|
1164
1216
|
}
|
|
1165
1217
|
logger.error('ChatDriver: empty model response after all retries');
|
|
1218
|
+
recordMetaEvent(this.sessionKey, 'turn.error', {
|
|
1219
|
+
reason: 'empty-response',
|
|
1220
|
+
agent: this.activeAgentName,
|
|
1221
|
+
provider: this.lastResolvedProviderName,
|
|
1222
|
+
});
|
|
1166
1223
|
this.appendToHistory({
|
|
1167
1224
|
role: 'assistant',
|
|
1168
1225
|
content: 'Remote agent returned no response.',
|
|
@@ -1281,6 +1338,11 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1281
1338
|
anyRealToolExecuted = true;
|
|
1282
1339
|
} catch (e) {
|
|
1283
1340
|
logger.error(`ChatDriver tool "${tc.name}" failed:`, e);
|
|
1341
|
+
recordMetaEvent(this.sessionKey, 'tool.failed', {
|
|
1342
|
+
tool: tc.name,
|
|
1343
|
+
agent: this.activeAgentName,
|
|
1344
|
+
message: e instanceof Error ? e.message : String(e),
|
|
1345
|
+
});
|
|
1284
1346
|
executedById.set(tc.id, {
|
|
1285
1347
|
toolCallId: tc.id,
|
|
1286
1348
|
content: `Tool error: ${(e as Error).message}`,
|
|
@@ -1361,6 +1423,11 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1361
1423
|
logger.error(
|
|
1362
1424
|
`ChatDriver: unknown-tool limit (${DEFAULT_MAX_UNKNOWN_TOOL_CALLS}) reached — stopping`,
|
|
1363
1425
|
);
|
|
1426
|
+
recordMetaEvent(this.sessionKey, 'turn.error', {
|
|
1427
|
+
reason: 'unknown-tool-limit',
|
|
1428
|
+
agent: this.activeAgentName,
|
|
1429
|
+
provider: this.lastResolvedProviderName,
|
|
1430
|
+
});
|
|
1364
1431
|
this.appendToHistory({
|
|
1365
1432
|
role: 'assistant',
|
|
1366
1433
|
content:
|
|
@@ -1387,6 +1454,11 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1387
1454
|
|
|
1388
1455
|
if (iterations >= this.maxToolIterations) {
|
|
1389
1456
|
logger.warn('ChatDriver: reached max tool iterations, stopping');
|
|
1457
|
+
recordMetaEvent(this.sessionKey, 'turn.error', {
|
|
1458
|
+
reason: 'max-iterations',
|
|
1459
|
+
agent: this.activeAgentName,
|
|
1460
|
+
provider: this.lastResolvedProviderName,
|
|
1461
|
+
});
|
|
1390
1462
|
this.appendToHistory({
|
|
1391
1463
|
role: 'assistant',
|
|
1392
1464
|
content:
|
|
@@ -1398,14 +1470,18 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1398
1470
|
}
|
|
1399
1471
|
|
|
1400
1472
|
private appendToHistory(message: ChatMessage): void {
|
|
1401
|
-
const tagged: ChatMessage =
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1473
|
+
const tagged: ChatMessage = {
|
|
1474
|
+
...message,
|
|
1475
|
+
// Stamp on first append; preserve any caller-supplied timestamp.
|
|
1476
|
+
timestamp: message.timestamp ?? new Date().toISOString(),
|
|
1477
|
+
...(this.activeAgentName
|
|
1478
|
+
? {
|
|
1479
|
+
agentName: this.activeAgentName,
|
|
1480
|
+
// Display-only — falls back to agentName in renderers when unset.
|
|
1481
|
+
agentLabel: this.activeAgentLabel,
|
|
1482
|
+
}
|
|
1483
|
+
: {}),
|
|
1484
|
+
};
|
|
1409
1485
|
this.history = [...this.history, tagged];
|
|
1410
1486
|
this.dispatchEvent(
|
|
1411
1487
|
new CustomEvent<ReadonlyArray<ChatMessage>>('history-updated', {
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
SystemPromptInput,
|
|
14
14
|
} from '../../config/config';
|
|
15
15
|
import { validateStaticAgentProviders } from '../../config/validate-providers';
|
|
16
|
+
import { recordMetaEvent } from '../../state/debug-event-log';
|
|
16
17
|
import { transformHistoryForAgent } from '../../utils/history-transform';
|
|
17
18
|
import { logger } from '../../utils/logger';
|
|
18
19
|
import type { AiDriver, AllAgentSummary } from '../ai-driver/ai-driver';
|
|
@@ -159,6 +160,7 @@ export class OrchestratingDriver extends EventTarget implements AiDriver {
|
|
|
159
160
|
options.maxToolIterations,
|
|
160
161
|
options.maxFoldOperations,
|
|
161
162
|
options.maxTurnSnapshots,
|
|
163
|
+
this.sessionKey,
|
|
162
164
|
);
|
|
163
165
|
|
|
164
166
|
// Proxy events from the shared driver
|
|
@@ -340,6 +342,15 @@ export class OrchestratingDriver extends EventTarget implements AiDriver {
|
|
|
340
342
|
const previousAgent = this.activeAgent;
|
|
341
343
|
const isSwitch = !previousAgent || previousAgent.name !== agent.name;
|
|
342
344
|
|
|
345
|
+
// Record the agent transition on the debug-log timeline. A `null` `from`
|
|
346
|
+
// marks the initial activation; a named `from` is a handoff between agents.
|
|
347
|
+
if (isSwitch) {
|
|
348
|
+
recordMetaEvent(this.sessionKey, 'agent.handoff', {
|
|
349
|
+
from: previousAgent?.name ?? null,
|
|
350
|
+
to: agent.name,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
343
354
|
// Fire lifecycle hooks around the swap — outgoing first, then incoming.
|
|
344
355
|
// Both are awaited so a heavy `onActivate` (e.g. machine restore) completes
|
|
345
356
|
// before the agent's first turn runs.
|
|
@@ -413,7 +424,10 @@ export class OrchestratingDriver extends EventTarget implements AiDriver {
|
|
|
413
424
|
|
|
414
425
|
if (previousAgent && previousAgent.name !== agent.name) {
|
|
415
426
|
const rawHistory = this.chatDriver.getHistory() as ChatMessage[];
|
|
416
|
-
this.chatDriver.loadHistory([
|
|
427
|
+
this.chatDriver.loadHistory([
|
|
428
|
+
...rawHistory,
|
|
429
|
+
{ role: 'system-event', content: agent.name, timestamp: new Date().toISOString() },
|
|
430
|
+
]);
|
|
417
431
|
}
|
|
418
432
|
|
|
419
433
|
this.chatDriver.setProviderHistoryTransform((h) => transformHistoryForAgent(h, agent.name));
|
|
@@ -606,7 +620,10 @@ export class OrchestratingDriver extends EventTarget implements AiDriver {
|
|
|
606
620
|
|
|
607
621
|
private appendInlineMessage(content: string): void {
|
|
608
622
|
const history = this.chatDriver.getHistory() as ChatMessage[];
|
|
609
|
-
this.chatDriver.loadHistory([
|
|
623
|
+
this.chatDriver.loadHistory([
|
|
624
|
+
...history,
|
|
625
|
+
{ role: 'assistant', content, timestamp: new Date().toISOString() },
|
|
626
|
+
]);
|
|
610
627
|
this.dispatchEvent(
|
|
611
628
|
new CustomEvent('history-updated', { detail: this.chatDriver.getHistory() }),
|
|
612
629
|
);
|