@genesislcap/ai-assistant 15.3.3-alpha-4899ddd.0 → 15.3.4

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.
Files changed (64) hide show
  1. package/dist/ai-assistant.api.json +5 -391
  2. package/dist/ai-assistant.d.ts +6 -603
  3. package/dist/chat-driver.cjs +26 -285
  4. package/dist/chat-driver.cjs.map +3 -3
  5. package/dist/chat-driver.mjs +26 -285
  6. package/dist/chat-driver.mjs.map +3 -3
  7. package/dist/custom-elements.json +10 -236
  8. package/dist/dts/channel/ai-activity-channel.d.ts +1 -51
  9. package/dist/dts/channel/ai-activity-channel.d.ts.map +1 -1
  10. package/dist/dts/components/chat-driver/chat-driver.d.ts +1 -99
  11. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  12. package/dist/dts/components/chat-driver/chat-driver.test.d.ts.map +1 -1
  13. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +0 -14
  14. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  15. package/dist/dts/main/main.d.ts +6 -384
  16. package/dist/dts/main/main.d.ts.map +1 -1
  17. package/dist/dts/main/main.styles.d.ts.map +1 -1
  18. package/dist/dts/main/main.template.d.ts +0 -53
  19. package/dist/dts/main/main.template.d.ts.map +1 -1
  20. package/dist/dts/state/ai-assistant-slice.d.ts +6 -143
  21. package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
  22. package/dist/dts/state/debug-event-log.d.ts +1 -6
  23. package/dist/dts/state/debug-event-log.d.ts.map +1 -1
  24. package/dist/dts/state/session-store.d.ts +0 -10
  25. package/dist/dts/state/session-store.d.ts.map +1 -1
  26. package/dist/esm/components/chat-driver/chat-driver.js +21 -263
  27. package/dist/esm/components/chat-driver/chat-driver.test.js +1 -464
  28. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +4 -89
  29. package/dist/esm/main/main.js +16 -680
  30. package/dist/esm/main/main.styles.js +0 -47
  31. package/dist/esm/main/main.template.js +4 -121
  32. package/dist/esm/state/ai-assistant-slice.js +7 -128
  33. package/dist/esm/state/ai-assistant-slice.test.js +1 -138
  34. package/dist/esm/state/debug-event-log.js +2 -7
  35. package/dist/esm/state/debug-event-log.test.js +1 -49
  36. package/dist/esm/state/persistence/session-snapshot.test.js +0 -18
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/docs/sub_agent.md +3 -20
  39. package/package.json +17 -17
  40. package/src/channel/ai-activity-channel.ts +2 -56
  41. package/src/components/chat-driver/chat-driver.test.ts +0 -549
  42. package/src/components/chat-driver/chat-driver.ts +14 -324
  43. package/src/components/orchestrating-driver/orchestrating-driver.ts +6 -101
  44. package/src/main/main.styles.ts +0 -47
  45. package/src/main/main.template.ts +4 -131
  46. package/src/main/main.ts +10 -680
  47. package/src/state/ai-assistant-slice.test.ts +0 -215
  48. package/src/state/ai-assistant-slice.ts +8 -181
  49. package/src/state/debug-event-log.test.ts +0 -63
  50. package/src/state/debug-event-log.ts +2 -7
  51. package/src/state/persistence/session-snapshot.test.ts +0 -22
  52. package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts +0 -2
  53. package/dist/dts/components/orchestrating-driver/orchestrating-driver.budget.test.d.ts.map +0 -1
  54. package/dist/dts/main/blocked-state.test.d.ts +0 -2
  55. package/dist/dts/main/blocked-state.test.d.ts.map +0 -1
  56. package/dist/dts/main/main.styles.test.d.ts +0 -2
  57. package/dist/dts/main/main.styles.test.d.ts.map +0 -1
  58. package/dist/esm/components/orchestrating-driver/orchestrating-driver.budget.test.js +0 -312
  59. package/dist/esm/main/blocked-state.test.js +0 -913
  60. package/dist/esm/main/main.styles.test.js +0 -86
  61. package/docs/migration-GENC-1464.md +0 -562
  62. package/src/components/orchestrating-driver/orchestrating-driver.budget.test.ts +0 -438
  63. package/src/main/blocked-state.test.ts +0 -1243
  64. package/src/main/main.styles.test.ts +0 -103
@@ -969,67 +969,6 @@ var GeminiProvider = class {
969
969
  }
970
970
  };
971
971
 
972
- // ../../foundation-ai/dist/esm/transports/budget-exhausted-error.js
973
- var BUDGET_EXCEEDED_CODE = "BUDGET_EXCEEDED";
974
- var VENDOR_LABELS = Object.freeze({
975
- anthropic: "Anthropic",
976
- gemini: "Gemini",
977
- openai: "OpenAI",
978
- chrome: "Chrome"
979
- });
980
- var BUDGETED_VENDORS = Object.freeze([
981
- "anthropic",
982
- "gemini"
983
- ]);
984
- var VENDOR_TYPE_BY_LABEL = new Map(Object.entries(VENDOR_LABELS).map(([type, label]) => [
985
- label.toLowerCase(),
986
- type
987
- ]));
988
- function vendorTypeOfLabel(label) {
989
- if (typeof label !== "string")
990
- return void 0;
991
- return VENDOR_TYPE_BY_LABEL.get(label.trim().toLowerCase());
992
- }
993
- var BUDGET_EXCEEDED_STATUS = 402;
994
- var DEFAULT_BUDGET_EXHAUSTED_MESSAGE = "You've reached your AI usage limit. Contact your administrator to raise it.";
995
- var BudgetExhaustedError = class extends Error {
996
- constructor(vendorLabel, budgetUsd, spentUsd, detail, extra) {
997
- super(`${vendorLabel} request refused: the AI usage budget is exhausted` + (budgetUsd != null ? ` (spent ${spentUsd != null ? `$${spentUsd}` : "an unknown amount"} of a $${budgetUsd} budget)` : "") + (detail ? `: ${detail}` : "") + ". Retrying will not clear this \u2014 the budget must be raised.");
998
- this.vendorLabel = vendorLabel;
999
- this.budgetUsd = budgetUsd;
1000
- this.spentUsd = spentUsd;
1001
- this.detail = detail;
1002
- this.name = "BudgetExhaustedError";
1003
- this.otherVendorAvailable = extra === null || extra === void 0 ? void 0 : extra.otherVendorAvailable;
1004
- this.serverVendor = extra === null || extra === void 0 ? void 0 : extra.serverVendor;
1005
- }
1006
- };
1007
- var isBudgetRejection = (status, code) => status === BUDGET_EXCEEDED_STATUS || code === BUDGET_EXCEEDED_CODE;
1008
- var parseJsonOrUndefined = (text) => {
1009
- try {
1010
- return JSON.parse(text);
1011
- } catch (_a) {
1012
- return void 0;
1013
- }
1014
- };
1015
- var codeOf = (payload) => {
1016
- const code = payload === null || payload === void 0 ? void 0 : payload.code;
1017
- return typeof code === "string" ? code : void 0;
1018
- };
1019
- var numberOrUndefined = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
1020
- var booleanOrUndefined = (value) => typeof value === "boolean" ? value : void 0;
1021
- var stringOrUndefined = (value) => typeof value === "string" && value.trim() !== "" ? value : void 0;
1022
- function budgetExhaustedFrom(vendorLabel, payload, fallbackDetail) {
1023
- var _a, _b, _c, _d;
1024
- const body = typeof payload === "object" && payload !== null ? payload : {};
1025
- const details = typeof body.details === "object" && body.details !== null ? body.details : {};
1026
- const detail = typeof body.error === "string" ? body.error : fallbackDetail;
1027
- return new BudgetExhaustedError(vendorLabel, (_a = numberOrUndefined(body.budgetUsd)) !== null && _a !== void 0 ? _a : numberOrUndefined(details.budgetUsd), (_b = numberOrUndefined(body.spentUsd)) !== null && _b !== void 0 ? _b : numberOrUndefined(details.spentUsd), detail, {
1028
- otherVendorAvailable: (_c = booleanOrUndefined(body.otherVendorAvailable)) !== null && _c !== void 0 ? _c : booleanOrUndefined(details.otherVendorAvailable),
1029
- serverVendor: (_d = stringOrUndefined(body.vendor)) !== null && _d !== void 0 ? _d : stringOrUndefined(details.vendor)
1030
- });
1031
- }
1032
-
1033
972
  // ../../foundation-ai/dist/esm/types/config.types.js
1034
973
  var SUPPORTED_GEMINI_MODEL_IDS = [
1035
974
  "gemini-2.5-pro",
@@ -1184,19 +1123,15 @@ function postWithRetry(options) {
1184
1123
  signal: combinedSignal,
1185
1124
  credentials
1186
1125
  });
1126
+ if (retryableStatuses.includes(response.status) && attempt < MAX_RETRIES) {
1127
+ logger.warn(`${vendorLabel}Transport: retryable status ${response.status}, retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
1128
+ clearTimeout(timeoutId);
1129
+ yield backoff(attempt);
1130
+ continue;
1131
+ }
1187
1132
  if (!response.ok) {
1188
- const errText = yield response.text();
1189
- const parsed = parseJsonOrUndefined(errText);
1190
- if (isBudgetRejection(response.status, codeOf(parsed))) {
1191
- throw budgetExhaustedFrom(vendorLabel, parsed, errText || void 0);
1192
- }
1193
- if (retryableStatuses.includes(response.status) && attempt < MAX_RETRIES) {
1194
- logger.warn(`${vendorLabel}Transport: retryable status ${response.status}, retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
1195
- clearTimeout(timeoutId);
1196
- yield backoff(attempt);
1197
- continue;
1198
- }
1199
- throw new Error(`${vendorLabel} request error ${response.status}: ${errText}`);
1133
+ const err = yield response.text();
1134
+ throw new Error(`${vendorLabel} request error ${response.status}: ${err}`);
1200
1135
  }
1201
1136
  if (((_b = (_a = response.headers) === null || _a === void 0 ? void 0 : _a.get("content-type")) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes("application/x-ndjson")) && response.body) {
1202
1137
  rearmStallTimer();
@@ -1206,9 +1141,6 @@ function postWithRetry(options) {
1206
1141
  if (frame.code === "UPSTREAM_STALLED") {
1207
1142
  throw new DOMException(`${vendorLabel} request stalled: ${(_c = frame.error) !== null && _c !== void 0 ? _c : "no upstream data"}`, "TimeoutError");
1208
1143
  }
1209
- if (isBudgetRejection(frame.status, frame.code)) {
1210
- throw budgetExhaustedFrom(vendorLabel, frame);
1211
- }
1212
1144
  if (retryableStatuses.includes(frame.status) && attempt < MAX_RETRIES) {
1213
1145
  logger.warn(`${vendorLabel}Transport: retryable status ${frame.status} (err frame), retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
1214
1146
  clearTimeout(timeoutId);
@@ -1671,7 +1603,7 @@ ${att.content}` });
1671
1603
  headers: requestHeaders,
1672
1604
  body,
1673
1605
  credentials,
1674
- vendorLabel: VENDOR_LABELS.anthropic,
1606
+ vendorLabel: "Anthropic",
1675
1607
  retryableStatuses: _AnthropicTransport.RETRYABLE_STATUSES,
1676
1608
  timeout: this.timeout,
1677
1609
  stallTimeout: this.stallTimeout,
@@ -2320,7 +2252,7 @@ ${att.content}`
2320
2252
  headers,
2321
2253
  body: payload,
2322
2254
  credentials,
2323
- vendorLabel: VENDOR_LABELS.gemini,
2255
+ vendorLabel: "Gemini",
2324
2256
  retryableStatuses: _GeminiTransport.RETRYABLE_STATUSES,
2325
2257
  timeout: this.timeout,
2326
2258
  stallTimeout: this.stallTimeout,
@@ -3022,19 +2954,6 @@ The above summarizes earlier parts of this conversation that were compacted. Con
3022
2954
  var TOOL_FOLD_SYMBOL = Symbol("toolFold");
3023
2955
 
3024
2956
  // src/components/chat-driver/chat-driver.ts
3025
- var budgetDetailOf = (e) => {
3026
- const vendor = vendorTypeOfLabel(e.vendorLabel) ?? vendorTypeOfLabel(e.serverVendor);
3027
- if (e.budgetUsd == null && e.spentUsd == null && !vendor) return void 0;
3028
- return {
3029
- budgetUsd: e.budgetUsd,
3030
- spentUsd: e.spentUsd,
3031
- vendorLabel: e.vendorLabel,
3032
- ...vendor ? { vendor } : {},
3033
- // Server-known truth the client cannot re-derive, so it rides all the way to
3034
- // the banner rather than being re-guessed there from registry membership.
3035
- ...e.otherVendorAvailable != null ? { otherVendorAvailable: e.otherVendorAvailable } : {}
3036
- };
3037
- };
3038
2957
  var DEFAULT_MAX_TOOL_ITERATIONS = 50;
3039
2958
  var DEFAULT_MAX_FOLD_OPERATIONS = 5;
3040
2959
  var DEFAULT_MAX_TURN_SNAPSHOTS = 400;
@@ -3217,22 +3136,6 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3217
3136
  * picked up on the next turn.
3218
3137
  */
3219
3138
  this.resolvedStatusCache = /* @__PURE__ */ new Map();
3220
- /**
3221
- * Set the moment a budget wall is observed anywhere in this turn — this
3222
- * driver's own 402, or a sub-agent's (which surfaces here only as a
3223
- * `'budget_exhausted'` tool result). Read at the top of the tool loop to end
3224
- * the turn before issuing another model call that would hit the same wall.
3225
- * Reset per turn alongside the other per-turn counters.
3226
- */
3227
- this.budgetExhaustedThisTurn = false;
3228
- /**
3229
- * Whether this turn's budget wall came from a SUB-AGENT rather than this
3230
- * driver's own request. Decides whether `lastResolvedProvider` is a valid
3231
- * attribution fallback: for an own wall it is the refusing vendor, for a
3232
- * child's wall it is the parent's vendor — the one known NOT to have refused.
3233
- * Reset per turn alongside `budgetWallDetail`.
3234
- */
3235
- this.budgetWallViaSubAgent = false;
3236
3139
  const {
3237
3140
  toolHandlers = {},
3238
3141
  toolDefinitions = [],
@@ -3242,13 +3145,11 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3242
3145
  maxFoldOperations = DEFAULT_MAX_FOLD_OPERATIONS,
3243
3146
  maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS,
3244
3147
  sessionKey = "",
3245
- activityBus = NOOP_ACTIVITY_BUS,
3246
- budgetExhaustedMessage = DEFAULT_BUDGET_EXHAUSTED_MESSAGE
3148
+ activityBus = NOOP_ACTIVITY_BUS
3247
3149
  } = config;
3248
3150
  this.maxToolIterations = maxToolIterations;
3249
3151
  this.sessionKey = sessionKey;
3250
3152
  this.activityBus = activityBus;
3251
- this.budgetExhaustedMessage = budgetExhaustedMessage;
3252
3153
  if (typeof toolHandlers === "function") {
3253
3154
  this.toolHandlersFactory = toolHandlers;
3254
3155
  this.toolHandlers = {};
@@ -3355,56 +3256,8 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3355
3256
  * (rather than set to `undefined`) so a happy-path result stays byte-identical to
3356
3257
  * the historical `{ reason: 'done' }`.
3357
3258
  */
3358
- turnDone(failureReason, budget) {
3359
- if (!failureReason) return { reason: "done" };
3360
- return budget ? { reason: "done", failureReason, budget } : { reason: "done", failureReason };
3361
- }
3362
- /**
3363
- * Terminal budget outcome for a wall hit **outside** the tool loop — today,
3364
- * `OrchestratingDriver`'s classification phase, which calls the provider
3365
- * directly and so never enters `runToolLoop`.
3366
- *
3367
- * Does **not** publish `tool-loop-end`: no `tool-loop-start` was published for
3368
- * the classify phase, and an unbalanced end would break start/end pairing for
3369
- * subscribers that rely on it. The driver **return value** is what reports this
3370
- * case — see `FoundationAiAssistant`'s latch, which reads both seams for
3371
- * exactly this reason.
3372
- *
3373
- * The non-sub-agent tail of the in-loop `BudgetExhaustedError` branch lives
3374
- * here so there is one copy of the log line, the debug-log entry, the
3375
- * transcript bubble and the result shape rather than two that can drift.
3376
- *
3377
- * @param pendingUserMessage - a user message that has NOT yet been appended,
3378
- * appended first so the answer does not end up replying to nothing. Only the
3379
- * classification seam passes it: `OrchestratingDriver` dispatches the user's
3380
- * text as an optimistic `history-updated` detail and leaves the real append
3381
- * to `chatDriver.sendMessage`, which never runs when `classify()` throws — so
3382
- * the bubble below would re-dispatch a history the user's own message was
3383
- * never in, and it would vanish from the transcript on the next render. The
3384
- * in-loop caller has already appended it and passes nothing.
3385
- *
3386
- * @internal
3387
- */
3388
- reportBudgetExhausted(e, pendingUserMessage) {
3389
- if (pendingUserMessage) this.appendToHistory(pendingUserMessage);
3390
- this.budgetExhaustedThisTurn = true;
3391
- logger2.error("ChatDriver: AI budget exhausted", e);
3392
- recordTurnError(this.sessionKey, "budget-exhausted", {
3393
- agent: this.activeAgentName,
3394
- provider: this.lastResolvedProviderName,
3395
- // The registry ALIAS (e.g. 'high') is what `provider` records; the vendor
3396
- // is the thing a per-vendor budget is actually scoped to, and it was known
3397
- // at both ends and discarded in the middle until now. Taken from the
3398
- // refusing transport's label first — this method also serves the
3399
- // classification seam, where `lastResolvedProvider` is the PREVIOUS turn's
3400
- // vendor (or nothing), because classify runs against the registry default.
3401
- vendor: vendorTypeOfLabel(e.vendorLabel) ?? vendorTypeOfLabel(e.serverVendor) ?? this.lastResolvedProvider,
3402
- budgetUsd: e.budgetUsd,
3403
- spentUsd: e.spentUsd,
3404
- isSubAgent: this.isSubAgent
3405
- });
3406
- this.appendToHistory({ role: "assistant", content: this.budgetExhaustedMessage });
3407
- return this.turnDone("budget-exhausted", budgetDetailOf(e));
3259
+ turnDone(failureReason) {
3260
+ return failureReason ? { reason: "done", failureReason } : { reason: "done" };
3408
3261
  }
3409
3262
  /** The typed failure reason on a loop result, or `undefined` for a clean turn / handoff. */
3410
3263
  static failureReasonOf(result) {
@@ -3414,28 +3267,10 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3414
3267
  * Build the `tool-loop-end` event detail for a turn's result. A failure carries a
3415
3268
  * `{ failureReason }` detail; a clean turn emits `undefined` — the historical shape,
3416
3269
  * kept byte-identical so subscribers see exactly what they always have.
3417
- *
3418
- * A budget failure additionally carries `vendor` — the concrete vendor
3419
- * (`'anthropic'`/`'gemini'`) the walled turn resolved to, which the driver knows
3420
- * and used to discard. Optional and additive: a subscriber reading only
3421
- * `failureReason` is unaffected, a non-budget failure still emits the historical
3422
- * `{ failureReason }` with no `vendor` key, and the value is a plain string so
3423
- * the detail stays structured-cloneable for the cross-tab hop. It is the field a
3424
- * per-vendor budget model needs and the one that would be awkward to retrofit.
3425
3270
  */
3426
- loopEndDetail(result) {
3271
+ static loopEndDetail(result) {
3427
3272
  const failureReason = _ChatDriver.failureReasonOf(result);
3428
- if (!failureReason) return void 0;
3429
- if (failureReason !== "budget-exhausted") {
3430
- return { failureReason };
3431
- }
3432
- const budget = result.reason === "done" ? result.budget : void 0;
3433
- const vendor = budget?.vendor ?? (this.budgetWallViaSubAgent ? void 0 : this.lastResolvedProvider);
3434
- return {
3435
- failureReason,
3436
- ...vendor ? { vendor } : {},
3437
- ...budget ? { budget } : {}
3438
- };
3273
+ return failureReason ? { failureReason } : void 0;
3439
3274
  }
3440
3275
  /**
3441
3276
  * Swap in a new agent's configuration. Called by OrchestratingDriver before
@@ -3621,9 +3456,9 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3621
3456
  * under a separate session key, so recording here would orphan the event off
3622
3457
  * the user-visible debug-log timeline.)
3623
3458
  */
3624
- failSubAgent(reason, budget) {
3459
+ failSubAgent(reason) {
3625
3460
  if (!this.isSubAgent || this.subAgentFailure) return;
3626
- this.subAgentFailure = budget ? { reason, budget } : { reason };
3461
+ this.subAgentFailure = { reason };
3627
3462
  }
3628
3463
  /**
3629
3464
  * Returns true if `releaseAgent` was called during the most recent turn.
@@ -4068,9 +3903,6 @@ Output format (strict):
4068
3903
  this.subAgentCompletion = void 0;
4069
3904
  this.subAgentFailure = void 0;
4070
3905
  this.agentReleaseRequested = false;
4071
- this.budgetExhaustedThisTurn = false;
4072
- this.budgetWallDetail = void 0;
4073
- this.budgetWallViaSubAgent = false;
4074
3906
  this.appendToHistory({ role: "user", content: userInput, attachments });
4075
3907
  this.turnStartedAt = Date.now();
4076
3908
  recordMetaEvent(this.sessionKey, "turn.start", {
@@ -4105,7 +3937,7 @@ Output format (strict):
4105
3937
  });
4106
3938
  this.busy = false;
4107
3939
  this.endTurn();
4108
- this.activityBus.publish("tool-loop-end", this.loopEndDetail(result));
3940
+ this.activityBus.publish("tool-loop-end", _ChatDriver.loopEndDetail(result));
4109
3941
  }
4110
3942
  }
4111
3943
  /**
@@ -4296,14 +4128,8 @@ Output format (strict):
4296
4128
  recordMetaEvent(this.sessionKey, "subagent.completed", { agent: name });
4297
4129
  return { outcome: { ok: true, result: completion.result }, trace };
4298
4130
  }
4299
- const failure = child.getSubAgentFailure();
4300
- const reason = failure?.reason ?? "max_iterations";
4131
+ const reason = child.getSubAgentFailure()?.reason ?? "max_iterations";
4301
4132
  recordMetaEvent(this.sessionKey, "subagent.failed", { agent: name, reason });
4302
- if (reason === "budget_exhausted") {
4303
- this.budgetExhaustedThisTurn = true;
4304
- this.budgetWallViaSubAgent = true;
4305
- this.budgetWallDetail ??= failure?.budget;
4306
- }
4307
4133
  return { outcome: { ok: false, reason }, trace };
4308
4134
  }
4309
4135
  /**
@@ -4316,9 +4142,6 @@ Output format (strict):
4316
4142
  this.beginTurn();
4317
4143
  this.subAgentCompletion = void 0;
4318
4144
  this.subAgentFailure = void 0;
4319
- this.budgetExhaustedThisTurn = false;
4320
- this.budgetWallDetail = void 0;
4321
- this.budgetWallViaSubAgent = false;
4322
4145
  this.turnStartedAt = Date.now();
4323
4146
  recordMetaEvent(this.sessionKey, "turn.start", {
4324
4147
  phase: "continueFromHistory",
@@ -4352,7 +4175,7 @@ Output format (strict):
4352
4175
  });
4353
4176
  this.busy = false;
4354
4177
  this.endTurn();
4355
- this.activityBus.publish("tool-loop-end", this.loopEndDetail(result));
4178
+ this.activityBus.publish("tool-loop-end", _ChatDriver.loopEndDetail(result));
4356
4179
  }
4357
4180
  }
4358
4181
  // ---------------------------------------------------------------------------
@@ -4506,31 +4329,6 @@ Output format (strict):
4506
4329
  if (this.turnController.signal.aborted) {
4507
4330
  return this.completeAbortedTurn();
4508
4331
  }
4509
- if (this.budgetExhaustedThisTurn) {
4510
- logger2.error("ChatDriver: ending the turn \u2014 a sub-agent hit the AI budget wall");
4511
- recordTurnError(this.sessionKey, "budget-exhausted", {
4512
- agent: this.activeAgentName,
4513
- provider: this.lastResolvedProviderName,
4514
- // The CHILD's vendor when it knew one, and NOTHING otherwise. On this
4515
- // path the wall is definitionally the child's, and on a mixed registry
4516
- // `lastResolvedProvider` is this driver's own vendor — the one known
4517
- // NOT to have refused. An unattributable child wall must degrade to
4518
- // the vendor-agnostic block (which `latchBlockedFrom` handles
4519
- // fail-safe), never to a vendor that is known to be wrong: naming the
4520
- // parent's vendor here walled BOTH — the child's via its own
4521
- // tool-loop-end, the parent's via this event — and derived `blocked`
4522
- // over headroom that still existed.
4523
- vendor: this.budgetWallDetail?.vendor,
4524
- via: "sub-agent",
4525
- isSubAgent: this.isSubAgent
4526
- });
4527
- if (this.isSubAgent) {
4528
- this.failSubAgent("budget_exhausted", this.budgetWallDetail);
4529
- } else {
4530
- this.appendToHistory({ role: "assistant", content: this.budgetExhaustedMessage });
4531
- }
4532
- return this.turnDone("budget-exhausted", this.budgetWallDetail);
4533
- }
4534
4332
  const promptCtx = {
4535
4333
  agentName: this.activeAgentName ?? "",
4536
4334
  history: this.history,
@@ -4701,25 +4499,6 @@ ${tailBody}
4701
4499
  }
4702
4500
  return this.turnDone("response-truncated");
4703
4501
  }
4704
- if (e instanceof BudgetExhaustedError) {
4705
- this.budgetExhaustedThisTurn = true;
4706
- this.budgetWallDetail = budgetDetailOf(e);
4707
- this.budgetWallViaSubAgent = false;
4708
- if (this.isSubAgent) {
4709
- logger2.error("ChatDriver: AI budget exhausted", e);
4710
- recordTurnError(this.sessionKey, "budget-exhausted", {
4711
- agent: this.activeAgentName,
4712
- provider: this.lastResolvedProviderName,
4713
- vendor: vendorTypeOfLabel(e.vendorLabel) ?? vendorTypeOfLabel(e.serverVendor) ?? this.lastResolvedProvider,
4714
- budgetUsd: e.budgetUsd,
4715
- spentUsd: e.spentUsd,
4716
- isSubAgent: true
4717
- });
4718
- this.failSubAgent("budget_exhausted", this.budgetWallDetail);
4719
- return this.turnDone("budget-exhausted", this.budgetWallDetail);
4720
- }
4721
- return this.reportBudgetExhausted(e);
4722
- }
4723
4502
  if (e instanceof DOMException && e.name === "TimeoutError") {
4724
4503
  logger2.error("ChatDriver: request timed out", e);
4725
4504
  recordTurnError(this.sessionKey, "exception", {
@@ -5195,17 +4974,6 @@ var OrchestratingDriver = class extends EventTarget {
5195
4974
  * user stops. Reset at the start of each `sendMessage`.
5196
4975
  */
5197
4976
  this.cancelled = false;
5198
- /**
5199
- * Whether the CURRENT turn's user message has reached the inner driver's
5200
- * history. False through the pre-first-turn classify (where the only echo of
5201
- * the message is an optimistic `history-updated` dispatch), true from the
5202
- * moment `chatDriver.sendMessage` is entered — including through every later
5203
- * handoff classify. Read by the budget-wall catch in `sendMessage` to decide
5204
- * whether `reportBudgetExhausted` must append the message itself: appending
5205
- * it when already appended duplicated it; not appending it when unappended
5206
- * made it vanish. Reset at the top of each `runOrchestratedTurn`.
5207
- */
5208
- this.userMessageAppended = false;
5209
4977
  /**
5210
4978
  * Sticky user pick from the picker (or the host's `setAgent` API). Only
5211
4979
  * changes on explicit user action. Survives flow completion: when a stateful
@@ -5247,8 +5015,7 @@ var OrchestratingDriver = class extends EventTarget {
5247
5015
  maxFoldOperations: options.maxFoldOperations,
5248
5016
  maxTurnSnapshots: options.maxTurnSnapshots,
5249
5017
  sessionKey: this.sessionKey,
5250
- activityBus: options.activityBus,
5251
- budgetExhaustedMessage: options.budgetExhaustedMessage
5018
+ activityBus: options.activityBus
5252
5019
  });
5253
5020
  this.chatDriver.addEventListener("history-updated", (e) => {
5254
5021
  this.dispatchEvent(new CustomEvent("history-updated", { detail: e.detail }));
@@ -5347,25 +5114,7 @@ var OrchestratingDriver = class extends EventTarget {
5347
5114
  return this.chatDriver.getSuggestions(history, prompt2, count, agentInfo);
5348
5115
  }
5349
5116
  async sendMessage(input, attachments) {
5350
- try {
5351
- return await this.runOrchestratedTurn(input, attachments);
5352
- } catch (e) {
5353
- if (e instanceof BudgetExhaustedError) {
5354
- return this.chatDriver.reportBudgetExhausted(
5355
- e,
5356
- this.userMessageAppended ? void 0 : {
5357
- role: "user",
5358
- content: input,
5359
- ...attachments ? { attachments } : {}
5360
- }
5361
- );
5362
- }
5363
- throw e;
5364
- }
5365
- }
5366
- async runOrchestratedTurn(input, attachments) {
5367
5117
  this.cancelled = false;
5368
- this.userMessageAppended = false;
5369
5118
  const history = this.chatDriver.getHistory();
5370
5119
  this.dispatchEvent(
5371
5120
  new CustomEvent("history-updated", {
@@ -5379,7 +5128,6 @@ var OrchestratingDriver = class extends EventTarget {
5379
5128
  let handoffs = 0;
5380
5129
  let handoffSummary = "";
5381
5130
  let remainingTask = "";
5382
- let lastResult;
5383
5131
  while (true) {
5384
5132
  if (this.cancelled) break;
5385
5133
  await this.applyAgent(currentAgent);
@@ -5388,10 +5136,8 @@ var OrchestratingDriver = class extends EventTarget {
5388
5136
  const contextPrimer = handoffSummary ? [{ role: "user", content: `[Context from previous agent]: ${handoffSummary}` }] : [];
5389
5137
  result = await this.chatDriver.continueFromHistory(contextPrimer);
5390
5138
  } else {
5391
- this.userMessageAppended = true;
5392
5139
  result = await this.chatDriver.sendMessage(input, attachments);
5393
5140
  }
5394
- lastResult = result;
5395
5141
  if (this.chatDriver.getAgentReleaseRequested()) {
5396
5142
  await this.releaseActiveAgent();
5397
5143
  break;
@@ -5413,7 +5159,7 @@ var OrchestratingDriver = class extends EventTarget {
5413
5159
  this.dispatchEvent(new CustomEvent("orchestrating-start"));
5414
5160
  currentAgent = await this.classify(remainingTask, updatedHistory);
5415
5161
  }
5416
- return lastResult?.reason === "done" ? lastResult : { reason: "done" };
5162
+ return { reason: "done" };
5417
5163
  }
5418
5164
  async continueFromHistory(transientPrimer) {
5419
5165
  return this.chatDriver.continueFromHistory(transientPrimer);
@@ -5596,7 +5342,6 @@ ${recentMessages}` : ""}`;
5596
5342
  }
5597
5343
  }
5598
5344
  };
5599
- let routedNoMatch = false;
5600
5345
  for (let attempt = 0; attempt <= this.classifierRetries; attempt += 1) {
5601
5346
  try {
5602
5347
  const options = {
@@ -5616,10 +5361,8 @@ ${recentMessages}` : ""}`;
5616
5361
  if (index >= 0 && index < this.specialists.length) {
5617
5362
  return this.specialists[index];
5618
5363
  }
5619
- routedNoMatch = true;
5620
5364
  break;
5621
5365
  } catch (e) {
5622
- if (e instanceof BudgetExhaustedError) throw e;
5623
5366
  logger2.warn(`OrchestratingDriver: classifier attempt ${attempt + 1} failed:`, e);
5624
5367
  if (attempt === this.classifierRetries) {
5625
5368
  logger2.error("OrchestratingDriver: classifier failed after all retries, using fallback");
@@ -5627,12 +5370,10 @@ ${recentMessages}` : ""}`;
5627
5370
  }
5628
5371
  }
5629
5372
  if (this.fallback) return this.fallback;
5630
- if (routedNoMatch) {
5631
- const specialistNames = this.specialists.map((s) => s.name).join(", ");
5632
- this.appendInlineMessage(
5633
- `I'm not sure how to help with that. I can assist with: ${specialistNames}.`
5634
- );
5635
- }
5373
+ const specialistNames = this.specialists.map((s) => s.name).join(", ");
5374
+ this.appendInlineMessage(
5375
+ `I'm not sure how to help with that. I can assist with: ${specialistNames}.`
5376
+ );
5636
5377
  return this.specialists[0];
5637
5378
  }
5638
5379
  /**