@genesislcap/ai-assistant 15.29.0 → 15.30.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.
@@ -58,6 +58,7 @@ import { deleteBankedBaseline } from '../utils/banked-usage-baselines';
58
58
  import { estimateContextTokens, isContextMeasurable, } from '../utils/context-tokens';
59
59
  import { logger } from '../utils/logger';
60
60
  import { filterVisibleMessages, trailingInteractionRow } from '../utils/message-partition';
61
+ import { normalizeDebugLogNote, resolveDebugLogNoteConfig, } from '../utils/resolve-debug-log-note';
61
62
  import { DEFAULT_TIMESTAMP_DISPLAY, preferenceUnchanged, resolvePreferenceBaseline, } from '../utils/resolve-preference-baseline';
62
63
  import { stripAgentHandlers } from '../utils/strip-agent-handlers';
63
64
  import { emptyUsage, sumUsage, totalTokens } from '../utils/sum-usage';
@@ -92,6 +93,33 @@ export const genesisIconSvg = `<svg viewBox="-4 0 30 26" fill="none" xmlns="http
92
93
  function sanitizeIconMarkup(markup) {
93
94
  return DOMPurify.sanitize(markup, { USE_PROFILES: { svg: true, svgFilters: true } });
94
95
  }
96
+ /**
97
+ * Await `outcome` (a promise, or a sync return) but give up after `ms`, rejecting with a
98
+ * named error. Used to bound a host callback we cannot otherwise trust to settle.
99
+ */
100
+ function settleWithin(outcome, ms, what) {
101
+ return __awaiter(this, void 0, void 0, function* () {
102
+ let timer;
103
+ const timeout = new Promise((_, reject) => {
104
+ timer = setTimeout(() => reject(new Error(`${what} did not settle within ${ms}ms`)), ms);
105
+ });
106
+ try {
107
+ yield Promise.race([Promise.resolve(outcome), timeout]);
108
+ }
109
+ finally {
110
+ clearTimeout(timer);
111
+ }
112
+ });
113
+ }
114
+ /**
115
+ * How long the agent-log Send button shows its 'Sent'/'Failed' outcome before
116
+ * returning to idle — long enough to read, short enough that a retry is never blocked.
117
+ */
118
+ const DEBUG_LOG_SUBMIT_RESULT_FLASH_MS = 4000;
119
+ /** Default ceiling on the host's `debugLogSubmit.send` before the submission is failed. */
120
+ const DEBUG_LOG_SUBMIT_TIMEOUT_MS = 30000;
121
+ /** Announce the remaining note length once this few characters are left. */
122
+ const DEBUG_LOG_NOTE_REMAINING_WARN = 20;
95
123
  /**
96
124
  * Reporter id for this element's OWN session persister in `brokenSources` (GENC-1511).
97
125
  *
@@ -306,6 +334,34 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
306
334
  * element has its own settings modal.
307
335
  */
308
336
  this.downloadingDebugLog = false;
337
+ /**
338
+ * True while `submitDebugLog` is building and handing off the log. Same role as
339
+ * `downloadingDebugLog` for the Send button: the lifetime-stitch and the host's
340
+ * `send` callback are async, so the button needs a visible in-flight state.
341
+ */
342
+ this.submittingDebugLog = false;
343
+ /**
344
+ * Outcome of the last log submission, shown briefly on the button ('Sent' /
345
+ * 'Failed — retry') then cleared by `setDebugLogSubmitResult`'s timer. `null` when idle.
346
+ */
347
+ this.debugLogSubmitResult = null;
348
+ /**
349
+ * Whether the agent-log compose box (note + Send/Cancel) is open. Opened by the row's
350
+ * Send button when `debugLogSubmit.note` is not `false`; closed by Cancel or a
351
+ * successful send. Stays open after a failure so the typed note is not lost.
352
+ */
353
+ this.debugLogComposeOpen = false;
354
+ /** The note typed in the compose box, raw (trimmed only at submit time). */
355
+ this.debugLogNote = '';
356
+ /** How long 'Sent' shows before idle. Overridable so tests need not wait 4 seconds. */
357
+ this.debugLogSubmitResultFlashMs = DEBUG_LOG_SUBMIT_RESULT_FLASH_MS;
358
+ /**
359
+ * Bumped by `resetDebugLogSubmitState`. An in-flight `submitDebugLog` captures it and
360
+ * drops its continuation when a reset (disconnect, session switch, Clear) happened
361
+ * while it awaited — otherwise the stale submission would write state, arm a timer on
362
+ * a torn-down element, or report 'Sent' into a session it never belonged to.
363
+ */
364
+ this.debugLogSubmitGeneration = 0;
309
365
  /**
310
366
  * GENC-1511 — persistence has failed repeatedly and it is no longer saving. Drives a BLOCKING
311
367
  * modal with a single Reload action: the whole failure class this ticket exists for is one the
@@ -2148,6 +2204,9 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
2148
2204
  return;
2149
2205
  }
2150
2206
  this._deferredSwitchToKey = undefined;
2207
+ // A note typed against the outgoing conversation must not caption the incoming one's
2208
+ // log, and an outcome flash belongs to the session that produced it.
2209
+ this.resetDebugLogSubmitState();
2151
2210
  // Cancel the outgoing session's debounced autosave FIRST, so it can't land in
2152
2211
  // the incoming session's file once `_sessionRef` swaps below.
2153
2212
  (_b = getPersister(oldKey)) === null || _b === void 0 ? void 0 : _b.cancelPendingSave();
@@ -2210,6 +2269,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
2210
2269
  // After `_sessionActive` is cleared, so the sync sees "disconnected" and stops.
2211
2270
  this.syncTimestampClock();
2212
2271
  this.stopLoadingTimer();
2272
+ this.resetDebugLogSubmitState();
2213
2273
  this.state = 'idle';
2214
2274
  // Note: `compacting`/`restoring` are NOT cleared here. They're written through the
2215
2275
  // in-flight op's captured session store (see `initPersistenceForNewStore` /
@@ -2968,6 +3028,9 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
2968
3028
  if (this.driver)
2969
3029
  this.teardownDriver(key);
2970
3030
  (_f = this._sessionRef) === null || _f === void 0 ? void 0 : _f.actions.aiAssistant.resetSession();
3031
+ // Same reason as in `switchSession`: the compose box and its note were about the
3032
+ // conversation just discarded.
3033
+ this.resetDebugLogSubmitState();
2971
3034
  // `resetSession` zeroes the store-backed `contextTokens`; the gate's held copy
2972
3035
  // is ours to drop. Leaving it would carry a block into a conversation that no
2973
3036
  // longer exists — the one thing `contextGate` must never do (GENC-1582).
@@ -3140,6 +3203,145 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
3140
3203
  get settingsSessionTokensVisible() {
3141
3204
  return this.settings.sessionTokensVisible;
3142
3205
  }
3206
+ /**
3207
+ * Whether the settings "Agent log" section renders — shown when either action is
3208
+ * enabled: the download button (`allowDebugDownload`) or the host-transport Send
3209
+ * button (`debugLogSubmit`). Each button is additionally gated on its own flag
3210
+ * inside the section, so a host can enable one without the other.
3211
+ */
3212
+ get settingsAgentLogSectionVisible() {
3213
+ const ui = this.chatConfig.ui;
3214
+ return (ui === null || ui === void 0 ? void 0 : ui.allowDebugDownload) === true || (ui === null || ui === void 0 ? void 0 : ui.debugLogSubmit) != null;
3215
+ }
3216
+ /**
3217
+ * The resolved compose-box settings for the agent-log Send button, or `null` when the
3218
+ * host opted out (`debugLogSubmit.note: false`) and the button sends on first click.
3219
+ */
3220
+ get debugLogNoteConfig() {
3221
+ var _a;
3222
+ return resolveDebugLogNoteConfig((_a = this.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.debugLogSubmit);
3223
+ }
3224
+ /**
3225
+ * Whether the typed note counts as empty — the ONE emptiness rule, shared by the
3226
+ * compose box's disabled state and its "required" hint so the two cannot drift apart.
3227
+ */
3228
+ get debugLogNoteMissing() {
3229
+ return normalizeDebugLogNote(this.debugLogNote) === undefined;
3230
+ }
3231
+ /** The Send button's idle label, as the host configured it. */
3232
+ get debugLogSubmitLabel() {
3233
+ var _a, _b, _c;
3234
+ return (_c = (_b = (_a = this.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.debugLogSubmit) === null || _b === void 0 ? void 0 : _b.label) !== null && _c !== void 0 ? _c : 'Send';
3235
+ }
3236
+ /**
3237
+ * Label on the agent-log row's Send button. While the compose box is open the row
3238
+ * button is just the (disabled) trigger, so it keeps its idle label; otherwise it
3239
+ * reflects the in-flight / flashed outcome of the last submission.
3240
+ */
3241
+ get debugLogSubmitButtonLabel() {
3242
+ if (this.debugLogComposeOpen)
3243
+ return this.debugLogSubmitLabel;
3244
+ if (this.submittingDebugLog)
3245
+ return 'Sending…';
3246
+ if (this.debugLogSubmitResult === 'sent')
3247
+ return 'Sent';
3248
+ if (this.debugLogSubmitResult === 'error')
3249
+ return 'Failed — retry';
3250
+ return this.debugLogSubmitLabel;
3251
+ }
3252
+ /**
3253
+ * The compose box's footer hint: a failure first, then the "required" nudge, else the
3254
+ * live counter. Visual only — the same information reaches assistive technology via
3255
+ * `debugLogNoteAriaLabel` (counter, required) and `debugLogSubmitAnnouncement` (outcome),
3256
+ * because IDREF attributes such as `aria-describedby` cannot point across the
3257
+ * design-system text-area's shadow boundary.
3258
+ */
3259
+ get debugLogComposeHint() {
3260
+ var _a;
3261
+ if (this.debugLogSubmitResult === 'error')
3262
+ return 'Sending failed — try again.';
3263
+ if (((_a = this.debugLogNoteConfig) === null || _a === void 0 ? void 0 : _a.required) && this.debugLogNoteMissing) {
3264
+ return 'A short note is required.';
3265
+ }
3266
+ return this.debugLogNoteCounter;
3267
+ }
3268
+ /**
3269
+ * Accessible name for the note. Deliberately STATIC apart from "(required)": a name
3270
+ * that changed per keystroke would be re-read in full by NVDA on every key and never
3271
+ * updated by JAWS. The running count reaches AT through `debugLogSubmitAnnouncement`
3272
+ * at the thresholds where it matters.
3273
+ */
3274
+ get debugLogNoteAriaLabel() {
3275
+ const note = this.debugLogNoteConfig;
3276
+ const required = (note === null || note === void 0 ? void 0 : note.required) ? ' (required)' : '';
3277
+ const max = note ? `, maximum ${note.maxLength} characters` : '';
3278
+ return `Note to send with the agent log${required}${max}.`;
3279
+ }
3280
+ /**
3281
+ * Text for the always-present live region beside the agent-log row, so the outcome of
3282
+ * a submission is announced even when the compose box is closed or opted out
3283
+ * (`note: false`), where the only visible change is the button label.
3284
+ */
3285
+ get debugLogSubmitAnnouncement() {
3286
+ var _a;
3287
+ if (this.submittingDebugLog)
3288
+ return 'Sending the agent log.';
3289
+ if (this.debugLogSubmitResult === 'sent')
3290
+ return 'Agent log sent.';
3291
+ if (this.debugLogSubmitResult === 'error')
3292
+ return 'Sending the agent log failed. Try again.';
3293
+ // The note's length, only as it nears the cap: `maxlength` is invisible to AT, so at
3294
+ // the limit keystrokes would be silently dropped with nothing changing anywhere.
3295
+ const max = (_a = this.debugLogNoteConfig) === null || _a === void 0 ? void 0 : _a.maxLength;
3296
+ if (this.debugLogComposeOpen && max) {
3297
+ const remaining = max - this.debugLogNote.length;
3298
+ if (remaining <= 0) {
3299
+ return `Note is at its ${max} character limit; further typing is dropped.`;
3300
+ }
3301
+ if (remaining <= DEBUG_LOG_NOTE_REMAINING_WARN)
3302
+ return `${remaining} characters remaining.`;
3303
+ }
3304
+ return '';
3305
+ }
3306
+ /**
3307
+ * The agent-log row's description, phrased for whichever actions the host enabled.
3308
+ * Send is called out as transmitting: Download keeps the log on the user's machine,
3309
+ * Send hands the full conversation to wherever the host's transport delivers it.
3310
+ */
3311
+ get debugLogRowDescription() {
3312
+ var _a;
3313
+ const ui = this.chatConfig.ui;
3314
+ const download = (ui === null || ui === void 0 ? void 0 : ui.allowDebugDownload) === true;
3315
+ const send = (ui === null || ui === void 0 ? void 0 : ui.debugLogSubmit) != null;
3316
+ if (send && ((_a = ui === null || ui === void 0 ? void 0 : ui.debugLogSubmit) === null || _a === void 0 ? void 0 : _a.description))
3317
+ return ui.debugLogSubmit.description;
3318
+ const timeline = "a structured debug timeline of this assistant's activity";
3319
+ // What actually travels (see `ChatDebugLogSubmission.logJson`): the lifetime stream,
3320
+ // not just this conversation — say so where the user commits to it.
3321
+ const transmits = 'transmits the full lifetime log — every conversation held here, including cleared ' +
3322
+ 'ones, with attachments, system prompts and agent configuration.';
3323
+ if (download && send)
3324
+ return `Export ${timeline}, or send it to support. Sending ${transmits}`;
3325
+ if (send)
3326
+ return `Send ${timeline} to support. This ${transmits}`;
3327
+ return "Export a structured debug timeline of this session's agent activity.";
3328
+ }
3329
+ /**
3330
+ * Whether the compose box's Send button is inert: a send is already in flight, or the
3331
+ * host made the note required and it is still blank.
3332
+ */
3333
+ get debugLogSendDisabled() {
3334
+ var _a;
3335
+ if (this.submittingDebugLog)
3336
+ return true;
3337
+ return ((_a = this.debugLogNoteConfig) === null || _a === void 0 ? void 0 : _a.required) === true && this.debugLogNoteMissing;
3338
+ }
3339
+ /** Live "typed / max" counter for the compose box. */
3340
+ get debugLogNoteCounter() {
3341
+ var _a;
3342
+ const max = (_a = this.debugLogNoteConfig) === null || _a === void 0 ? void 0 : _a.maxLength;
3343
+ return max ? `${this.debugLogNote.length}/${max}` : '';
3344
+ }
3143
3345
  /** Whether the chat header (and settings cog) should render. */
3144
3346
  get settingsCogVisible() {
3145
3347
  return this.settings.cogVisible;
@@ -3323,15 +3525,11 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
3323
3525
  }
3324
3526
  downloadDebugLog() {
3325
3527
  return __awaiter(this, void 0, void 0, function* () {
3326
- var _a, _b, _c;
3327
3528
  // Guard re-entry: the button is disabled while this runs, but a programmatic
3328
3529
  // or double-fire can't kick off a second stitch either.
3329
3530
  if (this.downloadingDebugLog)
3330
3531
  return;
3331
- const timestamp = new Date().toISOString().replace(/:/g, '-');
3332
- const agentName = ((_c = (_b = (_a = this.activeAgent) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : this.headerTitle) !== null && _c !== void 0 ? _c : 'chat')
3333
- .toLowerCase()
3334
- .replace(/\s+/g, '-');
3532
+ const filename = this.debugLogFilename();
3335
3533
  this.downloadingDebugLog = true;
3336
3534
  try {
3337
3535
  const payload = yield this.buildDownloadLog();
@@ -3339,7 +3537,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
3339
3537
  const url = URL.createObjectURL(blob);
3340
3538
  const a = document.createElement('a');
3341
3539
  a.href = url;
3342
- a.download = `${agentName}-${timestamp}.json`;
3540
+ a.download = filename;
3343
3541
  a.click();
3344
3542
  URL.revokeObjectURL(url);
3345
3543
  }
@@ -3348,6 +3546,187 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
3348
3546
  }
3349
3547
  });
3350
3548
  }
3549
+ /**
3550
+ * Hand the agent log to the host's `ui.debugLogSubmit.send` callback — the Send
3551
+ * button next to Download. Reuses `buildDownloadLog`, so the submitted log is
3552
+ * byte-identical to what Download saves (lifetime-stitched when the provider
3553
+ * persists diagnostics). The assistant is vendor-agnostic: the callback owns the
3554
+ * transport (Sentry feedback, a support API, an upload endpoint). Outcome lands in
3555
+ * `debugLogSubmitResult` so the button can flash 'Sent'/'Failed'; failures also go
3556
+ * to the logger with the cause.
3557
+ */
3558
+ submitDebugLog() {
3559
+ return __awaiter(this, void 0, void 0, function* () {
3560
+ var _a, _b;
3561
+ if (this.submittingDebugLog || this.debugLogSendDisabled)
3562
+ return;
3563
+ const config = (_a = this.chatConfig.ui) === null || _a === void 0 ? void 0 : _a.debugLogSubmit;
3564
+ if (!config)
3565
+ return;
3566
+ const generation = this.debugLogSubmitGeneration;
3567
+ const agentName = this.debugLogAgentName;
3568
+ const filename = this.debugLogFilename();
3569
+ // Read at click time, BEFORE the async stitch: the user pressed Send on this note.
3570
+ // Only a note the user could actually type travels with the log: with the compose box
3571
+ // opted out there is no note, whatever a stale `debugLogNote` might hold.
3572
+ const message = this.debugLogNoteConfig === null ? undefined : normalizeDebugLogNote(this.debugLogNote);
3573
+ this.submittingDebugLog = true;
3574
+ try {
3575
+ const payload = yield this.buildDownloadLog();
3576
+ if (generation !== this.debugLogSubmitGeneration)
3577
+ return;
3578
+ yield settleWithin(config.send({
3579
+ logJson: JSON.stringify(payload, null, 2),
3580
+ filename,
3581
+ agentName,
3582
+ host: window.location.host,
3583
+ message,
3584
+ }), (_b = config.timeoutMs) !== null && _b !== void 0 ? _b : DEBUG_LOG_SUBMIT_TIMEOUT_MS, 'debugLogSubmit.send');
3585
+ if (generation !== this.debugLogSubmitGeneration)
3586
+ return;
3587
+ // Success consumes the note; a failure (below) keeps the box open with it intact.
3588
+ this.debugLogComposeOpen = false;
3589
+ this.debugLogNote = '';
3590
+ this.setDebugLogSubmitResult('sent');
3591
+ this.restoreDebugLogTriggerFocus();
3592
+ }
3593
+ catch (e) {
3594
+ if (generation !== this.debugLogSubmitGeneration)
3595
+ return;
3596
+ logger.error('Submitting agent log failed:', e);
3597
+ // Persists (no flash timer) until the user acts — see `setDebugLogSubmitResult`.
3598
+ this.setDebugLogSubmitResult('error');
3599
+ this.restoreDebugLogRetryFocus();
3600
+ }
3601
+ finally {
3602
+ if (generation === this.debugLogSubmitGeneration)
3603
+ this.submittingDebugLog = false;
3604
+ }
3605
+ });
3606
+ }
3607
+ /** The agent name stamped on the log: the active agent, else the header title. */
3608
+ get debugLogAgentName() {
3609
+ var _a, _b, _c;
3610
+ return (_c = (_b = (_a = this.activeAgent) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : this.headerTitle) !== null && _c !== void 0 ? _c : 'chat';
3611
+ }
3612
+ /**
3613
+ * Filename for the agent log — the ONE derivation Download saves under and Send hands
3614
+ * to the host, so `ChatDebugLogSubmission.filename`'s "same stem as the download"
3615
+ * promise is enforced rather than relying on two copies agreeing.
3616
+ */
3617
+ debugLogFilename() {
3618
+ const stem = this.debugLogAgentName.toLowerCase().replace(/\s+/g, '-');
3619
+ return `${stem}-${new Date().toISOString().replace(/:/g, '-')}.json`;
3620
+ }
3621
+ /**
3622
+ * Back to idle: nothing in flight, no flash, no open compose box, no note. Called on
3623
+ * disconnect and on a session switch / Clear (a note typed against one conversation
3624
+ * must not caption the next one's log). On disconnect — the
3625
+ * flash timer would otherwise fire against a torn-down element, and clearing ONLY the
3626
+ * timer would strand `debugLogSubmitResult` at 'sent'/'error' for good on a pop-out /
3627
+ * pop-in (nothing else resets it), with the box remounting open on a stale note.
3628
+ */
3629
+ resetDebugLogSubmitState() {
3630
+ this.debugLogSubmitGeneration += 1;
3631
+ clearTimeout(this.debugLogSubmitResultTimer);
3632
+ this.debugLogSubmitResultTimer = undefined;
3633
+ // Including the in-flight flag: every escape route (Send, Cancel, the note, the row
3634
+ // button) is gated on it, so a host transport that never settles would otherwise
3635
+ // wedge the feature until reload.
3636
+ this.submittingDebugLog = false;
3637
+ this.debugLogSubmitResult = null;
3638
+ this.debugLogComposeOpen = false;
3639
+ this.debugLogNote = '';
3640
+ }
3641
+ /**
3642
+ * The row's Send button. With a compose box configured (the default) it opens the box
3643
+ * and focuses the note so the user can caption the log; with `note: false` it sends
3644
+ * straight away, as before.
3645
+ */
3646
+ openDebugLogCompose() {
3647
+ if (this.submittingDebugLog)
3648
+ return;
3649
+ if (this.debugLogNoteConfig === null) {
3650
+ void this.submitDebugLog();
3651
+ return;
3652
+ }
3653
+ // Opening again resets a lingering 'Failed' flash so the box starts clean.
3654
+ clearTimeout(this.debugLogSubmitResultTimer);
3655
+ this.debugLogSubmitResultTimer = undefined;
3656
+ this.debugLogSubmitResult = null;
3657
+ this.debugLogComposeOpen = true;
3658
+ // Next frame so the `when` block has rendered the text-area. The design-system
3659
+ // text-area does not delegate focus (see `maybeAutoFocusChatInput`), so drill in.
3660
+ requestAnimationFrame(() => {
3661
+ var _a, _b, _c;
3662
+ if (!this.debugLogComposeOpen || !this.isConnected)
3663
+ return;
3664
+ const el = this.debugLogNoteEl;
3665
+ (_c = ((_b = (_a = el === null || el === void 0 ? void 0 : el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('textarea')) !== null && _b !== void 0 ? _b : el)) === null || _c === void 0 ? void 0 : _c.focus();
3666
+ });
3667
+ }
3668
+ /** The note's `@input`: track the text, and let typing dismiss a standing failure. */
3669
+ noteDebugLogInput(value) {
3670
+ this.debugLogNote = value;
3671
+ if (this.debugLogSubmitResult === 'error')
3672
+ this.debugLogSubmitResult = null;
3673
+ }
3674
+ /** Close the compose box and discard the typed note. */
3675
+ cancelDebugLogCompose() {
3676
+ if (this.submittingDebugLog)
3677
+ return;
3678
+ this.debugLogComposeOpen = false;
3679
+ this.debugLogNote = '';
3680
+ this.restoreDebugLogTriggerFocus();
3681
+ }
3682
+ /**
3683
+ * Closing the compose box unmounts the focused text-area, which drops focus to the
3684
+ * document body and strands keyboard and screen-reader users. Hand it back to the row's
3685
+ * Send button — next frame, once the `when` block has torn down and the button, disabled
3686
+ * while the box was open, is interactive again.
3687
+ */
3688
+ restoreDebugLogTriggerFocus() {
3689
+ requestAnimationFrame(() => {
3690
+ var _a, _b;
3691
+ if (!this.isConnected || this.debugLogComposeOpen)
3692
+ return;
3693
+ (_b = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('[part="submit-log-button"]')) === null || _b === void 0 ? void 0 : _b.focus();
3694
+ });
3695
+ }
3696
+ /**
3697
+ * After a FAILED send the controls come back but the element that started the send
3698
+ * was disabled meanwhile, so focus has already dropped to the document body. Put it
3699
+ * where the retry happens: the note when the box is open, else the row trigger.
3700
+ */
3701
+ restoreDebugLogRetryFocus() {
3702
+ if (!this.debugLogComposeOpen) {
3703
+ this.restoreDebugLogTriggerFocus();
3704
+ return;
3705
+ }
3706
+ requestAnimationFrame(() => {
3707
+ var _a, _b, _c;
3708
+ if (!this.isConnected || !this.debugLogComposeOpen)
3709
+ return;
3710
+ const el = this.debugLogNoteEl;
3711
+ (_c = ((_b = (_a = el === null || el === void 0 ? void 0 : el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('textarea')) !== null && _b !== void 0 ? _b : el)) === null || _c === void 0 ? void 0 : _c.focus();
3712
+ });
3713
+ }
3714
+ /**
3715
+ * Record the submit outcome. 'sent' flashes and falls back to idle; 'error' PERSISTS
3716
+ * until the user acts (types, reopens the box, cancels, or retries) — a failure that
3717
+ * silently reverted to the counter after four seconds left no trace it had happened.
3718
+ */
3719
+ setDebugLogSubmitResult(result) {
3720
+ clearTimeout(this.debugLogSubmitResultTimer);
3721
+ this.debugLogSubmitResultTimer = undefined;
3722
+ this.debugLogSubmitResult = result;
3723
+ if (result !== 'sent')
3724
+ return;
3725
+ this.debugLogSubmitResultTimer = setTimeout(() => {
3726
+ this.debugLogSubmitResult = null;
3727
+ this.debugLogSubmitResultTimer = undefined;
3728
+ }, this.debugLogSubmitResultFlashMs);
3729
+ }
3351
3730
  /**
3352
3731
  * The log to download (GENC-1351 §5.8). When the provider persists diagnostics,
3353
3732
  * stitch the **whole lifetime** stream (every page load) rather than just the
@@ -3968,6 +4347,18 @@ __decorate([
3968
4347
  __decorate([
3969
4348
  observable
3970
4349
  ], FoundationAiAssistant.prototype, "downloadingDebugLog", void 0);
4350
+ __decorate([
4351
+ observable
4352
+ ], FoundationAiAssistant.prototype, "submittingDebugLog", void 0);
4353
+ __decorate([
4354
+ observable
4355
+ ], FoundationAiAssistant.prototype, "debugLogSubmitResult", void 0);
4356
+ __decorate([
4357
+ observable
4358
+ ], FoundationAiAssistant.prototype, "debugLogComposeOpen", void 0);
4359
+ __decorate([
4360
+ observable
4361
+ ], FoundationAiAssistant.prototype, "debugLogNote", void 0);
3971
4362
  __decorate([
3972
4363
  observable
3973
4364
  ], FoundationAiAssistant.prototype, "persistenceBroken", void 0);
@@ -4016,6 +4407,39 @@ __decorate([
4016
4407
  __decorate([
4017
4408
  volatile
4018
4409
  ], FoundationAiAssistant.prototype, "agentPickerEnabled", null);
4410
+ __decorate([
4411
+ volatile
4412
+ ], FoundationAiAssistant.prototype, "settingsAgentLogSectionVisible", null);
4413
+ __decorate([
4414
+ volatile
4415
+ ], FoundationAiAssistant.prototype, "debugLogNoteConfig", null);
4416
+ __decorate([
4417
+ volatile
4418
+ ], FoundationAiAssistant.prototype, "debugLogNoteMissing", null);
4419
+ __decorate([
4420
+ volatile
4421
+ ], FoundationAiAssistant.prototype, "debugLogSubmitLabel", null);
4422
+ __decorate([
4423
+ volatile
4424
+ ], FoundationAiAssistant.prototype, "debugLogSubmitButtonLabel", null);
4425
+ __decorate([
4426
+ volatile
4427
+ ], FoundationAiAssistant.prototype, "debugLogComposeHint", null);
4428
+ __decorate([
4429
+ volatile
4430
+ ], FoundationAiAssistant.prototype, "debugLogNoteAriaLabel", null);
4431
+ __decorate([
4432
+ volatile
4433
+ ], FoundationAiAssistant.prototype, "debugLogSubmitAnnouncement", null);
4434
+ __decorate([
4435
+ volatile
4436
+ ], FoundationAiAssistant.prototype, "debugLogRowDescription", null);
4437
+ __decorate([
4438
+ volatile
4439
+ ], FoundationAiAssistant.prototype, "debugLogSendDisabled", null);
4440
+ __decorate([
4441
+ volatile
4442
+ ], FoundationAiAssistant.prototype, "debugLogNoteCounter", null);
4019
4443
  __decorate([
4020
4444
  volatile
4021
4445
  ], FoundationAiAssistant.prototype, "pinnedAgentHint", null);
@@ -0,0 +1,37 @@
1
+ /** Default cap on the user's note. Short by design — it is a caption for the log, not a report. */
2
+ export const DEBUG_LOG_NOTE_DEFAULT_MAX_LENGTH = 500;
3
+ /** Default prompt in the empty note. */
4
+ export const DEBUG_LOG_NOTE_DEFAULT_PLACEHOLDER = 'What were you doing, or what went wrong? (optional)';
5
+ /** The same prompt for a `required` note — "(optional)" would contradict the disabled Send. */
6
+ export const DEBUG_LOG_NOTE_REQUIRED_PLACEHOLDER = 'What were you doing, or what went wrong?';
7
+ /**
8
+ * Resolve `debugLogSubmit.note` — `undefined`/`true` mean "compose box with defaults",
9
+ * `false` means "no compose box, send on first click" (→ `null`), and an object customises
10
+ * the box. Keeps the template free of the boolean-or-object dance.
11
+ */
12
+ export function resolveDebugLogNoteConfig(config) {
13
+ var _a;
14
+ if (!config || config.note === false)
15
+ return null;
16
+ // `typeof null === 'object'`: a host building its config dynamically can hand us `null`
17
+ // even though the type forbids it, and that must read as "defaults", not crash.
18
+ const note = typeof config.note === 'object' && config.note !== null ? config.note : {};
19
+ // `>= 1`, not `> 0`: a fractional cap under 1 would floor to 0 and lock the textarea.
20
+ const maxLength = typeof note.maxLength === 'number' && note.maxLength >= 1
21
+ ? Math.floor(note.maxLength)
22
+ : DEBUG_LOG_NOTE_DEFAULT_MAX_LENGTH;
23
+ const required = note.required === true;
24
+ return {
25
+ placeholder: (_a = note.placeholder) !== null && _a !== void 0 ? _a : (required ? DEBUG_LOG_NOTE_REQUIRED_PLACEHOLDER : DEBUG_LOG_NOTE_DEFAULT_PLACEHOLDER),
26
+ required,
27
+ maxLength,
28
+ };
29
+ }
30
+ /**
31
+ * Normalise the typed note for the submission: trimmed, and `undefined` when empty so a
32
+ * host can `message ?? fallback` without checking for whitespace.
33
+ */
34
+ export function normalizeDebugLogNote(note) {
35
+ const trimmed = note.trim();
36
+ return trimmed.length > 0 ? trimmed : undefined;
37
+ }
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.29.0",
4
+ "version": "15.30.0",
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.29.0",
78
- "@genesislcap/genx": "15.29.0",
79
- "@genesislcap/rollup-builder": "15.29.0",
80
- "@genesislcap/ts-builder": "15.29.0",
81
- "@genesislcap/uvu-playwright-builder": "15.29.0",
82
- "@genesislcap/vite-builder": "15.29.0",
83
- "@genesislcap/webpack-builder": "15.29.0",
77
+ "@genesislcap/foundation-testing": "15.30.0",
78
+ "@genesislcap/genx": "15.30.0",
79
+ "@genesislcap/rollup-builder": "15.30.0",
80
+ "@genesislcap/ts-builder": "15.30.0",
81
+ "@genesislcap/uvu-playwright-builder": "15.30.0",
82
+ "@genesislcap/vite-builder": "15.30.0",
83
+ "@genesislcap/webpack-builder": "15.30.0",
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.29.0",
90
- "@genesislcap/foundation-logger": "15.29.0",
91
- "@genesislcap/foundation-notifications": "15.29.0",
92
- "@genesislcap/foundation-redux": "15.29.0",
93
- "@genesislcap/foundation-ui": "15.29.0",
94
- "@genesislcap/foundation-utils": "15.29.0",
95
- "@genesislcap/rapid-design-system": "15.29.0",
96
- "@genesislcap/web-core": "15.29.0",
89
+ "@genesislcap/foundation-ai": "15.30.0",
90
+ "@genesislcap/foundation-logger": "15.30.0",
91
+ "@genesislcap/foundation-notifications": "15.30.0",
92
+ "@genesislcap/foundation-redux": "15.30.0",
93
+ "@genesislcap/foundation-ui": "15.30.0",
94
+ "@genesislcap/foundation-utils": "15.30.0",
95
+ "@genesislcap/rapid-design-system": "15.30.0",
96
+ "@genesislcap/web-core": "15.30.0",
97
97
  "dompurify": "^3.3.1",
98
98
  "marked": "^17.0.3"
99
99
  },