@genesislcap/ai-assistant 15.24.0 → 15.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { __awaiter, __rest } from "tslib";
2
- import { BudgetExhaustedError, DEFAULT_BUDGET_EXHAUSTED_MESSAGE, DEFAULT_PROVIDER_REFUSED_MESSAGE, ContextOverflowError, DEFAULT_CONTEXT_OVERFLOW_MESSAGE, ProviderRefusedError, isObservableAIProviderRegistry, MalformedFunctionCallError, ResponseTruncatedError, vendorTypeOfLabel, } from '@genesislcap/foundation-ai';
2
+ import { BudgetExhaustedError, ContextOverflowError, ProviderRefusedError, isObservableAIProviderRegistry, MalformedFunctionCallError, ResponseTruncatedError, vendorTypeOfLabel, } from '@genesislcap/foundation-ai';
3
3
  import { NOOP_ACTIVITY_BUS } from '../../channel/ai-activity-bus';
4
4
  import { resolveChatProvider } from '../../config/validate-providers';
5
5
  import { clearSession, getMetaEvents, mergeMetaEvents, recordMetaEvent, recordTurnError, recordTurnRetry, } from '../../state/debug-event-log';
@@ -10,6 +10,7 @@ import { applyHistoryCap, buildCompactionSummaryPrompt, planCompaction, normaliz
10
10
  import { logger } from '../../utils/logger';
11
11
  import { messageUsage, sumUsage } from '../../utils/sum-usage';
12
12
  import { TOOL_FOLD_SYMBOL } from '../../utils/tool-fold';
13
+ import { TURN_FAILURE_EXITS } from './turn-failures';
13
14
  /**
14
15
  * Lift the reportable facts off a {@link BudgetExhaustedError}, or `undefined`
15
16
  * when it carries none.
@@ -472,6 +473,35 @@ export class ChatDriver extends EventTarget {
472
473
  // a shape unchanged for a consumer that structurally compares it.
473
474
  return Object.assign(Object.assign({ reason: 'done', failureReason }, (budget ? { budget } : {})), (providerRefused ? { providerRefused } : {}));
474
475
  }
476
+ /**
477
+ * End the turn on a terminal failure: fail upward when this driver is a sub-agent, speak
478
+ * to the user when it is not, and hand the reason to the caller either way.
479
+ *
480
+ * Only that fork lives here. Each exit keeps its own `recordTurnError` call, because the
481
+ * telemetry payload is what legitimately differs between them — attempt counts, token
482
+ * figures, tool names — and folding it in would mean one optional field per condition.
483
+ *
484
+ * `overrides.copy` serves the two walls whose prose is composed per occurrence:
485
+ * `budgetExhaustedBubble` names the vendor and can offer another, `providerRefusedBubble`
486
+ * prefers a host-supplied message. Every other exit reads its copy from the table.
487
+ *
488
+ * One exit stays out: the `BudgetExhaustedError` catch. Not because its shape differs —
489
+ * its two telemetry payloads are field-for-field identical — but because its main-turn
490
+ * branch calls `reportBudgetExhausted`, which is public and also serves
491
+ * `OrchestratingDriver`'s classification phase, where no tool loop is running. That
492
+ * method has to survive either way, so routing the catch through here would relocate the
493
+ * duplication rather than remove it.
494
+ */
495
+ endTurnWithFailure(exit, overrides) {
496
+ var _a;
497
+ if (this.isSubAgent) {
498
+ this.failSubAgent(exit.subReason, overrides === null || overrides === void 0 ? void 0 : overrides.budget, overrides === null || overrides === void 0 ? void 0 : overrides.providerRefused);
499
+ }
500
+ else {
501
+ this.appendDriverNotice((_a = overrides === null || overrides === void 0 ? void 0 : overrides.copy) !== null && _a !== void 0 ? _a : exit.copy);
502
+ }
503
+ return this.turnDone(exit.resultReason, overrides === null || overrides === void 0 ? void 0 : overrides.budget, overrides === null || overrides === void 0 ? void 0 : overrides.providerRefused);
504
+ }
475
505
  /**
476
506
  * Terminal budget outcome for a wall hit **outside** the tool loop — today,
477
507
  * `OrchestratingDriver`'s classification phase, which calls the provider
@@ -512,7 +542,7 @@ export class ChatDriver extends EventTarget {
512
542
  // said "switch" while the transcript bubble directly under it said only
513
543
  // "contact your administrator" (GENC-1464 tester finding).
514
544
  if ((wall === null || wall === void 0 ? void 0 : wall.otherVendorAvailable) !== true)
515
- return DEFAULT_BUDGET_EXHAUSTED_MESSAGE;
545
+ return TURN_FAILURE_EXITS.budgetExhausted.copy;
516
546
  // The label is display copy, nothing more — attribution (which vendor gets
517
547
  // WALLED) stays `budgetDetailOf`'s job. The parameter is the shared shape of
518
548
  // both call sites: the thrown `BudgetExhaustedError` on the direct path, and
@@ -555,33 +585,7 @@ export class ChatDriver extends EventTarget {
555
585
  */
556
586
  providerRefusedBubble() {
557
587
  var _a;
558
- return (_a = this.providerRefusedMessageOverride) !== null && _a !== void 0 ? _a : DEFAULT_PROVIDER_REFUSED_MESSAGE;
559
- }
560
- /**
561
- * Terminal provider-refusal outcome. Mirrors `reportBudgetExhausted` so the two walls behave
562
- * identically from the caller's side, while keeping their diagnostics distinct.
563
- *
564
- * `kind` reaches the debug log and the result but never the transcript: it is what lets an operator
565
- * tell "top up the account" from "rotate the key", and with cause-free user copy this is the only
566
- * place that distinction survives.
567
- */
568
- reportProviderRefused(e) {
569
- var _a;
570
- const detail = providerRefusedDetailOf(e);
571
- this.providerRefusedThisTurn = true;
572
- this.providerRefusedDetail = detail;
573
- logger.error('ChatDriver: provider refused the request', e);
574
- recordTurnError(this.sessionKey, 'provider-refused', {
575
- agent: this.activeAgentName,
576
- provider: this.lastResolvedProviderName,
577
- vendor: (_a = vendorTypeOfLabel(e.vendorLabel)) !== null && _a !== void 0 ? _a : this.lastResolvedProvider,
578
- kind: e.kind,
579
- upstreamStatus: e.upstreamStatus,
580
- upstreamType: e.upstreamType,
581
- isSubAgent: this.isSubAgent,
582
- });
583
- this.appendDriverNotice(this.providerRefusedBubble());
584
- return this.turnDone('provider-refused', undefined, detail);
588
+ return (_a = this.providerRefusedMessageOverride) !== null && _a !== void 0 ? _a : TURN_FAILURE_EXITS.providerRefused.copy;
585
589
  }
586
590
  /** The typed failure reason on a loop result, or `undefined` for a clean turn / handoff. */
587
591
  static failureReasonOf(result) {
@@ -2272,18 +2276,15 @@ export class ChatDriver extends EventTarget {
2272
2276
  via: 'sub-agent',
2273
2277
  isSubAgent: this.isSubAgent,
2274
2278
  });
2275
- if (this.isSubAgent) {
2276
- // The detail is forwarded, not just the reason — this driver may itself be an INTERMEDIATE
2277
- // sub-agent, and a refusal that started in a grandchild reaches its own parent only through
2278
- // this call. Dropping it here still stopped the top-level turn (the reason travels), but
2279
- // arrived with no vendor and no kind, which is the whole diagnostic this payload exists to
2280
- // carry across hops. The adjacent budget path forwards `budgetWallDetail` for the same reason.
2281
- this.failSubAgent('provider_refused', undefined, this.providerRefusedDetail);
2282
- }
2283
- else {
2284
- this.appendDriverNotice(this.providerRefusedBubble());
2285
- }
2286
- return this.turnDone('provider-refused', undefined, this.providerRefusedDetail);
2279
+ // The detail is forwarded, not just the reason — this driver may itself be an INTERMEDIATE
2280
+ // sub-agent, and a refusal that started in a grandchild reaches its own parent only through
2281
+ // this call. Dropping it still stopped the top-level turn (the reason travels), but it
2282
+ // arrived with no vendor and no kind, which is the whole diagnostic this payload exists to
2283
+ // carry across hops. The adjacent budget path forwards `budgetWallDetail` for the same reason.
2284
+ return this.endTurnWithFailure(TURN_FAILURE_EXITS.providerRefused, {
2285
+ copy: this.providerRefusedBubble(),
2286
+ providerRefused: this.providerRefusedDetail,
2287
+ });
2287
2288
  }
2288
2289
  if (this.budgetExhaustedThisTurn) {
2289
2290
  logger.error('ChatDriver: ending the turn — a sub-agent hit the AI budget wall');
@@ -2303,20 +2304,17 @@ export class ChatDriver extends EventTarget {
2303
2304
  via: 'sub-agent',
2304
2305
  isSubAgent: this.isSubAgent,
2305
2306
  });
2306
- if (this.isSubAgent) {
2307
- this.failSubAgent('budget_exhausted', this.budgetWallDetail);
2308
- }
2309
- else {
2310
- // Composed from the DETAIL, not an error object: on this path the wall
2311
- // arrived as a sub-agent's failure payload — `budgetWallDetail`, first
2312
- // attribution wins — and an unattributable child wall (detail
2313
- // undefined) composes the neutral default.
2314
- this.appendDriverNotice(this.budgetExhaustedBubble(this.budgetWallDetail));
2315
- }
2316
- // Carried onto the result so `loopEndDetail` publishes the refusing
2317
- // vendor rather than falling through to `lastResolvedProvider`, and the
2318
- // host's latch walls the vendor that actually ran out.
2319
- return this.turnDone('budget-exhausted', this.budgetWallDetail);
2307
+ // The copy is composed from the DETAIL, not an error object: on this path the
2308
+ // wall arrived as a sub-agent's failure payload — `budgetWallDetail`, first
2309
+ // attribution wins — and an unattributable child wall (detail undefined)
2310
+ // composes the neutral default. The same detail rides the result, so
2311
+ // `loopEndDetail` publishes the refusing vendor rather than falling through to
2312
+ // `lastResolvedProvider`, and the host's latch walls the vendor that actually
2313
+ // ran out.
2314
+ return this.endTurnWithFailure(TURN_FAILURE_EXITS.budgetExhausted, {
2315
+ copy: this.budgetExhaustedBubble(this.budgetWallDetail),
2316
+ budget: this.budgetWallDetail,
2317
+ });
2320
2318
  }
2321
2319
  const promptCtx = {
2322
2320
  agentName: (_c = this.activeAgentName) !== null && _c !== void 0 ? _c : '',
@@ -2552,14 +2550,7 @@ export class ChatDriver extends EventTarget {
2552
2550
  iterations,
2553
2551
  isSubAgent: this.isSubAgent,
2554
2552
  });
2555
- if (this.isSubAgent) {
2556
- this.failSubAgent('context_exhausted');
2557
- }
2558
- else {
2559
- this.appendDriverNotice('I had to stop here — this conversation has filled the available context. ' +
2560
- 'Compact it to free up room, then ask me to continue.');
2561
- }
2562
- return this.turnDone('context-exhausted');
2553
+ return this.endTurnWithFailure(TURN_FAILURE_EXITS.contextGuard);
2563
2554
  }
2564
2555
  let response;
2565
2556
  try {
@@ -2589,14 +2580,7 @@ export class ChatDriver extends EventTarget {
2589
2580
  finishMessage: e.finishMessage,
2590
2581
  isSubAgent: this.isSubAgent,
2591
2582
  });
2592
- if (this.isSubAgent) {
2593
- // Bubble a typed failure to the parent instead of speaking to the user.
2594
- this.failSubAgent('malformed_tool_call');
2595
- }
2596
- else {
2597
- this.appendDriverNotice('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.');
2598
- }
2599
- return this.turnDone('malformed-function-call');
2583
+ return this.endTurnWithFailure(TURN_FAILURE_EXITS.malformedToolCall);
2600
2584
  }
2601
2585
  // The response was truncated at the provider's output-token cap while it
2602
2586
  // still carried a tool call — its arguments are incomplete and unusable.
@@ -2615,14 +2599,7 @@ export class ChatDriver extends EventTarget {
2615
2599
  tools: e.toolNames,
2616
2600
  isSubAgent: this.isSubAgent,
2617
2601
  });
2618
- if (this.isSubAgent) {
2619
- // Bubble a typed failure to the parent instead of speaking to the user.
2620
- this.failSubAgent('response_truncated');
2621
- }
2622
- else {
2623
- this.appendDriverNotice('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.');
2624
- }
2625
- return this.turnDone('response-truncated');
2602
+ return this.endTurnWithFailure(TURN_FAILURE_EXITS.responseTruncated);
2626
2603
  }
2627
2604
  // The AI-spend budget is gone and the proxy refused the request (HTTP
2628
2605
  // 402). Terminal in the strongest sense available to us: unlike a
@@ -2662,35 +2639,35 @@ export class ChatDriver extends EventTarget {
2662
2639
  via: 'provider',
2663
2640
  isSubAgent: this.isSubAgent,
2664
2641
  });
2665
- if (this.isSubAgent) {
2666
- this.failSubAgent('context_exhausted');
2667
- }
2668
- else {
2669
- this.appendDriverNotice(DEFAULT_CONTEXT_OVERFLOW_MESSAGE);
2670
- }
2671
- return this.turnDone('context-exhausted');
2642
+ return this.endTurnWithFailure(TURN_FAILURE_EXITS.contextOverflow);
2672
2643
  }
2673
2644
  if (e instanceof ProviderRefusedError) {
2674
- if (this.isSubAgent) {
2675
- logger.error('ChatDriver: provider refused the request', e);
2676
- recordTurnError(this.sessionKey, 'provider-refused', {
2677
- agent: this.activeAgentName,
2678
- provider: this.lastResolvedProviderName,
2679
- vendor: (_f = vendorTypeOfLabel(e.vendorLabel)) !== null && _f !== void 0 ? _f : this.lastResolvedProvider,
2680
- kind: e.kind,
2681
- upstreamStatus: e.upstreamStatus,
2682
- upstreamType: e.upstreamType,
2683
- isSubAgent: true,
2684
- });
2685
- // The detail is BUBBLED, not merely flagged. Setting `providerRefusedThisTurn` on `this`
2686
- // would be pointless here — `this` is the CHILD, and the parent that must stop is a
2687
- // different driver instance. `invokeSubAgent` reads this payload off the child and sets its
2688
- // own flag; see the branch there.
2689
- const detail = providerRefusedDetailOf(e);
2690
- this.failSubAgent('provider_refused', undefined, detail);
2691
- return this.turnDone('provider-refused', undefined, detail);
2645
+ const detail = providerRefusedDetailOf(e);
2646
+ if (!this.isSubAgent) {
2647
+ // Flagged only on the driver whose own turn must stop. On the sub-agent path the
2648
+ // detail is BUBBLED instead: `this` is the CHILD, and the parent that must stop is a
2649
+ // different driver instance, so a flag here would help nobody — `invokeSubAgent`
2650
+ // reads the payload off the child and sets its own.
2651
+ this.providerRefusedThisTurn = true;
2652
+ this.providerRefusedDetail = detail;
2692
2653
  }
2693
- return this.reportProviderRefused(e);
2654
+ logger.error('ChatDriver: provider refused the request', e);
2655
+ // `kind` reaches the debug log and the result but never the transcript: it is what lets
2656
+ // an operator tell "top up the account" from "rotate the key", and with cause-free user
2657
+ // copy this is the only place that distinction survives.
2658
+ recordTurnError(this.sessionKey, 'provider-refused', {
2659
+ agent: this.activeAgentName,
2660
+ provider: this.lastResolvedProviderName,
2661
+ vendor: (_f = vendorTypeOfLabel(e.vendorLabel)) !== null && _f !== void 0 ? _f : this.lastResolvedProvider,
2662
+ kind: e.kind,
2663
+ upstreamStatus: e.upstreamStatus,
2664
+ upstreamType: e.upstreamType,
2665
+ isSubAgent: this.isSubAgent,
2666
+ });
2667
+ return this.endTurnWithFailure(TURN_FAILURE_EXITS.providerRefused, {
2668
+ copy: this.providerRefusedBubble(),
2669
+ providerRefused: detail,
2670
+ });
2694
2671
  }
2695
2672
  if (e instanceof BudgetExhaustedError) {
2696
2673
  // Flagged as well as returned: a sub-agent's wall reaches the PARENT
@@ -2737,16 +2714,10 @@ export class ChatDriver extends EventTarget {
2737
2714
  message: e.message,
2738
2715
  isSubAgent: this.isSubAgent,
2739
2716
  });
2740
- if (this.isSubAgent) {
2741
- // Bubble a typed failure to the parent instead of speaking to the user.
2742
- this.failSubAgent('timeout');
2743
- }
2744
- else {
2745
- this.appendDriverNotice('The request timed out. You can ask me to try again, or break this into a smaller step.');
2746
- }
2747
- // Recorded as `exception` above (there is no separate `timeout` member of
2748
- // TurnFailureReason for the main turn); surface the same reason here.
2749
- return this.turnDone('exception');
2717
+ // The caller sees `exception`, matching the telemetry above: there is no
2718
+ // `timeout` member of TurnFailureReason. A sub-agent gets the specific reason —
2719
+ // see the `timeout` entry in TURN_FAILURE_EXITS.
2720
+ return this.endTurnWithFailure(TURN_FAILURE_EXITS.timeout);
2750
2721
  }
2751
2722
  // The request was aborted: either a user cancel (turnController) or a
2752
2723
  // driver dispose (lifecycleController, chained into the turn). A user
@@ -2827,23 +2798,15 @@ export class ChatDriver extends EventTarget {
2827
2798
  iterations -= 1;
2828
2799
  continue;
2829
2800
  }
2830
- const failureReason = isRefusal ? 'refusal' : 'empty-response';
2801
+ const exit = isRefusal ? TURN_FAILURE_EXITS.refusal : TURN_FAILURE_EXITS.emptyResponse;
2831
2802
  logger.error(isRefusal
2832
2803
  ? 'ChatDriver: model refused the request (safety decline) — not retrying'
2833
2804
  : 'ChatDriver: empty model response after all retries');
2834
- recordTurnError(this.sessionKey, failureReason, Object.assign({ agent: this.activeAgentName, provider: this.lastResolvedProviderName, attempts: emptyResponseAttempts,
2805
+ recordTurnError(this.sessionKey, exit.resultReason, Object.assign({ agent: this.activeAgentName, provider: this.lastResolvedProviderName, attempts: emptyResponseAttempts,
2835
2806
  // See the retry above: a zero here says the agent had no tools, which is most of the
2836
2807
  // explanation for a blank turn and is otherwise invisible.
2837
2808
  toolCount: this.toolDefinitions.length, isSubAgent: this.isSubAgent }, response.responseMeta));
2838
- if (this.isSubAgent) {
2839
- this.failSubAgent(isRefusal ? 'refusal' : 'empty_response');
2840
- }
2841
- else {
2842
- this.appendDriverNotice(isRefusal
2843
- ? 'This request was declined. Try rephrasing it, or breaking it into smaller steps.'
2844
- : '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.');
2845
- }
2846
- return this.turnDone(failureReason);
2809
+ return this.endTurnWithFailure(exit);
2847
2810
  }
2848
2811
  else {
2849
2812
  // Split one model response into separate, individually-toggleable messages so each has its
@@ -3187,13 +3150,7 @@ export class ChatDriver extends EventTarget {
3187
3150
  availableTools: Object.keys(this.toolHandlers),
3188
3151
  isSubAgent: this.isSubAgent,
3189
3152
  });
3190
- if (this.isSubAgent) {
3191
- this.failSubAgent('unknown_tool_limit');
3192
- }
3193
- else {
3194
- this.appendDriverNotice("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.");
3195
- }
3196
- return this.turnDone('unknown-tool-limit');
3153
+ return this.endTurnWithFailure(TURN_FAILURE_EXITS.unknownToolLimit);
3197
3154
  }
3198
3155
  const firstContinuation = systemCalls[0];
3199
3156
  if (firstContinuation) {
@@ -3208,7 +3165,6 @@ export class ChatDriver extends EventTarget {
3208
3165
  }
3209
3166
  // The loop fell through: either it hit the iteration cap (a failure) or it
3210
3167
  // broke on a clean final answer (success). Only the former carries a reason.
3211
- let failureReason;
3212
3168
  if (iterations >= this.maxToolIterations) {
3213
3169
  logger.warn('ChatDriver: reached max tool iterations, stopping');
3214
3170
  recordTurnError(this.sessionKey, 'max-iterations', {
@@ -3218,15 +3174,9 @@ export class ChatDriver extends EventTarget {
3218
3174
  limit: this.maxToolIterations,
3219
3175
  isSubAgent: this.isSubAgent,
3220
3176
  });
3221
- if (this.isSubAgent) {
3222
- this.failSubAgent('max_iterations');
3223
- }
3224
- else {
3225
- this.appendDriverNotice("I've reached my limit for this response. You can ask me to continue and I'll pick up where I left off.");
3226
- }
3227
- failureReason = 'max-iterations';
3177
+ return this.endTurnWithFailure(TURN_FAILURE_EXITS.maxIterations);
3228
3178
  }
3229
- return this.turnDone(failureReason);
3179
+ return this.turnDone();
3230
3180
  });
3231
3181
  }
3232
3182
  /**
@@ -0,0 +1,84 @@
1
+ import { DEFAULT_BUDGET_EXHAUSTED_MESSAGE, DEFAULT_CONTEXT_OVERFLOW_MESSAGE, DEFAULT_PROVIDER_REFUSED_MESSAGE, } from '@genesislcap/foundation-ai';
2
+ /**
3
+ * The terminal outcomes of a tool loop.
4
+ *
5
+ * `contextGuard` and `contextOverflow` share a `subReason` because one failure is reached
6
+ * two ways the user experiences differently: the guard stops at the edge of the window with
7
+ * the transcript intact, so its copy can point at compaction, while an overflow means the
8
+ * provider has already rejected the request.
9
+ *
10
+ * Three entries take their copy from `@genesislcap/foundation-ai`, which owns the default
11
+ * message for every failure it also exports an error class for. The rest is for conditions
12
+ * this driver detects itself, which have no upstream home.
13
+ */
14
+ export const TURN_FAILURE_EXITS = {
15
+ contextGuard: {
16
+ subReason: 'context_exhausted',
17
+ resultReason: 'context-exhausted',
18
+ copy: 'I had to stop here — this conversation has filled the available context. ' +
19
+ 'Compact it to free up room, then ask me to continue.',
20
+ },
21
+ contextOverflow: {
22
+ subReason: 'context_exhausted',
23
+ resultReason: 'context-exhausted',
24
+ copy: DEFAULT_CONTEXT_OVERFLOW_MESSAGE,
25
+ },
26
+ malformedToolCall: {
27
+ subReason: 'malformed_tool_call',
28
+ // Not `malformed-tool-call`: the main-turn reason is named after the provider finish
29
+ // reason (`MALFORMED_FUNCTION_CALL`) that produces it, and is part of the published
30
+ // TurnFailureReason union, so the two names cannot be brought into line here.
31
+ resultReason: 'malformed-function-call',
32
+ copy: '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.',
33
+ },
34
+ responseTruncated: {
35
+ subReason: 'response_truncated',
36
+ resultReason: 'response-truncated',
37
+ copy: '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.',
38
+ },
39
+ timeout: {
40
+ subReason: 'timeout',
41
+ // There is no `timeout` member of TurnFailureReason, so the main turn reports the
42
+ // generic `exception`. A sub-agent gets the specific reason because its parent
43
+ // branches on it to decide whether a retry is worth attempting.
44
+ resultReason: 'exception',
45
+ copy: 'The request timed out. You can ask me to try again, or break this into a smaller step.',
46
+ },
47
+ unknownToolLimit: {
48
+ subReason: 'unknown_tool_limit',
49
+ resultReason: 'unknown-tool-limit',
50
+ copy: "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.",
51
+ },
52
+ maxIterations: {
53
+ subReason: 'max_iterations',
54
+ resultReason: 'max-iterations',
55
+ copy: "I've reached my limit for this response. You can ask me to continue and I'll pick up where I left off.",
56
+ },
57
+ refusal: {
58
+ subReason: 'refusal',
59
+ resultReason: 'refusal',
60
+ copy: 'This request was declined. Try rephrasing it, or breaking it into smaller steps.',
61
+ },
62
+ emptyResponse: {
63
+ subReason: 'empty_response',
64
+ resultReason: 'empty-response',
65
+ copy: '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.',
66
+ },
67
+ providerRefused: {
68
+ subReason: 'provider_refused',
69
+ resultReason: 'provider-refused',
70
+ // Overridden per wall by `ChatDriver.providerRefusedBubble`, which prefers a
71
+ // host-supplied message; this is the fallback that method returns.
72
+ copy: DEFAULT_PROVIDER_REFUSED_MESSAGE,
73
+ },
74
+ budgetExhausted: {
75
+ subReason: 'budget_exhausted',
76
+ resultReason: 'budget-exhausted',
77
+ // Overridden per wall by `ChatDriver.budgetExhaustedBubble`, which upgrades this with
78
+ // "switch to another AI" when a second vendor is reachable.
79
+ copy: DEFAULT_BUDGET_EXHAUSTED_MESSAGE,
80
+ },
81
+ // `satisfies`, not an annotation: an annotation would widen `resultReason` to
82
+ // `TurnFailureReason` and cost the literal types that let `turn-failures.test.ts` pin the
83
+ // two deliberate divergences at compile time instead of at runtime.
84
+ };
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": "15.24.0",
4
+ "version": "15.24.2",
5
5
  "license": "SEE LICENSE IN license.txt",
6
6
  "main": "dist/esm/index.js",
7
7
  "types": "dist/ai-assistant.d.ts",
@@ -74,26 +74,26 @@
74
74
  }
75
75
  },
76
76
  "devDependencies": {
77
- "@genesislcap/foundation-testing": "15.24.0",
78
- "@genesislcap/genx": "15.24.0",
79
- "@genesislcap/rollup-builder": "15.24.0",
80
- "@genesislcap/ts-builder": "15.24.0",
81
- "@genesislcap/uvu-playwright-builder": "15.24.0",
82
- "@genesislcap/vite-builder": "15.24.0",
83
- "@genesislcap/webpack-builder": "15.24.0",
77
+ "@genesislcap/foundation-testing": "15.24.2",
78
+ "@genesislcap/genx": "15.24.2",
79
+ "@genesislcap/rollup-builder": "15.24.2",
80
+ "@genesislcap/ts-builder": "15.24.2",
81
+ "@genesislcap/uvu-playwright-builder": "15.24.2",
82
+ "@genesislcap/vite-builder": "15.24.2",
83
+ "@genesislcap/webpack-builder": "15.24.2",
84
84
  "@types/dompurify": "^3.0.5",
85
85
  "@types/marked": "^5.0.2",
86
86
  "esbuild": "0.25.12"
87
87
  },
88
88
  "dependencies": {
89
- "@genesislcap/foundation-ai": "15.24.0",
90
- "@genesislcap/foundation-logger": "15.24.0",
91
- "@genesislcap/foundation-notifications": "15.24.0",
92
- "@genesislcap/foundation-redux": "15.24.0",
93
- "@genesislcap/foundation-ui": "15.24.0",
94
- "@genesislcap/foundation-utils": "15.24.0",
95
- "@genesislcap/rapid-design-system": "15.24.0",
96
- "@genesislcap/web-core": "15.24.0",
89
+ "@genesislcap/foundation-ai": "15.24.2",
90
+ "@genesislcap/foundation-logger": "15.24.2",
91
+ "@genesislcap/foundation-notifications": "15.24.2",
92
+ "@genesislcap/foundation-redux": "15.24.2",
93
+ "@genesislcap/foundation-ui": "15.24.2",
94
+ "@genesislcap/foundation-utils": "15.24.2",
95
+ "@genesislcap/rapid-design-system": "15.24.2",
96
+ "@genesislcap/web-core": "15.24.2",
97
97
  "dompurify": "^3.3.1",
98
98
  "marked": "^17.0.3"
99
99
  },
@@ -1,2 +0,0 @@
1
- *.md -diff
2
- *.md linguist-generated