@genesislcap/ai-assistant 14.451.1 → 14.451.2
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 +1 -1
- package/dist/ai-assistant.d.ts +12 -4
- 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/state/debug-event-log.d.ts +34 -6
- package/dist/dts/state/debug-event-log.d.ts.map +1 -1
- package/dist/esm/components/chat-driver/chat-driver.js +66 -18
- package/dist/esm/components/chat-interaction-wrapper/chat-interaction-wrapper.js +3 -3
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +1 -1
- package/dist/esm/state/debug-event-log.js +45 -10
- package/package.json +16 -16
- package/src/components/chat-driver/chat-driver.ts +67 -18
- package/src/components/chat-interaction-wrapper/chat-interaction-wrapper.ts +3 -3
- package/src/components/orchestrating-driver/orchestrating-driver.ts +1 -1
- package/src/state/debug-event-log.ts +74 -10
|
@@ -9623,7 +9623,7 @@
|
|
|
9623
9623
|
{
|
|
9624
9624
|
"kind": "Interface",
|
|
9625
9625
|
"canonicalReference": "@genesislcap/ai-assistant!TurnSnapshot:interface",
|
|
9626
|
-
"docComment": "/**\n * One captured frame of what the LLM saw on a single tool-loop iteration. The driver records these as a ring buffer (cap: configurable via `chatConfig.agent.maxTurnSnapshots`, default
|
|
9626
|
+
"docComment": "/**\n * One captured frame of what the LLM saw on a single tool-loop iteration. The driver records these as a ring buffer (cap: configurable via `chatConfig.agent.maxTurnSnapshots`, default 400) so the export log can show, per turn: which agent was active, the resolved system prompt, the tool names visible to the LLM, and any agent-supplied debug snapshot (e.g. machine state for stateful agents).\n *\n * @beta\n */\n",
|
|
9627
9627
|
"excerptTokens": [
|
|
9628
9628
|
{
|
|
9629
9629
|
"kind": "Content",
|
package/dist/ai-assistant.d.ts
CHANGED
|
@@ -625,6 +625,12 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
|
|
|
625
625
|
private consecutiveFoldOps;
|
|
626
626
|
/** Consecutive unknown-tool calls without a real tool call. Reset on real tool execution. */
|
|
627
627
|
private consecutiveUnknownToolCalls;
|
|
628
|
+
/**
|
|
629
|
+
* Distinct unknown-tool names seen in the current consecutive streak — stamped
|
|
630
|
+
* onto the `unknown-tool-limit` turn.error so a triager knows which tools were
|
|
631
|
+
* hallucinated. Reset alongside `consecutiveUnknownToolCalls`.
|
|
632
|
+
*/
|
|
633
|
+
private readonly recentUnknownToolNames;
|
|
628
634
|
private readonly maxFoldOperations;
|
|
629
635
|
/** Sub-agents declared on the active agent config, keyed by name. */
|
|
630
636
|
private subAgentsMap;
|
|
@@ -1562,8 +1568,10 @@ declare type MetaEventImportance = 'high' | 'normal' | 'low';
|
|
|
1562
1568
|
* there's no reason to pay immutable-reducer cost or flood the Redux DevTools
|
|
1563
1569
|
* action log with debug noise.
|
|
1564
1570
|
*
|
|
1565
|
-
* Surfaced
|
|
1566
|
-
*
|
|
1571
|
+
* Surfaced in `getDebugLog().timeline`, merged chronologically with messages and
|
|
1572
|
+
* turn snapshots. Ring-buffered — the oldest non-`high` events are evicted first
|
|
1573
|
+
* while `high`-importance failure events are retained — so a long-lived session
|
|
1574
|
+
* stays bounded in normal use without ever dropping a failure signal.
|
|
1567
1575
|
*
|
|
1568
1576
|
* @internal
|
|
1569
1577
|
*/
|
|
@@ -1571,7 +1579,7 @@ declare type MetaEventImportance = 'high' | 'normal' | 'low';
|
|
|
1571
1579
|
* Catalogue of meta event names. This is the documented surface — extend it as
|
|
1572
1580
|
* new events are wired in (Tier 2/3 lifecycle, interaction, provider events).
|
|
1573
1581
|
*/
|
|
1574
|
-
declare type MetaEventType = 'assistant.connected' | 'assistant.disconnected' | 'assistant.popout' | 'assistant.popin' | 'driver.created' | 'driver.wired' | 'driver.unwired' | 'state.changed' | 'turn.start' | 'turn.end' | 'turn.error' | 'tool.failed' | 'agent.handoff' | 'agent.pinned' | 'agent.unpinned' | 'provider.selected' | 'interaction.requested' | 'interaction.resolved' | 'context.updated' | 'context.threshold-crossed' | 'panel.toggled' | 'attachment.added' | 'file.read-failed' | 'suggestions.failed';
|
|
1582
|
+
declare type MetaEventType = 'assistant.connected' | 'assistant.disconnected' | 'assistant.popout' | 'assistant.popin' | 'driver.created' | 'driver.wired' | 'driver.unwired' | 'state.changed' | 'turn.start' | 'turn.end' | 'turn.retry' | 'turn.error' | 'tool.failed' | 'agent.handoff' | 'agent.pinned' | 'agent.unpinned' | 'provider.selected' | 'interaction.requested' | 'interaction.resolved' | 'context.updated' | 'context.threshold-crossed' | 'panel.toggled' | 'attachment.added' | 'file.read-failed' | 'suggestions.failed';
|
|
1575
1583
|
|
|
1576
1584
|
/**
|
|
1577
1585
|
* Orchestrates multiple specialist agents. Sits between `FoundationAiAssistant`
|
|
@@ -2009,7 +2017,7 @@ export declare interface ToolTreeNode extends ChatToolDefinition {
|
|
|
2009
2017
|
/**
|
|
2010
2018
|
* One captured frame of what the LLM saw on a single tool-loop iteration.
|
|
2011
2019
|
* The driver records these as a ring buffer (cap: configurable via
|
|
2012
|
-
* `chatConfig.agent.maxTurnSnapshots`, default
|
|
2020
|
+
* `chatConfig.agent.maxTurnSnapshots`, default 400) so the export log can show,
|
|
2013
2021
|
* per turn: which agent was active, the resolved system prompt, the tool names
|
|
2014
2022
|
* visible to the LLM, and any agent-supplied debug snapshot (e.g. machine
|
|
2015
2023
|
* state for stateful agents).
|
|
@@ -12,7 +12,7 @@ export type ChatHistoryUpdatedEvent = CustomEvent<ReadonlyArray<ChatMessage>>;
|
|
|
12
12
|
/**
|
|
13
13
|
* One captured frame of what the LLM saw on a single tool-loop iteration.
|
|
14
14
|
* The driver records these as a ring buffer (cap: configurable via
|
|
15
|
-
* `chatConfig.agent.maxTurnSnapshots`, default
|
|
15
|
+
* `chatConfig.agent.maxTurnSnapshots`, default 400) so the export log can show,
|
|
16
16
|
* per turn: which agent was active, the resolved system prompt, the tool names
|
|
17
17
|
* visible to the LLM, and any agent-supplied debug snapshot (e.g. machine
|
|
18
18
|
* state for stateful agents).
|
|
@@ -116,6 +116,12 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
|
|
|
116
116
|
private consecutiveFoldOps;
|
|
117
117
|
/** Consecutive unknown-tool calls without a real tool call. Reset on real tool execution. */
|
|
118
118
|
private consecutiveUnknownToolCalls;
|
|
119
|
+
/**
|
|
120
|
+
* Distinct unknown-tool names seen in the current consecutive streak — stamped
|
|
121
|
+
* onto the `unknown-tool-limit` turn.error so a triager knows which tools were
|
|
122
|
+
* hallucinated. Reset alongside `consecutiveUnknownToolCalls`.
|
|
123
|
+
*/
|
|
124
|
+
private readonly recentUnknownToolNames;
|
|
119
125
|
private readonly maxFoldOperations;
|
|
120
126
|
/** Sub-agents declared on the active agent config, keyed by name. */
|
|
121
127
|
private subAgentsMap;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-driver.d.ts","sourceRoot":"","sources":["../../../../src/components/chat-driver/chat-driver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,WAAW,EAKX,yBAAyB,EAE1B,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EACV,WAAW,EAGX,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EAClB,MAAM,qBAAqB,CAAC;AAM7B,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"chat-driver.d.ts","sourceRoot":"","sources":["../../../../src/components/chat-driver/chat-driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
* there's no reason to pay immutable-reducer cost or flood the Redux DevTools
|
|
17
17
|
* action log with debug noise.
|
|
18
18
|
*
|
|
19
|
-
* Surfaced
|
|
20
|
-
*
|
|
19
|
+
* Surfaced in `getDebugLog().timeline`, merged chronologically with messages and
|
|
20
|
+
* turn snapshots. Ring-buffered — the oldest non-`high` events are evicted first
|
|
21
|
+
* while `high`-importance failure events are retained — so a long-lived session
|
|
22
|
+
* stays bounded in normal use without ever dropping a failure signal.
|
|
21
23
|
*
|
|
22
24
|
* @internal
|
|
23
25
|
*/
|
|
@@ -25,7 +27,7 @@
|
|
|
25
27
|
* Catalogue of meta event names. This is the documented surface — extend it as
|
|
26
28
|
* new events are wired in (Tier 2/3 lifecycle, interaction, provider events).
|
|
27
29
|
*/
|
|
28
|
-
export type MetaEventType = 'assistant.connected' | 'assistant.disconnected' | 'assistant.popout' | 'assistant.popin' | 'driver.created' | 'driver.wired' | 'driver.unwired' | 'state.changed' | 'turn.start' | 'turn.end' | 'turn.error' | 'tool.failed' | 'agent.handoff' | 'agent.pinned' | 'agent.unpinned' | 'provider.selected' | 'interaction.requested' | 'interaction.resolved' | 'context.updated' | 'context.threshold-crossed' | 'panel.toggled' | 'attachment.added' | 'file.read-failed' | 'suggestions.failed';
|
|
30
|
+
export type MetaEventType = 'assistant.connected' | 'assistant.disconnected' | 'assistant.popout' | 'assistant.popin' | 'driver.created' | 'driver.wired' | 'driver.unwired' | 'state.changed' | 'turn.start' | 'turn.end' | 'turn.retry' | 'turn.error' | 'tool.failed' | 'agent.handoff' | 'agent.pinned' | 'agent.unpinned' | 'provider.selected' | 'interaction.requested' | 'interaction.resolved' | 'context.updated' | 'context.threshold-crossed' | 'panel.toggled' | 'attachment.added' | 'file.read-failed' | 'suggestions.failed';
|
|
29
31
|
/**
|
|
30
32
|
* How much a reader should care about an event — lets a consumer (or an AI
|
|
31
33
|
* agent) filter the timeline: skip `low` UI/bookkeeping noise, skim `normal`
|
|
@@ -57,12 +59,38 @@ export interface MetaEvent {
|
|
|
57
59
|
detail?: Record<string, unknown>;
|
|
58
60
|
}
|
|
59
61
|
/**
|
|
60
|
-
* Append a meta event to the timeline for `key`.
|
|
61
|
-
*
|
|
62
|
-
*
|
|
62
|
+
* Append a meta event to the timeline for `key`. Once the buffer exceeds
|
|
63
|
+
* {@link DEFAULT_MAX_META_EVENTS}, evicts the oldest *non-`high`* event;
|
|
64
|
+
* `high`-importance events are never evicted. An empty `key` is bucketed under
|
|
65
|
+
* `''`, matching how drivers/stores handle an absent session identity, so
|
|
63
66
|
* callers never need to guard.
|
|
64
67
|
*/
|
|
65
68
|
export declare function recordMetaEvent(key: string, type: MetaEventType, detail?: Record<string, unknown>): void;
|
|
69
|
+
/**
|
|
70
|
+
* Why a turn failed or was retried — stamped as `detail.reason` on `turn.error`
|
|
71
|
+
* and `turn.retry` events. Enumerated so the set stays in sync with the README
|
|
72
|
+
* and call sites can't drift to ad-hoc strings.
|
|
73
|
+
*
|
|
74
|
+
* - `exception` — an uncaught error escaped the tool loop (catch-all).
|
|
75
|
+
* - `malformed-function-call`— the provider returned an unparseable tool call.
|
|
76
|
+
* - `empty-response` — the model returned no content and no tool calls.
|
|
77
|
+
* - `unknown-tool-limit` — the model repeatedly called tools that don't exist.
|
|
78
|
+
* - `max-iterations` — the tool loop hit its iteration cap.
|
|
79
|
+
*/
|
|
80
|
+
export type TurnFailureReason = 'exception' | 'malformed-function-call' | 'empty-response' | 'unknown-tool-limit' | 'max-iterations';
|
|
81
|
+
/**
|
|
82
|
+
* Record a turn-ending failure (`turn.error`, importance `high`). The `reason`
|
|
83
|
+
* is typed so the high-importance triage surface stays consistent; pass any
|
|
84
|
+
* diagnostic specifics (attempt counts, offending tool names, etc.) in `detail`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function recordTurnError(key: string, reason: TurnFailureReason, detail?: Record<string, unknown>): void;
|
|
87
|
+
/**
|
|
88
|
+
* Record a recoverable retry *within* a turn (`turn.retry`, importance `normal`)
|
|
89
|
+
* — e.g. one malformed/empty attempt that will be retried, as distinct from the
|
|
90
|
+
* final attempt that bails out as a `turn.error`. Include `attempt`/`maxAttempts`
|
|
91
|
+
* in `detail` so the timeline shows which try this was.
|
|
92
|
+
*/
|
|
93
|
+
export declare function recordTurnRetry(key: string, reason: TurnFailureReason, detail?: Record<string, unknown>): void;
|
|
66
94
|
/** Returns the meta-event timeline for `key`, or an empty array if none recorded. */
|
|
67
95
|
export declare function getMetaEvents(key: string): ReadonlyArray<MetaEvent>;
|
|
68
96
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debug-event-log.d.ts","sourceRoot":"","sources":["../../../src/state/debug-event-log.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"debug-event-log.d.ts","sourceRoot":"","sources":["../../../src/state/debug-event-log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;GAGG;AACH,MAAM,MAAM,aAAa,GAErB,qBAAqB,GACrB,wBAAwB,GACxB,kBAAkB,GAClB,iBAAiB,GAEjB,gBAAgB,GAChB,cAAc,GACd,gBAAgB,GAEhB,eAAe,GACf,YAAY,GACZ,UAAU,GACV,YAAY,GACZ,YAAY,GACZ,aAAa,GAEb,eAAe,GACf,cAAc,GACd,gBAAgB,GAChB,mBAAmB,GAEnB,uBAAuB,GACvB,sBAAsB,GAEtB,iBAAiB,GACjB,2BAA2B,GAE3B,eAAe,GACf,kBAAkB,GAClB,kBAAkB,GAClB,oBAAoB,CAAC;AAEzB;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE5D;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,aAAa,EAAE,mBAAmB,CA4B5E,CAAC;AAEF,4CAA4C;AAC5C,MAAM,WAAW,SAAS;IACxB,0FAA0F;IAC1F,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,IAAI,EAAE,aAAa,CAAC;IACpB,6EAA6E;IAC7E,UAAU,EAAE,mBAAmB,CAAC;IAChC,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAmBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,aAAa,EACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,IAAI,CAyBN;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,iBAAiB,GACzB,WAAW,GACX,yBAAyB,GACzB,gBAAgB,GAChB,oBAAoB,GACpB,gBAAgB,CAAC;AAErB;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,iBAAiB,EACzB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,IAAI,CAEN;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,iBAAiB,EACzB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,IAAI,CAEN;AAED,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,CAEnE;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAW7C,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
|
|
@@ -2,13 +2,21 @@ 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
|
+
import { recordMetaEvent, recordTurnError, recordTurnRetry } from '../../state/debug-event-log';
|
|
6
6
|
import { applyHistoryCap } from '../../utils/history-transform';
|
|
7
7
|
import { logger } from '../../utils/logger';
|
|
8
8
|
import { TOOL_FOLD_SYMBOL } from '../../utils/tool-fold';
|
|
9
9
|
const DEFAULT_MAX_TOOL_ITERATIONS = 50;
|
|
10
10
|
const DEFAULT_MAX_FOLD_OPERATIONS = 5;
|
|
11
|
-
|
|
11
|
+
// TODO: dedup system prompts in-memory to allow raising this cap much higher.
|
|
12
|
+
// Each TurnSnapshot stores the full resolved `systemPrompt`, so the buffer's
|
|
13
|
+
// memory cost scales with cap × prompt size — a stable agent repeats a multi-KB
|
|
14
|
+
// prompt every turn. `getDebugLog` already dedups identical consecutive prompts
|
|
15
|
+
// at export time (`<repeated — identical to turn N>`); applying the same dedup
|
|
16
|
+
// at capture time (store the prompt only when it changes, back-reference
|
|
17
|
+
// otherwise) would make snapshots nearly as cheap as meta events and let this
|
|
18
|
+
// cap reach thousands for full-session capture without the memory blowup.
|
|
19
|
+
const DEFAULT_MAX_TURN_SNAPSHOTS = 400;
|
|
12
20
|
const DEFAULT_MAX_UNKNOWN_TOOL_CALLS = 5;
|
|
13
21
|
const MAX_MALFORMED_RETRIES = 2;
|
|
14
22
|
const MAX_EMPTY_RESPONSE_RETRIES = 3;
|
|
@@ -46,6 +54,12 @@ export class ChatDriver extends EventTarget {
|
|
|
46
54
|
this.consecutiveFoldOps = 0;
|
|
47
55
|
/** Consecutive unknown-tool calls without a real tool call. Reset on real tool execution. */
|
|
48
56
|
this.consecutiveUnknownToolCalls = 0;
|
|
57
|
+
/**
|
|
58
|
+
* Distinct unknown-tool names seen in the current consecutive streak — stamped
|
|
59
|
+
* onto the `unknown-tool-limit` turn.error so a triager knows which tools were
|
|
60
|
+
* hallucinated. Reset alongside `consecutiveUnknownToolCalls`.
|
|
61
|
+
*/
|
|
62
|
+
this.recentUnknownToolNames = new Set();
|
|
49
63
|
/** Sub-agents declared on the active agent config, keyed by name. */
|
|
50
64
|
this.subAgentsMap = new Map();
|
|
51
65
|
/**
|
|
@@ -494,13 +508,17 @@ export class ChatDriver extends EventTarget {
|
|
|
494
508
|
}
|
|
495
509
|
catch (e) {
|
|
496
510
|
logger.error('ChatDriver error:', e);
|
|
497
|
-
|
|
511
|
+
recordTurnError(this.sessionKey, 'exception', {
|
|
498
512
|
phase: 'sendMessage',
|
|
499
513
|
agent: this.activeAgentName,
|
|
500
514
|
provider: this.lastResolvedProviderName,
|
|
515
|
+
name: e instanceof Error ? e.name : undefined,
|
|
501
516
|
message: e instanceof Error ? e.message : String(e),
|
|
502
517
|
});
|
|
503
|
-
this.appendToHistory({
|
|
518
|
+
this.appendToHistory({
|
|
519
|
+
role: 'assistant',
|
|
520
|
+
content: 'Sorry, something went wrong on my end. Please try again in a moment.',
|
|
521
|
+
});
|
|
504
522
|
return { reason: 'done' };
|
|
505
523
|
}
|
|
506
524
|
finally {
|
|
@@ -644,13 +662,17 @@ export class ChatDriver extends EventTarget {
|
|
|
644
662
|
}
|
|
645
663
|
catch (e) {
|
|
646
664
|
logger.error('ChatDriver error:', e);
|
|
647
|
-
|
|
665
|
+
recordTurnError(this.sessionKey, 'exception', {
|
|
648
666
|
phase: 'continueFromHistory',
|
|
649
667
|
agent: this.activeAgentName,
|
|
650
668
|
provider: this.lastResolvedProviderName,
|
|
669
|
+
name: e instanceof Error ? e.name : undefined,
|
|
651
670
|
message: e instanceof Error ? e.message : String(e),
|
|
652
671
|
});
|
|
653
|
-
this.appendToHistory({
|
|
672
|
+
this.appendToHistory({
|
|
673
|
+
role: 'assistant',
|
|
674
|
+
content: 'Sorry, something went wrong on my end. Please try again in a moment.',
|
|
675
|
+
});
|
|
654
676
|
return { reason: 'done' };
|
|
655
677
|
}
|
|
656
678
|
finally {
|
|
@@ -798,7 +820,7 @@ export class ChatDriver extends EventTarget {
|
|
|
798
820
|
// oxlint-disable-next-line complexity
|
|
799
821
|
runToolLoop(userInput, attachments, transientPrimer) {
|
|
800
822
|
return __awaiter(this, void 0, void 0, function* () {
|
|
801
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
823
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
802
824
|
if (!this.systemPrompt) {
|
|
803
825
|
logger.warn('ChatDriver: no systemPrompt set. The assistant will have no instructions — provide a systemPrompt via agents config or the foundation-ai-assistant property.');
|
|
804
826
|
}
|
|
@@ -898,18 +920,26 @@ export class ChatDriver extends EventTarget {
|
|
|
898
920
|
malformedAttempts += 1;
|
|
899
921
|
if (malformedAttempts < MAX_MALFORMED_RETRIES) {
|
|
900
922
|
logger.warn(`ChatDriver: MALFORMED_FUNCTION_CALL, retrying (${malformedAttempts}/${MAX_MALFORMED_RETRIES})`);
|
|
923
|
+
recordTurnRetry(this.sessionKey, 'malformed-function-call', {
|
|
924
|
+
agent: this.activeAgentName,
|
|
925
|
+
provider: this.lastResolvedProviderName,
|
|
926
|
+
attempt: malformedAttempts,
|
|
927
|
+
maxAttempts: MAX_MALFORMED_RETRIES,
|
|
928
|
+
finishMessage: e.finishMessage,
|
|
929
|
+
});
|
|
901
930
|
iterations -= 1;
|
|
902
931
|
continue;
|
|
903
932
|
}
|
|
904
933
|
logger.error('ChatDriver: MALFORMED_FUNCTION_CALL, max retries reached');
|
|
905
|
-
|
|
906
|
-
reason: 'malformed-function-call',
|
|
934
|
+
recordTurnError(this.sessionKey, 'malformed-function-call', {
|
|
907
935
|
agent: this.activeAgentName,
|
|
908
936
|
provider: this.lastResolvedProviderName,
|
|
937
|
+
attempts: malformedAttempts,
|
|
938
|
+
finishMessage: e.finishMessage,
|
|
909
939
|
});
|
|
910
940
|
this.appendToHistory({
|
|
911
941
|
role: 'assistant',
|
|
912
|
-
content:
|
|
942
|
+
content: 'While working on your request, I repeatedly called my tools incorrectly. This often works on a second try — would you like me to try again? If it happens again, try breaking your request into smaller steps.',
|
|
913
943
|
});
|
|
914
944
|
return { reason: 'done' };
|
|
915
945
|
}
|
|
@@ -921,18 +951,24 @@ export class ChatDriver extends EventTarget {
|
|
|
921
951
|
emptyResponseAttempts += 1;
|
|
922
952
|
if (emptyResponseAttempts < MAX_EMPTY_RESPONSE_RETRIES) {
|
|
923
953
|
logger.warn(`ChatDriver: empty model response, retrying (${emptyResponseAttempts}/${MAX_EMPTY_RESPONSE_RETRIES})`);
|
|
954
|
+
recordTurnRetry(this.sessionKey, 'empty-response', {
|
|
955
|
+
agent: this.activeAgentName,
|
|
956
|
+
provider: this.lastResolvedProviderName,
|
|
957
|
+
attempt: emptyResponseAttempts,
|
|
958
|
+
maxAttempts: MAX_EMPTY_RESPONSE_RETRIES,
|
|
959
|
+
});
|
|
924
960
|
iterations -= 1;
|
|
925
961
|
continue;
|
|
926
962
|
}
|
|
927
963
|
logger.error('ChatDriver: empty model response after all retries');
|
|
928
|
-
|
|
929
|
-
reason: 'empty-response',
|
|
964
|
+
recordTurnError(this.sessionKey, 'empty-response', {
|
|
930
965
|
agent: this.activeAgentName,
|
|
931
966
|
provider: this.lastResolvedProviderName,
|
|
967
|
+
attempts: emptyResponseAttempts,
|
|
932
968
|
});
|
|
933
969
|
this.appendToHistory({
|
|
934
970
|
role: 'assistant',
|
|
935
|
-
content: '
|
|
971
|
+
content: 'While working on your request, I repeatedly generated a blank response. This often works on a second try — would you like me to try again? If it happens again, try breaking your request into smaller steps.',
|
|
936
972
|
});
|
|
937
973
|
return { reason: 'done' };
|
|
938
974
|
}
|
|
@@ -1014,6 +1050,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1014
1050
|
logger.warn(`ChatDriver: no handler registered for tool "${tc.name}" (${this.consecutiveUnknownToolCalls}/${DEFAULT_MAX_UNKNOWN_TOOL_CALLS}). Available tools: ${Object.keys(this.toolHandlers).join(', ') || '(none)'}`);
|
|
1015
1051
|
executedById.set(tc.id, { toolCallId: tc.id, content: `Unknown tool: ${tc.name}` });
|
|
1016
1052
|
unknownToolIds.add(tc.id);
|
|
1053
|
+
this.recentUnknownToolNames.add(tc.name);
|
|
1017
1054
|
if (this.consecutiveUnknownToolCalls >= DEFAULT_MAX_UNKNOWN_TOOL_CALLS) {
|
|
1018
1055
|
hitUnknownToolLimit = true;
|
|
1019
1056
|
}
|
|
@@ -1051,6 +1088,7 @@ export class ChatDriver extends EventTarget {
|
|
|
1051
1088
|
if (anyRealToolExecuted) {
|
|
1052
1089
|
this.consecutiveFoldOps = 0;
|
|
1053
1090
|
this.consecutiveUnknownToolCalls = 0;
|
|
1091
|
+
this.recentUnknownToolNames.clear();
|
|
1054
1092
|
}
|
|
1055
1093
|
// Tag tool calls with fold UI metadata before appending results
|
|
1056
1094
|
const foldPath = this.foldStack.map((f) => f.foldName);
|
|
@@ -1105,14 +1143,23 @@ export class ChatDriver extends EventTarget {
|
|
|
1105
1143
|
}
|
|
1106
1144
|
if (hitUnknownToolLimit) {
|
|
1107
1145
|
logger.error(`ChatDriver: unknown-tool limit (${DEFAULT_MAX_UNKNOWN_TOOL_CALLS}) reached — stopping`);
|
|
1108
|
-
|
|
1109
|
-
|
|
1146
|
+
const unknownTools = [
|
|
1147
|
+
...new Set([
|
|
1148
|
+
...this.recentUnknownToolNames,
|
|
1149
|
+
...((_h = response.toolCalls) !== null && _h !== void 0 ? _h : [])
|
|
1150
|
+
.filter((tc) => unknownToolIds.has(tc.id))
|
|
1151
|
+
.map((tc) => tc.name),
|
|
1152
|
+
]),
|
|
1153
|
+
];
|
|
1154
|
+
recordTurnError(this.sessionKey, 'unknown-tool-limit', {
|
|
1110
1155
|
agent: this.activeAgentName,
|
|
1111
1156
|
provider: this.lastResolvedProviderName,
|
|
1157
|
+
unknownTools,
|
|
1158
|
+
availableTools: Object.keys(this.toolHandlers),
|
|
1112
1159
|
});
|
|
1113
1160
|
this.appendToHistory({
|
|
1114
1161
|
role: 'assistant',
|
|
1115
|
-
content: "I'm sorry, I repeatedly tried to use tools that
|
|
1162
|
+
content: "I'm sorry, I repeatedly tried to use tools that aren't available to me, so I couldn't complete that. If a 'Download agent log' option appears in the Settings (cog) menu, you can download the log and share it with whoever set up this assistant to help fix the issue.",
|
|
1116
1163
|
});
|
|
1117
1164
|
return { reason: 'done' };
|
|
1118
1165
|
}
|
|
@@ -1129,10 +1176,11 @@ export class ChatDriver extends EventTarget {
|
|
|
1129
1176
|
}
|
|
1130
1177
|
if (iterations >= this.maxToolIterations) {
|
|
1131
1178
|
logger.warn('ChatDriver: reached max tool iterations, stopping');
|
|
1132
|
-
|
|
1133
|
-
reason: 'max-iterations',
|
|
1179
|
+
recordTurnError(this.sessionKey, 'max-iterations', {
|
|
1134
1180
|
agent: this.activeAgentName,
|
|
1135
1181
|
provider: this.lastResolvedProviderName,
|
|
1182
|
+
iterations,
|
|
1183
|
+
limit: this.maxToolIterations,
|
|
1136
1184
|
});
|
|
1137
1185
|
this.appendToHistory({
|
|
1138
1186
|
role: 'assistant',
|
|
@@ -87,9 +87,9 @@ let AiChatInteractionWrapper = class AiChatInteractionWrapper extends GenesisEle
|
|
|
87
87
|
this._resizeObserver = undefined;
|
|
88
88
|
this.container.replaceChildren();
|
|
89
89
|
if (!customElements.get(this.componentName)) {
|
|
90
|
-
logger.warn(`Interactive component "
|
|
90
|
+
logger.warn(`Interactive component "${this.componentName}" is not registered in the customElements registry.`);
|
|
91
91
|
const errorDiv = document.createElement('div');
|
|
92
|
-
errorDiv.textContent = `Error: Component "
|
|
92
|
+
errorDiv.textContent = `Error: Component "${this.componentName}" is not available in this application.`;
|
|
93
93
|
this.container.appendChild(errorDiv);
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
@@ -121,7 +121,7 @@ let AiChatInteractionWrapper = class AiChatInteractionWrapper extends GenesisEle
|
|
|
121
121
|
catch (e) {
|
|
122
122
|
logger.error(`Failed to create interactive component: \${this.componentName}`, e);
|
|
123
123
|
const errorDiv = document.createElement('div');
|
|
124
|
-
errorDiv.textContent = `Error: Could not load component
|
|
124
|
+
errorDiv.textContent = `Error: Could not load component ${this.componentName}`;
|
|
125
125
|
this.container.appendChild(errorDiv);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
@@ -222,7 +222,7 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
222
222
|
}
|
|
223
223
|
handoffs += 1;
|
|
224
224
|
if (handoffs > this.maxHandoffs) {
|
|
225
|
-
this.appendInlineMessage(`I wasn't able to fully complete your request — the task required more hand-offs between specialists than allowed
|
|
225
|
+
this.appendInlineMessage(`I wasn't able to fully complete your request — the task required more hand-offs between specialists than allowed. Please try breaking your request into smaller steps.`);
|
|
226
226
|
break;
|
|
227
227
|
}
|
|
228
228
|
handoffSummary = result.summary;
|
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
* there's no reason to pay immutable-reducer cost or flood the Redux DevTools
|
|
17
17
|
* action log with debug noise.
|
|
18
18
|
*
|
|
19
|
-
* Surfaced
|
|
20
|
-
*
|
|
19
|
+
* Surfaced in `getDebugLog().timeline`, merged chronologically with messages and
|
|
20
|
+
* turn snapshots. Ring-buffered — the oldest non-`high` events are evicted first
|
|
21
|
+
* while `high`-importance failure events are retained — so a long-lived session
|
|
22
|
+
* stays bounded in normal use without ever dropping a failure signal.
|
|
21
23
|
*
|
|
22
24
|
* @internal
|
|
23
25
|
*/
|
|
@@ -45,6 +47,7 @@ export const META_EVENT_IMPORTANCE = {
|
|
|
45
47
|
'state.changed': 'normal',
|
|
46
48
|
'turn.start': 'normal',
|
|
47
49
|
'turn.end': 'normal',
|
|
50
|
+
'turn.retry': 'normal',
|
|
48
51
|
'agent.handoff': 'normal',
|
|
49
52
|
'agent.pinned': 'normal',
|
|
50
53
|
'agent.unpinned': 'normal',
|
|
@@ -57,13 +60,20 @@ export const META_EVENT_IMPORTANCE = {
|
|
|
57
60
|
'panel.toggled': 'low',
|
|
58
61
|
'attachment.added': 'low',
|
|
59
62
|
};
|
|
60
|
-
/**
|
|
61
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Soft ring-buffer cap. Once exceeded, the oldest *non-`high`* event is evicted;
|
|
65
|
+
* `high`-importance events (failures and hard limits) are never dropped — they're
|
|
66
|
+
* the whole reason the log exists. So a buffer dominated by `high` events is
|
|
67
|
+
* allowed to float above this cap rather than lose a failure signal; in normal
|
|
68
|
+
* use the frequent `low`/`normal` events keep it near the cap. Entries are cheap.
|
|
69
|
+
*/
|
|
70
|
+
const DEFAULT_MAX_META_EVENTS = 400;
|
|
62
71
|
const registry = new Map();
|
|
63
72
|
/**
|
|
64
|
-
* Append a meta event to the timeline for `key`.
|
|
65
|
-
*
|
|
66
|
-
*
|
|
73
|
+
* Append a meta event to the timeline for `key`. Once the buffer exceeds
|
|
74
|
+
* {@link DEFAULT_MAX_META_EVENTS}, evicts the oldest *non-`high`* event;
|
|
75
|
+
* `high`-importance events are never evicted. An empty `key` is bucketed under
|
|
76
|
+
* `''`, matching how drivers/stores handle an absent session identity, so
|
|
67
77
|
* callers never need to guard.
|
|
68
78
|
*/
|
|
69
79
|
export function recordMetaEvent(key, type, detail) {
|
|
@@ -81,9 +91,34 @@ export function recordMetaEvent(key, type, detail) {
|
|
|
81
91
|
});
|
|
82
92
|
buffer.next += 1;
|
|
83
93
|
if (buffer.events.length > DEFAULT_MAX_META_EVENTS) {
|
|
84
|
-
|
|
94
|
+
// Drop the oldest event that isn't `high` importance. `high` events
|
|
95
|
+
// (failures/limits) are retained even past the cap — losing a failure
|
|
96
|
+
// signal is worse than briefly exceeding the buffer size. If every retained
|
|
97
|
+
// event is `high` (all-failures session), nothing is evicted and the buffer
|
|
98
|
+
// grows; in practice frequent `low`/`normal` events keep it bounded.
|
|
99
|
+
const evictAt = buffer.events.findIndex((e) => e.importance !== 'high');
|
|
100
|
+
if (evictAt !== -1) {
|
|
101
|
+
buffer.events.splice(evictAt, 1);
|
|
102
|
+
}
|
|
85
103
|
}
|
|
86
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Record a turn-ending failure (`turn.error`, importance `high`). The `reason`
|
|
107
|
+
* is typed so the high-importance triage surface stays consistent; pass any
|
|
108
|
+
* diagnostic specifics (attempt counts, offending tool names, etc.) in `detail`.
|
|
109
|
+
*/
|
|
110
|
+
export function recordTurnError(key, reason, detail) {
|
|
111
|
+
recordMetaEvent(key, 'turn.error', Object.assign({ reason }, detail));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Record a recoverable retry *within* a turn (`turn.retry`, importance `normal`)
|
|
115
|
+
* — e.g. one malformed/empty attempt that will be retried, as distinct from the
|
|
116
|
+
* final attempt that bails out as a `turn.error`. Include `attempt`/`maxAttempts`
|
|
117
|
+
* in `detail` so the timeline shows which try this was.
|
|
118
|
+
*/
|
|
119
|
+
export function recordTurnRetry(key, reason, detail) {
|
|
120
|
+
recordMetaEvent(key, 'turn.retry', Object.assign({ reason }, detail));
|
|
121
|
+
}
|
|
87
122
|
/** Returns the meta-event timeline for `key`, or an empty array if none recorded. */
|
|
88
123
|
export function getMetaEvents(key) {
|
|
89
124
|
var _a, _b;
|
|
@@ -102,8 +137,8 @@ export const DEBUG_LOG_README = [
|
|
|
102
137
|
"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
138
|
"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
139
|
"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 —
|
|
140
|
+
"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, retries, 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. 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.",
|
|
141
|
+
'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), 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, plus reason-specific diagnostics: attempts, finishMessage, unknownTools + availableTools, iterations + limit, or name + message for exceptions), 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
142
|
"`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
143
|
'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
144
|
];
|
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.451.
|
|
4
|
+
"version": "14.451.2",
|
|
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.451.
|
|
68
|
-
"@genesislcap/genx": "14.451.
|
|
69
|
-
"@genesislcap/rollup-builder": "14.451.
|
|
70
|
-
"@genesislcap/ts-builder": "14.451.
|
|
71
|
-
"@genesislcap/uvu-playwright-builder": "14.451.
|
|
72
|
-
"@genesislcap/vite-builder": "14.451.
|
|
73
|
-
"@genesislcap/webpack-builder": "14.451.
|
|
67
|
+
"@genesislcap/foundation-testing": "14.451.2",
|
|
68
|
+
"@genesislcap/genx": "14.451.2",
|
|
69
|
+
"@genesislcap/rollup-builder": "14.451.2",
|
|
70
|
+
"@genesislcap/ts-builder": "14.451.2",
|
|
71
|
+
"@genesislcap/uvu-playwright-builder": "14.451.2",
|
|
72
|
+
"@genesislcap/vite-builder": "14.451.2",
|
|
73
|
+
"@genesislcap/webpack-builder": "14.451.2",
|
|
74
74
|
"@types/dompurify": "^3.0.5",
|
|
75
75
|
"@types/marked": "^5.0.2"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@genesislcap/foundation-ai": "14.451.
|
|
79
|
-
"@genesislcap/foundation-logger": "14.451.
|
|
80
|
-
"@genesislcap/foundation-redux": "14.451.
|
|
81
|
-
"@genesislcap/foundation-ui": "14.451.
|
|
82
|
-
"@genesislcap/foundation-utils": "14.451.
|
|
83
|
-
"@genesislcap/rapid-design-system": "14.451.
|
|
84
|
-
"@genesislcap/web-core": "14.451.
|
|
78
|
+
"@genesislcap/foundation-ai": "14.451.2",
|
|
79
|
+
"@genesislcap/foundation-logger": "14.451.2",
|
|
80
|
+
"@genesislcap/foundation-redux": "14.451.2",
|
|
81
|
+
"@genesislcap/foundation-ui": "14.451.2",
|
|
82
|
+
"@genesislcap/foundation-utils": "14.451.2",
|
|
83
|
+
"@genesislcap/rapid-design-system": "14.451.2",
|
|
84
|
+
"@genesislcap/web-core": "14.451.2",
|
|
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": "44a0946f3ff1f184fd6fd8f877108b0dbaf2d9e0"
|
|
97
97
|
}
|
|
@@ -22,7 +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
|
+
import { recordMetaEvent, recordTurnError, recordTurnRetry } from '../../state/debug-event-log';
|
|
26
26
|
import { applyHistoryCap } from '../../utils/history-transform';
|
|
27
27
|
import { logger } from '../../utils/logger';
|
|
28
28
|
import { TOOL_FOLD_SYMBOL, type ToolFold } from '../../utils/tool-fold';
|
|
@@ -30,7 +30,15 @@ import type { AiDriver, AllAgentSummary } from '../ai-driver/ai-driver';
|
|
|
30
30
|
|
|
31
31
|
const DEFAULT_MAX_TOOL_ITERATIONS = 50;
|
|
32
32
|
const DEFAULT_MAX_FOLD_OPERATIONS = 5;
|
|
33
|
-
|
|
33
|
+
// TODO: dedup system prompts in-memory to allow raising this cap much higher.
|
|
34
|
+
// Each TurnSnapshot stores the full resolved `systemPrompt`, so the buffer's
|
|
35
|
+
// memory cost scales with cap × prompt size — a stable agent repeats a multi-KB
|
|
36
|
+
// prompt every turn. `getDebugLog` already dedups identical consecutive prompts
|
|
37
|
+
// at export time (`<repeated — identical to turn N>`); applying the same dedup
|
|
38
|
+
// at capture time (store the prompt only when it changes, back-reference
|
|
39
|
+
// otherwise) would make snapshots nearly as cheap as meta events and let this
|
|
40
|
+
// cap reach thousands for full-session capture without the memory blowup.
|
|
41
|
+
const DEFAULT_MAX_TURN_SNAPSHOTS = 400;
|
|
34
42
|
const DEFAULT_MAX_UNKNOWN_TOOL_CALLS = 5;
|
|
35
43
|
const MAX_MALFORMED_RETRIES = 2;
|
|
36
44
|
const MAX_EMPTY_RESPONSE_RETRIES = 3;
|
|
@@ -53,7 +61,7 @@ export type ChatHistoryUpdatedEvent = CustomEvent<ReadonlyArray<ChatMessage>>;
|
|
|
53
61
|
/**
|
|
54
62
|
* One captured frame of what the LLM saw on a single tool-loop iteration.
|
|
55
63
|
* The driver records these as a ring buffer (cap: configurable via
|
|
56
|
-
* `chatConfig.agent.maxTurnSnapshots`, default
|
|
64
|
+
* `chatConfig.agent.maxTurnSnapshots`, default 400) so the export log can show,
|
|
57
65
|
* per turn: which agent was active, the resolved system prompt, the tool names
|
|
58
66
|
* visible to the LLM, and any agent-supplied debug snapshot (e.g. machine
|
|
59
67
|
* state for stateful agents).
|
|
@@ -178,6 +186,12 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
178
186
|
private consecutiveFoldOps = 0;
|
|
179
187
|
/** Consecutive unknown-tool calls without a real tool call. Reset on real tool execution. */
|
|
180
188
|
private consecutiveUnknownToolCalls = 0;
|
|
189
|
+
/**
|
|
190
|
+
* Distinct unknown-tool names seen in the current consecutive streak — stamped
|
|
191
|
+
* onto the `unknown-tool-limit` turn.error so a triager knows which tools were
|
|
192
|
+
* hallucinated. Reset alongside `consecutiveUnknownToolCalls`.
|
|
193
|
+
*/
|
|
194
|
+
private readonly recentUnknownToolNames = new Set<string>();
|
|
181
195
|
private readonly maxFoldOperations: number;
|
|
182
196
|
|
|
183
197
|
/** Sub-agents declared on the active agent config, keyed by name. */
|
|
@@ -700,13 +714,17 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
700
714
|
return await this.runToolLoop(userInput, attachments);
|
|
701
715
|
} catch (e) {
|
|
702
716
|
logger.error('ChatDriver error:', e);
|
|
703
|
-
|
|
717
|
+
recordTurnError(this.sessionKey, 'exception', {
|
|
704
718
|
phase: 'sendMessage',
|
|
705
719
|
agent: this.activeAgentName,
|
|
706
720
|
provider: this.lastResolvedProviderName,
|
|
721
|
+
name: e instanceof Error ? e.name : undefined,
|
|
707
722
|
message: e instanceof Error ? e.message : String(e),
|
|
708
723
|
});
|
|
709
|
-
this.appendToHistory({
|
|
724
|
+
this.appendToHistory({
|
|
725
|
+
role: 'assistant',
|
|
726
|
+
content: 'Sorry, something went wrong on my end. Please try again in a moment.',
|
|
727
|
+
});
|
|
710
728
|
return { reason: 'done' };
|
|
711
729
|
} finally {
|
|
712
730
|
recordMetaEvent(this.sessionKey, 'turn.end', {
|
|
@@ -885,13 +903,17 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
885
903
|
return await this.runToolLoop('', undefined, transientPrimer);
|
|
886
904
|
} catch (e) {
|
|
887
905
|
logger.error('ChatDriver error:', e);
|
|
888
|
-
|
|
906
|
+
recordTurnError(this.sessionKey, 'exception', {
|
|
889
907
|
phase: 'continueFromHistory',
|
|
890
908
|
agent: this.activeAgentName,
|
|
891
909
|
provider: this.lastResolvedProviderName,
|
|
910
|
+
name: e instanceof Error ? e.name : undefined,
|
|
892
911
|
message: e instanceof Error ? e.message : String(e),
|
|
893
912
|
});
|
|
894
|
-
this.appendToHistory({
|
|
913
|
+
this.appendToHistory({
|
|
914
|
+
role: 'assistant',
|
|
915
|
+
content: 'Sorry, something went wrong on my end. Please try again in a moment.',
|
|
916
|
+
});
|
|
895
917
|
return { reason: 'done' };
|
|
896
918
|
} finally {
|
|
897
919
|
recordMetaEvent(this.sessionKey, 'turn.end', {
|
|
@@ -1183,19 +1205,27 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1183
1205
|
logger.warn(
|
|
1184
1206
|
`ChatDriver: MALFORMED_FUNCTION_CALL, retrying (${malformedAttempts}/${MAX_MALFORMED_RETRIES})`,
|
|
1185
1207
|
);
|
|
1208
|
+
recordTurnRetry(this.sessionKey, 'malformed-function-call', {
|
|
1209
|
+
agent: this.activeAgentName,
|
|
1210
|
+
provider: this.lastResolvedProviderName,
|
|
1211
|
+
attempt: malformedAttempts,
|
|
1212
|
+
maxAttempts: MAX_MALFORMED_RETRIES,
|
|
1213
|
+
finishMessage: e.finishMessage,
|
|
1214
|
+
});
|
|
1186
1215
|
iterations -= 1;
|
|
1187
1216
|
continue;
|
|
1188
1217
|
}
|
|
1189
1218
|
logger.error('ChatDriver: MALFORMED_FUNCTION_CALL, max retries reached');
|
|
1190
|
-
|
|
1191
|
-
reason: 'malformed-function-call',
|
|
1219
|
+
recordTurnError(this.sessionKey, 'malformed-function-call', {
|
|
1192
1220
|
agent: this.activeAgentName,
|
|
1193
1221
|
provider: this.lastResolvedProviderName,
|
|
1222
|
+
attempts: malformedAttempts,
|
|
1223
|
+
finishMessage: e.finishMessage,
|
|
1194
1224
|
});
|
|
1195
1225
|
this.appendToHistory({
|
|
1196
1226
|
role: 'assistant',
|
|
1197
1227
|
content:
|
|
1198
|
-
|
|
1228
|
+
'While working on your request, I repeatedly called my tools incorrectly. This often works on a second try — would you like me to try again? If it happens again, try breaking your request into smaller steps.',
|
|
1199
1229
|
});
|
|
1200
1230
|
return { reason: 'done' };
|
|
1201
1231
|
}
|
|
@@ -1211,18 +1241,25 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1211
1241
|
logger.warn(
|
|
1212
1242
|
`ChatDriver: empty model response, retrying (${emptyResponseAttempts}/${MAX_EMPTY_RESPONSE_RETRIES})`,
|
|
1213
1243
|
);
|
|
1244
|
+
recordTurnRetry(this.sessionKey, 'empty-response', {
|
|
1245
|
+
agent: this.activeAgentName,
|
|
1246
|
+
provider: this.lastResolvedProviderName,
|
|
1247
|
+
attempt: emptyResponseAttempts,
|
|
1248
|
+
maxAttempts: MAX_EMPTY_RESPONSE_RETRIES,
|
|
1249
|
+
});
|
|
1214
1250
|
iterations -= 1;
|
|
1215
1251
|
continue;
|
|
1216
1252
|
}
|
|
1217
1253
|
logger.error('ChatDriver: empty model response after all retries');
|
|
1218
|
-
|
|
1219
|
-
reason: 'empty-response',
|
|
1254
|
+
recordTurnError(this.sessionKey, 'empty-response', {
|
|
1220
1255
|
agent: this.activeAgentName,
|
|
1221
1256
|
provider: this.lastResolvedProviderName,
|
|
1257
|
+
attempts: emptyResponseAttempts,
|
|
1222
1258
|
});
|
|
1223
1259
|
this.appendToHistory({
|
|
1224
1260
|
role: 'assistant',
|
|
1225
|
-
content:
|
|
1261
|
+
content:
|
|
1262
|
+
'While working on your request, I repeatedly generated a blank response. This often works on a second try — would you like me to try again? If it happens again, try breaking your request into smaller steps.',
|
|
1226
1263
|
});
|
|
1227
1264
|
return { reason: 'done' };
|
|
1228
1265
|
} else if (isThinkingStep) {
|
|
@@ -1318,6 +1355,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1318
1355
|
);
|
|
1319
1356
|
executedById.set(tc.id, { toolCallId: tc.id, content: `Unknown tool: ${tc.name}` });
|
|
1320
1357
|
unknownToolIds.add(tc.id);
|
|
1358
|
+
this.recentUnknownToolNames.add(tc.name);
|
|
1321
1359
|
if (this.consecutiveUnknownToolCalls >= DEFAULT_MAX_UNKNOWN_TOOL_CALLS) {
|
|
1322
1360
|
hitUnknownToolLimit = true;
|
|
1323
1361
|
}
|
|
@@ -1357,6 +1395,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1357
1395
|
if (anyRealToolExecuted) {
|
|
1358
1396
|
this.consecutiveFoldOps = 0;
|
|
1359
1397
|
this.consecutiveUnknownToolCalls = 0;
|
|
1398
|
+
this.recentUnknownToolNames.clear();
|
|
1360
1399
|
}
|
|
1361
1400
|
|
|
1362
1401
|
// Tag tool calls with fold UI metadata before appending results
|
|
@@ -1423,15 +1462,24 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1423
1462
|
logger.error(
|
|
1424
1463
|
`ChatDriver: unknown-tool limit (${DEFAULT_MAX_UNKNOWN_TOOL_CALLS}) reached — stopping`,
|
|
1425
1464
|
);
|
|
1426
|
-
|
|
1427
|
-
|
|
1465
|
+
const unknownTools = [
|
|
1466
|
+
...new Set([
|
|
1467
|
+
...this.recentUnknownToolNames,
|
|
1468
|
+
...(response.toolCalls ?? [])
|
|
1469
|
+
.filter((tc) => unknownToolIds.has(tc.id))
|
|
1470
|
+
.map((tc) => tc.name),
|
|
1471
|
+
]),
|
|
1472
|
+
];
|
|
1473
|
+
recordTurnError(this.sessionKey, 'unknown-tool-limit', {
|
|
1428
1474
|
agent: this.activeAgentName,
|
|
1429
1475
|
provider: this.lastResolvedProviderName,
|
|
1476
|
+
unknownTools,
|
|
1477
|
+
availableTools: Object.keys(this.toolHandlers),
|
|
1430
1478
|
});
|
|
1431
1479
|
this.appendToHistory({
|
|
1432
1480
|
role: 'assistant',
|
|
1433
1481
|
content:
|
|
1434
|
-
"I'm sorry, I repeatedly tried to use tools that
|
|
1482
|
+
"I'm sorry, I repeatedly tried to use tools that aren't available to me, so I couldn't complete that. If a 'Download agent log' option appears in the Settings (cog) menu, you can download the log and share it with whoever set up this assistant to help fix the issue.",
|
|
1435
1483
|
});
|
|
1436
1484
|
return { reason: 'done' };
|
|
1437
1485
|
}
|
|
@@ -1454,10 +1502,11 @@ export class ChatDriver extends EventTarget implements AiDriver {
|
|
|
1454
1502
|
|
|
1455
1503
|
if (iterations >= this.maxToolIterations) {
|
|
1456
1504
|
logger.warn('ChatDriver: reached max tool iterations, stopping');
|
|
1457
|
-
|
|
1458
|
-
reason: 'max-iterations',
|
|
1505
|
+
recordTurnError(this.sessionKey, 'max-iterations', {
|
|
1459
1506
|
agent: this.activeAgentName,
|
|
1460
1507
|
provider: this.lastResolvedProviderName,
|
|
1508
|
+
iterations,
|
|
1509
|
+
limit: this.maxToolIterations,
|
|
1461
1510
|
});
|
|
1462
1511
|
this.appendToHistory({
|
|
1463
1512
|
role: 'assistant',
|
|
@@ -109,10 +109,10 @@ export class AiChatInteractionWrapper extends GenesisElement {
|
|
|
109
109
|
|
|
110
110
|
if (!customElements.get(this.componentName)) {
|
|
111
111
|
logger.warn(
|
|
112
|
-
`Interactive component "
|
|
112
|
+
`Interactive component "${this.componentName}" is not registered in the customElements registry.`,
|
|
113
113
|
);
|
|
114
114
|
const errorDiv = document.createElement('div');
|
|
115
|
-
errorDiv.textContent = `Error: Component "
|
|
115
|
+
errorDiv.textContent = `Error: Component "${this.componentName}" is not available in this application.`;
|
|
116
116
|
this.container.appendChild(errorDiv);
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
@@ -150,7 +150,7 @@ export class AiChatInteractionWrapper extends GenesisElement {
|
|
|
150
150
|
} catch (e) {
|
|
151
151
|
logger.error(`Failed to create interactive component: \${this.componentName}`, e);
|
|
152
152
|
const errorDiv = document.createElement('div');
|
|
153
|
-
errorDiv.textContent = `Error: Could not load component
|
|
153
|
+
errorDiv.textContent = `Error: Could not load component ${this.componentName}`;
|
|
154
154
|
this.container.appendChild(errorDiv);
|
|
155
155
|
}
|
|
156
156
|
}
|
|
@@ -316,7 +316,7 @@ export class OrchestratingDriver extends EventTarget implements AiDriver {
|
|
|
316
316
|
handoffs += 1;
|
|
317
317
|
if (handoffs > this.maxHandoffs) {
|
|
318
318
|
this.appendInlineMessage(
|
|
319
|
-
`I wasn't able to fully complete your request — the task required more hand-offs between specialists than allowed
|
|
319
|
+
`I wasn't able to fully complete your request — the task required more hand-offs between specialists than allowed. Please try breaking your request into smaller steps.`,
|
|
320
320
|
);
|
|
321
321
|
break;
|
|
322
322
|
}
|
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
* there's no reason to pay immutable-reducer cost or flood the Redux DevTools
|
|
17
17
|
* action log with debug noise.
|
|
18
18
|
*
|
|
19
|
-
* Surfaced
|
|
20
|
-
*
|
|
19
|
+
* Surfaced in `getDebugLog().timeline`, merged chronologically with messages and
|
|
20
|
+
* turn snapshots. Ring-buffered — the oldest non-`high` events are evicted first
|
|
21
|
+
* while `high`-importance failure events are retained — so a long-lived session
|
|
22
|
+
* stays bounded in normal use without ever dropping a failure signal.
|
|
21
23
|
*
|
|
22
24
|
* @internal
|
|
23
25
|
*/
|
|
@@ -40,6 +42,7 @@ export type MetaEventType =
|
|
|
40
42
|
| 'state.changed'
|
|
41
43
|
| 'turn.start'
|
|
42
44
|
| 'turn.end'
|
|
45
|
+
| 'turn.retry'
|
|
43
46
|
| 'turn.error'
|
|
44
47
|
| 'tool.failed'
|
|
45
48
|
// Routing / providers
|
|
@@ -91,6 +94,7 @@ export const META_EVENT_IMPORTANCE: Record<MetaEventType, MetaEventImportance> =
|
|
|
91
94
|
'state.changed': 'normal',
|
|
92
95
|
'turn.start': 'normal',
|
|
93
96
|
'turn.end': 'normal',
|
|
97
|
+
'turn.retry': 'normal',
|
|
94
98
|
'agent.handoff': 'normal',
|
|
95
99
|
'agent.pinned': 'normal',
|
|
96
100
|
'agent.unpinned': 'normal',
|
|
@@ -119,8 +123,14 @@ export interface MetaEvent {
|
|
|
119
123
|
detail?: Record<string, unknown>;
|
|
120
124
|
}
|
|
121
125
|
|
|
122
|
-
/**
|
|
123
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Soft ring-buffer cap. Once exceeded, the oldest *non-`high`* event is evicted;
|
|
128
|
+
* `high`-importance events (failures and hard limits) are never dropped — they're
|
|
129
|
+
* the whole reason the log exists. So a buffer dominated by `high` events is
|
|
130
|
+
* allowed to float above this cap rather than lose a failure signal; in normal
|
|
131
|
+
* use the frequent `low`/`normal` events keep it near the cap. Entries are cheap.
|
|
132
|
+
*/
|
|
133
|
+
const DEFAULT_MAX_META_EVENTS = 400;
|
|
124
134
|
|
|
125
135
|
interface MetaEventBuffer {
|
|
126
136
|
events: MetaEvent[];
|
|
@@ -131,9 +141,10 @@ interface MetaEventBuffer {
|
|
|
131
141
|
const registry = new Map<string, MetaEventBuffer>();
|
|
132
142
|
|
|
133
143
|
/**
|
|
134
|
-
* Append a meta event to the timeline for `key`.
|
|
135
|
-
*
|
|
136
|
-
*
|
|
144
|
+
* Append a meta event to the timeline for `key`. Once the buffer exceeds
|
|
145
|
+
* {@link DEFAULT_MAX_META_EVENTS}, evicts the oldest *non-`high`* event;
|
|
146
|
+
* `high`-importance events are never evicted. An empty `key` is bucketed under
|
|
147
|
+
* `''`, matching how drivers/stores handle an absent session identity, so
|
|
137
148
|
* callers never need to guard.
|
|
138
149
|
*/
|
|
139
150
|
export function recordMetaEvent(
|
|
@@ -155,10 +166,63 @@ export function recordMetaEvent(
|
|
|
155
166
|
});
|
|
156
167
|
buffer.next += 1;
|
|
157
168
|
if (buffer.events.length > DEFAULT_MAX_META_EVENTS) {
|
|
158
|
-
|
|
169
|
+
// Drop the oldest event that isn't `high` importance. `high` events
|
|
170
|
+
// (failures/limits) are retained even past the cap — losing a failure
|
|
171
|
+
// signal is worse than briefly exceeding the buffer size. If every retained
|
|
172
|
+
// event is `high` (all-failures session), nothing is evicted and the buffer
|
|
173
|
+
// grows; in practice frequent `low`/`normal` events keep it bounded.
|
|
174
|
+
const evictAt = buffer.events.findIndex((e) => e.importance !== 'high');
|
|
175
|
+
if (evictAt !== -1) {
|
|
176
|
+
buffer.events.splice(evictAt, 1);
|
|
177
|
+
}
|
|
159
178
|
}
|
|
160
179
|
}
|
|
161
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Why a turn failed or was retried — stamped as `detail.reason` on `turn.error`
|
|
183
|
+
* and `turn.retry` events. Enumerated so the set stays in sync with the README
|
|
184
|
+
* and call sites can't drift to ad-hoc strings.
|
|
185
|
+
*
|
|
186
|
+
* - `exception` — an uncaught error escaped the tool loop (catch-all).
|
|
187
|
+
* - `malformed-function-call`— the provider returned an unparseable tool call.
|
|
188
|
+
* - `empty-response` — the model returned no content and no tool calls.
|
|
189
|
+
* - `unknown-tool-limit` — the model repeatedly called tools that don't exist.
|
|
190
|
+
* - `max-iterations` — the tool loop hit its iteration cap.
|
|
191
|
+
*/
|
|
192
|
+
export type TurnFailureReason =
|
|
193
|
+
| 'exception'
|
|
194
|
+
| 'malformed-function-call'
|
|
195
|
+
| 'empty-response'
|
|
196
|
+
| 'unknown-tool-limit'
|
|
197
|
+
| 'max-iterations';
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Record a turn-ending failure (`turn.error`, importance `high`). The `reason`
|
|
201
|
+
* is typed so the high-importance triage surface stays consistent; pass any
|
|
202
|
+
* diagnostic specifics (attempt counts, offending tool names, etc.) in `detail`.
|
|
203
|
+
*/
|
|
204
|
+
export function recordTurnError(
|
|
205
|
+
key: string,
|
|
206
|
+
reason: TurnFailureReason,
|
|
207
|
+
detail?: Record<string, unknown>,
|
|
208
|
+
): void {
|
|
209
|
+
recordMetaEvent(key, 'turn.error', { reason, ...detail });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Record a recoverable retry *within* a turn (`turn.retry`, importance `normal`)
|
|
214
|
+
* — e.g. one malformed/empty attempt that will be retried, as distinct from the
|
|
215
|
+
* final attempt that bails out as a `turn.error`. Include `attempt`/`maxAttempts`
|
|
216
|
+
* in `detail` so the timeline shows which try this was.
|
|
217
|
+
*/
|
|
218
|
+
export function recordTurnRetry(
|
|
219
|
+
key: string,
|
|
220
|
+
reason: TurnFailureReason,
|
|
221
|
+
detail?: Record<string, unknown>,
|
|
222
|
+
): void {
|
|
223
|
+
recordMetaEvent(key, 'turn.retry', { reason, ...detail });
|
|
224
|
+
}
|
|
225
|
+
|
|
162
226
|
/** Returns the meta-event timeline for `key`, or an empty array if none recorded. */
|
|
163
227
|
export function getMetaEvents(key: string): ReadonlyArray<MetaEvent> {
|
|
164
228
|
return registry.get(key)?.events ?? [];
|
|
@@ -177,8 +241,8 @@ export const DEBUG_LOG_README: readonly string[] = [
|
|
|
177
241
|
"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.",
|
|
178
242
|
"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.",
|
|
179
243
|
"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'.",
|
|
180
|
-
"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.",
|
|
181
|
-
'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 —
|
|
244
|
+
"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, retries, 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. 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.",
|
|
245
|
+
'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), 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, plus reason-specific diagnostics: attempts, finishMessage, unknownTools + availableTools, iterations + limit, or name + message for exceptions), 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.',
|
|
182
246
|
"`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.",
|
|
183
247
|
'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.',
|
|
184
248
|
];
|