@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
@@ -1016,67 +1016,6 @@ var GeminiProvider = class {
1016
1016
  }
1017
1017
  };
1018
1018
 
1019
- // ../../foundation-ai/dist/esm/transports/budget-exhausted-error.js
1020
- var BUDGET_EXCEEDED_CODE = "BUDGET_EXCEEDED";
1021
- var VENDOR_LABELS = Object.freeze({
1022
- anthropic: "Anthropic",
1023
- gemini: "Gemini",
1024
- openai: "OpenAI",
1025
- chrome: "Chrome"
1026
- });
1027
- var BUDGETED_VENDORS = Object.freeze([
1028
- "anthropic",
1029
- "gemini"
1030
- ]);
1031
- var VENDOR_TYPE_BY_LABEL = new Map(Object.entries(VENDOR_LABELS).map(([type, label]) => [
1032
- label.toLowerCase(),
1033
- type
1034
- ]));
1035
- function vendorTypeOfLabel(label) {
1036
- if (typeof label !== "string")
1037
- return void 0;
1038
- return VENDOR_TYPE_BY_LABEL.get(label.trim().toLowerCase());
1039
- }
1040
- var BUDGET_EXCEEDED_STATUS = 402;
1041
- var DEFAULT_BUDGET_EXHAUSTED_MESSAGE = "You've reached your AI usage limit. Contact your administrator to raise it.";
1042
- var BudgetExhaustedError = class extends Error {
1043
- constructor(vendorLabel, budgetUsd, spentUsd, detail, extra) {
1044
- 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.");
1045
- this.vendorLabel = vendorLabel;
1046
- this.budgetUsd = budgetUsd;
1047
- this.spentUsd = spentUsd;
1048
- this.detail = detail;
1049
- this.name = "BudgetExhaustedError";
1050
- this.otherVendorAvailable = extra === null || extra === void 0 ? void 0 : extra.otherVendorAvailable;
1051
- this.serverVendor = extra === null || extra === void 0 ? void 0 : extra.serverVendor;
1052
- }
1053
- };
1054
- var isBudgetRejection = (status, code) => status === BUDGET_EXCEEDED_STATUS || code === BUDGET_EXCEEDED_CODE;
1055
- var parseJsonOrUndefined = (text) => {
1056
- try {
1057
- return JSON.parse(text);
1058
- } catch (_a) {
1059
- return void 0;
1060
- }
1061
- };
1062
- var codeOf = (payload) => {
1063
- const code = payload === null || payload === void 0 ? void 0 : payload.code;
1064
- return typeof code === "string" ? code : void 0;
1065
- };
1066
- var numberOrUndefined = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
1067
- var booleanOrUndefined = (value) => typeof value === "boolean" ? value : void 0;
1068
- var stringOrUndefined = (value) => typeof value === "string" && value.trim() !== "" ? value : void 0;
1069
- function budgetExhaustedFrom(vendorLabel, payload, fallbackDetail) {
1070
- var _a, _b, _c, _d;
1071
- const body = typeof payload === "object" && payload !== null ? payload : {};
1072
- const details = typeof body.details === "object" && body.details !== null ? body.details : {};
1073
- const detail = typeof body.error === "string" ? body.error : fallbackDetail;
1074
- 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, {
1075
- otherVendorAvailable: (_c = booleanOrUndefined(body.otherVendorAvailable)) !== null && _c !== void 0 ? _c : booleanOrUndefined(details.otherVendorAvailable),
1076
- serverVendor: (_d = stringOrUndefined(body.vendor)) !== null && _d !== void 0 ? _d : stringOrUndefined(details.vendor)
1077
- });
1078
- }
1079
-
1080
1019
  // ../../foundation-ai/dist/esm/types/config.types.js
1081
1020
  var SUPPORTED_GEMINI_MODEL_IDS = [
1082
1021
  "gemini-2.5-pro",
@@ -1231,19 +1170,15 @@ function postWithRetry(options) {
1231
1170
  signal: combinedSignal,
1232
1171
  credentials
1233
1172
  });
1173
+ if (retryableStatuses.includes(response.status) && attempt < MAX_RETRIES) {
1174
+ logger.warn(`${vendorLabel}Transport: retryable status ${response.status}, retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
1175
+ clearTimeout(timeoutId);
1176
+ yield backoff(attempt);
1177
+ continue;
1178
+ }
1234
1179
  if (!response.ok) {
1235
- const errText = yield response.text();
1236
- const parsed = parseJsonOrUndefined(errText);
1237
- if (isBudgetRejection(response.status, codeOf(parsed))) {
1238
- throw budgetExhaustedFrom(vendorLabel, parsed, errText || void 0);
1239
- }
1240
- if (retryableStatuses.includes(response.status) && attempt < MAX_RETRIES) {
1241
- logger.warn(`${vendorLabel}Transport: retryable status ${response.status}, retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
1242
- clearTimeout(timeoutId);
1243
- yield backoff(attempt);
1244
- continue;
1245
- }
1246
- throw new Error(`${vendorLabel} request error ${response.status}: ${errText}`);
1180
+ const err = yield response.text();
1181
+ throw new Error(`${vendorLabel} request error ${response.status}: ${err}`);
1247
1182
  }
1248
1183
  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) {
1249
1184
  rearmStallTimer();
@@ -1253,9 +1188,6 @@ function postWithRetry(options) {
1253
1188
  if (frame.code === "UPSTREAM_STALLED") {
1254
1189
  throw new DOMException(`${vendorLabel} request stalled: ${(_c = frame.error) !== null && _c !== void 0 ? _c : "no upstream data"}`, "TimeoutError");
1255
1190
  }
1256
- if (isBudgetRejection(frame.status, frame.code)) {
1257
- throw budgetExhaustedFrom(vendorLabel, frame);
1258
- }
1259
1191
  if (retryableStatuses.includes(frame.status) && attempt < MAX_RETRIES) {
1260
1192
  logger.warn(`${vendorLabel}Transport: retryable status ${frame.status} (err frame), retrying (attempt ${attempt + 1}/${MAX_RETRIES})`);
1261
1193
  clearTimeout(timeoutId);
@@ -1718,7 +1650,7 @@ ${att.content}` });
1718
1650
  headers: requestHeaders,
1719
1651
  body,
1720
1652
  credentials,
1721
- vendorLabel: VENDOR_LABELS.anthropic,
1653
+ vendorLabel: "Anthropic",
1722
1654
  retryableStatuses: _AnthropicTransport.RETRYABLE_STATUSES,
1723
1655
  timeout: this.timeout,
1724
1656
  stallTimeout: this.stallTimeout,
@@ -2367,7 +2299,7 @@ ${att.content}`
2367
2299
  headers,
2368
2300
  body: payload,
2369
2301
  credentials,
2370
- vendorLabel: VENDOR_LABELS.gemini,
2302
+ vendorLabel: "Gemini",
2371
2303
  retryableStatuses: _GeminiTransport.RETRYABLE_STATUSES,
2372
2304
  timeout: this.timeout,
2373
2305
  stallTimeout: this.stallTimeout,
@@ -3069,19 +3001,6 @@ The above summarizes earlier parts of this conversation that were compacted. Con
3069
3001
  var TOOL_FOLD_SYMBOL = Symbol("toolFold");
3070
3002
 
3071
3003
  // src/components/chat-driver/chat-driver.ts
3072
- var budgetDetailOf = (e) => {
3073
- const vendor = vendorTypeOfLabel(e.vendorLabel) ?? vendorTypeOfLabel(e.serverVendor);
3074
- if (e.budgetUsd == null && e.spentUsd == null && !vendor) return void 0;
3075
- return {
3076
- budgetUsd: e.budgetUsd,
3077
- spentUsd: e.spentUsd,
3078
- vendorLabel: e.vendorLabel,
3079
- ...vendor ? { vendor } : {},
3080
- // Server-known truth the client cannot re-derive, so it rides all the way to
3081
- // the banner rather than being re-guessed there from registry membership.
3082
- ...e.otherVendorAvailable != null ? { otherVendorAvailable: e.otherVendorAvailable } : {}
3083
- };
3084
- };
3085
3004
  var DEFAULT_MAX_TOOL_ITERATIONS = 50;
3086
3005
  var DEFAULT_MAX_FOLD_OPERATIONS = 5;
3087
3006
  var DEFAULT_MAX_TURN_SNAPSHOTS = 400;
@@ -3264,22 +3183,6 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3264
3183
  * picked up on the next turn.
3265
3184
  */
3266
3185
  this.resolvedStatusCache = /* @__PURE__ */ new Map();
3267
- /**
3268
- * Set the moment a budget wall is observed anywhere in this turn — this
3269
- * driver's own 402, or a sub-agent's (which surfaces here only as a
3270
- * `'budget_exhausted'` tool result). Read at the top of the tool loop to end
3271
- * the turn before issuing another model call that would hit the same wall.
3272
- * Reset per turn alongside the other per-turn counters.
3273
- */
3274
- this.budgetExhaustedThisTurn = false;
3275
- /**
3276
- * Whether this turn's budget wall came from a SUB-AGENT rather than this
3277
- * driver's own request. Decides whether `lastResolvedProvider` is a valid
3278
- * attribution fallback: for an own wall it is the refusing vendor, for a
3279
- * child's wall it is the parent's vendor — the one known NOT to have refused.
3280
- * Reset per turn alongside `budgetWallDetail`.
3281
- */
3282
- this.budgetWallViaSubAgent = false;
3283
3186
  const {
3284
3187
  toolHandlers = {},
3285
3188
  toolDefinitions = [],
@@ -3289,13 +3192,11 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3289
3192
  maxFoldOperations = DEFAULT_MAX_FOLD_OPERATIONS,
3290
3193
  maxTurnSnapshots = DEFAULT_MAX_TURN_SNAPSHOTS,
3291
3194
  sessionKey = "",
3292
- activityBus = NOOP_ACTIVITY_BUS,
3293
- budgetExhaustedMessage = DEFAULT_BUDGET_EXHAUSTED_MESSAGE
3195
+ activityBus = NOOP_ACTIVITY_BUS
3294
3196
  } = config;
3295
3197
  this.maxToolIterations = maxToolIterations;
3296
3198
  this.sessionKey = sessionKey;
3297
3199
  this.activityBus = activityBus;
3298
- this.budgetExhaustedMessage = budgetExhaustedMessage;
3299
3200
  if (typeof toolHandlers === "function") {
3300
3201
  this.toolHandlersFactory = toolHandlers;
3301
3202
  this.toolHandlers = {};
@@ -3402,56 +3303,8 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3402
3303
  * (rather than set to `undefined`) so a happy-path result stays byte-identical to
3403
3304
  * the historical `{ reason: 'done' }`.
3404
3305
  */
3405
- turnDone(failureReason, budget) {
3406
- if (!failureReason) return { reason: "done" };
3407
- return budget ? { reason: "done", failureReason, budget } : { reason: "done", failureReason };
3408
- }
3409
- /**
3410
- * Terminal budget outcome for a wall hit **outside** the tool loop — today,
3411
- * `OrchestratingDriver`'s classification phase, which calls the provider
3412
- * directly and so never enters `runToolLoop`.
3413
- *
3414
- * Does **not** publish `tool-loop-end`: no `tool-loop-start` was published for
3415
- * the classify phase, and an unbalanced end would break start/end pairing for
3416
- * subscribers that rely on it. The driver **return value** is what reports this
3417
- * case — see `FoundationAiAssistant`'s latch, which reads both seams for
3418
- * exactly this reason.
3419
- *
3420
- * The non-sub-agent tail of the in-loop `BudgetExhaustedError` branch lives
3421
- * here so there is one copy of the log line, the debug-log entry, the
3422
- * transcript bubble and the result shape rather than two that can drift.
3423
- *
3424
- * @param pendingUserMessage - a user message that has NOT yet been appended,
3425
- * appended first so the answer does not end up replying to nothing. Only the
3426
- * classification seam passes it: `OrchestratingDriver` dispatches the user's
3427
- * text as an optimistic `history-updated` detail and leaves the real append
3428
- * to `chatDriver.sendMessage`, which never runs when `classify()` throws — so
3429
- * the bubble below would re-dispatch a history the user's own message was
3430
- * never in, and it would vanish from the transcript on the next render. The
3431
- * in-loop caller has already appended it and passes nothing.
3432
- *
3433
- * @internal
3434
- */
3435
- reportBudgetExhausted(e, pendingUserMessage) {
3436
- if (pendingUserMessage) this.appendToHistory(pendingUserMessage);
3437
- this.budgetExhaustedThisTurn = true;
3438
- logger2.error("ChatDriver: AI budget exhausted", e);
3439
- recordTurnError(this.sessionKey, "budget-exhausted", {
3440
- agent: this.activeAgentName,
3441
- provider: this.lastResolvedProviderName,
3442
- // The registry ALIAS (e.g. 'high') is what `provider` records; the vendor
3443
- // is the thing a per-vendor budget is actually scoped to, and it was known
3444
- // at both ends and discarded in the middle until now. Taken from the
3445
- // refusing transport's label first — this method also serves the
3446
- // classification seam, where `lastResolvedProvider` is the PREVIOUS turn's
3447
- // vendor (or nothing), because classify runs against the registry default.
3448
- vendor: vendorTypeOfLabel(e.vendorLabel) ?? vendorTypeOfLabel(e.serverVendor) ?? this.lastResolvedProvider,
3449
- budgetUsd: e.budgetUsd,
3450
- spentUsd: e.spentUsd,
3451
- isSubAgent: this.isSubAgent
3452
- });
3453
- this.appendToHistory({ role: "assistant", content: this.budgetExhaustedMessage });
3454
- return this.turnDone("budget-exhausted", budgetDetailOf(e));
3306
+ turnDone(failureReason) {
3307
+ return failureReason ? { reason: "done", failureReason } : { reason: "done" };
3455
3308
  }
3456
3309
  /** The typed failure reason on a loop result, or `undefined` for a clean turn / handoff. */
3457
3310
  static failureReasonOf(result) {
@@ -3461,28 +3314,10 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3461
3314
  * Build the `tool-loop-end` event detail for a turn's result. A failure carries a
3462
3315
  * `{ failureReason }` detail; a clean turn emits `undefined` — the historical shape,
3463
3316
  * kept byte-identical so subscribers see exactly what they always have.
3464
- *
3465
- * A budget failure additionally carries `vendor` — the concrete vendor
3466
- * (`'anthropic'`/`'gemini'`) the walled turn resolved to, which the driver knows
3467
- * and used to discard. Optional and additive: a subscriber reading only
3468
- * `failureReason` is unaffected, a non-budget failure still emits the historical
3469
- * `{ failureReason }` with no `vendor` key, and the value is a plain string so
3470
- * the detail stays structured-cloneable for the cross-tab hop. It is the field a
3471
- * per-vendor budget model needs and the one that would be awkward to retrofit.
3472
3317
  */
3473
- loopEndDetail(result) {
3318
+ static loopEndDetail(result) {
3474
3319
  const failureReason = _ChatDriver.failureReasonOf(result);
3475
- if (!failureReason) return void 0;
3476
- if (failureReason !== "budget-exhausted") {
3477
- return { failureReason };
3478
- }
3479
- const budget = result.reason === "done" ? result.budget : void 0;
3480
- const vendor = budget?.vendor ?? (this.budgetWallViaSubAgent ? void 0 : this.lastResolvedProvider);
3481
- return {
3482
- failureReason,
3483
- ...vendor ? { vendor } : {},
3484
- ...budget ? { budget } : {}
3485
- };
3320
+ return failureReason ? { failureReason } : void 0;
3486
3321
  }
3487
3322
  /**
3488
3323
  * Swap in a new agent's configuration. Called by OrchestratingDriver before
@@ -3668,9 +3503,9 @@ var ChatDriver = class _ChatDriver extends EventTarget {
3668
3503
  * under a separate session key, so recording here would orphan the event off
3669
3504
  * the user-visible debug-log timeline.)
3670
3505
  */
3671
- failSubAgent(reason, budget) {
3506
+ failSubAgent(reason) {
3672
3507
  if (!this.isSubAgent || this.subAgentFailure) return;
3673
- this.subAgentFailure = budget ? { reason, budget } : { reason };
3508
+ this.subAgentFailure = { reason };
3674
3509
  }
3675
3510
  /**
3676
3511
  * Returns true if `releaseAgent` was called during the most recent turn.
@@ -4115,9 +3950,6 @@ Output format (strict):
4115
3950
  this.subAgentCompletion = void 0;
4116
3951
  this.subAgentFailure = void 0;
4117
3952
  this.agentReleaseRequested = false;
4118
- this.budgetExhaustedThisTurn = false;
4119
- this.budgetWallDetail = void 0;
4120
- this.budgetWallViaSubAgent = false;
4121
3953
  this.appendToHistory({ role: "user", content: userInput, attachments });
4122
3954
  this.turnStartedAt = Date.now();
4123
3955
  recordMetaEvent(this.sessionKey, "turn.start", {
@@ -4152,7 +3984,7 @@ Output format (strict):
4152
3984
  });
4153
3985
  this.busy = false;
4154
3986
  this.endTurn();
4155
- this.activityBus.publish("tool-loop-end", this.loopEndDetail(result));
3987
+ this.activityBus.publish("tool-loop-end", _ChatDriver.loopEndDetail(result));
4156
3988
  }
4157
3989
  }
4158
3990
  /**
@@ -4343,14 +4175,8 @@ Output format (strict):
4343
4175
  recordMetaEvent(this.sessionKey, "subagent.completed", { agent: name });
4344
4176
  return { outcome: { ok: true, result: completion.result }, trace };
4345
4177
  }
4346
- const failure = child.getSubAgentFailure();
4347
- const reason = failure?.reason ?? "max_iterations";
4178
+ const reason = child.getSubAgentFailure()?.reason ?? "max_iterations";
4348
4179
  recordMetaEvent(this.sessionKey, "subagent.failed", { agent: name, reason });
4349
- if (reason === "budget_exhausted") {
4350
- this.budgetExhaustedThisTurn = true;
4351
- this.budgetWallViaSubAgent = true;
4352
- this.budgetWallDetail ??= failure?.budget;
4353
- }
4354
4180
  return { outcome: { ok: false, reason }, trace };
4355
4181
  }
4356
4182
  /**
@@ -4363,9 +4189,6 @@ Output format (strict):
4363
4189
  this.beginTurn();
4364
4190
  this.subAgentCompletion = void 0;
4365
4191
  this.subAgentFailure = void 0;
4366
- this.budgetExhaustedThisTurn = false;
4367
- this.budgetWallDetail = void 0;
4368
- this.budgetWallViaSubAgent = false;
4369
4192
  this.turnStartedAt = Date.now();
4370
4193
  recordMetaEvent(this.sessionKey, "turn.start", {
4371
4194
  phase: "continueFromHistory",
@@ -4399,7 +4222,7 @@ Output format (strict):
4399
4222
  });
4400
4223
  this.busy = false;
4401
4224
  this.endTurn();
4402
- this.activityBus.publish("tool-loop-end", this.loopEndDetail(result));
4225
+ this.activityBus.publish("tool-loop-end", _ChatDriver.loopEndDetail(result));
4403
4226
  }
4404
4227
  }
4405
4228
  // ---------------------------------------------------------------------------
@@ -4553,31 +4376,6 @@ Output format (strict):
4553
4376
  if (this.turnController.signal.aborted) {
4554
4377
  return this.completeAbortedTurn();
4555
4378
  }
4556
- if (this.budgetExhaustedThisTurn) {
4557
- logger2.error("ChatDriver: ending the turn \u2014 a sub-agent hit the AI budget wall");
4558
- recordTurnError(this.sessionKey, "budget-exhausted", {
4559
- agent: this.activeAgentName,
4560
- provider: this.lastResolvedProviderName,
4561
- // The CHILD's vendor when it knew one, and NOTHING otherwise. On this
4562
- // path the wall is definitionally the child's, and on a mixed registry
4563
- // `lastResolvedProvider` is this driver's own vendor — the one known
4564
- // NOT to have refused. An unattributable child wall must degrade to
4565
- // the vendor-agnostic block (which `latchBlockedFrom` handles
4566
- // fail-safe), never to a vendor that is known to be wrong: naming the
4567
- // parent's vendor here walled BOTH — the child's via its own
4568
- // tool-loop-end, the parent's via this event — and derived `blocked`
4569
- // over headroom that still existed.
4570
- vendor: this.budgetWallDetail?.vendor,
4571
- via: "sub-agent",
4572
- isSubAgent: this.isSubAgent
4573
- });
4574
- if (this.isSubAgent) {
4575
- this.failSubAgent("budget_exhausted", this.budgetWallDetail);
4576
- } else {
4577
- this.appendToHistory({ role: "assistant", content: this.budgetExhaustedMessage });
4578
- }
4579
- return this.turnDone("budget-exhausted", this.budgetWallDetail);
4580
- }
4581
4379
  const promptCtx = {
4582
4380
  agentName: this.activeAgentName ?? "",
4583
4381
  history: this.history,
@@ -4748,25 +4546,6 @@ ${tailBody}
4748
4546
  }
4749
4547
  return this.turnDone("response-truncated");
4750
4548
  }
4751
- if (e instanceof BudgetExhaustedError) {
4752
- this.budgetExhaustedThisTurn = true;
4753
- this.budgetWallDetail = budgetDetailOf(e);
4754
- this.budgetWallViaSubAgent = false;
4755
- if (this.isSubAgent) {
4756
- logger2.error("ChatDriver: AI budget exhausted", e);
4757
- recordTurnError(this.sessionKey, "budget-exhausted", {
4758
- agent: this.activeAgentName,
4759
- provider: this.lastResolvedProviderName,
4760
- vendor: vendorTypeOfLabel(e.vendorLabel) ?? vendorTypeOfLabel(e.serverVendor) ?? this.lastResolvedProvider,
4761
- budgetUsd: e.budgetUsd,
4762
- spentUsd: e.spentUsd,
4763
- isSubAgent: true
4764
- });
4765
- this.failSubAgent("budget_exhausted", this.budgetWallDetail);
4766
- return this.turnDone("budget-exhausted", this.budgetWallDetail);
4767
- }
4768
- return this.reportBudgetExhausted(e);
4769
- }
4770
4549
  if (e instanceof DOMException && e.name === "TimeoutError") {
4771
4550
  logger2.error("ChatDriver: request timed out", e);
4772
4551
  recordTurnError(this.sessionKey, "exception", {
@@ -5242,17 +5021,6 @@ var OrchestratingDriver = class extends EventTarget {
5242
5021
  * user stops. Reset at the start of each `sendMessage`.
5243
5022
  */
5244
5023
  this.cancelled = false;
5245
- /**
5246
- * Whether the CURRENT turn's user message has reached the inner driver's
5247
- * history. False through the pre-first-turn classify (where the only echo of
5248
- * the message is an optimistic `history-updated` dispatch), true from the
5249
- * moment `chatDriver.sendMessage` is entered — including through every later
5250
- * handoff classify. Read by the budget-wall catch in `sendMessage` to decide
5251
- * whether `reportBudgetExhausted` must append the message itself: appending
5252
- * it when already appended duplicated it; not appending it when unappended
5253
- * made it vanish. Reset at the top of each `runOrchestratedTurn`.
5254
- */
5255
- this.userMessageAppended = false;
5256
5024
  /**
5257
5025
  * Sticky user pick from the picker (or the host's `setAgent` API). Only
5258
5026
  * changes on explicit user action. Survives flow completion: when a stateful
@@ -5294,8 +5062,7 @@ var OrchestratingDriver = class extends EventTarget {
5294
5062
  maxFoldOperations: options.maxFoldOperations,
5295
5063
  maxTurnSnapshots: options.maxTurnSnapshots,
5296
5064
  sessionKey: this.sessionKey,
5297
- activityBus: options.activityBus,
5298
- budgetExhaustedMessage: options.budgetExhaustedMessage
5065
+ activityBus: options.activityBus
5299
5066
  });
5300
5067
  this.chatDriver.addEventListener("history-updated", (e) => {
5301
5068
  this.dispatchEvent(new CustomEvent("history-updated", { detail: e.detail }));
@@ -5394,25 +5161,7 @@ var OrchestratingDriver = class extends EventTarget {
5394
5161
  return this.chatDriver.getSuggestions(history, prompt2, count, agentInfo);
5395
5162
  }
5396
5163
  async sendMessage(input, attachments) {
5397
- try {
5398
- return await this.runOrchestratedTurn(input, attachments);
5399
- } catch (e) {
5400
- if (e instanceof BudgetExhaustedError) {
5401
- return this.chatDriver.reportBudgetExhausted(
5402
- e,
5403
- this.userMessageAppended ? void 0 : {
5404
- role: "user",
5405
- content: input,
5406
- ...attachments ? { attachments } : {}
5407
- }
5408
- );
5409
- }
5410
- throw e;
5411
- }
5412
- }
5413
- async runOrchestratedTurn(input, attachments) {
5414
5164
  this.cancelled = false;
5415
- this.userMessageAppended = false;
5416
5165
  const history = this.chatDriver.getHistory();
5417
5166
  this.dispatchEvent(
5418
5167
  new CustomEvent("history-updated", {
@@ -5426,7 +5175,6 @@ var OrchestratingDriver = class extends EventTarget {
5426
5175
  let handoffs = 0;
5427
5176
  let handoffSummary = "";
5428
5177
  let remainingTask = "";
5429
- let lastResult;
5430
5178
  while (true) {
5431
5179
  if (this.cancelled) break;
5432
5180
  await this.applyAgent(currentAgent);
@@ -5435,10 +5183,8 @@ var OrchestratingDriver = class extends EventTarget {
5435
5183
  const contextPrimer = handoffSummary ? [{ role: "user", content: `[Context from previous agent]: ${handoffSummary}` }] : [];
5436
5184
  result = await this.chatDriver.continueFromHistory(contextPrimer);
5437
5185
  } else {
5438
- this.userMessageAppended = true;
5439
5186
  result = await this.chatDriver.sendMessage(input, attachments);
5440
5187
  }
5441
- lastResult = result;
5442
5188
  if (this.chatDriver.getAgentReleaseRequested()) {
5443
5189
  await this.releaseActiveAgent();
5444
5190
  break;
@@ -5460,7 +5206,7 @@ var OrchestratingDriver = class extends EventTarget {
5460
5206
  this.dispatchEvent(new CustomEvent("orchestrating-start"));
5461
5207
  currentAgent = await this.classify(remainingTask, updatedHistory);
5462
5208
  }
5463
- return lastResult?.reason === "done" ? lastResult : { reason: "done" };
5209
+ return { reason: "done" };
5464
5210
  }
5465
5211
  async continueFromHistory(transientPrimer) {
5466
5212
  return this.chatDriver.continueFromHistory(transientPrimer);
@@ -5643,7 +5389,6 @@ ${recentMessages}` : ""}`;
5643
5389
  }
5644
5390
  }
5645
5391
  };
5646
- let routedNoMatch = false;
5647
5392
  for (let attempt = 0; attempt <= this.classifierRetries; attempt += 1) {
5648
5393
  try {
5649
5394
  const options = {
@@ -5663,10 +5408,8 @@ ${recentMessages}` : ""}`;
5663
5408
  if (index >= 0 && index < this.specialists.length) {
5664
5409
  return this.specialists[index];
5665
5410
  }
5666
- routedNoMatch = true;
5667
5411
  break;
5668
5412
  } catch (e) {
5669
- if (e instanceof BudgetExhaustedError) throw e;
5670
5413
  logger2.warn(`OrchestratingDriver: classifier attempt ${attempt + 1} failed:`, e);
5671
5414
  if (attempt === this.classifierRetries) {
5672
5415
  logger2.error("OrchestratingDriver: classifier failed after all retries, using fallback");
@@ -5674,12 +5417,10 @@ ${recentMessages}` : ""}`;
5674
5417
  }
5675
5418
  }
5676
5419
  if (this.fallback) return this.fallback;
5677
- if (routedNoMatch) {
5678
- const specialistNames = this.specialists.map((s) => s.name).join(", ");
5679
- this.appendInlineMessage(
5680
- `I'm not sure how to help with that. I can assist with: ${specialistNames}.`
5681
- );
5682
- }
5420
+ const specialistNames = this.specialists.map((s) => s.name).join(", ");
5421
+ this.appendInlineMessage(
5422
+ `I'm not sure how to help with that. I can assist with: ${specialistNames}.`
5423
+ );
5683
5424
  return this.specialists[0];
5684
5425
  }
5685
5426
  /**