@genesislcap/ai-assistant 14.493.1-alpha-8ff3328.0 → 14.495.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -856,7 +856,7 @@
856
856
  {
857
857
  "kind": "PropertySignature",
858
858
  "canonicalReference": "@genesislcap/ai-assistant!AgenticActivityEvents#\"tool-loop-end\":member",
859
- "docComment": "/**\n * Fired when the tool loop completes for the current turn and control returns to the user on the **main chat prompt** (i.e. the next free-form user message can now be accepted).\n *\n * Boundary: this is the moment the user regains control of the main chat input. It is **not** fired when a tool handler hands a widget to the user mid-loop (e.g. while a planning widget is awaiting a click) — widget pauses are still inside the loop, the assistant still owns the turn, and the main chat input may be hidden during them. Wait for this event when you need to know that the assistant is fully idle and the user can type freely again.\n *\n * Emitted from the finally block of each `ChatDriver`'s `sendMessage` and `continueFromHistory`, so a multi-agent handoff fires this event once per agent.\n *\n * Distinct from {@link AgenticActivityEvents.'halo-stop'} — the halo event tracks the spinner animation lifecycle (which can flap mid-turn as tool calls resolve), whereas this event is a one-shot per turn boundary. Use this for hooks tied to a turn ending (telemetry flushes, post-turn workspace transitions, deferred state recomputation).\n */\n",
859
+ "docComment": "/**\n * Fired when the tool loop completes for the current turn and control returns to the user on the **main chat prompt** (i.e. the next free-form user message can now be accepted).\n *\n * Boundary: this is the moment the user regains control of the main chat input. It is **not** fired when a tool handler hands a widget to the user mid-loop (e.g. while a planning widget is awaiting a click) — widget pauses are still inside the loop, the assistant still owns the turn, and the main chat input may be hidden during them. Wait for this event when you need to know that the assistant is fully idle and the user can type freely again.\n *\n * Emitted from the finally block of each `ChatDriver`'s `sendMessage` and `continueFromHistory`, so a multi-agent handoff fires this event once per agent.\n *\n * Distinct from {@link AgenticActivityEvents.'halo-stop'} — the halo event tracks the spinner animation lifecycle (which can flap mid-turn as tool calls resolve), whereas this event is a one-shot per turn boundary. Use this for hooks tied to a turn ending (telemetry flushes, post-turn workspace transitions, deferred state recomputation).\n *\n * The detail carries the turn's outcome (PTC-0): on a failure it is `{ failureReason }` with the typed `TurnFailureReason`; on a clean completion, a user cancel, a driver dispose, and an agent handoff the detail is `undefined` — the historical shape, kept byte-identical so subscribers that only care about the boundary can keep ignoring it. Structured-cloneable so it survives the cross-tab BroadcastChannel hop.\n */\n",
860
860
  "excerptTokens": [
861
861
  {
862
862
  "kind": "Content",
@@ -864,7 +864,16 @@
864
864
  },
865
865
  {
866
866
  "kind": "Content",
867
- "text": "undefined"
867
+ "text": "{\n failureReason?: "
868
+ },
869
+ {
870
+ "kind": "Reference",
871
+ "text": "TurnFailureReason",
872
+ "canonicalReference": "@genesislcap/foundation-ai!TurnFailureReason:type"
873
+ },
874
+ {
875
+ "kind": "Content",
876
+ "text": ";\n } | undefined"
868
877
  },
869
878
  {
870
879
  "kind": "Content",
@@ -877,7 +886,7 @@
877
886
  "name": "\"tool-loop-end\"",
878
887
  "propertyTypeTokenRange": {
879
888
  "startIndex": 1,
880
- "endIndex": 2
889
+ "endIndex": 4
881
890
  }
882
891
  },
883
892
  {
@@ -18,6 +18,7 @@ import { InterfaceSymbol } from '@microsoft/fast-foundation';
18
18
  import type { Modal } from '@genesislcap/rapid-design-system';
19
19
  import { ModelTagAppearance } from '@genesislcap/foundation-ai';
20
20
  import type { SubAgentFailureReason } from '@genesislcap/foundation-ai';
21
+ import type { TurnFailureReason } from '@genesislcap/foundation-ai';
21
22
  import { ViewTemplate } from '@genesislcap/web-core';
22
23
 
23
24
  /** Sentinel value used by the segmented control / select to represent "Auto". */
@@ -179,8 +180,16 @@ export declare interface AgenticActivityEvents {
179
180
  * spinner animation lifecycle (which can flap mid-turn as tool calls resolve), whereas
180
181
  * this event is a one-shot per turn boundary. Use this for hooks tied to a turn ending
181
182
  * (telemetry flushes, post-turn workspace transitions, deferred state recomputation).
183
+ *
184
+ * The detail carries the turn's outcome (PTC-0): on a failure it is `{ failureReason }`
185
+ * with the typed `TurnFailureReason`; on a clean completion, a user cancel, a
186
+ * driver dispose, and an agent handoff the detail is `undefined` — the historical shape,
187
+ * kept byte-identical so subscribers that only care about the boundary can keep ignoring
188
+ * it. Structured-cloneable so it survives the cross-tab BroadcastChannel hop.
182
189
  */
183
- 'tool-loop-end': undefined;
190
+ 'tool-loop-end': {
191
+ failureReason?: TurnFailureReason;
192
+ } | undefined;
184
193
  /**
185
194
  * Fired when a tool handler hands a widget to the user mid-loop and parks awaiting it
186
195
  * (`requestInteraction`) — the turn is suspended between provider calls, with no request
@@ -1268,6 +1277,23 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
1268
1277
  * cached history would otherwise gain a stray marker on remount).
1269
1278
  */
1270
1279
  private completeAbortedTurn;
1280
+ /**
1281
+ * Build the `done` loop result, carrying the typed failure reason when the turn
1282
+ * bailed (PTC-0). The discriminant stays `'done'` either way — the same value a
1283
+ * clean turn returns — so consumers matching on `reason === 'done'` are unchanged;
1284
+ * `failureReason` is simply present on a failure and absent on success. Omitted
1285
+ * (rather than set to `undefined`) so a happy-path result stays byte-identical to
1286
+ * the historical `{ reason: 'done' }`.
1287
+ */
1288
+ private turnDone;
1289
+ /** The typed failure reason on a loop result, or `undefined` for a clean turn / handoff. */
1290
+ private static failureReasonOf;
1291
+ /**
1292
+ * Build the `tool-loop-end` event detail for a turn's result. A failure carries a
1293
+ * `{ failureReason }` detail; a clean turn emits `undefined` — the historical shape,
1294
+ * kept byte-identical so subscribers see exactly what they always have.
1295
+ */
1296
+ private static loopEndDetail;
1271
1297
  /**
1272
1298
  * Swap in a new agent's configuration. Called by OrchestratingDriver before
1273
1299
  * each specialist turn so the shared driver runs with the right tools and prompt.
@@ -3698,6 +3724,8 @@ export declare interface ToolTreeNode extends ChatToolDefinition {
3698
3724
  tools?: ToolTreeNode[];
3699
3725
  }
3700
3726
 
3727
+ export { TurnFailureReason }
3728
+
3701
3729
  /**
3702
3730
  * One captured frame of what the LLM saw on a single tool-loop iteration.
3703
3731
  * The driver records these as a ring buffer (cap: configurable via
@@ -151,6 +151,14 @@
151
151
  "package": "./utils/tool-fold"
152
152
  }
153
153
  },
154
+ {
155
+ "kind": "js",
156
+ "name": "TurnFailureReason",
157
+ "declaration": {
158
+ "name": "TurnFailureReason",
159
+ "module": "./state/debug-event-log"
160
+ }
161
+ },
154
162
  {
155
163
  "kind": "js",
156
164
  "name": "TimelineMessage",
@@ -3971,6 +3979,14 @@
3971
3979
  "module": "src/state/debug-event-log.ts"
3972
3980
  }
3973
3981
  },
3982
+ {
3983
+ "kind": "js",
3984
+ "name": "TurnFailureReason",
3985
+ "declaration": {
3986
+ "name": "TurnFailureReason",
3987
+ "module": "src/state/debug-event-log.ts"
3988
+ }
3989
+ },
3974
3990
  {
3975
3991
  "kind": "js",
3976
3992
  "name": "recordTurnError",
@@ -6892,6 +6908,66 @@
6892
6908
  },
6893
6909
  "description": "Finish a turn whose signal aborted. A user cancel adds a subtle \"Stopped.\"\nmarker; a dispose-driven abort stops quietly (the widget is gone and the\ncached history would otherwise gain a stray marker on remount)."
6894
6910
  },
6911
+ {
6912
+ "kind": "method",
6913
+ "name": "turnDone",
6914
+ "privacy": "private",
6915
+ "return": {
6916
+ "type": {
6917
+ "text": "ChatDriverResult"
6918
+ }
6919
+ },
6920
+ "parameters": [
6921
+ {
6922
+ "name": "failureReason",
6923
+ "optional": true,
6924
+ "type": {
6925
+ "text": "TurnFailureReason"
6926
+ }
6927
+ }
6928
+ ],
6929
+ "description": "Build the `done` loop result, carrying the typed failure reason when the turn\nbailed (PTC-0). The discriminant stays `'done'` either way — the same value a\nclean turn returns — so consumers matching on `reason === 'done'` are unchanged;\n`failureReason` is simply present on a failure and absent on success. Omitted\n(rather than set to `undefined`) so a happy-path result stays byte-identical to\nthe historical `{ reason: 'done' }`."
6930
+ },
6931
+ {
6932
+ "kind": "method",
6933
+ "name": "failureReasonOf",
6934
+ "privacy": "private",
6935
+ "static": true,
6936
+ "return": {
6937
+ "type": {
6938
+ "text": "TurnFailureReason | undefined"
6939
+ }
6940
+ },
6941
+ "parameters": [
6942
+ {
6943
+ "name": "result",
6944
+ "type": {
6945
+ "text": "ChatDriverResult"
6946
+ }
6947
+ }
6948
+ ],
6949
+ "description": "The typed failure reason on a loop result, or `undefined` for a clean turn / handoff."
6950
+ },
6951
+ {
6952
+ "kind": "method",
6953
+ "name": "loopEndDetail",
6954
+ "privacy": "private",
6955
+ "static": true,
6956
+ "return": {
6957
+ "type": {
6958
+ "text": "{ failureReason: TurnFailureReason } | undefined"
6959
+ }
6960
+ },
6961
+ "parameters": [
6962
+ {
6963
+ "name": "result",
6964
+ "type": {
6965
+ "text": "ChatDriverResult"
6966
+ }
6967
+ }
6968
+ ],
6969
+ "description": "Build the `tool-loop-end` event detail for a turn's result. A failure carries a\n`{ failureReason }` detail; a clean turn emits `undefined` — the historical shape,\nkept byte-identical so subscribers see exactly what they always have."
6970
+ },
6895
6971
  {
6896
6972
  "kind": "method",
6897
6973
  "name": "applyAgent",
@@ -1,3 +1,4 @@
1
+ import type { TurnFailureReason } from '@genesislcap/foundation-ai';
1
2
  /**
2
3
  * Event map for the AI activity bus.
3
4
  *
@@ -61,8 +62,16 @@ export interface AgenticActivityEvents {
61
62
  * spinner animation lifecycle (which can flap mid-turn as tool calls resolve), whereas
62
63
  * this event is a one-shot per turn boundary. Use this for hooks tied to a turn ending
63
64
  * (telemetry flushes, post-turn workspace transitions, deferred state recomputation).
65
+ *
66
+ * The detail carries the turn's outcome (PTC-0): on a failure it is `{ failureReason }`
67
+ * with the typed `TurnFailureReason`; on a clean completion, a user cancel, a
68
+ * driver dispose, and an agent handoff the detail is `undefined` — the historical shape,
69
+ * kept byte-identical so subscribers that only care about the boundary can keep ignoring
70
+ * it. Structured-cloneable so it survives the cross-tab BroadcastChannel hop.
64
71
  */
65
- 'tool-loop-end': undefined;
72
+ 'tool-loop-end': {
73
+ failureReason?: TurnFailureReason;
74
+ } | undefined;
66
75
  /**
67
76
  * Fired when a tool handler hands a widget to the user mid-loop and parks awaiting it
68
77
  * (`requestInteraction`) — the turn is suspended between provider calls, with no request
@@ -1 +1 @@
1
- {"version":3,"file":"ai-activity-channel.d.ts","sourceRoot":"","sources":["../../../src/channel/ai-activity-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,qBAAqB;IACpC,2FAA2F;IAC3F,YAAY,EAAE;QAAE,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACtC,iFAAiF;IACjF,WAAW,EAAE,SAAS,CAAC;IACvB,mJAAmJ;IACnJ,aAAa,EAAE,SAAS,CAAC;IACzB,wJAAwJ;IACxJ,YAAY,EAAE,SAAS,CAAC;IACxB;;;;;;;;;;;;;;;;;;OAkBG;IACH,iBAAiB,EAAE,SAAS,CAAC;IAC7B;;;;;;;;;;;;;;;;;;OAkBG;IACH,eAAe,EAAE,SAAS,CAAC;IAC3B;;;;;;;;;;;OAWG;IACH,uBAAuB,EAAE,SAAS,CAAC;IACnC;;;;;OAKG;IACH,sBAAsB,EAAE,SAAS,CAAC;CACnC"}
1
+ {"version":3,"file":"ai-activity-channel.d.ts","sourceRoot":"","sources":["../../../src/channel/ai-activity-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEpE;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,qBAAqB;IACpC,2FAA2F;IAC3F,YAAY,EAAE;QAAE,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACtC,iFAAiF;IACjF,WAAW,EAAE,SAAS,CAAC;IACvB,mJAAmJ;IACnJ,aAAa,EAAE,SAAS,CAAC;IACzB,wJAAwJ;IACxJ,YAAY,EAAE,SAAS,CAAC;IACxB;;;;;;;;;;;;;;;;;;OAkBG;IACH,iBAAiB,EAAE,SAAS,CAAC;IAC7B;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,eAAe,EAAE;QAAE,aAAa,CAAC,EAAE,iBAAiB,CAAA;KAAE,GAAG,SAAS,CAAC;IACnE;;;;;;;;;;;OAWG;IACH,uBAAuB,EAAE,SAAS,CAAC;IACnC;;;;;OAKG;IACH,sBAAsB,EAAE,SAAS,CAAC;CACnC"}
@@ -398,6 +398,23 @@ export declare class ChatDriver extends EventTarget implements AiDriver {
398
398
  * cached history would otherwise gain a stray marker on remount).
399
399
  */
400
400
  private completeAbortedTurn;
401
+ /**
402
+ * Build the `done` loop result, carrying the typed failure reason when the turn
403
+ * bailed (PTC-0). The discriminant stays `'done'` either way — the same value a
404
+ * clean turn returns — so consumers matching on `reason === 'done'` are unchanged;
405
+ * `failureReason` is simply present on a failure and absent on success. Omitted
406
+ * (rather than set to `undefined`) so a happy-path result stays byte-identical to
407
+ * the historical `{ reason: 'done' }`.
408
+ */
409
+ private turnDone;
410
+ /** The typed failure reason on a loop result, or `undefined` for a clean turn / handoff. */
411
+ private static failureReasonOf;
412
+ /**
413
+ * Build the `tool-loop-end` event detail for a turn's result. A failure carries a
414
+ * `{ failureReason }` detail; a clean turn emits `undefined` — the historical shape,
415
+ * kept byte-identical so subscribers see exactly what they always have.
416
+ */
417
+ private static loopEndDetail;
401
418
  /**
402
419
  * Swap in a new agent's configuration. Called by OrchestratingDriver before
403
420
  * each specialist turn so the shared driver runs with the right tools and prompt.
@@ -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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"chat-driver.test.d.ts","sourceRoot":"","sources":["../../../../src/components/chat-driver/chat-driver.test.ts"],"names":[],"mappings":"AAsBA,OAAO,uBAAuB,CAAC"}
1
+ {"version":3,"file":"chat-driver.test.d.ts","sourceRoot":"","sources":["../../../../src/components/chat-driver/chat-driver.test.ts"],"names":[],"mappings":"AA4BA,OAAO,uBAAuB,CAAC"}
@@ -16,6 +16,7 @@ export * from './state/persistence';
16
16
  export * from './provider/ai-provider-switcher';
17
17
  export * from './provider/assistant-app-settings';
18
18
  export * from './utils/tool-fold';
19
+ export type { TurnFailureReason } from './state/debug-event-log';
19
20
  export type { TimelineMessage } from './utils/flatten-sub-agent-messages';
20
21
  export type { CostSessionModelEntry, CostSessionRecord } from './utils/cost-session-history';
21
22
  export type { ModelTagAppearance } from '@genesislcap/foundation-ai';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,YAAY,EAAE,+BAA+B,EAAE,MAAM,gEAAgE,CAAC;AACtH,cAAc,mCAAmC,CAAC;AAClD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iCAAiC,CAAC;AAChD,cAAc,mCAAmC,CAAC;AAClD,cAAc,mBAAmB,CAAC;AAClC,YAAY,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,YAAY,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAC7F,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,YAAY,EAAE,+BAA+B,EAAE,MAAM,gEAAgE,CAAC;AACtH,cAAc,mCAAmC,CAAC;AAClD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iCAAiC,CAAC;AAChD,cAAc,mCAAmC,CAAC;AAClD,cAAc,mBAAmB,CAAC;AAClC,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,YAAY,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,YAAY,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAC7F,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAC"}
@@ -23,6 +23,7 @@
23
23
  *
24
24
  * @internal
25
25
  */
26
+ import type { TurnFailureReason } from '@genesislcap/foundation-ai';
26
27
  /**
27
28
  * Catalogue of meta event names. This is the documented surface — extend it as
28
29
  * new events are wired in (Tier 2/3 lifecycle, interaction, provider events).
@@ -68,19 +69,14 @@ export interface MetaEvent {
68
69
  export declare function recordMetaEvent(key: string, type: MetaEventType, detail?: Record<string, unknown>): void;
69
70
  /**
70
71
  * 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 it couldn't dispatch,
78
- * whether hallucinated or stale (real earlier, retired now).
79
- * - `max-iterations` — the tool loop hit its iteration cap.
80
- * - `response-truncated` — a turn stopped at the provider's output-token cap with an
81
- * incomplete tool call; deterministic, so it bails without retry.
72
+ * and `turn.retry` events, and (since PTC-0) surfaced at the driver's loop
73
+ * boundary via {@link ChatDriverResult.failureReason}. Re-exported from
74
+ * `@genesislcap/foundation-ai` — where it lives alongside `ChatDriverResult` so
75
+ * the boundary field and this log surface can never drift apart — under its
76
+ * historical name so existing importers are unaffected. Enumerated so the set
77
+ * stays in sync with the README and call sites can't drift to ad-hoc strings.
82
78
  */
83
- export type TurnFailureReason = 'exception' | 'malformed-function-call' | 'empty-response' | 'unknown-tool-limit' | 'max-iterations' | 'response-truncated';
79
+ export type { TurnFailureReason };
84
80
  /**
85
81
  * Record a turn-ending failure (`turn.error`, importance `high`). The `reason`
86
82
  * is typed so the high-importance triage surface stays consistent; pass any
@@ -1 +1 @@
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,GACb,iBAAiB,GACjB,kBAAkB,GAClB,oBAAoB,GACpB,iBAAiB,GAEjB,eAAe,GACf,cAAc,GACd,gBAAgB,GAChB,mBAAmB,GAEnB,uBAAuB,GACvB,sBAAsB,GAEtB,iBAAiB,GACjB,2BAA2B,GAC3B,mBAAmB,GACnB,mBAAmB,GAEnB,eAAe,GACf,kBAAkB,GAClB,kBAAkB,GAClB,oBAAoB,GAEpB,iBAAiB,GACjB,kBAAkB,GAClB,yBAAyB,GACzB,qBAAqB,GACrB,mBAAmB,GACnB,wBAAwB,GACxB,2BAA2B,GAC3B,sBAAsB,GACtB,4BAA4B,CAAC;AAEjC;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE5D;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,aAAa,EAAE,mBAAmB,CA2C5E,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;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,iBAAiB,GACzB,WAAW,GACX,yBAAyB,GACzB,gBAAgB,GAChB,oBAAoB,GACpB,gBAAgB,GAChB,oBAAoB,CAAC;AAEzB;;;;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;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,SAAS,EAAE,GAAG,IAAI,CA0BrF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAc7C,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,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEpE;;;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,GACb,iBAAiB,GACjB,kBAAkB,GAClB,oBAAoB,GACpB,iBAAiB,GAEjB,eAAe,GACf,cAAc,GACd,gBAAgB,GAChB,mBAAmB,GAEnB,uBAAuB,GACvB,sBAAsB,GAEtB,iBAAiB,GACjB,2BAA2B,GAC3B,mBAAmB,GACnB,mBAAmB,GAEnB,eAAe,GACf,kBAAkB,GAClB,kBAAkB,GAClB,oBAAoB,GAEpB,iBAAiB,GACjB,kBAAkB,GAClB,yBAAyB,GACzB,qBAAqB,GACrB,mBAAmB,GACnB,wBAAwB,GACxB,2BAA2B,GAC3B,sBAAsB,GACtB,4BAA4B,CAAC;AAEjC;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE5D;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,aAAa,EAAE,mBAAmB,CA2C5E,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;;;;;;;;GAQG;AACH,YAAY,EAAE,iBAAiB,EAAE,CAAC;AAElC;;;;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;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,SAAS,EAAE,GAAG,IAAI,CA0BrF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAc7C,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
@@ -337,6 +337,30 @@ export class ChatDriver extends EventTarget {
337
337
  }
338
338
  return { reason: 'done' };
339
339
  }
340
+ /**
341
+ * Build the `done` loop result, carrying the typed failure reason when the turn
342
+ * bailed (PTC-0). The discriminant stays `'done'` either way — the same value a
343
+ * clean turn returns — so consumers matching on `reason === 'done'` are unchanged;
344
+ * `failureReason` is simply present on a failure and absent on success. Omitted
345
+ * (rather than set to `undefined`) so a happy-path result stays byte-identical to
346
+ * the historical `{ reason: 'done' }`.
347
+ */
348
+ turnDone(failureReason) {
349
+ return failureReason ? { reason: 'done', failureReason } : { reason: 'done' };
350
+ }
351
+ /** The typed failure reason on a loop result, or `undefined` for a clean turn / handoff. */
352
+ static failureReasonOf(result) {
353
+ return result.reason === 'done' ? result.failureReason : undefined;
354
+ }
355
+ /**
356
+ * Build the `tool-loop-end` event detail for a turn's result. A failure carries a
357
+ * `{ failureReason }` detail; a clean turn emits `undefined` — the historical shape,
358
+ * kept byte-identical so subscribers see exactly what they always have.
359
+ */
360
+ static loopEndDetail(result) {
361
+ const failureReason = ChatDriver.failureReasonOf(result);
362
+ return failureReason ? { failureReason } : undefined;
363
+ }
340
364
  /**
341
365
  * Swap in a new agent's configuration. Called by OrchestratingDriver before
342
366
  * each specialist turn so the shared driver runs with the right tools and prompt.
@@ -1062,8 +1086,11 @@ export class ChatDriver extends EventTarget {
1062
1086
  agent: this.activeAgentName,
1063
1087
  });
1064
1088
  agenticActivityBus.publish('tool-loop-start', undefined);
1089
+ // Captured so the `finally` can carry the turn's outcome onto `tool-loop-end`.
1090
+ let result = { reason: 'done' };
1065
1091
  try {
1066
- return yield this.runToolLoop(userInput, attachments);
1092
+ result = yield this.runToolLoop(userInput, attachments);
1093
+ return result;
1067
1094
  }
1068
1095
  catch (e) {
1069
1096
  logger.error('ChatDriver error:', e);
@@ -1078,7 +1105,8 @@ export class ChatDriver extends EventTarget {
1078
1105
  role: 'assistant',
1079
1106
  content: 'Sorry, something went wrong on my end. Please try again in a moment.',
1080
1107
  });
1081
- return { reason: 'done' };
1108
+ result = this.turnDone('exception');
1109
+ return result;
1082
1110
  }
1083
1111
  finally {
1084
1112
  recordMetaEvent(this.sessionKey, 'turn.end', {
@@ -1088,7 +1116,7 @@ export class ChatDriver extends EventTarget {
1088
1116
  });
1089
1117
  this.busy = false;
1090
1118
  this.endTurn();
1091
- agenticActivityBus.publish('tool-loop-end', undefined);
1119
+ agenticActivityBus.publish('tool-loop-end', ChatDriver.loopEndDetail(result));
1092
1120
  }
1093
1121
  });
1094
1122
  }
@@ -1373,8 +1401,11 @@ export class ChatDriver extends EventTarget {
1373
1401
  agent: this.activeAgentName,
1374
1402
  });
1375
1403
  agenticActivityBus.publish('tool-loop-start', undefined);
1404
+ // Captured so the `finally` can carry the turn's outcome onto `tool-loop-end`.
1405
+ let result = { reason: 'done' };
1376
1406
  try {
1377
- return yield this.runToolLoop('', undefined, transientPrimer);
1407
+ result = yield this.runToolLoop('', undefined, transientPrimer);
1408
+ return result;
1378
1409
  }
1379
1410
  catch (e) {
1380
1411
  logger.error('ChatDriver error:', e);
@@ -1389,7 +1420,8 @@ export class ChatDriver extends EventTarget {
1389
1420
  role: 'assistant',
1390
1421
  content: 'Sorry, something went wrong on my end. Please try again in a moment.',
1391
1422
  });
1392
- return { reason: 'done' };
1423
+ result = this.turnDone('exception');
1424
+ return result;
1393
1425
  }
1394
1426
  finally {
1395
1427
  recordMetaEvent(this.sessionKey, 'turn.end', {
@@ -1399,7 +1431,7 @@ export class ChatDriver extends EventTarget {
1399
1431
  });
1400
1432
  this.busy = false;
1401
1433
  this.endTurn();
1402
- agenticActivityBus.publish('tool-loop-end', undefined);
1434
+ agenticActivityBus.publish('tool-loop-end', ChatDriver.loopEndDetail(result));
1403
1435
  }
1404
1436
  });
1405
1437
  }
@@ -1784,7 +1816,7 @@ export class ChatDriver extends EventTarget {
1784
1816
  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.',
1785
1817
  });
1786
1818
  }
1787
- return { reason: 'done' };
1819
+ return this.turnDone('malformed-function-call');
1788
1820
  }
1789
1821
  // The response was truncated at the provider's output-token cap while it
1790
1822
  // still carried a tool call — its arguments are incomplete and unusable.
@@ -1813,7 +1845,7 @@ export class ChatDriver extends EventTarget {
1813
1845
  content: 'My response was cut off because a single step reached the model output limit. This usually means one step tried to produce too much at once — try breaking your request into smaller steps.',
1814
1846
  });
1815
1847
  }
1816
- return { reason: 'done' };
1848
+ return this.turnDone('response-truncated');
1817
1849
  }
1818
1850
  // A request timeout from the transport (tagged `TimeoutError`) is not a
1819
1851
  // bug on our end — surface it distinctly instead of letting it fall
@@ -1839,7 +1871,9 @@ export class ChatDriver extends EventTarget {
1839
1871
  content: 'The request timed out. You can ask me to try again, or break this into a smaller step.',
1840
1872
  });
1841
1873
  }
1842
- return { reason: 'done' };
1874
+ // Recorded as `exception` above (there is no separate `timeout` member of
1875
+ // TurnFailureReason for the main turn); surface the same reason here.
1876
+ return this.turnDone('exception');
1843
1877
  }
1844
1878
  // The request was aborted: either a user cancel (turnController) or a
1845
1879
  // driver dispose (lifecycleController, chained into the turn). A user
@@ -1899,7 +1933,7 @@ export class ChatDriver extends EventTarget {
1899
1933
  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.',
1900
1934
  });
1901
1935
  }
1902
- return { reason: 'done' };
1936
+ return this.turnDone('empty-response');
1903
1937
  }
1904
1938
  else {
1905
1939
  // Split one model response into separate, individually-toggleable messages so each has its
@@ -2213,7 +2247,7 @@ export class ChatDriver extends EventTarget {
2213
2247
  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.",
2214
2248
  });
2215
2249
  }
2216
- return { reason: 'done' };
2250
+ return this.turnDone('unknown-tool-limit');
2217
2251
  }
2218
2252
  const firstContinuation = systemCalls[0];
2219
2253
  if (firstContinuation) {
@@ -2223,9 +2257,12 @@ export class ChatDriver extends EventTarget {
2223
2257
  // Sub-agent early exit — checked here so the exit point mirrors the
2224
2258
  // system-call pattern above. Set by completeSubAgent() in a tool handler.
2225
2259
  if (this.subAgentCompletion) {
2226
- return { reason: 'done' };
2260
+ return this.turnDone();
2227
2261
  }
2228
2262
  }
2263
+ // The loop fell through: either it hit the iteration cap (a failure) or it
2264
+ // broke on a clean final answer (success). Only the former carries a reason.
2265
+ let failureReason;
2229
2266
  if (iterations >= this.maxToolIterations) {
2230
2267
  logger.warn('ChatDriver: reached max tool iterations, stopping');
2231
2268
  recordTurnError(this.sessionKey, 'max-iterations', {
@@ -2244,8 +2281,9 @@ export class ChatDriver extends EventTarget {
2244
2281
  content: "I've reached my limit for this response. You can ask me to continue and I'll pick up where I left off.",
2245
2282
  });
2246
2283
  }
2284
+ failureReason = 'max-iterations';
2247
2285
  }
2248
- return { reason: 'done' };
2286
+ return this.turnDone(failureReason);
2249
2287
  });
2250
2288
  }
2251
2289
  appendToHistory(message) {
@@ -1,5 +1,5 @@
1
1
  import { __awaiter } from "tslib";
2
- import { isChatToolCallUnknown } from '@genesislcap/foundation-ai';
2
+ import { isChatToolCallUnknown, MalformedFunctionCallError, ResponseTruncatedError, } from '@genesislcap/foundation-ai';
3
3
  import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
4
4
  import { agenticActivityBus } from '../../channel/ai-activity-bus';
5
5
  import { clearMetaEventRegistry, getMetaEvents } from '../../state/debug-event-log';
@@ -1621,3 +1621,135 @@ condense('phaseEnd: collapses at endPhase() while the agent keeps running', () =
1621
1621
  assert.is((_a = events[0].detail) === null || _a === void 0 ? void 0 : _a.trigger, 'phaseEnd');
1622
1622
  }));
1623
1623
  condense.run();
1624
+ // ---------------------------------------------------------------------------
1625
+ // turn-outcome surfacing (PTC-0) — the typed failure reason must reach BOTH
1626
+ // the loop-boundary result (`ChatDriverResult.failureReason`) and the
1627
+ // `tool-loop-end` activity-bus event detail. Historically every exit flattened
1628
+ // to `{ reason: 'done' }` and the bus detail was `undefined`; these lock the
1629
+ // two seams together, one per `TurnFailureReason`, plus a happy-path compat
1630
+ // check that the legacy shape is byte-unchanged.
1631
+ // ---------------------------------------------------------------------------
1632
+ const outcome = createLogicSuite('ChatDriver turn-outcome surfacing');
1633
+ outcome.after(() => {
1634
+ agenticActivityBus.close();
1635
+ });
1636
+ /**
1637
+ * Subscribe to `tool-loop-end` and expose the most recent detail seen. `fired()` reports
1638
+ * whether the event was seen at all — distinct from `detail()`, which is `undefined` both
1639
+ * when no event fired and when a clean turn fired with the historical `undefined` detail.
1640
+ */
1641
+ const captureLoopEnd = () => {
1642
+ let last;
1643
+ let seen = false;
1644
+ const stop = agenticActivityBus.subscribe('tool-loop-end', (d) => {
1645
+ last = d;
1646
+ seen = true;
1647
+ });
1648
+ return { detail: () => (seen ? last : undefined), fired: () => seen, stop };
1649
+ };
1650
+ /** A ChatDriver with an explicit (small) tool-iteration cap. */
1651
+ const makeCappedDriver = (config, provider, maxIterations, sessionKey = '') => {
1652
+ const driver = new ChatDriver(makeRegistry(provider), {}, [], undefined, undefined, maxIterations, 5, undefined, sessionKey);
1653
+ driver.applyAgent(config);
1654
+ return driver;
1655
+ };
1656
+ /**
1657
+ * Assert that a turn driven by `provider` surfaces `expected` at BOTH seams.
1658
+ * `driverFor` lets the max-iterations case swap in a low cap.
1659
+ */
1660
+ const assertSurfacesReason = (label_1, provider_1, expected_1, ...args_1) => __awaiter(void 0, [label_1, provider_1, expected_1, ...args_1], void 0, function* (label, provider, expected, driverFor = (c, p, k) => makeDriver(c, p, k)) {
1661
+ var _a;
1662
+ clearMetaEventRegistry();
1663
+ const sessionKey = `outcome-${label}`;
1664
+ const config = agent({
1665
+ name: 'Static',
1666
+ toolDefinitions: [def('noop')],
1667
+ toolHandlers: { noop: () => __awaiter(void 0, void 0, void 0, function* () { return 'ok'; }) },
1668
+ });
1669
+ const driver = driverFor(config, provider, sessionKey);
1670
+ const cap = captureLoopEnd();
1671
+ const result = yield driver.sendMessage('go');
1672
+ // Seam 1 — the loop-boundary result. Discriminant stays 'done' (compat).
1673
+ assert.is(result.reason, 'done', `[${label}] discriminant stays 'done'`);
1674
+ assert.is(result.reason === 'done' ? result.failureReason : undefined, expected, `[${label}] result.failureReason surfaces the typed reason`);
1675
+ // Seam 2 — the activity-bus tool-loop-end detail.
1676
+ assert.ok(cap.detail(), `[${label}] a tool-loop-end event fired`);
1677
+ assert.is(cap.detail().failureReason, expected, `[${label}] the bus detail carries the same reason`);
1678
+ // Consistency: the debug-log turn.error records the same taxonomy.
1679
+ const err = getMetaEvents(sessionKey).find((e) => e.type === 'turn.error');
1680
+ assert.ok(err, `[${label}] a turn.error is recorded`);
1681
+ assert.is((_a = err.detail) === null || _a === void 0 ? void 0 : _a.reason, expected, `[${label}] the debug-log reason matches`);
1682
+ cap.stop();
1683
+ });
1684
+ /** A provider that returns a MALFORMED_FUNCTION_CALL on every call. */
1685
+ const malformedProvider = () => ({
1686
+ chat: () => __awaiter(void 0, void 0, void 0, function* () {
1687
+ throw new MalformedFunctionCallError('bad call');
1688
+ }),
1689
+ });
1690
+ /** A provider that returns an empty response on every call. */
1691
+ const emptyProvider = () => ({
1692
+ chat: () => __awaiter(void 0, void 0, void 0, function* () { return ({ role: 'assistant', content: '' }); }),
1693
+ });
1694
+ /** A provider that throws a ResponseTruncatedError (deterministic, no retry). */
1695
+ const truncatedProvider = () => ({
1696
+ chat: () => __awaiter(void 0, void 0, void 0, function* () {
1697
+ throw new ResponseTruncatedError('test-model', 1024, 1024, ['noop']);
1698
+ }),
1699
+ });
1700
+ /** A provider that throws a generic error (the sendMessage catch-all → 'exception'). */
1701
+ const throwingProvider = () => ({
1702
+ chat: () => __awaiter(void 0, void 0, void 0, function* () {
1703
+ throw new Error('boom');
1704
+ }),
1705
+ });
1706
+ /** A provider that never stops calling a valid tool (drives the iteration cap). */
1707
+ const neverStopsProvider = () => {
1708
+ let n = 0;
1709
+ return {
1710
+ chat: () => __awaiter(void 0, void 0, void 0, function* () {
1711
+ const id = `noop-${n}`;
1712
+ n += 1;
1713
+ return { role: 'assistant', content: '', toolCalls: [{ id, name: 'noop', args: {} }] };
1714
+ }),
1715
+ };
1716
+ };
1717
+ /** A provider that keeps calling a tool with no handler (hallucinated → limit). */
1718
+ const hallucinatedProvider = () => scriptedProvider(Array.from({ length: 6 }, (_u, i) => callsTool('ghost', `ghost-${i}`)));
1719
+ outcome('malformed-function-call surfaces at both seams', () => __awaiter(void 0, void 0, void 0, function* () {
1720
+ yield assertSurfacesReason('malformed', malformedProvider(), 'malformed-function-call');
1721
+ }));
1722
+ outcome('empty-response surfaces at both seams', () => __awaiter(void 0, void 0, void 0, function* () {
1723
+ yield assertSurfacesReason('empty', emptyProvider(), 'empty-response');
1724
+ }));
1725
+ outcome('response-truncated surfaces at both seams', () => __awaiter(void 0, void 0, void 0, function* () {
1726
+ yield assertSurfacesReason('truncated', truncatedProvider(), 'response-truncated');
1727
+ }));
1728
+ outcome('exception surfaces at both seams', () => __awaiter(void 0, void 0, void 0, function* () {
1729
+ yield assertSurfacesReason('exception', throwingProvider(), 'exception');
1730
+ }));
1731
+ outcome('unknown-tool-limit surfaces at both seams', () => __awaiter(void 0, void 0, void 0, function* () {
1732
+ yield assertSurfacesReason('unknown-tool', hallucinatedProvider(), 'unknown-tool-limit');
1733
+ }));
1734
+ outcome('max-iterations surfaces at both seams', () => __awaiter(void 0, void 0, void 0, function* () {
1735
+ yield assertSurfacesReason('max-iter', neverStopsProvider(), 'max-iterations', (c, p, k) => makeCappedDriver(c, p, 2, k));
1736
+ }));
1737
+ outcome('a clean turn leaves the legacy shape byte-unchanged (no failureReason)', () => __awaiter(void 0, void 0, void 0, function* () {
1738
+ clearMetaEventRegistry();
1739
+ const config = agent({ name: 'Static' });
1740
+ // Plain-text reply ends the turn cleanly on the first call.
1741
+ const driver = makeDriver(config, scriptedProvider([{ role: 'assistant', content: 'hi' }]), 'outcome-ok');
1742
+ const cap = captureLoopEnd();
1743
+ const result = yield driver.sendMessage('go');
1744
+ // The result is exactly `{ reason: 'done' }` — no `failureReason` key added.
1745
+ assert.equal(result, { reason: 'done' }, 'happy-path result is the historical shape');
1746
+ assert.not.ok('failureReason' in result, 'no failureReason key is present on a clean turn');
1747
+ // The bus event still fires, and its detail is the historical `undefined` — a clean
1748
+ // turn emits no detail object at all (byte-shape compat), not `{ failureReason: undefined }`.
1749
+ assert.ok(cap.fired(), 'a tool-loop-end event fired');
1750
+ assert.is(cap.detail(), undefined, 'a clean turn emits the historical undefined detail');
1751
+ // And no turn.error was recorded.
1752
+ assert.not.ok(getMetaEvents('outcome-ok').some((e) => e.type === 'turn.error'), 'a clean turn records no turn.error');
1753
+ cap.stop();
1754
+ }));
1755
+ outcome.run();
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.493.1-alpha-8ff3328.0",
4
+ "version": "14.495.0",
5
5
  "license": "SEE LICENSE IN license.txt",
6
6
  "main": "dist/esm/index.js",
7
7
  "types": "dist/ai-assistant.d.ts",
@@ -64,25 +64,25 @@
64
64
  }
65
65
  },
66
66
  "devDependencies": {
67
- "@genesislcap/foundation-testing": "14.493.1-alpha-8ff3328.0",
68
- "@genesislcap/genx": "14.493.1-alpha-8ff3328.0",
69
- "@genesislcap/rollup-builder": "14.493.1-alpha-8ff3328.0",
70
- "@genesislcap/ts-builder": "14.493.1-alpha-8ff3328.0",
71
- "@genesislcap/uvu-playwright-builder": "14.493.1-alpha-8ff3328.0",
72
- "@genesislcap/vite-builder": "14.493.1-alpha-8ff3328.0",
73
- "@genesislcap/webpack-builder": "14.493.1-alpha-8ff3328.0",
67
+ "@genesislcap/foundation-testing": "14.495.0",
68
+ "@genesislcap/genx": "14.495.0",
69
+ "@genesislcap/rollup-builder": "14.495.0",
70
+ "@genesislcap/ts-builder": "14.495.0",
71
+ "@genesislcap/uvu-playwright-builder": "14.495.0",
72
+ "@genesislcap/vite-builder": "14.495.0",
73
+ "@genesislcap/webpack-builder": "14.495.0",
74
74
  "@types/dompurify": "^3.0.5",
75
75
  "@types/marked": "^5.0.2"
76
76
  },
77
77
  "dependencies": {
78
- "@genesislcap/foundation-ai": "14.493.1-alpha-8ff3328.0",
79
- "@genesislcap/foundation-logger": "14.493.1-alpha-8ff3328.0",
80
- "@genesislcap/foundation-notifications": "14.493.1-alpha-8ff3328.0",
81
- "@genesislcap/foundation-redux": "14.493.1-alpha-8ff3328.0",
82
- "@genesislcap/foundation-ui": "14.493.1-alpha-8ff3328.0",
83
- "@genesislcap/foundation-utils": "14.493.1-alpha-8ff3328.0",
84
- "@genesislcap/rapid-design-system": "14.493.1-alpha-8ff3328.0",
85
- "@genesislcap/web-core": "14.493.1-alpha-8ff3328.0",
78
+ "@genesislcap/foundation-ai": "14.495.0",
79
+ "@genesislcap/foundation-logger": "14.495.0",
80
+ "@genesislcap/foundation-notifications": "14.495.0",
81
+ "@genesislcap/foundation-redux": "14.495.0",
82
+ "@genesislcap/foundation-ui": "14.495.0",
83
+ "@genesislcap/foundation-utils": "14.495.0",
84
+ "@genesislcap/rapid-design-system": "14.495.0",
85
+ "@genesislcap/web-core": "14.495.0",
86
86
  "dompurify": "^3.3.1",
87
87
  "marked": "^17.0.3"
88
88
  },
@@ -95,5 +95,5 @@
95
95
  "access": "public"
96
96
  },
97
97
  "customElements": "dist/custom-elements.json",
98
- "gitHead": "8c565dfbaf36c06f5a28278da59043c5453c564a"
98
+ "gitHead": "24d26a75271717d40c0b6168b983bd8536a3db7f"
99
99
  }
@@ -1,3 +1,5 @@
1
+ import type { TurnFailureReason } from '@genesislcap/foundation-ai';
2
+
1
3
  /**
2
4
  * Event map for the AI activity bus.
3
5
  *
@@ -59,8 +61,14 @@ export interface AgenticActivityEvents {
59
61
  * spinner animation lifecycle (which can flap mid-turn as tool calls resolve), whereas
60
62
  * this event is a one-shot per turn boundary. Use this for hooks tied to a turn ending
61
63
  * (telemetry flushes, post-turn workspace transitions, deferred state recomputation).
64
+ *
65
+ * The detail carries the turn's outcome (PTC-0): on a failure it is `{ failureReason }`
66
+ * with the typed `TurnFailureReason`; on a clean completion, a user cancel, a
67
+ * driver dispose, and an agent handoff the detail is `undefined` — the historical shape,
68
+ * kept byte-identical so subscribers that only care about the boundary can keep ignoring
69
+ * it. Structured-cloneable so it survives the cross-tab BroadcastChannel hop.
62
70
  */
63
- 'tool-loop-end': undefined;
71
+ 'tool-loop-end': { failureReason?: TurnFailureReason } | undefined;
64
72
  /**
65
73
  * Fired when a tool handler hands a widget to the user mid-loop and parks awaiting it
66
74
  * (`requestInteraction`) — the turn is suspended between provider calls, with no request
@@ -2,14 +2,20 @@ import type {
2
2
  AIProvider,
3
3
  AIProviderRegistry,
4
4
  CachePolicy,
5
+ ChatDriverResult,
5
6
  ChatMessage,
6
7
  ChatRequestOptions,
7
8
  ChatToolCall,
8
9
  ChatToolChoice,
9
10
  ChatToolDefinition,
10
11
  InteractionResult,
12
+ TurnFailureReason,
13
+ } from '@genesislcap/foundation-ai';
14
+ import {
15
+ isChatToolCallUnknown,
16
+ MalformedFunctionCallError,
17
+ ResponseTruncatedError,
11
18
  } from '@genesislcap/foundation-ai';
12
- import { isChatToolCallUnknown } from '@genesislcap/foundation-ai';
13
19
  import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
14
20
  import { agenticActivityBus } from '../../channel/ai-activity-bus';
15
21
  import type { AgentConfig } from '../../config/config';
@@ -2175,3 +2181,210 @@ condense('phaseEnd: collapses at endPhase() while the agent keeps running', asyn
2175
2181
  });
2176
2182
 
2177
2183
  condense.run();
2184
+
2185
+ // ---------------------------------------------------------------------------
2186
+ // turn-outcome surfacing (PTC-0) — the typed failure reason must reach BOTH
2187
+ // the loop-boundary result (`ChatDriverResult.failureReason`) and the
2188
+ // `tool-loop-end` activity-bus event detail. Historically every exit flattened
2189
+ // to `{ reason: 'done' }` and the bus detail was `undefined`; these lock the
2190
+ // two seams together, one per `TurnFailureReason`, plus a happy-path compat
2191
+ // check that the legacy shape is byte-unchanged.
2192
+ // ---------------------------------------------------------------------------
2193
+
2194
+ const outcome = createLogicSuite('ChatDriver turn-outcome surfacing');
2195
+
2196
+ outcome.after(() => {
2197
+ agenticActivityBus.close();
2198
+ });
2199
+
2200
+ /**
2201
+ * Subscribe to `tool-loop-end` and expose the most recent detail seen. `fired()` reports
2202
+ * whether the event was seen at all — distinct from `detail()`, which is `undefined` both
2203
+ * when no event fired and when a clean turn fired with the historical `undefined` detail.
2204
+ */
2205
+ const captureLoopEnd = (): {
2206
+ detail: () => { failureReason?: TurnFailureReason } | undefined;
2207
+ fired: () => boolean;
2208
+ stop: () => void;
2209
+ } => {
2210
+ let last: { failureReason?: TurnFailureReason } | undefined;
2211
+ let seen = false;
2212
+ const stop = agenticActivityBus.subscribe('tool-loop-end', (d) => {
2213
+ last = d;
2214
+ seen = true;
2215
+ });
2216
+ return { detail: () => (seen ? last : undefined), fired: () => seen, stop };
2217
+ };
2218
+
2219
+ /** A ChatDriver with an explicit (small) tool-iteration cap. */
2220
+ const makeCappedDriver = (
2221
+ config: AgentConfig,
2222
+ provider: AIProvider,
2223
+ maxIterations: number,
2224
+ sessionKey = '',
2225
+ ): ChatDriver => {
2226
+ const driver = new ChatDriver(
2227
+ makeRegistry(provider),
2228
+ {},
2229
+ [],
2230
+ undefined,
2231
+ undefined,
2232
+ maxIterations,
2233
+ 5,
2234
+ undefined,
2235
+ sessionKey,
2236
+ );
2237
+ driver.applyAgent(config);
2238
+ return driver;
2239
+ };
2240
+
2241
+ /**
2242
+ * Assert that a turn driven by `provider` surfaces `expected` at BOTH seams.
2243
+ * `driverFor` lets the max-iterations case swap in a low cap.
2244
+ */
2245
+ const assertSurfacesReason = async (
2246
+ label: string,
2247
+ provider: AIProvider,
2248
+ expected: TurnFailureReason,
2249
+ driverFor: (config: AgentConfig, provider: AIProvider, sessionKey: string) => ChatDriver = (
2250
+ c,
2251
+ p,
2252
+ k,
2253
+ ) => makeDriver(c, p, k),
2254
+ ): Promise<void> => {
2255
+ clearMetaEventRegistry();
2256
+ const sessionKey = `outcome-${label}`;
2257
+ const config = agent({
2258
+ name: 'Static',
2259
+ toolDefinitions: [def('noop')],
2260
+ toolHandlers: { noop: async () => 'ok' },
2261
+ });
2262
+ const driver = driverFor(config, provider, sessionKey);
2263
+ const cap = captureLoopEnd();
2264
+
2265
+ const result: ChatDriverResult = await driver.sendMessage('go');
2266
+
2267
+ // Seam 1 — the loop-boundary result. Discriminant stays 'done' (compat).
2268
+ assert.is(result.reason, 'done', `[${label}] discriminant stays 'done'`);
2269
+ assert.is(
2270
+ result.reason === 'done' ? result.failureReason : undefined,
2271
+ expected,
2272
+ `[${label}] result.failureReason surfaces the typed reason`,
2273
+ );
2274
+
2275
+ // Seam 2 — the activity-bus tool-loop-end detail.
2276
+ assert.ok(cap.detail(), `[${label}] a tool-loop-end event fired`);
2277
+ assert.is(
2278
+ cap.detail()!.failureReason,
2279
+ expected,
2280
+ `[${label}] the bus detail carries the same reason`,
2281
+ );
2282
+
2283
+ // Consistency: the debug-log turn.error records the same taxonomy.
2284
+ const err = getMetaEvents(sessionKey).find((e) => e.type === 'turn.error');
2285
+ assert.ok(err, `[${label}] a turn.error is recorded`);
2286
+ assert.is(err!.detail?.reason, expected, `[${label}] the debug-log reason matches`);
2287
+
2288
+ cap.stop();
2289
+ };
2290
+
2291
+ /** A provider that returns a MALFORMED_FUNCTION_CALL on every call. */
2292
+ const malformedProvider = (): AIProvider => ({
2293
+ chat: async (): Promise<ChatMessage> => {
2294
+ throw new MalformedFunctionCallError('bad call');
2295
+ },
2296
+ });
2297
+
2298
+ /** A provider that returns an empty response on every call. */
2299
+ const emptyProvider = (): AIProvider => ({
2300
+ chat: async (): Promise<ChatMessage> => ({ role: 'assistant', content: '' }),
2301
+ });
2302
+
2303
+ /** A provider that throws a ResponseTruncatedError (deterministic, no retry). */
2304
+ const truncatedProvider = (): AIProvider => ({
2305
+ chat: async (): Promise<ChatMessage> => {
2306
+ throw new ResponseTruncatedError('test-model', 1024, 1024, ['noop']);
2307
+ },
2308
+ });
2309
+
2310
+ /** A provider that throws a generic error (the sendMessage catch-all → 'exception'). */
2311
+ const throwingProvider = (): AIProvider => ({
2312
+ chat: async (): Promise<ChatMessage> => {
2313
+ throw new Error('boom');
2314
+ },
2315
+ });
2316
+
2317
+ /** A provider that never stops calling a valid tool (drives the iteration cap). */
2318
+ const neverStopsProvider = (): AIProvider => {
2319
+ let n = 0;
2320
+ return {
2321
+ chat: async (): Promise<ChatMessage> => {
2322
+ const id = `noop-${n}`;
2323
+ n += 1;
2324
+ return { role: 'assistant', content: '', toolCalls: [{ id, name: 'noop', args: {} }] };
2325
+ },
2326
+ };
2327
+ };
2328
+
2329
+ /** A provider that keeps calling a tool with no handler (hallucinated → limit). */
2330
+ const hallucinatedProvider = (): AIProvider =>
2331
+ scriptedProvider(Array.from({ length: 6 }, (_u, i) => callsTool('ghost', `ghost-${i}`)));
2332
+
2333
+ outcome('malformed-function-call surfaces at both seams', async () => {
2334
+ await assertSurfacesReason('malformed', malformedProvider(), 'malformed-function-call');
2335
+ });
2336
+
2337
+ outcome('empty-response surfaces at both seams', async () => {
2338
+ await assertSurfacesReason('empty', emptyProvider(), 'empty-response');
2339
+ });
2340
+
2341
+ outcome('response-truncated surfaces at both seams', async () => {
2342
+ await assertSurfacesReason('truncated', truncatedProvider(), 'response-truncated');
2343
+ });
2344
+
2345
+ outcome('exception surfaces at both seams', async () => {
2346
+ await assertSurfacesReason('exception', throwingProvider(), 'exception');
2347
+ });
2348
+
2349
+ outcome('unknown-tool-limit surfaces at both seams', async () => {
2350
+ await assertSurfacesReason('unknown-tool', hallucinatedProvider(), 'unknown-tool-limit');
2351
+ });
2352
+
2353
+ outcome('max-iterations surfaces at both seams', async () => {
2354
+ await assertSurfacesReason('max-iter', neverStopsProvider(), 'max-iterations', (c, p, k) =>
2355
+ makeCappedDriver(c, p, 2, k),
2356
+ );
2357
+ });
2358
+
2359
+ outcome('a clean turn leaves the legacy shape byte-unchanged (no failureReason)', async () => {
2360
+ clearMetaEventRegistry();
2361
+ const config = agent({ name: 'Static' });
2362
+ // Plain-text reply ends the turn cleanly on the first call.
2363
+ const driver = makeDriver(
2364
+ config,
2365
+ scriptedProvider([{ role: 'assistant', content: 'hi' }]),
2366
+ 'outcome-ok',
2367
+ );
2368
+ const cap = captureLoopEnd();
2369
+
2370
+ const result: ChatDriverResult = await driver.sendMessage('go');
2371
+
2372
+ // The result is exactly `{ reason: 'done' }` — no `failureReason` key added.
2373
+ assert.equal(result, { reason: 'done' }, 'happy-path result is the historical shape');
2374
+ assert.not.ok('failureReason' in result, 'no failureReason key is present on a clean turn');
2375
+
2376
+ // The bus event still fires, and its detail is the historical `undefined` — a clean
2377
+ // turn emits no detail object at all (byte-shape compat), not `{ failureReason: undefined }`.
2378
+ assert.ok(cap.fired(), 'a tool-loop-end event fired');
2379
+ assert.is(cap.detail(), undefined, 'a clean turn emits the historical undefined detail');
2380
+
2381
+ // And no turn.error was recorded.
2382
+ assert.not.ok(
2383
+ getMetaEvents('outcome-ok').some((e) => e.type === 'turn.error'),
2384
+ 'a clean turn records no turn.error',
2385
+ );
2386
+
2387
+ cap.stop();
2388
+ });
2389
+
2390
+ outcome.run();
@@ -17,6 +17,7 @@ import type {
17
17
  InteractionResult,
18
18
  SubAgentFailureReason,
19
19
  SubAgentRequestOptions,
20
+ TurnFailureReason,
20
21
  } from '@genesislcap/foundation-ai';
21
22
  import {
22
23
  isObservableAIProviderRegistry,
@@ -631,6 +632,35 @@ export class ChatDriver extends EventTarget implements AiDriver {
631
632
  return { reason: 'done' };
632
633
  }
633
634
 
635
+ /**
636
+ * Build the `done` loop result, carrying the typed failure reason when the turn
637
+ * bailed (PTC-0). The discriminant stays `'done'` either way — the same value a
638
+ * clean turn returns — so consumers matching on `reason === 'done'` are unchanged;
639
+ * `failureReason` is simply present on a failure and absent on success. Omitted
640
+ * (rather than set to `undefined`) so a happy-path result stays byte-identical to
641
+ * the historical `{ reason: 'done' }`.
642
+ */
643
+ private turnDone(failureReason?: TurnFailureReason): ChatDriverResult {
644
+ return failureReason ? { reason: 'done', failureReason } : { reason: 'done' };
645
+ }
646
+
647
+ /** The typed failure reason on a loop result, or `undefined` for a clean turn / handoff. */
648
+ private static failureReasonOf(result: ChatDriverResult): TurnFailureReason | undefined {
649
+ return result.reason === 'done' ? result.failureReason : undefined;
650
+ }
651
+
652
+ /**
653
+ * Build the `tool-loop-end` event detail for a turn's result. A failure carries a
654
+ * `{ failureReason }` detail; a clean turn emits `undefined` — the historical shape,
655
+ * kept byte-identical so subscribers see exactly what they always have.
656
+ */
657
+ private static loopEndDetail(
658
+ result: ChatDriverResult,
659
+ ): { failureReason: TurnFailureReason } | undefined {
660
+ const failureReason = ChatDriver.failureReasonOf(result);
661
+ return failureReason ? { failureReason } : undefined;
662
+ }
663
+
634
664
  /**
635
665
  * Swap in a new agent's configuration. Called by OrchestratingDriver before
636
666
  * each specialist turn so the shared driver runs with the right tools and prompt.
@@ -1412,8 +1442,11 @@ export class ChatDriver extends EventTarget implements AiDriver {
1412
1442
  });
1413
1443
  agenticActivityBus.publish('tool-loop-start', undefined);
1414
1444
 
1445
+ // Captured so the `finally` can carry the turn's outcome onto `tool-loop-end`.
1446
+ let result: ChatDriverResult = { reason: 'done' };
1415
1447
  try {
1416
- return await this.runToolLoop(userInput, attachments);
1448
+ result = await this.runToolLoop(userInput, attachments);
1449
+ return result;
1417
1450
  } catch (e) {
1418
1451
  logger.error('ChatDriver error:', e);
1419
1452
  recordTurnError(this.sessionKey, 'exception', {
@@ -1427,7 +1460,8 @@ export class ChatDriver extends EventTarget implements AiDriver {
1427
1460
  role: 'assistant',
1428
1461
  content: 'Sorry, something went wrong on my end. Please try again in a moment.',
1429
1462
  });
1430
- return { reason: 'done' };
1463
+ result = this.turnDone('exception');
1464
+ return result;
1431
1465
  } finally {
1432
1466
  recordMetaEvent(this.sessionKey, 'turn.end', {
1433
1467
  phase: 'sendMessage',
@@ -1436,7 +1470,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
1436
1470
  });
1437
1471
  this.busy = false;
1438
1472
  this.endTurn();
1439
- agenticActivityBus.publish('tool-loop-end', undefined);
1473
+ agenticActivityBus.publish('tool-loop-end', ChatDriver.loopEndDetail(result));
1440
1474
  }
1441
1475
  }
1442
1476
 
@@ -1782,8 +1816,11 @@ export class ChatDriver extends EventTarget implements AiDriver {
1782
1816
  agent: this.activeAgentName,
1783
1817
  });
1784
1818
  agenticActivityBus.publish('tool-loop-start', undefined);
1819
+ // Captured so the `finally` can carry the turn's outcome onto `tool-loop-end`.
1820
+ let result: ChatDriverResult = { reason: 'done' };
1785
1821
  try {
1786
- return await this.runToolLoop('', undefined, transientPrimer);
1822
+ result = await this.runToolLoop('', undefined, transientPrimer);
1823
+ return result;
1787
1824
  } catch (e) {
1788
1825
  logger.error('ChatDriver error:', e);
1789
1826
  recordTurnError(this.sessionKey, 'exception', {
@@ -1797,7 +1834,8 @@ export class ChatDriver extends EventTarget implements AiDriver {
1797
1834
  role: 'assistant',
1798
1835
  content: 'Sorry, something went wrong on my end. Please try again in a moment.',
1799
1836
  });
1800
- return { reason: 'done' };
1837
+ result = this.turnDone('exception');
1838
+ return result;
1801
1839
  } finally {
1802
1840
  recordMetaEvent(this.sessionKey, 'turn.end', {
1803
1841
  phase: 'continueFromHistory',
@@ -1806,7 +1844,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
1806
1844
  });
1807
1845
  this.busy = false;
1808
1846
  this.endTurn();
1809
- agenticActivityBus.publish('tool-loop-end', undefined);
1847
+ agenticActivityBus.publish('tool-loop-end', ChatDriver.loopEndDetail(result));
1810
1848
  }
1811
1849
  }
1812
1850
 
@@ -2244,7 +2282,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
2244
2282
  '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.',
2245
2283
  });
2246
2284
  }
2247
- return { reason: 'done' };
2285
+ return this.turnDone('malformed-function-call');
2248
2286
  }
2249
2287
  // The response was truncated at the provider's output-token cap while it
2250
2288
  // still carried a tool call — its arguments are incomplete and unusable.
@@ -2273,7 +2311,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
2273
2311
  'My response was cut off because a single step reached the model output limit. This usually means one step tried to produce too much at once — try breaking your request into smaller steps.',
2274
2312
  });
2275
2313
  }
2276
- return { reason: 'done' };
2314
+ return this.turnDone('response-truncated');
2277
2315
  }
2278
2316
  // A request timeout from the transport (tagged `TimeoutError`) is not a
2279
2317
  // bug on our end — surface it distinctly instead of letting it fall
@@ -2299,7 +2337,9 @@ export class ChatDriver extends EventTarget implements AiDriver {
2299
2337
  'The request timed out. You can ask me to try again, or break this into a smaller step.',
2300
2338
  });
2301
2339
  }
2302
- return { reason: 'done' };
2340
+ // Recorded as `exception` above (there is no separate `timeout` member of
2341
+ // TurnFailureReason for the main turn); surface the same reason here.
2342
+ return this.turnDone('exception');
2303
2343
  }
2304
2344
  // The request was aborted: either a user cancel (turnController) or a
2305
2345
  // driver dispose (lifecycleController, chained into the turn). A user
@@ -2380,7 +2420,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
2380
2420
  '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.',
2381
2421
  });
2382
2422
  }
2383
- return { reason: 'done' };
2423
+ return this.turnDone('empty-response');
2384
2424
  } else {
2385
2425
  // Split one model response into separate, individually-toggleable messages so each has its
2386
2426
  // own visibility toggle and debug-log category:
@@ -2744,7 +2784,7 @@ export class ChatDriver extends EventTarget implements AiDriver {
2744
2784
  "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.",
2745
2785
  });
2746
2786
  }
2747
- return { reason: 'done' };
2787
+ return this.turnDone('unknown-tool-limit');
2748
2788
  }
2749
2789
 
2750
2790
  const firstContinuation = systemCalls[0];
@@ -2759,10 +2799,13 @@ export class ChatDriver extends EventTarget implements AiDriver {
2759
2799
  // Sub-agent early exit — checked here so the exit point mirrors the
2760
2800
  // system-call pattern above. Set by completeSubAgent() in a tool handler.
2761
2801
  if (this.subAgentCompletion) {
2762
- return { reason: 'done' };
2802
+ return this.turnDone();
2763
2803
  }
2764
2804
  }
2765
2805
 
2806
+ // The loop fell through: either it hit the iteration cap (a failure) or it
2807
+ // broke on a clean final answer (success). Only the former carries a reason.
2808
+ let failureReason: TurnFailureReason | undefined;
2766
2809
  if (iterations >= this.maxToolIterations) {
2767
2810
  logger.warn('ChatDriver: reached max tool iterations, stopping');
2768
2811
  recordTurnError(this.sessionKey, 'max-iterations', {
@@ -2781,9 +2824,10 @@ export class ChatDriver extends EventTarget implements AiDriver {
2781
2824
  "I've reached my limit for this response. You can ask me to continue and I'll pick up where I left off.",
2782
2825
  });
2783
2826
  }
2827
+ failureReason = 'max-iterations';
2784
2828
  }
2785
2829
 
2786
- return { reason: 'done' };
2830
+ return this.turnDone(failureReason);
2787
2831
  }
2788
2832
 
2789
2833
  private appendToHistory(message: ChatMessage): void {
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@ export * from './state/persistence';
16
16
  export * from './provider/ai-provider-switcher';
17
17
  export * from './provider/assistant-app-settings';
18
18
  export * from './utils/tool-fold';
19
+ export type { TurnFailureReason } from './state/debug-event-log';
19
20
  export type { TimelineMessage } from './utils/flatten-sub-agent-messages';
20
21
  export type { CostSessionModelEntry, CostSessionRecord } from './utils/cost-session-history';
21
22
  export type { ModelTagAppearance } from '@genesislcap/foundation-ai';
@@ -24,6 +24,8 @@
24
24
  * @internal
25
25
  */
26
26
 
27
+ import type { TurnFailureReason } from '@genesislcap/foundation-ai';
28
+
27
29
  /**
28
30
  * Catalogue of meta event names. This is the documented surface — extend it as
29
31
  * new events are wired in (Tier 2/3 lifecycle, interaction, provider events).
@@ -211,25 +213,14 @@ export function recordMetaEvent(
211
213
 
212
214
  /**
213
215
  * Why a turn failed or was retried — stamped as `detail.reason` on `turn.error`
214
- * and `turn.retry` events. Enumerated so the set stays in sync with the README
215
- * and call sites can't drift to ad-hoc strings.
216
- *
217
- * - `exception` — an uncaught error escaped the tool loop (catch-all).
218
- * - `malformed-function-call`— the provider returned an unparseable tool call.
219
- * - `empty-response` — the model returned no content and no tool calls.
220
- * - `unknown-tool-limit` — the model repeatedly called tools it couldn't dispatch,
221
- * whether hallucinated or stale (real earlier, retired now).
222
- * - `max-iterations` — the tool loop hit its iteration cap.
223
- * - `response-truncated` — a turn stopped at the provider's output-token cap with an
224
- * incomplete tool call; deterministic, so it bails without retry.
216
+ * and `turn.retry` events, and (since PTC-0) surfaced at the driver's loop
217
+ * boundary via {@link ChatDriverResult.failureReason}. Re-exported from
218
+ * `@genesislcap/foundation-ai` — where it lives alongside `ChatDriverResult` so
219
+ * the boundary field and this log surface can never drift apart — under its
220
+ * historical name so existing importers are unaffected. Enumerated so the set
221
+ * stays in sync with the README and call sites can't drift to ad-hoc strings.
225
222
  */
226
- export type TurnFailureReason =
227
- | 'exception'
228
- | 'malformed-function-call'
229
- | 'empty-response'
230
- | 'unknown-tool-limit'
231
- | 'max-iterations'
232
- | 'response-truncated';
223
+ export type { TurnFailureReason };
233
224
 
234
225
  /**
235
226
  * Record a turn-ending failure (`turn.error`, importance `high`). The `reason`