@genesislcap/ai-assistant 14.451.1 → 14.451.2-FUI-2550.1

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.
@@ -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 40) 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",
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",
@@ -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 under `getDebugLog().meta.events`. Ring-buffered so a long-lived
1566
- * session can't grow the timeline unbounded.
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 40) so the export log can show,
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 40) so the export log can show,
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":"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"}
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 under `getDebugLog().meta.events`. Ring-buffered so a long-lived
20
- * session can't grow the timeline unbounded.
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`. Evicts the oldest entry once
61
- * the buffer exceeds {@link DEFAULT_MAX_META_EVENTS}. An empty `key` is bucketed
62
- * under `''`, matching how drivers/stores handle an absent session identity, so
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;;;;;;;;;;;;;;;;;;;;;;GAsBG;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,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,CA2B5E,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;AAaD;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,aAAa,EACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,IAAI,CAiBN;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"}
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
- const DEFAULT_MAX_TURN_SNAPSHOTS = 40;
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
- recordMetaEvent(this.sessionKey, 'turn.error', {
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({ role: 'assistant', content: 'Sorry, something went wrong.' });
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
- recordMetaEvent(this.sessionKey, 'turn.error', {
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({ role: 'assistant', content: 'Sorry, something went wrong.' });
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
- recordMetaEvent(this.sessionKey, 'turn.error', {
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: "I'm sorry, I wasn't able to complete that request. Please try rephrasing or breaking it into smaller steps.",
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
- recordMetaEvent(this.sessionKey, 'turn.error', {
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: 'Remote agent returned no response.',
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
- recordMetaEvent(this.sessionKey, 'turn.error', {
1109
- reason: 'unknown-tool-limit',
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 don't exist. Please check your agent configuration or try rephrasing your request.",
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
- recordMetaEvent(this.sessionKey, 'turn.error', {
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 "\${this.componentName}" is not registered in the customElements registry.`);
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 "\${this.componentName}" is not available in this application.`;
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 \${this.componentName}`;
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 (max: ${this.maxHandoffs}). Please try breaking your request into smaller steps.`);
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 under `getDebugLog().meta.events`. Ring-buffered so a long-lived
20
- * session can't grow the timeline unbounded.
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
- /** Default ring-buffer cap. ~5× the turn-snapshot cap — entries are cheap. */
61
- const DEFAULT_MAX_META_EVENTS = 200;
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`. 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
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
- buffer.events.shift();
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 — 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.',
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.1",
4
+ "version": "14.451.2-FUI-2550.1",
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.1",
68
- "@genesislcap/genx": "14.451.1",
69
- "@genesislcap/rollup-builder": "14.451.1",
70
- "@genesislcap/ts-builder": "14.451.1",
71
- "@genesislcap/uvu-playwright-builder": "14.451.1",
72
- "@genesislcap/vite-builder": "14.451.1",
73
- "@genesislcap/webpack-builder": "14.451.1",
67
+ "@genesislcap/foundation-testing": "14.451.2-FUI-2550.1",
68
+ "@genesislcap/genx": "14.451.2-FUI-2550.1",
69
+ "@genesislcap/rollup-builder": "14.451.2-FUI-2550.1",
70
+ "@genesislcap/ts-builder": "14.451.2-FUI-2550.1",
71
+ "@genesislcap/uvu-playwright-builder": "14.451.2-FUI-2550.1",
72
+ "@genesislcap/vite-builder": "14.451.2-FUI-2550.1",
73
+ "@genesislcap/webpack-builder": "14.451.2-FUI-2550.1",
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.1",
79
- "@genesislcap/foundation-logger": "14.451.1",
80
- "@genesislcap/foundation-redux": "14.451.1",
81
- "@genesislcap/foundation-ui": "14.451.1",
82
- "@genesislcap/foundation-utils": "14.451.1",
83
- "@genesislcap/rapid-design-system": "14.451.1",
84
- "@genesislcap/web-core": "14.451.1",
78
+ "@genesislcap/foundation-ai": "14.451.2-FUI-2550.1",
79
+ "@genesislcap/foundation-logger": "14.451.2-FUI-2550.1",
80
+ "@genesislcap/foundation-redux": "14.451.2-FUI-2550.1",
81
+ "@genesislcap/foundation-ui": "14.451.2-FUI-2550.1",
82
+ "@genesislcap/foundation-utils": "14.451.2-FUI-2550.1",
83
+ "@genesislcap/rapid-design-system": "14.451.2-FUI-2550.1",
84
+ "@genesislcap/web-core": "14.451.2-FUI-2550.1",
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": "63decfd7190d1701b06dadb80d4a45ef5b29c4fe"
96
+ "gitHead": "66dcfaa6711a02dc94ffc5934a69d312054dfe0b"
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
- const DEFAULT_MAX_TURN_SNAPSHOTS = 40;
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 40) so the export log can show,
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
- recordMetaEvent(this.sessionKey, 'turn.error', {
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({ role: 'assistant', content: 'Sorry, something went wrong.' });
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
- recordMetaEvent(this.sessionKey, 'turn.error', {
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({ role: 'assistant', content: 'Sorry, something went wrong.' });
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
- recordMetaEvent(this.sessionKey, 'turn.error', {
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
- "I'm sorry, I wasn't able to complete that request. Please try rephrasing or breaking it into smaller steps.",
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
- recordMetaEvent(this.sessionKey, 'turn.error', {
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: 'Remote agent returned no response.',
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
- recordMetaEvent(this.sessionKey, 'turn.error', {
1427
- reason: 'unknown-tool-limit',
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 don't exist. Please check your agent configuration or try rephrasing your request.",
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
- recordMetaEvent(this.sessionKey, 'turn.error', {
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 "\${this.componentName}" is not registered in the customElements registry.`,
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 "\${this.componentName}" is not available in this application.`;
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 \${this.componentName}`;
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 (max: ${this.maxHandoffs}). Please try breaking your request into smaller steps.`,
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 under `getDebugLog().meta.events`. Ring-buffered so a long-lived
20
- * session can't grow the timeline unbounded.
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
- /** Default ring-buffer cap. ~5× the turn-snapshot cap — entries are cheap. */
123
- const DEFAULT_MAX_META_EVENTS = 200;
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`. Evicts the oldest entry once
135
- * the buffer exceeds {@link DEFAULT_MAX_META_EVENTS}. An empty `key` is bucketed
136
- * under `''`, matching how drivers/stores handle an absent session identity, so
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
- buffer.events.shift();
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 — 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.',
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
  ];