oysterun 1.5.5 → 1.5.7-beta.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.
Files changed (41) hide show
  1. package/dev/client/web/index.html +1736 -151
  2. package/dev/client/web-chat/dist/assets/{ReactPrism-9mfIwAdN.js → ReactPrism-DNQ9nZSp.js} +1 -1
  3. package/dev/client/web-chat/dist/assets/{ReactPrism-9mfIwAdN.js.map → ReactPrism-DNQ9nZSp.js.map} +1 -1
  4. package/dev/client/web-chat/dist/assets/{index-4B1xHv-h.css → index-BFaINJYC.css} +1 -1
  5. package/dev/client/web-chat/dist/assets/{index-CpnKlml1.js → index-D3mRy5zG.js} +92 -92
  6. package/dev/client/web-chat/dist/assets/index-D3mRy5zG.js.map +1 -0
  7. package/dev/client/web-chat/dist/assets/{index-DWS0uvcS.js → index-Dt0tJQEo.js} +2 -2
  8. package/dev/client/web-chat/dist/assets/{index-DWS0uvcS.js.map → index-Dt0tJQEo.js.map} +1 -1
  9. package/dev/client/web-chat/dist/assets/{index-DKemUlfN.js → index-kph-A9_0.js} +1 -1
  10. package/dev/client/web-chat/dist/assets/{index-DKemUlfN.js.map → index-kph-A9_0.js.map} +1 -1
  11. package/dev/client/web-chat/dist/assets/{pdf-_pQ2yOcq.js → pdf-Dz-8X_5I.js} +3 -3
  12. package/dev/client/web-chat/dist/assets/{pdf-_pQ2yOcq.js.map → pdf-Dz-8X_5I.js.map} +1 -1
  13. package/dev/client/web-chat/dist/assets/{web-_J72Rkyo.js → web-8cohp67o.js} +1 -1
  14. package/dev/client/web-chat/dist/assets/{web-_J72Rkyo.js.map → web-8cohp67o.js.map} +1 -1
  15. package/dev/client/web-chat/dist/assets/{web-hiRi-dhx.js → web-z_YJW2TC.js} +1 -1
  16. package/dev/client/web-chat/dist/assets/{web-hiRi-dhx.js.map → web-z_YJW2TC.js.map} +1 -1
  17. package/dev/client/web-chat/dist/index.html +2 -2
  18. package/docs/routec/dev/cloud_deplyments.md +138 -0
  19. package/docs/routec/dev/provider_catalog_readiness.md +71 -0
  20. package/docs/routec/dev/scheduler_mail_skill.md +3 -0
  21. package/docs/routec/form_UI_fields.md +3 -4
  22. package/docs/routec/skill_installation.md +35 -0
  23. package/host-service/assets/product-skills/Oysterun/SKILL.md +9 -0
  24. package/host-service/assets/voice-input/D1.wav +0 -0
  25. package/host-service/browser-root-project-index.mjs +126 -2
  26. package/host-service/config.mjs +7 -0
  27. package/host-service/folder-browser.mjs +8 -9
  28. package/host-service/package.json +4 -2
  29. package/host-service/portable-scheduler-definitions.mjs +16 -15
  30. package/host-service/provider-auth.mjs +10 -1
  31. package/host-service/routec-matrix-storage-adapter.mjs +4 -0
  32. package/host-service/server.mjs +570 -154
  33. package/host-service/session-manager.mjs +1 -0
  34. package/host-service/templates/demo-agents/oysterun-github-tracker/.oysterun/schedulers.json +0 -24
  35. package/host-service/voice-input-owner-capture.mjs +225 -0
  36. package/host-service/voice-input-recording-collector.mjs +369 -0
  37. package/host-service/voice-input-service.mjs +755 -0
  38. package/host-service/voice-input-usage-contract.mjs +63 -0
  39. package/host-service/voice-input-usage-store.mjs +409 -0
  40. package/package.json +2 -1
  41. package/dev/client/web-chat/dist/assets/index-CpnKlml1.js.map +0 -1
@@ -2061,6 +2061,117 @@
2061
2061
  line-height: 1.35;
2062
2062
  }
2063
2063
 
2064
+ .settings-list-page .voice-input-secret-input {
2065
+ -webkit-text-security: disc;
2066
+ }
2067
+
2068
+ .settings-list-page .voice-input-secret-input::placeholder {
2069
+ -webkit-text-security: none;
2070
+ color: var(--tc-surface-low);
2071
+ opacity: 1;
2072
+ }
2073
+
2074
+ .settings-list-page .voice-input-token-status {
2075
+ text-align: left;
2076
+ overflow-wrap: anywhere;
2077
+ }
2078
+
2079
+ .settings-list-page .voice-input-sample-player {
2080
+ display: block;
2081
+ width: 100%;
2082
+ max-width: 520px;
2083
+ height: 44px;
2084
+ }
2085
+
2086
+ .settings-list-page .voice-input-sample-expected {
2087
+ display: inline-block;
2088
+ margin-left: 4px;
2089
+ }
2090
+
2091
+ .settings-list-page .voice-input-warning {
2092
+ color: var(--danger, #c53030);
2093
+ font-weight: 700;
2094
+ }
2095
+
2096
+ .settings-list-page .host-voice-input-toggle:disabled {
2097
+ cursor: not-allowed;
2098
+ }
2099
+
2100
+ .settings-list-page .voice-input-usage-summary {
2101
+ display: grid;
2102
+ gap: 8px;
2103
+ margin-top: 14px;
2104
+ padding-top: 14px;
2105
+ border-top: 1px solid var(--line);
2106
+ color: var(--ink);
2107
+ font-size: 13px;
2108
+ line-height: 1.45;
2109
+ }
2110
+
2111
+ .settings-list-page .voice-input-usage-summary.hidden {
2112
+ display: none;
2113
+ }
2114
+
2115
+ .settings-list-page .voice-input-usage-detail-link {
2116
+ justify-self: start;
2117
+ min-height: 38px;
2118
+ padding: 6px 0;
2119
+ border: 0;
2120
+ color: var(--accent-strong, #0b6f64);
2121
+ background: transparent;
2122
+ font-weight: 800;
2123
+ }
2124
+
2125
+ .voice-input-usage-page {
2126
+ width: min(100%, 920px);
2127
+ margin: 0 auto;
2128
+ }
2129
+
2130
+ .voice-input-usage-table-wrap {
2131
+ width: 100%;
2132
+ overflow-x: auto;
2133
+ border: 1px solid var(--line);
2134
+ border-radius: 8px;
2135
+ background: #fff;
2136
+ }
2137
+
2138
+ .voice-input-usage-table {
2139
+ width: 100%;
2140
+ min-width: 680px;
2141
+ border-collapse: collapse;
2142
+ color: var(--ink);
2143
+ font-size: 14px;
2144
+ }
2145
+
2146
+ .voice-input-usage-table th,
2147
+ .voice-input-usage-table td {
2148
+ padding: 12px 14px;
2149
+ border-bottom: 1px solid var(--line);
2150
+ text-align: right;
2151
+ white-space: nowrap;
2152
+ }
2153
+
2154
+ .voice-input-usage-table th:first-child,
2155
+ .voice-input-usage-table td:first-child {
2156
+ text-align: left;
2157
+ }
2158
+
2159
+ .voice-input-usage-table thead th {
2160
+ background: #f3eee6;
2161
+ font-size: 12px;
2162
+ font-weight: 850;
2163
+ text-transform: uppercase;
2164
+ }
2165
+
2166
+ .voice-input-usage-table tbody th {
2167
+ font-weight: 750;
2168
+ }
2169
+
2170
+ .voice-input-usage-table tbody tr:last-child th,
2171
+ .voice-input-usage-table tbody tr:last-child td {
2172
+ border-bottom: 0;
2173
+ }
2174
+
2064
2175
  .settings-list-page .field {
2065
2176
  padding: 14px;
2066
2177
  border: 1px solid var(--line);
@@ -8496,6 +8607,16 @@
8496
8607
  </button>
8497
8608
  </div>
8498
8609
  <div class="field-hint" id="runtime-hint"></div>
8610
+ <button
8611
+ id="create-provider-retry-btn"
8612
+ class="btn-ghost btn-compact hidden"
8613
+ type="button"
8614
+ onClick=${call(
8615
+ "retryHostPreferencesProviderSection"
8616
+ )}
8617
+ >
8618
+ Retry
8619
+ </button>
8499
8620
  </div>
8500
8621
  </div>
8501
8622
  <label
@@ -8659,6 +8780,13 @@
8659
8780
  data-session-setup-web-shared="web.enabled"
8660
8781
  />
8661
8782
  </label>
8783
+ <div
8784
+ id="create-website-authority-hint"
8785
+ class="field-hint field-warning"
8786
+ >
8787
+ Website is available only for agents in the Browser
8788
+ Root.
8789
+ </div>
8662
8790
  </div>
8663
8791
  <label
8664
8792
  class="kv-label"
@@ -9109,6 +9237,168 @@
9109
9237
  </div>
9110
9238
  </div>
9111
9239
  </section>
9240
+ <section class="table-panel">
9241
+ <div class="table-title">
9242
+ <div class="table-title-main">
9243
+ <strong id="host-voice-input-title">
9244
+ Voice Input
9245
+ </strong>
9246
+ <div class="table-title-copy">
9247
+ Record from the Chat composer and append a GPT-4o
9248
+ transcript for review before sending.
9249
+ </div>
9250
+ </div>
9251
+ <span class="pill" id="host-voice-input-state-pill">
9252
+ Disabled
9253
+ </span>
9254
+ </div>
9255
+ <div class="kv-table">
9256
+ <div class="kv-label">OpenAI API Token</div>
9257
+ <div class="kv-value">
9258
+ <div class="value-stack">
9259
+ <div class="control-cell">
9260
+ <input
9261
+ id="host-voice-input-token"
9262
+ class="voice-input-secret-input"
9263
+ type="text"
9264
+ inputmode="text"
9265
+ autocomplete="off"
9266
+ autocapitalize="none"
9267
+ autocorrect="off"
9268
+ spellcheck=${false}
9269
+ placeholder="Enter a new token"
9270
+ />
9271
+ <button
9272
+ class="btn-primary btn-compact"
9273
+ id="host-voice-input-discard-token-draft-btn"
9274
+ type="button"
9275
+ onClick=${call(
9276
+ "discardHostVoiceInputTokenDraft"
9277
+ )}
9278
+ >
9279
+ Discard New Token
9280
+ </button>
9281
+ <button
9282
+ class="btn-primary btn-compact"
9283
+ id="host-voice-input-remove-saved-token-btn"
9284
+ type="button"
9285
+ onClick=${call(
9286
+ "removeHostVoiceInputSavedToken"
9287
+ )}
9288
+ >
9289
+ Remove Saved Token
9290
+ </button>
9291
+ </div>
9292
+ <div
9293
+ class="field-hint mono-path voice-input-token-status"
9294
+ id="host-voice-input-token-status"
9295
+ >
9296
+ No token saved.
9297
+ </div>
9298
+ <div
9299
+ class="field-hint voice-input-warning"
9300
+ id="host-voice-input-client-warning"
9301
+ ></div>
9302
+ </div>
9303
+ </div>
9304
+ <div class="kv-label">Sample</div>
9305
+ <div class="kv-value">
9306
+ <div class="value-stack">
9307
+ <audio
9308
+ class="voice-input-sample-player"
9309
+ id="host-voice-input-sample-player"
9310
+ controls
9311
+ preload="metadata"
9312
+ ></audio>
9313
+ <div
9314
+ class="field-hint"
9315
+ id="host-voice-input-sample-status"
9316
+ ></div>
9317
+ <div class="field-hint">
9318
+ <strong>Expected transcript:</strong>
9319
+ <span
9320
+ class="voice-input-sample-expected"
9321
+ id="host-voice-input-sample-expected"
9322
+ >
9323
+ How much money can I withdraw per day from the
9324
+ ATM?
9325
+ </span>
9326
+ </div>
9327
+ <div class="toolbar">
9328
+ <button
9329
+ class="btn-primary"
9330
+ id="host-voice-input-test-btn"
9331
+ type="button"
9332
+ onClick=${call("testHostVoiceInput")}
9333
+ >
9334
+ Test &amp; Save Token
9335
+ </button>
9336
+ </div>
9337
+ <div
9338
+ class="field-hint"
9339
+ id="host-voice-input-test-status"
9340
+ ></div>
9341
+ <div
9342
+ class="mono-path"
9343
+ id="host-voice-input-test-transcript"
9344
+ ></div>
9345
+ </div>
9346
+ </div>
9347
+ <div class="kv-label">Availability</div>
9348
+ <div class="kv-value settings-checkbox-row-value">
9349
+ <div class="toggle-row">
9350
+ <span>
9351
+ <strong>Enable Voice Input</strong>
9352
+ <div
9353
+ class="field-hint"
9354
+ id="host-voice-input-availability"
9355
+ >
9356
+ Test and save a valid OpenAI API token first.
9357
+ </div>
9358
+ </span>
9359
+ <button
9360
+ class="scheduler-row-toggle host-voice-input-toggle"
9361
+ id="host-voice-input-enabled-toggle"
9362
+ type="button"
9363
+ role="switch"
9364
+ aria-checked="false"
9365
+ aria-disabled="true"
9366
+ aria-label="Enable Voice Input"
9367
+ onClick=${call("toggleHostVoiceInputEnabled")}
9368
+ >
9369
+ <span aria-hidden="true"></span>
9370
+ </button>
9371
+ </div>
9372
+ <div
9373
+ class="field-hint voice-input-warning"
9374
+ id="host-voice-input-enable-warning"
9375
+ >
9376
+ Test and save a valid OpenAI API token before
9377
+ enabling Voice Input.
9378
+ </div>
9379
+ <div
9380
+ class="voice-input-usage-summary hidden"
9381
+ id="host-voice-input-usage-summary"
9382
+ >
9383
+ <div id="host-voice-input-usage-24h"></div>
9384
+ <div id="host-voice-input-usage-7d"></div>
9385
+ <div id="host-voice-input-usage-30d"></div>
9386
+ <div
9387
+ class="field-hint voice-input-warning hidden"
9388
+ id="host-voice-input-usage-error"
9389
+ ></div>
9390
+ <button
9391
+ class="btn-ghost voice-input-usage-detail-link"
9392
+ id="host-voice-input-usage-detail-btn"
9393
+ type="button"
9394
+ onClick=${call("openHostVoiceInputUsageDetail")}
9395
+ >
9396
+ View Usage Stats Detail
9397
+ </button>
9398
+ </div>
9399
+ </div>
9400
+ </div>
9401
+ </section>
9112
9402
  <section class="table-panel">
9113
9403
  <div class="table-title">
9114
9404
  <div class="table-title-main">
@@ -9785,6 +10075,55 @@
9785
10075
  `;
9786
10076
  }
9787
10077
 
10078
+ function VoiceInputUsageView() {
10079
+ return html`
10080
+ <section
10081
+ id="voice-input-usage-view"
10082
+ class="view view-page-scroll"
10083
+ data-oysterun-voice-input-usage-page="true"
10084
+ >
10085
+ <div class="page-screen">
10086
+ <section class="panel page-form-panel voice-input-usage-page">
10087
+ <div
10088
+ class="field-hint"
10089
+ id="voice-input-usage-generated-at"
10090
+ ></div>
10091
+ <div
10092
+ class="error-box hidden"
10093
+ id="voice-input-usage-error"
10094
+ ></div>
10095
+ <div
10096
+ class="voice-input-usage-table-wrap"
10097
+ id="voice-input-usage-table-wrap"
10098
+ >
10099
+ <table class="voice-input-usage-table">
10100
+ <thead>
10101
+ <tr>
10102
+ <th scope="col">Metric</th>
10103
+ <th scope="col">24 hours</th>
10104
+ <th scope="col">7 days</th>
10105
+ <th scope="col">30 days</th>
10106
+ </tr>
10107
+ </thead>
10108
+ <tbody id="voice-input-usage-table-body"></tbody>
10109
+ </table>
10110
+ </div>
10111
+ <div class="toolbar">
10112
+ <button
10113
+ class="btn-primary hidden"
10114
+ id="voice-input-usage-retry-btn"
10115
+ type="button"
10116
+ onClick=${call("retryHostVoiceInputUsage")}
10117
+ >
10118
+ Retry
10119
+ </button>
10120
+ </div>
10121
+ </section>
10122
+ </div>
10123
+ </section>
10124
+ `;
10125
+ }
10126
+
9788
10127
  function ChangePasswordView() {
9789
10128
  return html`
9790
10129
  <section id="change-password-view" class="view view-page-scroll">
@@ -10573,19 +10912,6 @@ Terminal output will appear here.</pre
10573
10912
  class="session-profile-action-stack"
10574
10913
  data-session-profile-action-block="session_identity"
10575
10914
  >
10576
- <label
10577
- class="toggle-row"
10578
- data-session-profile-identity-update-config-row="session_identity"
10579
- >
10580
- <span>
10581
- <strong>Update .oysterun/config.json</strong>
10582
- </span>
10583
- <input
10584
- id="agent-profile-identity-update-config"
10585
- type="checkbox"
10586
- data-session-profile-update-config="session_identity"
10587
- />
10588
- </label>
10589
10915
  <button
10590
10916
  class="btn-secondary session-profile-save-row"
10591
10917
  id="agent-profile-identity-save-btn"
@@ -11399,6 +11725,7 @@ Waiting for Claude login output…</pre
11399
11725
  <${SessionLoopView} />
11400
11726
  <${SessionSetupView} />
11401
11727
  <${HostPreferencesView} />
11728
+ <${VoiceInputUsageView} />
11402
11729
  <${ChangePasswordView} />
11403
11730
  <${TerminalView} />
11404
11731
  <${FolderPickerView} />
@@ -13325,6 +13652,12 @@ Waiting for Claude login output…</pre
13325
13652
  let lastCreateStartActivationAt = 0;
13326
13653
  let createFormPreferenceVersion = 0;
13327
13654
  let latestCreateFolderConfigRequestId = 0;
13655
+ let createWebsiteAuthorityState = {
13656
+ folderPath: "",
13657
+ projectId: "",
13658
+ eligible: false,
13659
+ reason: "Website is available only for agents in the Browser Root.",
13660
+ };
13328
13661
  let createFormDraftRouteKey = "";
13329
13662
  let createSubmitState = {
13330
13663
  inFlight: false,
@@ -13375,8 +13708,37 @@ Waiting for Claude login output…</pre
13375
13708
  preview: null,
13376
13709
  roots: {},
13377
13710
  };
13711
+ const PROVIDER_CATALOG_STATUS_UNKNOWN = "unknown";
13712
+ const PROVIDER_CATALOG_STATUS_PENDING = "pending";
13713
+ const PROVIDER_CATALOG_STATUS_READY = "ready";
13714
+ const PROVIDER_CATALOG_STATUS_UNAVAILABLE = "unavailable";
13715
+ const PROVIDER_CATALOG_STATUS_REQUEST_FAILED = "request_failed";
13716
+
13717
+ function getProviderCatalogStatusForProviders(providers) {
13718
+ return providers.some(
13719
+ (entry) =>
13720
+ entry.commandAvailable === true && entry.runtimeSupported === true
13721
+ )
13722
+ ? PROVIDER_CATALOG_STATUS_READY
13723
+ : PROVIDER_CATALOG_STATUS_UNAVAILABLE;
13724
+ }
13725
+
13726
+ function isProviderCatalogInitialPending() {
13727
+ return (
13728
+ providerCatalogState.status === PROVIDER_CATALOG_STATUS_UNKNOWN ||
13729
+ providerCatalogState.status === PROVIDER_CATALOG_STATUS_PENDING
13730
+ );
13731
+ }
13732
+
13733
+ function isProviderCatalogRequestFailed() {
13734
+ return (
13735
+ providerCatalogState.status === PROVIDER_CATALOG_STATUS_REQUEST_FAILED
13736
+ );
13737
+ }
13738
+
13378
13739
  function createProviderCatalogState() {
13379
13740
  return {
13741
+ status: PROVIDER_CATALOG_STATUS_UNKNOWN,
13380
13742
  loaded: false,
13381
13743
  loading: false,
13382
13744
  error: "",
@@ -13446,6 +13808,7 @@ Waiting for Claude login output…</pre
13446
13808
  notificationSoundWebEnabled = true,
13447
13809
  notificationSoundAppEnabled = true,
13448
13810
  dailyTelemetryEnabled = false,
13811
+ voiceInputEnabled = false,
13449
13812
  defaultWebsiteAccess = HOST_DEFAULT_SESSION_DEFAULTS.web.access,
13450
13813
  defaultNotificationsEnabled = HOST_DEFAULT_SESSION_DEFAULTS
13451
13814
  .notifications.enabled,
@@ -13472,6 +13835,7 @@ Waiting for Claude login output…</pre
13472
13835
  notificationSoundWebEnabled,
13473
13836
  notificationSoundAppEnabled,
13474
13837
  dailyTelemetryEnabled,
13838
+ voiceInputEnabled,
13475
13839
  defaultWebsiteAccess,
13476
13840
  defaultNotificationsEnabled,
13477
13841
  defaultRuntimeCapabilities: normalizeRuntimeCapabilityState(
@@ -13503,6 +13867,34 @@ Waiting for Claude login output…</pre
13503
13867
  dailyTelemetryEnabled: false,
13504
13868
  dailyTelemetryConsentRecordedAt: "",
13505
13869
  dailyTelemetryHostIdPresent: false,
13870
+ voiceInputEnabled: false,
13871
+ voiceInputAvailable: false,
13872
+ voiceInputCanEnable: false,
13873
+ voiceInputTokenConfigured: false,
13874
+ voiceInputTokenValidated: false,
13875
+ voiceInputTokenMasked: "",
13876
+ voiceInputTokenDraft: "",
13877
+ voiceInputValidatedAt: "",
13878
+ voiceInputValidatedModel: "",
13879
+ voiceInputValidatedSampleExact: null,
13880
+ voiceInputModel: "gpt-4o-transcribe",
13881
+ voiceInputSampleExpected:
13882
+ "How much money can I withdraw per day from the ATM?",
13883
+ voiceInputSampleLoading: false,
13884
+ voiceInputSampleLoaded: false,
13885
+ voiceInputSampleError: "",
13886
+ voiceInputTestRunning: false,
13887
+ voiceInputTokenClearing: false,
13888
+ voiceInputTestStatus: "",
13889
+ voiceInputTestError: "",
13890
+ voiceInputTestTranscript: "",
13891
+ voiceInputTestLatencyMs: null,
13892
+ voiceInputNativeAppVersion: "",
13893
+ voiceInputNativeAppVersionLoaded: false,
13894
+ voiceInputUsage: null,
13895
+ voiceInputUsageLoading: false,
13896
+ voiceInputUsageLoaded: false,
13897
+ voiceInputUsageError: "",
13506
13898
  defaultWebsiteAccess: HOST_DEFAULT_SESSION_DEFAULTS.web.access,
13507
13899
  defaultNotificationsEnabled:
13508
13900
  HOST_DEFAULT_SESSION_DEFAULTS.notifications.enabled,
@@ -13576,6 +13968,7 @@ Waiting for Claude login output…</pre
13576
13968
  };
13577
13969
  }
13578
13970
  let providerCatalogState = createProviderCatalogState();
13971
+ let providerCatalogLoadPromise = null;
13579
13972
  let providerModelRefreshState = {
13580
13973
  provider: "",
13581
13974
  refreshing: false,
@@ -13601,6 +13994,10 @@ Waiting for Claude login output…</pre
13601
13994
  let browserSurfaceState = createBrowserSurfaceState();
13602
13995
  let browserSurfaceLaunchRequestId = 0;
13603
13996
  let hostPreferencesState = createHostPreferencesState();
13997
+ let hostVoiceInputSampleObjectUrl = "";
13998
+ let hostVoiceInputSampleLoadPromise = null;
13999
+ let hostVoiceInputNativeAppVersionLoadPromise = null;
14000
+ let hostVoiceInputUsageLoadPromise = null;
13604
14001
  let hostRestartHandoffTimer = null;
13605
14002
  let hostRestartLoginGatePollTimer = null;
13606
14003
  let hostRestartLoginGatePollGeneration = 0;
@@ -14026,6 +14423,7 @@ Waiting for Claude login output…</pre
14026
14423
  $("host-notification-app-sound-toggle")?.checked !== false,
14027
14424
  dailyTelemetryEnabled:
14028
14425
  $("host-daily-telemetry-toggle")?.checked === true,
14426
+ voiceInputEnabled: getHostVoiceInputEnabledControlValue(),
14029
14427
  defaultWebsiteAccess: hostPreferencesState.defaultWebsiteAccess,
14030
14428
  defaultNotificationsEnabled:
14031
14429
  $("host-default-notifications-enabled")?.checked !== false,
@@ -14080,6 +14478,7 @@ Waiting for Claude login output…</pre
14080
14478
  notificationSoundWebEnabled: draft.notificationSoundWebEnabled,
14081
14479
  notificationSoundAppEnabled: draft.notificationSoundAppEnabled,
14082
14480
  dailyTelemetryEnabled: draft.dailyTelemetryEnabled,
14481
+ voiceInputEnabled: draft.voiceInputEnabled,
14083
14482
  defaultWebsiteAccess: draft.defaultWebsiteAccess,
14084
14483
  defaultNotificationsEnabled: draft.defaultNotificationsEnabled,
14085
14484
  defaultRuntimeCapabilities: normalizeRuntimeCapabilityState(
@@ -14112,6 +14511,7 @@ Waiting for Claude login output…</pre
14112
14511
  a.notificationSoundWebEnabled === b.notificationSoundWebEnabled &&
14113
14512
  a.notificationSoundAppEnabled === b.notificationSoundAppEnabled &&
14114
14513
  a.dailyTelemetryEnabled === b.dailyTelemetryEnabled &&
14514
+ a.voiceInputEnabled === b.voiceInputEnabled &&
14115
14515
  a.defaultWebsiteAccess === b.defaultWebsiteAccess &&
14116
14516
  a.defaultNotificationsEnabled === b.defaultNotificationsEnabled &&
14117
14517
  JSON.stringify(a.defaultRuntimeCapabilities) ===
@@ -14171,6 +14571,7 @@ Waiting for Claude login output…</pre
14171
14571
  const normalized = normalizeClientRoute(route);
14172
14572
  return (
14173
14573
  normalized.screen === "host-preferences" ||
14574
+ normalized.screen === "voice-input-usage" ||
14174
14575
  (normalized.screen === "folder-picker" &&
14175
14576
  normalized.targetInputId === HOST_PREFERENCES_FOLDER_PICKER_TARGET)
14176
14577
  );
@@ -14186,6 +14587,8 @@ Waiting for Claude login output…</pre
14186
14587
  defaultBrowsePath: savedDraft.defaultBrowsePath,
14187
14588
  notificationSoundWebEnabled: savedDraft.notificationSoundWebEnabled,
14188
14589
  notificationSoundAppEnabled: savedDraft.notificationSoundAppEnabled,
14590
+ dailyTelemetryEnabled: savedDraft.dailyTelemetryEnabled,
14591
+ voiceInputEnabled: savedDraft.voiceInputEnabled,
14189
14592
  defaultWebsiteAccess: savedDraft.defaultWebsiteAccess,
14190
14593
  defaultNotificationsEnabled: savedDraft.defaultNotificationsEnabled,
14191
14594
  defaultRuntimeCapabilities: normalizeRuntimeCapabilityState(
@@ -14221,6 +14624,7 @@ Waiting for Claude login output…</pre
14221
14624
  const normalized = normalizeClientRoute(route);
14222
14625
  return (
14223
14626
  normalized.screen === "host-preferences" ||
14627
+ normalized.screen === "voice-input-usage" ||
14224
14628
  normalized.screen === "agent-profile"
14225
14629
  );
14226
14630
  }
@@ -14229,6 +14633,14 @@ Waiting for Claude login output…</pre
14229
14633
  const current = normalizeClientRoute(currentRoute);
14230
14634
  const next = normalizeClientRoute(nextRoute);
14231
14635
  if (current.screen === next.screen) return true;
14636
+ if (
14637
+ (current.screen === "host-preferences" &&
14638
+ next.screen === "voice-input-usage") ||
14639
+ (current.screen === "voice-input-usage" &&
14640
+ next.screen === "host-preferences")
14641
+ ) {
14642
+ return true;
14643
+ }
14232
14644
  if (
14233
14645
  current.screen === "host-preferences" &&
14234
14646
  next.screen === "folder-picker" &&
@@ -14340,11 +14752,8 @@ Waiting for Claude login output…</pre
14340
14752
  liveSession.sessionName || liveSession.session_name || ""
14341
14753
  ).trim();
14342
14754
  const draftName = String(agentProfileRenameState.draft || "").trim();
14343
- const updateConfig =
14344
- $("agent-profile-identity-update-config")?.checked === true;
14345
14755
  return (
14346
14756
  agentProfileRenameState.dirty === true ||
14347
- updateConfig === true ||
14348
14757
  (Boolean(draftName) && draftName !== currentName)
14349
14758
  );
14350
14759
  }
@@ -14384,7 +14793,10 @@ Waiting for Claude login output…</pre
14384
14793
 
14385
14794
  function getP75DirtyFormState() {
14386
14795
  const current = normalizeClientRoute(getCurrentRoute());
14387
- if (current.screen === "host-preferences") {
14796
+ if (
14797
+ current.screen === "host-preferences" ||
14798
+ current.screen === "voice-input-usage"
14799
+ ) {
14388
14800
  return hasUnsavedHostPreferencesChanges()
14389
14801
  ? {
14390
14802
  screen: "host-preferences",
@@ -15124,6 +15536,9 @@ Waiting for Claude login output…</pre
15124
15536
  // topbar/back handlers execute a declared route map instead of browser history.
15125
15537
  function getFallbackRoute(route = currentRoute) {
15126
15538
  const normalized = normalizeClientRoute(route);
15539
+ if (normalized.screen === "voice-input-usage") {
15540
+ return buildSettingsRoute();
15541
+ }
15127
15542
  if (normalized.screen === "file-preview") {
15128
15543
  // Source-aware File Preview routes return to their validated source first.
15129
15544
  // Direct or invalid routes still fall back to the containing Explorer.
@@ -15184,6 +15599,7 @@ Waiting for Claude login output…</pre
15184
15599
  const normalized = normalizeClientRoute(route);
15185
15600
  return (
15186
15601
  normalized.screen === "chat" ||
15602
+ normalized.screen === "voice-input-usage" ||
15187
15603
  normalized.screen === "file-preview" ||
15188
15604
  (normalized.screen === "mail" && Boolean(normalized.mailId)) ||
15189
15605
  (normalized.screen === "folder-picker" &&
@@ -19213,12 +19629,13 @@ Waiting for Claude login output…</pre
19213
19629
  entry,
19214
19630
  providerId,
19215
19631
  commandAvailable,
19632
+ authSupported,
19216
19633
  canStartLogin
19217
19634
  ) {
19218
19635
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
19219
19636
  return {
19220
19637
  provider: providerId,
19221
- supported: false,
19638
+ supported: authSupported,
19222
19639
  canStartLogin,
19223
19640
  state: "unknown",
19224
19641
  message: "",
@@ -19316,6 +19733,7 @@ Waiting for Claude login output…</pre
19316
19733
  entry.auth_status,
19317
19734
  providerId,
19318
19735
  entry.command_available === true,
19736
+ entry.auth_supported === true,
19319
19737
  entry.auth_start_supported === true
19320
19738
  ),
19321
19739
  modelCatalogStatus:
@@ -19426,6 +19844,20 @@ Waiting for Claude login output…</pre
19426
19844
  return getProviderCatalogEntry(providerId)?.commandAvailable === true;
19427
19845
  }
19428
19846
 
19847
+ function describeProviderCommandAvailability(providerId) {
19848
+ if (isProviderCatalogInitialPending()) {
19849
+ return "Checking availability on this Host...";
19850
+ }
19851
+ if (isProviderCatalogRequestFailed()) {
19852
+ return "Could not load availability. Retry the provider check.";
19853
+ }
19854
+ return isProviderAvailable(providerId)
19855
+ ? "Available on this Host."
19856
+ : `Unavailable on this Host. Blank disables ${getProviderDisplayLabel(
19857
+ providerId
19858
+ )} explicitly.`;
19859
+ }
19860
+
19429
19861
  function getProviderAuthStatus(providerId) {
19430
19862
  return getProviderCatalogEntry(providerId)?.authStatus || null;
19431
19863
  }
@@ -19441,6 +19873,7 @@ Waiting for Claude login output…</pre
19441
19873
  nextStatus,
19442
19874
  providerId,
19443
19875
  entry.commandAvailable === true,
19876
+ entry.authSupported === true,
19444
19877
  entry.authStartSupported === true
19445
19878
  ),
19446
19879
  }
@@ -19478,7 +19911,12 @@ Waiting for Claude login output…</pre
19478
19911
  function buildDefaultProviderWarning(
19479
19912
  savedProvider = hostPreferencesState.defaultProvider
19480
19913
  ) {
19481
- if (!savedProvider || isProviderAvailable(savedProvider)) {
19914
+ if (
19915
+ !savedProvider ||
19916
+ isProviderCatalogInitialPending() ||
19917
+ isProviderCatalogRequestFailed() ||
19918
+ isProviderAvailable(savedProvider)
19919
+ ) {
19482
19920
  return "";
19483
19921
  }
19484
19922
  const effectiveProvider =
@@ -21160,6 +21598,10 @@ Waiting for Claude login output…</pre
21160
21598
  return normalizeClientRoute({ screen: "host-preferences" });
21161
21599
  }
21162
21600
 
21601
+ function buildVoiceInputUsageRoute() {
21602
+ return normalizeClientRoute({ screen: "voice-input-usage" });
21603
+ }
21604
+
21163
21605
  function buildChangePasswordRoute() {
21164
21606
  return normalizeClientRoute({ screen: "change-password" });
21165
21607
  }
@@ -21424,6 +21866,9 @@ Waiting for Claude login output…</pre
21424
21866
  if (screen === "host-preferences") {
21425
21867
  return { screen: "host-preferences", warning };
21426
21868
  }
21869
+ if (screen === "voice-input-usage") {
21870
+ return { screen: "voice-input-usage", warning };
21871
+ }
21427
21872
  if (screen === "change-password") {
21428
21873
  return { screen: "change-password", warning };
21429
21874
  }
@@ -21655,6 +22100,9 @@ Waiting for Claude login output…</pre
21655
22100
  if (pathname === "/app/settings") {
21656
22101
  return buildSettingsRoute();
21657
22102
  }
22103
+ if (pathname === "/app/host-preferences/voice-input-usage") {
22104
+ return buildVoiceInputUsageRoute();
22105
+ }
21658
22106
  if (pathname === "/app/change-password") {
21659
22107
  return buildChangePasswordRoute();
21660
22108
  }
@@ -21782,6 +22230,8 @@ Waiting for Claude login output…</pre
21782
22230
  }
21783
22231
  } else if (normalized.screen === "host-preferences") {
21784
22232
  url.pathname = "/app/settings";
22233
+ } else if (normalized.screen === "voice-input-usage") {
22234
+ url.pathname = "/app/host-preferences/voice-input-usage";
21785
22235
  } else if (normalized.screen === "change-password") {
21786
22236
  url.pathname = "/app/change-password";
21787
22237
  } else if (normalized.screen === "terminal") {
@@ -22013,6 +22463,12 @@ Waiting for Claude login output…</pre
22013
22463
  }
22014
22464
  } else if (normalized.screen === "host-preferences") {
22015
22465
  renderHostPreferences();
22466
+ if (hostPreferencesState.voiceInputEnabled) {
22467
+ void loadHostVoiceInputUsage({ force: true });
22468
+ }
22469
+ } else if (normalized.screen === "voice-input-usage") {
22470
+ renderHostVoiceInputUsageDetail();
22471
+ await loadHostVoiceInputUsage();
22016
22472
  } else if (normalized.screen === "change-password") {
22017
22473
  renderChangePasswordView();
22018
22474
  } else if (normalized.screen === "terminal") {
@@ -24974,6 +25430,7 @@ Waiting for Claude login output…</pre
24974
25430
  explorer: "file_explorer",
24975
25431
  "session-setup": "session_setup",
24976
25432
  "host-preferences": "host_preferences",
25433
+ "voice-input-usage": "host_preferences",
24977
25434
  terminal: "terminal",
24978
25435
  "folder-picker": "file_explorer",
24979
25436
  "file-preview": "file_preview",
@@ -26803,6 +27260,7 @@ Waiting for Claude login output…</pre
26803
27260
  function renderAgentProfileWebsiteAccessRow(liveSession) {
26804
27261
  syncAgentProfileWebsiteAccessSession(liveSession);
26805
27262
  const website = getLiveSessionWebsite(liveSession);
27263
+ const websiteAuthorized = website?.browser_root_authorized === true;
26806
27264
  const enabled = website?.enabled === true;
26807
27265
  const access = normalizeAgentWebAccess(website?.access);
26808
27266
  const websitePassword = getOwnerVisibleWebsitePassword(website);
@@ -26836,16 +27294,27 @@ Waiting for Claude login output…</pre
26836
27294
  type="checkbox"
26837
27295
  data-session-profile-web-enabled="web.enabled"
26838
27296
  ${enabled ? "checked" : ""}
26839
- ${saving ? "disabled" : ""}
27297
+ ${saving || !websiteAuthorized ? "disabled" : ""}
26840
27298
  />
26841
27299
  </label>
27300
+ ${
27301
+ websiteAuthorized
27302
+ ? ""
27303
+ : `<div
27304
+ class="field-hint field-warning"
27305
+ data-session-profile-website-authority="outside_browser_root"
27306
+ >${esc(
27307
+ website?.reason_message ||
27308
+ "Website is available only for agents in the Browser Root."
27309
+ )}</div>`
27310
+ }
26842
27311
  <label class="field compact-field" for="agent-profile-website-access">
26843
27312
  <span>Access</span>
26844
27313
  <select
26845
27314
  id="agent-profile-website-access"
26846
27315
  data-session-profile-web-access="web.access"
26847
27316
  onchange="window.syncAgentProfileWebsitePasswordMode && window.syncAgentProfileWebsitePasswordMode()"
26848
- ${saving ? "disabled" : ""}
27317
+ ${saving || !websiteAuthorized ? "disabled" : ""}
26849
27318
  >${renderAgentWebAccessOptions(access)}</select>
26850
27319
  </label>
26851
27320
  <label
@@ -26863,7 +27332,11 @@ Waiting for Claude login output…</pre
26863
27332
  value="${esc(websitePassword)}"
26864
27333
  data-session-profile-web-local="web.password"
26865
27334
  oninput="window.syncAgentProfileWebsitePasswordMode && window.syncAgentProfileWebsitePasswordMode()"
26866
- ${access === "password" && !saving ? "" : "disabled"}
27335
+ ${
27336
+ access === "password" && !saving && websiteAuthorized
27337
+ ? ""
27338
+ : "disabled"
27339
+ }
26867
27340
  />
26868
27341
  <div
26869
27342
  id="agent-profile-website-password-status"
@@ -26895,7 +27368,7 @@ Waiting for Claude login output…</pre
26895
27368
  data-session-profile-save="website"
26896
27369
  data-session-profile-web-access-save="explicit"
26897
27370
  onclick="window.saveAgentProfileWebsiteAccess && window.saveAgentProfileWebsiteAccess()"
26898
- ${saving ? "disabled" : ""}
27371
+ ${saving || !websiteAuthorized ? "disabled" : ""}
26899
27372
  >${saving ? "Saving..." : "Save"}</button>
26900
27373
  </div>
26901
27374
  ${
@@ -26914,6 +27387,9 @@ Waiting for Claude login output…</pre
26914
27387
  }
26915
27388
 
26916
27389
  function syncAgentProfileWebsitePasswordMode() {
27390
+ const liveSession = getSelectedLiveSession();
27391
+ const websiteAuthorized =
27392
+ getLiveSessionWebsite(liveSession)?.browser_root_authorized === true;
26917
27393
  const access = normalizeAgentWebAccess(
26918
27394
  $("agent-profile-website-access")?.value
26919
27395
  );
@@ -26923,13 +27399,12 @@ Waiting for Claude login output…</pre
26923
27399
  const passwordMode = access === "password";
26924
27400
  if (row) row.classList.remove("hidden");
26925
27401
  if (input) {
26926
- input.disabled = !passwordMode;
27402
+ input.disabled = !websiteAuthorized || !passwordMode;
26927
27403
  }
26928
27404
  if (status) {
26929
- status.textContent = formatOwnerWebsitePasswordHint(
26930
- access,
26931
- input?.value || ""
26932
- );
27405
+ status.textContent = websiteAuthorized
27406
+ ? formatOwnerWebsitePasswordHint(access, input?.value || "")
27407
+ : "Website is available only for agents in the Browser Root.";
26933
27408
  }
26934
27409
  }
26935
27410
 
@@ -26938,6 +27413,19 @@ Waiting for Claude login output…</pre
26938
27413
  if (!liveSession?.sessionId) return;
26939
27414
  syncAgentProfileWebsiteAccessSession(liveSession);
26940
27415
  if (agentProfileWebsiteAccessState.saving === true) return;
27416
+ const previousWebsite = getLiveSessionWebsite(liveSession);
27417
+ if (previousWebsite?.browser_root_authorized !== true) {
27418
+ agentProfileWebsiteAccessState = {
27419
+ ...agentProfileWebsiteAccessState,
27420
+ saving: false,
27421
+ error:
27422
+ previousWebsite?.reason_message ||
27423
+ "Website is available only for agents in the Browser Root.",
27424
+ status: "",
27425
+ };
27426
+ renderAgentProfileView();
27427
+ return;
27428
+ }
26941
27429
  const select = $("agent-profile-website-access");
26942
27430
  const updateConfig = true;
26943
27431
  const nextEnabled =
@@ -26947,7 +27435,6 @@ Waiting for Claude login output…</pre
26947
27435
  nextAccess === "password"
26948
27436
  ? String($("agent-profile-website-password")?.value || "").trim()
26949
27437
  : "";
26950
- const previousWebsite = getLiveSessionWebsite(liveSession);
26951
27438
  if (nextAccess === "password" && !nextWebsitePassword) {
26952
27439
  agentProfileWebsiteAccessState = {
26953
27440
  ...agentProfileWebsiteAccessState,
@@ -29566,6 +30053,12 @@ Waiting for Claude login output…</pre
29566
30053
  subtitle: "",
29567
30054
  };
29568
30055
  }
30056
+ if (screen === "voice-input-usage") {
30057
+ return {
30058
+ title: "Voice Input Usage",
30059
+ subtitle: "",
30060
+ };
30061
+ }
29569
30062
  if (screen === "change-password") {
29570
30063
  return {
29571
30064
  title: "Change Password",
@@ -29926,6 +30419,10 @@ Waiting for Claude login output…</pre
29926
30419
  "active",
29927
30420
  screen === "host-preferences"
29928
30421
  );
30422
+ $("voice-input-usage-view").classList.toggle(
30423
+ "active",
30424
+ screen === "voice-input-usage"
30425
+ );
29929
30426
  $("change-password-view").classList.toggle(
29930
30427
  "active",
29931
30428
  screen === "change-password"
@@ -29979,6 +30476,14 @@ Waiting for Claude login output…</pre
29979
30476
  }
29980
30477
  if (screen === "host-preferences") {
29981
30478
  renderHostPreferences();
30479
+ } else if (
30480
+ hostPreferencesState.voiceInputSampleLoaded ||
30481
+ hostVoiceInputSampleObjectUrl
30482
+ ) {
30483
+ releaseHostVoiceInputSampleAudio();
30484
+ }
30485
+ if (screen === "voice-input-usage") {
30486
+ renderHostVoiceInputUsageDetail();
29982
30487
  }
29983
30488
  if (screen === "change-password") {
29984
30489
  renderChangePasswordView();
@@ -30008,6 +30513,10 @@ Waiting for Claude login output…</pre
30008
30513
  void navigateToRoute(buildSettingsRoute());
30009
30514
  return;
30010
30515
  }
30516
+ if (entry.screen === "voice-input-usage") {
30517
+ void navigateToRoute(buildVoiceInputUsageRoute());
30518
+ return;
30519
+ }
30011
30520
  if (entry.screen === "change-password") {
30012
30521
  void navigateToRoute(buildChangePasswordRoute());
30013
30522
  return;
@@ -34274,6 +34783,11 @@ Waiting for Claude login output…</pre
34274
34783
  const notificationSoundAppEnabled =
34275
34784
  data.notification_sound_app_enabled !== false;
34276
34785
  const dailyTelemetryEnabled = data.daily_telemetry_enabled === true;
34786
+ const voiceInput =
34787
+ data.voice_input && typeof data.voice_input === "object"
34788
+ ? data.voice_input
34789
+ : {};
34790
+ const voiceInputEnabled = voiceInput.enabled === true;
34277
34791
  const debugShowCapabilityUi = data.debug_show_capability_ui === true;
34278
34792
  const showInterfaceStyleInSessionSetupProfile =
34279
34793
  data.show_interface_style_in_session_setup_profile === true;
@@ -34310,6 +34824,7 @@ Waiting for Claude login output…</pre
34310
34824
  notificationSoundWebEnabled,
34311
34825
  notificationSoundAppEnabled,
34312
34826
  dailyTelemetryEnabled,
34827
+ voiceInputEnabled,
34313
34828
  defaultWebsiteAccess: normalizeAgentWebAccess(webDefaults.access),
34314
34829
  defaultNotificationsEnabled: notificationDefaults.enabled !== false,
34315
34830
  defaultRuntimeCapabilities: runtimeCapabilityDefaults,
@@ -34366,6 +34881,42 @@ Waiting for Claude login output…</pre
34366
34881
  : "",
34367
34882
  dailyTelemetryHostIdPresent:
34368
34883
  data.daily_telemetry_host_id_present === true,
34884
+ voiceInputEnabled,
34885
+ voiceInputAvailable: voiceInput.available === true,
34886
+ voiceInputCanEnable: voiceInput.can_enable === true,
34887
+ voiceInputTokenConfigured: voiceInput.token_configured === true,
34888
+ voiceInputTokenValidated: voiceInput.token_validated === true,
34889
+ voiceInputTokenMasked:
34890
+ typeof voiceInput.token_masked === "string"
34891
+ ? voiceInput.token_masked
34892
+ : "",
34893
+ voiceInputTokenDraft: "",
34894
+ voiceInputValidatedAt:
34895
+ typeof voiceInput.validated_at === "string"
34896
+ ? voiceInput.validated_at
34897
+ : "",
34898
+ voiceInputValidatedModel:
34899
+ typeof voiceInput.validated_model === "string"
34900
+ ? voiceInput.validated_model
34901
+ : "",
34902
+ voiceInputValidatedSampleExact:
34903
+ typeof voiceInput.validated_sample_exact === "boolean"
34904
+ ? voiceInput.validated_sample_exact
34905
+ : null,
34906
+ voiceInputModel:
34907
+ typeof voiceInput.model === "string"
34908
+ ? voiceInput.model
34909
+ : "gpt-4o-transcribe",
34910
+ voiceInputSampleExpected:
34911
+ typeof voiceInput.sample_expected === "string"
34912
+ ? voiceInput.sample_expected
34913
+ : "How much money can I withdraw per day from the ATM?",
34914
+ voiceInputTestRunning: false,
34915
+ voiceInputTokenClearing: false,
34916
+ voiceInputTestStatus: "",
34917
+ voiceInputTestError: "",
34918
+ voiceInputTestTranscript: "",
34919
+ voiceInputTestLatencyMs: null,
34369
34920
  defaultWebsiteAccess: normalizeAgentWebAccess(webDefaults.access),
34370
34921
  defaultNotificationsEnabled: notificationDefaults.enabled !== false,
34371
34922
  defaultRuntimeCapabilities: runtimeCapabilityDefaults,
@@ -34455,17 +35006,6 @@ Waiting for Claude login output…</pre
34455
35006
  });
34456
35007
  syncSessionSetupInterfaceStyleVisibility();
34457
35008
  renderCreateTelegramFeatureVisibility();
34458
- if (!providerCatalogState.loaded) {
34459
- providerCatalogState = {
34460
- ...providerCatalogState,
34461
- providers: KNOWN_PROVIDER_ORDER.map((providerId) => ({
34462
- id: providerId,
34463
- label: getProviderDisplayLabel(providerId),
34464
- commandAvailable: false,
34465
- runtimeSupported: true,
34466
- })),
34467
- };
34468
- }
34469
35009
  renderHostPreferences();
34470
35010
  setCreateRuntime(getEffectiveCreateRuntime(createRuntime), {
34471
35011
  preserveSelection: true,
@@ -34477,11 +35017,18 @@ Waiting for Claude login output…</pre
34477
35017
  const normalizedProviders = Array.isArray(data?.providers)
34478
35018
  ? data.providers.map(normalizeProviderCatalogEntry).filter(Boolean)
34479
35019
  : [];
35020
+ const providers = normalizedProviders.map((entry) => {
35021
+ const currentEntry = getProviderCatalogEntry(entry.id);
35022
+ return currentEntry?.authStatus?.checkedAt
35023
+ ? { ...entry, authStatus: currentEntry.authStatus }
35024
+ : entry;
35025
+ });
34480
35026
  providerCatalogState = {
35027
+ status: getProviderCatalogStatusForProviders(providers),
34481
35028
  loaded: true,
34482
35029
  loading: false,
34483
35030
  error: "",
34484
- providers: normalizedProviders,
35031
+ providers,
34485
35032
  };
34486
35033
  hostPreferencesState = {
34487
35034
  ...hostPreferencesState,
@@ -34502,36 +35049,50 @@ Waiting for Claude login output…</pre
34502
35049
  });
34503
35050
  }
34504
35051
 
34505
- async function loadProviderCatalog(opts = {}) {
34506
- if (providerCatalogState.loading) return;
35052
+ function loadProviderCatalog(opts = {}) {
35053
+ if (providerCatalogLoadPromise) return providerCatalogLoadPromise;
34507
35054
  providerCatalogState = {
34508
35055
  ...providerCatalogState,
35056
+ status: providerCatalogState.loaded
35057
+ ? providerCatalogState.status
35058
+ : PROVIDER_CATALOG_STATUS_PENDING,
34509
35059
  loading: true,
34510
35060
  error: "",
34511
35061
  };
34512
35062
  renderHostPreferences();
34513
- try {
34514
- const data = await readDashboardProviderCatalog({
34515
- force: opts.force === true,
34516
- });
34517
- applyProviderCatalogData(data);
34518
- } catch (err) {
34519
- if (await handleHostPreferencesAuthFailure(err)) return;
34520
- providerCatalogState = {
34521
- ...providerCatalogState,
34522
- loading: false,
34523
- error: err.message || "Could not load provider status.",
34524
- };
34525
- renderHostPreferences();
34526
- if (opts.quiet !== true) {
34527
- showBanner(providerCatalogState.error, true);
35063
+ renderCreateRuntimeAvailability();
35064
+ providerCatalogLoadPromise = (async () => {
35065
+ try {
35066
+ const data = await readDashboardProviderCatalog({
35067
+ force: opts.force === true,
35068
+ });
35069
+ applyProviderCatalogData(data);
35070
+ return data;
35071
+ } catch (err) {
35072
+ if (await handleHostPreferencesAuthFailure(err)) return null;
35073
+ providerCatalogState = {
35074
+ ...providerCatalogState,
35075
+ status: providerCatalogState.loaded
35076
+ ? providerCatalogState.status
35077
+ : PROVIDER_CATALOG_STATUS_REQUEST_FAILED,
35078
+ loading: false,
35079
+ error: err.message || "Could not load provider availability.",
35080
+ };
35081
+ renderHostPreferences();
35082
+ renderCreateRuntimeAvailability();
35083
+ if (opts.quiet !== true) {
35084
+ showBanner(providerCatalogState.error, true);
35085
+ }
35086
+ return null;
35087
+ } finally {
35088
+ providerCatalogLoadPromise = null;
34528
35089
  }
34529
- }
35090
+ })();
35091
+ return providerCatalogLoadPromise;
34530
35092
  }
34531
35093
 
34532
35094
  function retryHostPreferencesProviderSection() {
34533
- if (providerCatalogState.loading) return;
34534
- void loadProviderCatalog({ force: true, quiet: true });
35095
+ return loadProviderCatalog({ force: true, quiet: true });
34535
35096
  }
34536
35097
 
34537
35098
  function clearProviderAuthJobPollTimer() {
@@ -34703,7 +35264,6 @@ Waiting for Claude login output…</pre
34703
35264
  ? response.provider.trim()
34704
35265
  : "claude";
34705
35266
  if (response.provider_auth) {
34706
- invalidateDashboardDataCaches({ providers: true });
34707
35267
  setProviderCatalogAuthStatus(providerId, response.provider_auth);
34708
35268
  }
34709
35269
  providerAuthModalState = {
@@ -34715,6 +35275,7 @@ Waiting for Claude login output…</pre
34715
35275
  response.provider_auth,
34716
35276
  providerId,
34717
35277
  getProviderCatalogEntry(providerId)?.commandAvailable === true,
35278
+ getProviderCatalogEntry(providerId)?.authSupported === true,
34718
35279
  getProviderCatalogEntry(providerId)?.authStartSupported === true
34719
35280
  )
34720
35281
  : getProviderAuthStatus(providerId),
@@ -34786,13 +35347,13 @@ Waiting for Claude login output…</pre
34786
35347
  "GET",
34787
35348
  `/provider-auth/status?provider=${encodeURIComponent(providerId)}`
34788
35349
  );
34789
- invalidateDashboardDataCaches({ providers: true });
34790
35350
  setProviderCatalogAuthStatus(providerId, data.provider_auth || null);
34791
35351
  if (providerAuthModalState.provider === providerId) {
34792
35352
  const normalizedStatus = normalizeProviderAuthStatus(
34793
35353
  data.provider_auth,
34794
35354
  providerId,
34795
35355
  getProviderCatalogEntry(providerId)?.commandAvailable === true,
35356
+ getProviderCatalogEntry(providerId)?.authSupported === true,
34796
35357
  getProviderCatalogEntry(providerId)?.authStartSupported === true
34797
35358
  );
34798
35359
  providerAuthModalState = {
@@ -34961,6 +35522,7 @@ Waiting for Claude login output…</pre
34961
35522
  data.provider_auth,
34962
35523
  providerId,
34963
35524
  getProviderCatalogEntry(providerId)?.commandAvailable === true,
35525
+ getProviderCatalogEntry(providerId)?.authSupported === true,
34964
35526
  getProviderCatalogEntry(providerId)?.authStartSupported === true
34965
35527
  ),
34966
35528
  job,
@@ -35646,7 +36208,11 @@ Waiting for Claude login output…</pre
35646
36208
  if (!availableProviderIds.length) {
35647
36209
  const option = document.createElement("option");
35648
36210
  option.value = "";
35649
- option.textContent = "No available providers";
36211
+ option.textContent = isProviderCatalogInitialPending()
36212
+ ? "Checking providers..."
36213
+ : isProviderCatalogRequestFailed()
36214
+ ? "Provider status unavailable"
36215
+ : "No available providers";
35650
36216
  option.disabled = true;
35651
36217
  option.selected = true;
35652
36218
  select.appendChild(option);
@@ -35695,6 +36261,8 @@ Waiting for Claude login output…</pre
35695
36261
  );
35696
36262
  const runtimeLocked =
35697
36263
  isResumeMode() && Boolean(pendingResumeSourceRuntime);
36264
+ const providerCatalogPending = isProviderCatalogInitialPending();
36265
+ const providerCatalogFailed = isProviderCatalogRequestFailed();
35698
36266
  setVisible("runtime-claude", claudeVisible);
35699
36267
  setVisible("runtime-codex", codexVisible);
35700
36268
  setVisible("runtime-debug-fixture", debugFixtureVisible);
@@ -35740,6 +36308,26 @@ Waiting for Claude login output…</pre
35740
36308
  }
35741
36309
  refreshCreateStartButtonEnabled();
35742
36310
  applyCreateSessionSetupProofAttributes();
36311
+ setVisible("create-provider-retry-btn", providerCatalogFailed);
36312
+ $("create-provider-retry-btn")?.toggleAttribute(
36313
+ "disabled",
36314
+ providerCatalogState.loading
36315
+ );
36316
+ if (providerCatalogPending || providerCatalogFailed) {
36317
+ if (modelSelect) {
36318
+ modelSelect.innerHTML = "";
36319
+ modelSelect.disabled = true;
36320
+ }
36321
+ $("claude-options").classList.add("hidden");
36322
+ $("codex-options").classList.add("hidden");
36323
+ if (runtimeHintEl) {
36324
+ runtimeHintEl.textContent = providerCatalogFailed
36325
+ ? "Could not load provider status. Retry the provider check."
36326
+ : "Checking providers...";
36327
+ }
36328
+ renderCreateProviderAuthCard();
36329
+ return;
36330
+ }
35743
36331
  if (!visibleProviderIds.length) {
35744
36332
  if (modelSelect) {
35745
36333
  modelSelect.innerHTML = "";
@@ -35894,6 +36482,711 @@ Waiting for Claude login output…</pre
35894
36482
  };
35895
36483
  }
35896
36484
 
36485
+ function getHostVoiceInputEnabledControlValue() {
36486
+ return (
36487
+ $("host-voice-input-enabled-toggle")?.getAttribute("aria-checked") ===
36488
+ "true"
36489
+ );
36490
+ }
36491
+
36492
+ function setHostVoiceInputEnabledControlValue(enabled) {
36493
+ const toggle = $("host-voice-input-enabled-toggle");
36494
+ if (!toggle) return;
36495
+ toggle.setAttribute("aria-checked", enabled ? "true" : "false");
36496
+ }
36497
+
36498
+ function isHostVoiceInputTokenCandidateTestable(value) {
36499
+ return typeof value === "string" && value.trim().length >= 10;
36500
+ }
36501
+
36502
+ const OYSTERUN_VOICE_INPUT_MINIMUM_NATIVE_APP_VERSION = "1.6.0";
36503
+
36504
+ function parseOysterunNumericVersion(value) {
36505
+ const match = String(value || "")
36506
+ .trim()
36507
+ .match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?$/);
36508
+ if (!match) return null;
36509
+ return [
36510
+ Number.parseInt(match[1], 10),
36511
+ Number.parseInt(match[2] || "0", 10),
36512
+ Number.parseInt(match[3] || "0", 10),
36513
+ ];
36514
+ }
36515
+
36516
+ function isOysterunVersionBefore(value, minimum) {
36517
+ const version = parseOysterunNumericVersion(value);
36518
+ const required = parseOysterunNumericVersion(minimum);
36519
+ if (!version || !required) return false;
36520
+ for (let index = 0; index < required.length; index += 1) {
36521
+ if (version[index] < required[index]) return true;
36522
+ if (version[index] > required[index]) return false;
36523
+ }
36524
+ return false;
36525
+ }
36526
+
36527
+ async function loadHostVoiceInputNativeAppVersion() {
36528
+ if (!isOysterunCapacitorIOSRuntime()) {
36529
+ hostPreferencesState = {
36530
+ ...hostPreferencesState,
36531
+ voiceInputNativeAppVersion: "",
36532
+ voiceInputNativeAppVersionLoaded: true,
36533
+ };
36534
+ return;
36535
+ }
36536
+ if (hostPreferencesState.voiceInputNativeAppVersionLoaded) return;
36537
+ if (hostVoiceInputNativeAppVersionLoadPromise) {
36538
+ return hostVoiceInputNativeAppVersionLoadPromise;
36539
+ }
36540
+ hostVoiceInputNativeAppVersionLoadPromise = (async () => {
36541
+ let version = "";
36542
+ const plugin = getOysterunCapacitorAppPlugin();
36543
+ if (plugin) {
36544
+ try {
36545
+ const appInfo = await plugin.getInfo();
36546
+ version =
36547
+ typeof appInfo?.version === "string"
36548
+ ? appInfo.version.trim()
36549
+ : "";
36550
+ } catch (err) {
36551
+ console.warn(
36552
+ "[oysterun-voice-input] native app version unavailable",
36553
+ err
36554
+ );
36555
+ }
36556
+ }
36557
+ hostPreferencesState = {
36558
+ ...hostPreferencesState,
36559
+ voiceInputNativeAppVersion: version,
36560
+ voiceInputNativeAppVersionLoaded: true,
36561
+ };
36562
+ if (getCurrentScreen() === "host-preferences") {
36563
+ renderHostVoiceInputPreferences({
36564
+ disabled:
36565
+ hostPreferencesState.loading || hostPreferencesState.saving,
36566
+ });
36567
+ }
36568
+ })().finally(() => {
36569
+ hostVoiceInputNativeAppVersionLoadPromise = null;
36570
+ });
36571
+ return hostVoiceInputNativeAppVersionLoadPromise;
36572
+ }
36573
+
36574
+ function getHostVoiceInputClientWarning() {
36575
+ if (isOysterunCapacitorIOSRuntime()) {
36576
+ const appVersion =
36577
+ hostPreferencesState.voiceInputNativeAppVersion || "";
36578
+ if (
36579
+ hostPreferencesState.voiceInputNativeAppVersionLoaded &&
36580
+ isOysterunVersionBefore(
36581
+ appVersion,
36582
+ OYSTERUN_VOICE_INPUT_MINIMUM_NATIVE_APP_VERSION
36583
+ )
36584
+ ) {
36585
+ return `Voice Input recording requires Oysterun App 1.6 or newer. This App is ${appVersion}. Update the App before using this feature.`;
36586
+ }
36587
+ const capacitor = window.Capacitor;
36588
+ if (
36589
+ typeof capacitor?.isPluginAvailable === "function" &&
36590
+ capacitor.isPluginAvailable("OysterunVoiceRecorder")
36591
+ ) {
36592
+ return "";
36593
+ }
36594
+ return "This Oysterun app build does not include native voice recording. Token testing remains available here.";
36595
+ }
36596
+ if (window.isSecureContext !== true) {
36597
+ return "Microphone recording requires HTTPS or a supported native app. Token testing remains available here.";
36598
+ }
36599
+ if (!navigator.mediaDevices?.getUserMedia) {
36600
+ return "This browser does not expose microphone recording. Token testing remains available here.";
36601
+ }
36602
+ if (typeof MediaRecorder === "undefined") {
36603
+ return "This browser does not support the required recorder. Token testing remains available here.";
36604
+ }
36605
+ return "";
36606
+ }
36607
+
36608
+ function applyHostVoiceInputStatusPayload(
36609
+ voiceInput,
36610
+ { syncSavedDraft = true } = {}
36611
+ ) {
36612
+ if (!voiceInput || typeof voiceInput !== "object") return;
36613
+ const voiceInputEnabled = voiceInput.enabled === true;
36614
+ hostPreferencesState = {
36615
+ ...hostPreferencesState,
36616
+ voiceInputEnabled,
36617
+ voiceInputAvailable: voiceInput.available === true,
36618
+ voiceInputCanEnable: voiceInput.can_enable === true,
36619
+ voiceInputTokenConfigured: voiceInput.token_configured === true,
36620
+ voiceInputTokenValidated: voiceInput.token_validated === true,
36621
+ voiceInputTokenMasked:
36622
+ typeof voiceInput.token_masked === "string"
36623
+ ? voiceInput.token_masked
36624
+ : "",
36625
+ voiceInputValidatedAt:
36626
+ typeof voiceInput.validated_at === "string"
36627
+ ? voiceInput.validated_at
36628
+ : "",
36629
+ voiceInputValidatedModel:
36630
+ typeof voiceInput.validated_model === "string"
36631
+ ? voiceInput.validated_model
36632
+ : "",
36633
+ voiceInputValidatedSampleExact:
36634
+ typeof voiceInput.validated_sample_exact === "boolean"
36635
+ ? voiceInput.validated_sample_exact
36636
+ : null,
36637
+ voiceInputModel:
36638
+ typeof voiceInput.model === "string"
36639
+ ? voiceInput.model
36640
+ : hostPreferencesState.voiceInputModel,
36641
+ voiceInputSampleExpected:
36642
+ typeof voiceInput.sample_expected === "string"
36643
+ ? voiceInput.sample_expected
36644
+ : hostPreferencesState.voiceInputSampleExpected,
36645
+ ...(syncSavedDraft
36646
+ ? {
36647
+ savedDraft: {
36648
+ ...(hostPreferencesState.savedDraft ||
36649
+ buildHostPreferencesDraft()),
36650
+ voiceInputEnabled,
36651
+ },
36652
+ }
36653
+ : {}),
36654
+ };
36655
+ }
36656
+
36657
+ function releaseHostVoiceInputSampleAudio() {
36658
+ const player = $("host-voice-input-sample-player");
36659
+ if (player) {
36660
+ player.pause();
36661
+ player.removeAttribute("src");
36662
+ player.load();
36663
+ }
36664
+ if (hostVoiceInputSampleObjectUrl) {
36665
+ URL.revokeObjectURL(hostVoiceInputSampleObjectUrl);
36666
+ hostVoiceInputSampleObjectUrl = "";
36667
+ }
36668
+ hostVoiceInputSampleLoadPromise = null;
36669
+ hostPreferencesState = {
36670
+ ...hostPreferencesState,
36671
+ voiceInputSampleLoading: false,
36672
+ voiceInputSampleLoaded: false,
36673
+ voiceInputSampleError: "",
36674
+ };
36675
+ }
36676
+
36677
+ function ensureHostVoiceInputSampleAudio() {
36678
+ if (
36679
+ hostPreferencesState.voiceInputSampleLoaded ||
36680
+ hostVoiceInputSampleLoadPromise
36681
+ ) {
36682
+ return hostVoiceInputSampleLoadPromise || Promise.resolve();
36683
+ }
36684
+ hostPreferencesState = {
36685
+ ...hostPreferencesState,
36686
+ voiceInputSampleLoading: true,
36687
+ voiceInputSampleError: "",
36688
+ };
36689
+ hostVoiceInputSampleLoadPromise = (async () => {
36690
+ try {
36691
+ const response = await apiRawWithAuthRetry(
36692
+ "GET",
36693
+ "/dev/preferences/voice-input/sample"
36694
+ );
36695
+ const blob = await response.blob();
36696
+ if (!blob.size) {
36697
+ throw new Error("Voice Input sample audio is empty.");
36698
+ }
36699
+ if (hostVoiceInputSampleObjectUrl) {
36700
+ URL.revokeObjectURL(hostVoiceInputSampleObjectUrl);
36701
+ }
36702
+ hostVoiceInputSampleObjectUrl = URL.createObjectURL(blob);
36703
+ hostPreferencesState = {
36704
+ ...hostPreferencesState,
36705
+ voiceInputSampleLoading: false,
36706
+ voiceInputSampleLoaded: true,
36707
+ voiceInputSampleError: "",
36708
+ };
36709
+ } catch (err) {
36710
+ if (await handleHostPreferencesAuthFailure(err)) return;
36711
+ hostPreferencesState = {
36712
+ ...hostPreferencesState,
36713
+ voiceInputSampleLoading: false,
36714
+ voiceInputSampleLoaded: false,
36715
+ voiceInputSampleError:
36716
+ err.message || "Could not load Voice Input sample audio.",
36717
+ };
36718
+ } finally {
36719
+ hostVoiceInputSampleLoadPromise = null;
36720
+ if (getCurrentScreen() === "host-preferences") {
36721
+ renderHostVoiceInputPreferences({
36722
+ disabled:
36723
+ hostPreferencesState.loading || hostPreferencesState.saving,
36724
+ });
36725
+ }
36726
+ }
36727
+ })();
36728
+ return hostVoiceInputSampleLoadPromise;
36729
+ }
36730
+
36731
+ function formatVoiceInputUsageTokens(value) {
36732
+ const normalized = Number(value);
36733
+ return Number.isFinite(normalized)
36734
+ ? Math.max(0, Math.round(normalized)).toLocaleString("en-US")
36735
+ : "0";
36736
+ }
36737
+
36738
+ function formatVoiceInputUsageCost(value) {
36739
+ const normalized = Number(value);
36740
+ const usd = Number.isFinite(normalized)
36741
+ ? Math.max(0, normalized) / 1_000_000_000
36742
+ : 0;
36743
+ return `US$${usd.toFixed(6)}`;
36744
+ }
36745
+
36746
+ function formatVoiceInputUsageDuration(value) {
36747
+ const milliseconds = Number(value);
36748
+ if (!Number.isFinite(milliseconds) || milliseconds <= 0) return "0 s";
36749
+ const seconds = milliseconds / 1000;
36750
+ if (seconds < 60) return `${seconds.toFixed(seconds < 10 ? 1 : 0)} s`;
36751
+ const minutes = seconds / 60;
36752
+ if (minutes < 60) return `${minutes.toFixed(1)} min`;
36753
+ return `${(minutes / 60).toFixed(1)} hr`;
36754
+ }
36755
+
36756
+ function formatVoiceInputUsageSummary(label, windowStats) {
36757
+ if (!windowStats) return `${label}: No usage data.`;
36758
+ const unknownCostRequests = Number(
36759
+ windowStats.unknown_cost_requests || 0
36760
+ );
36761
+ return `${label}: ${formatVoiceInputUsageTokens(
36762
+ windowStats.total_tokens
36763
+ )} tokens used · Estimated cost: ${formatVoiceInputUsageCost(
36764
+ windowStats.estimated_cost_nano_usd
36765
+ )}${
36766
+ unknownCostRequests > 0
36767
+ ? ` · ${unknownCostRequests.toLocaleString("en-US")} request${
36768
+ unknownCostRequests === 1 ? "" : "s"
36769
+ } not included`
36770
+ : ""
36771
+ }`;
36772
+ }
36773
+
36774
+ function renderHostVoiceInputUsageSummary() {
36775
+ const container = $("host-voice-input-usage-summary");
36776
+ if (!container) return;
36777
+ const enabled = hostPreferencesState.voiceInputEnabled === true;
36778
+ container.classList.toggle("hidden", !enabled);
36779
+ if (!enabled) return;
36780
+ const usage = hostPreferencesState.voiceInputUsage;
36781
+ const loading =
36782
+ hostPreferencesState.voiceInputUsageLoading === true && !usage;
36783
+ const error = hostPreferencesState.voiceInputUsageError || "";
36784
+ setText(
36785
+ "host-voice-input-usage-24h",
36786
+ loading
36787
+ ? "Last 24 hours: Loading usage..."
36788
+ : formatVoiceInputUsageSummary(
36789
+ "Last 24 hours",
36790
+ usage?.windows?.["24h"]
36791
+ )
36792
+ );
36793
+ setText(
36794
+ "host-voice-input-usage-7d",
36795
+ loading
36796
+ ? "Last 7 days: Loading usage..."
36797
+ : formatVoiceInputUsageSummary(
36798
+ "Last 7 days",
36799
+ usage?.windows?.["7d"]
36800
+ )
36801
+ );
36802
+ setText(
36803
+ "host-voice-input-usage-30d",
36804
+ loading
36805
+ ? "Last 30 days: Loading usage..."
36806
+ : formatVoiceInputUsageSummary(
36807
+ "Last 30 days",
36808
+ usage?.windows?.["30d"]
36809
+ )
36810
+ );
36811
+ setText("host-voice-input-usage-error", error);
36812
+ $("host-voice-input-usage-error")?.classList.toggle("hidden", !error);
36813
+ $("host-voice-input-usage-detail-btn")?.toggleAttribute(
36814
+ "disabled",
36815
+ loading
36816
+ );
36817
+ }
36818
+
36819
+ function getVoiceInputUsageDetailRows(usage) {
36820
+ const windows = ["24h", "7d", "30d"].map(
36821
+ (windowId) => usage?.windows?.[windowId] || {}
36822
+ );
36823
+ return [
36824
+ [
36825
+ "Requests",
36826
+ ...windows.map((item) =>
36827
+ formatVoiceInputUsageTokens(item.request_count)
36828
+ ),
36829
+ ],
36830
+ [
36831
+ "Successful transcriptions",
36832
+ ...windows.map((item) =>
36833
+ formatVoiceInputUsageTokens(item.successful_transcriptions)
36834
+ ),
36835
+ ],
36836
+ [
36837
+ "Failed requests",
36838
+ ...windows.map((item) =>
36839
+ formatVoiceInputUsageTokens(item.failed_requests)
36840
+ ),
36841
+ ],
36842
+ [
36843
+ "Token tests",
36844
+ ...windows.map((item) =>
36845
+ formatVoiceInputUsageTokens(item.token_tests)
36846
+ ),
36847
+ ],
36848
+ [
36849
+ "Recorded audio",
36850
+ ...windows.map((item) =>
36851
+ formatVoiceInputUsageDuration(item.recorded_duration_ms)
36852
+ ),
36853
+ ],
36854
+ [
36855
+ "Input tokens",
36856
+ ...windows.map((item) =>
36857
+ formatVoiceInputUsageTokens(item.input_tokens)
36858
+ ),
36859
+ ],
36860
+ [
36861
+ "Output tokens",
36862
+ ...windows.map((item) =>
36863
+ formatVoiceInputUsageTokens(item.output_tokens)
36864
+ ),
36865
+ ],
36866
+ [
36867
+ "Total tokens",
36868
+ ...windows.map((item) =>
36869
+ formatVoiceInputUsageTokens(item.total_tokens)
36870
+ ),
36871
+ ],
36872
+ [
36873
+ "Average provider latency",
36874
+ ...windows.map((item) =>
36875
+ item.average_provider_latency_ms === null ||
36876
+ item.average_provider_latency_ms === undefined
36877
+ ? "--"
36878
+ : `${formatVoiceInputUsageTokens(
36879
+ item.average_provider_latency_ms
36880
+ )} ms`
36881
+ ),
36882
+ ],
36883
+ [
36884
+ "Estimated cost",
36885
+ ...windows.map((item) =>
36886
+ formatVoiceInputUsageCost(item.estimated_cost_nano_usd)
36887
+ ),
36888
+ ],
36889
+ [
36890
+ "Requests not included in cost",
36891
+ ...windows.map((item) =>
36892
+ formatVoiceInputUsageTokens(item.unknown_cost_requests)
36893
+ ),
36894
+ ],
36895
+ ];
36896
+ }
36897
+
36898
+ function renderHostVoiceInputUsageDetail() {
36899
+ const usage = hostPreferencesState.voiceInputUsage;
36900
+ const loading = hostPreferencesState.voiceInputUsageLoading === true;
36901
+ const error = hostPreferencesState.voiceInputUsageError || "";
36902
+ setText(
36903
+ "voice-input-usage-generated-at",
36904
+ usage?.generated_at
36905
+ ? `Updated ${new Date(usage.generated_at).toLocaleString()}`
36906
+ : loading
36907
+ ? "Loading usage..."
36908
+ : ""
36909
+ );
36910
+ setText("voice-input-usage-error", error);
36911
+ $("voice-input-usage-error")?.classList.toggle("hidden", !error);
36912
+ $("voice-input-usage-retry-btn")?.classList.toggle("hidden", !error);
36913
+ const tableWrap = $("voice-input-usage-table-wrap");
36914
+ tableWrap?.classList.toggle("hidden", !usage);
36915
+ const tableBody = $("voice-input-usage-table-body");
36916
+ if (!tableBody || !usage) return;
36917
+ const rows = getVoiceInputUsageDetailRows(usage).map((cells) => {
36918
+ const row = document.createElement("tr");
36919
+ cells.forEach((cell, index) => {
36920
+ const element = document.createElement(index === 0 ? "th" : "td");
36921
+ element.textContent = cell;
36922
+ if (index === 0) element.scope = "row";
36923
+ row.appendChild(element);
36924
+ });
36925
+ return row;
36926
+ });
36927
+ tableBody.replaceChildren(...rows);
36928
+ }
36929
+
36930
+ async function loadHostVoiceInputUsage({ force = false } = {}) {
36931
+ if (hostVoiceInputUsageLoadPromise) {
36932
+ return hostVoiceInputUsageLoadPromise;
36933
+ }
36934
+ if (
36935
+ !force &&
36936
+ hostPreferencesState.voiceInputUsageLoaded &&
36937
+ hostPreferencesState.voiceInputUsage
36938
+ ) {
36939
+ return hostPreferencesState.voiceInputUsage;
36940
+ }
36941
+ hostPreferencesState = {
36942
+ ...hostPreferencesState,
36943
+ voiceInputUsageLoading: true,
36944
+ voiceInputUsageError: "",
36945
+ };
36946
+ renderHostVoiceInputUsageSummary();
36947
+ renderHostVoiceInputUsageDetail();
36948
+ hostVoiceInputUsageLoadPromise = (async () => {
36949
+ try {
36950
+ const usage = await apiWithAuthRetry(
36951
+ "GET",
36952
+ "/dev/preferences/voice-input/usage"
36953
+ );
36954
+ hostPreferencesState = {
36955
+ ...hostPreferencesState,
36956
+ voiceInputUsage: usage,
36957
+ voiceInputUsageLoading: false,
36958
+ voiceInputUsageLoaded: true,
36959
+ voiceInputUsageError: "",
36960
+ };
36961
+ return usage;
36962
+ } catch (err) {
36963
+ if (await handleHostPreferencesAuthFailure(err)) return null;
36964
+ hostPreferencesState = {
36965
+ ...hostPreferencesState,
36966
+ voiceInputUsageLoading: false,
36967
+ voiceInputUsageLoaded: false,
36968
+ voiceInputUsageError:
36969
+ err.message || "Could not load Voice Input usage.",
36970
+ };
36971
+ return null;
36972
+ } finally {
36973
+ hostVoiceInputUsageLoadPromise = null;
36974
+ renderHostVoiceInputUsageSummary();
36975
+ renderHostVoiceInputUsageDetail();
36976
+ }
36977
+ })();
36978
+ return hostVoiceInputUsageLoadPromise;
36979
+ }
36980
+
36981
+ function openHostVoiceInputUsageDetail() {
36982
+ void navigateToRoute(buildVoiceInputUsageRoute());
36983
+ }
36984
+
36985
+ function retryHostVoiceInputUsage() {
36986
+ void loadHostVoiceInputUsage({ force: true });
36987
+ }
36988
+
36989
+ function renderHostVoiceInputPreferences({ disabled = false } = {}) {
36990
+ const toggle = $("host-voice-input-enabled-toggle");
36991
+ const voiceInputEnabled =
36992
+ hostPreferencesState.voiceInputEnabled === true;
36993
+ const voiceInputModel =
36994
+ typeof hostPreferencesState.voiceInputModel === "string"
36995
+ ? hostPreferencesState.voiceInputModel.trim()
36996
+ : "";
36997
+ setText(
36998
+ "host-voice-input-title",
36999
+ voiceInputModel ? `Voice Input (${voiceInputModel})` : "Voice Input"
37000
+ );
37001
+ setHostVoiceInputEnabledControlValue(voiceInputEnabled);
37002
+ const tokenInput = $("host-voice-input-token");
37003
+ if (tokenInput && document.activeElement !== tokenInput) {
37004
+ tokenInput.value = hostPreferencesState.voiceInputTokenDraft || "";
37005
+ }
37006
+ const tokenCandidate =
37007
+ tokenInput?.value?.trim() ||
37008
+ hostPreferencesState.voiceInputTokenDraft ||
37009
+ "";
37010
+ const tokenConfigured =
37011
+ hostPreferencesState.voiceInputTokenConfigured === true;
37012
+ const tokenValidated =
37013
+ hostPreferencesState.voiceInputTokenValidated === true;
37014
+ const pendingReplacement = Boolean(tokenCandidate);
37015
+ if (tokenInput) {
37016
+ tokenInput.placeholder =
37017
+ tokenConfigured && hostPreferencesState.voiceInputTokenMasked
37018
+ ? hostPreferencesState.voiceInputTokenMasked
37019
+ : "Enter a new token";
37020
+ tokenInput.setAttribute(
37021
+ "aria-label",
37022
+ tokenConfigured && !pendingReplacement
37023
+ ? `Saved OpenAI API token ${hostPreferencesState.voiceInputTokenMasked}. Enter a new token to replace it.`
37024
+ : "OpenAI API token"
37025
+ );
37026
+ }
37027
+ setText(
37028
+ "host-voice-input-token-status",
37029
+ pendingReplacement
37030
+ ? tokenConfigured
37031
+ ? "New token entered. Test & Save Token to replace the saved token, or discard this draft."
37032
+ : "New token entered. Run Test & Save Token."
37033
+ : tokenConfigured
37034
+ ? tokenValidated
37035
+ ? "Validated and saved."
37036
+ : "Saved token requires validation."
37037
+ : "No token saved."
37038
+ );
37039
+ setText(
37040
+ "host-voice-input-client-warning",
37041
+ getHostVoiceInputClientWarning()
37042
+ );
37043
+ setText(
37044
+ "host-voice-input-availability",
37045
+ hostPreferencesState.voiceInputAvailable
37046
+ ? `Ready with ${hostPreferencesState.voiceInputModel}.`
37047
+ : pendingReplacement
37048
+ ? voiceInputEnabled
37049
+ ? "The saved token remains active. Test or discard the replacement draft."
37050
+ : "Test or discard the replacement draft before enabling Voice Input."
37051
+ : hostPreferencesState.voiceInputCanEnable
37052
+ ? "Token validation passed. Enable Voice Input and save Preferences."
37053
+ : "Test and save a valid OpenAI API token first."
37054
+ );
37055
+ const pill = $("host-voice-input-state-pill");
37056
+ if (pill) {
37057
+ pill.textContent = hostPreferencesState.voiceInputAvailable
37058
+ ? "Ready"
37059
+ : hostPreferencesState.voiceInputCanEnable
37060
+ ? "Validated"
37061
+ : "Disabled";
37062
+ }
37063
+ setText(
37064
+ "host-voice-input-sample-expected",
37065
+ hostPreferencesState.voiceInputSampleExpected
37066
+ );
37067
+ const samplePlayer = $("host-voice-input-sample-player");
37068
+ if (
37069
+ samplePlayer &&
37070
+ hostVoiceInputSampleObjectUrl &&
37071
+ samplePlayer.src !== hostVoiceInputSampleObjectUrl
37072
+ ) {
37073
+ samplePlayer.src = hostVoiceInputSampleObjectUrl;
37074
+ }
37075
+ setText(
37076
+ "host-voice-input-sample-status",
37077
+ hostPreferencesState.voiceInputSampleLoading
37078
+ ? "Loading D1.wav..."
37079
+ : hostPreferencesState.voiceInputSampleError
37080
+ ? hostPreferencesState.voiceInputSampleError
37081
+ : hostPreferencesState.voiceInputSampleLoaded
37082
+ ? "D1.wav"
37083
+ : ""
37084
+ );
37085
+ const testStatus =
37086
+ hostPreferencesState.voiceInputTestError ||
37087
+ hostPreferencesState.voiceInputTestStatus ||
37088
+ "";
37089
+ setText(
37090
+ "host-voice-input-test-status",
37091
+ hostPreferencesState.voiceInputTestLatencyMs !== null
37092
+ ? `${testStatus} ${hostPreferencesState.voiceInputTestLatencyMs} ms`
37093
+ : testStatus
37094
+ );
37095
+ setText(
37096
+ "host-voice-input-test-transcript",
37097
+ hostPreferencesState.voiceInputTestTranscript
37098
+ ? `Actual transcript: ${hostPreferencesState.voiceInputTestTranscript}`
37099
+ : ""
37100
+ );
37101
+ const testStatusElement = $("host-voice-input-test-status");
37102
+ testStatusElement?.classList.toggle(
37103
+ "voice-input-warning",
37104
+ Boolean(hostPreferencesState.voiceInputTestError)
37105
+ );
37106
+ const canEnable = hostPreferencesState.voiceInputCanEnable === true;
37107
+ toggle?.toggleAttribute(
37108
+ "disabled",
37109
+ disabled ||
37110
+ hostPreferencesState.voiceInputTestRunning ||
37111
+ hostPreferencesState.voiceInputTokenClearing ||
37112
+ (!voiceInputEnabled && (!canEnable || pendingReplacement))
37113
+ );
37114
+ toggle?.setAttribute(
37115
+ "aria-disabled",
37116
+ toggle?.disabled ? "true" : "false"
37117
+ );
37118
+ const enableWarning = $("host-voice-input-enable-warning");
37119
+ setText(
37120
+ "host-voice-input-enable-warning",
37121
+ pendingReplacement
37122
+ ? "Test or discard the new token before enabling Voice Input."
37123
+ : "Test and save a valid OpenAI API token before enabling Voice Input."
37124
+ );
37125
+ enableWarning?.classList.toggle(
37126
+ "hidden",
37127
+ voiceInputEnabled || (canEnable && !pendingReplacement)
37128
+ );
37129
+ tokenInput?.toggleAttribute(
37130
+ "disabled",
37131
+ disabled ||
37132
+ hostPreferencesState.voiceInputTestRunning ||
37133
+ hostPreferencesState.voiceInputTokenClearing
37134
+ );
37135
+ const discardDraftButton = $(
37136
+ "host-voice-input-discard-token-draft-btn"
37137
+ );
37138
+ discardDraftButton?.classList.toggle("hidden", !pendingReplacement);
37139
+ discardDraftButton?.toggleAttribute(
37140
+ "disabled",
37141
+ disabled ||
37142
+ hostPreferencesState.voiceInputTestRunning ||
37143
+ hostPreferencesState.voiceInputTokenClearing
37144
+ );
37145
+ const removeSavedButton = $("host-voice-input-remove-saved-token-btn");
37146
+ removeSavedButton?.classList.toggle(
37147
+ "hidden",
37148
+ pendingReplacement || !tokenConfigured
37149
+ );
37150
+ removeSavedButton?.toggleAttribute(
37151
+ "disabled",
37152
+ disabled ||
37153
+ hostPreferencesState.voiceInputTestRunning ||
37154
+ hostPreferencesState.voiceInputTokenClearing
37155
+ );
37156
+ const testButton = $("host-voice-input-test-btn");
37157
+ if (testButton) {
37158
+ testButton.disabled =
37159
+ disabled ||
37160
+ hostPreferencesState.voiceInputTestRunning ||
37161
+ hostPreferencesState.voiceInputTokenClearing ||
37162
+ (pendingReplacement
37163
+ ? !isHostVoiceInputTokenCandidateTestable(tokenCandidate)
37164
+ : !tokenConfigured);
37165
+ testButton.textContent = hostPreferencesState.voiceInputTestRunning
37166
+ ? "Testing..."
37167
+ : tokenCandidate
37168
+ ? "Test & Save Token"
37169
+ : tokenConfigured
37170
+ ? "Retest Saved Token"
37171
+ : "Test & Save Token";
37172
+ }
37173
+ if (
37174
+ getCurrentScreen() === "host-preferences" &&
37175
+ !hostPreferencesState.voiceInputSampleLoaded &&
37176
+ !hostPreferencesState.voiceInputSampleLoading
37177
+ ) {
37178
+ void ensureHostVoiceInputSampleAudio();
37179
+ }
37180
+ renderHostVoiceInputUsageSummary();
37181
+ if (
37182
+ voiceInputEnabled &&
37183
+ !hostPreferencesState.voiceInputUsageLoaded &&
37184
+ !hostPreferencesState.voiceInputUsageLoading
37185
+ ) {
37186
+ void loadHostVoiceInputUsage();
37187
+ }
37188
+ }
37189
+
35897
37190
  function renderHostPreferences() {
35898
37191
  const pathInput = $("host-default-browse-path");
35899
37192
  if (
@@ -36024,10 +37317,8 @@ Waiting for Claude login output…</pre
36024
37317
  basicPreferencesUnavailable;
36025
37318
  const providerSectionStatusVisible =
36026
37319
  providerCatalogState.loading || Boolean(providerCatalogState.error);
36027
- const providerSectionInitialPending =
36028
- providerCatalogState.loading && !providerCatalogState.loaded;
36029
- const providerSectionUnavailable =
36030
- !providerCatalogState.loaded && Boolean(providerCatalogState.error);
37320
+ const providerSectionInitialPending = isProviderCatalogInitialPending();
37321
+ const providerSectionUnavailable = isProviderCatalogRequestFailed();
36031
37322
  const providerControlsDisabled =
36032
37323
  disabled ||
36033
37324
  providerSectionInitialPending ||
@@ -36035,12 +37326,12 @@ Waiting for Claude login output…</pre
36035
37326
  let providerSectionStatusCopy = "";
36036
37327
  if (providerCatalogState.loading) {
36037
37328
  providerSectionStatusCopy = providerCatalogState.loaded
36038
- ? "Refreshing provider status..."
36039
- : "Checking provider status...";
37329
+ ? "Refreshing provider availability..."
37330
+ : "Checking provider availability...";
36040
37331
  } else if (providerCatalogState.error) {
36041
37332
  providerSectionStatusCopy = providerCatalogState.loaded
36042
- ? "Could not refresh provider status. Existing values remain available."
36043
- : "Could not load provider status.";
37333
+ ? "Could not refresh provider availability. Existing values remain available."
37334
+ : "Could not load provider availability.";
36044
37335
  }
36045
37336
  setVisible(
36046
37337
  "host-provider-section-status-label",
@@ -36428,15 +37719,11 @@ Waiting for Claude login output…</pre
36428
37719
  );
36429
37720
  setText(
36430
37721
  "host-claude-command-status",
36431
- isProviderAvailable("claude")
36432
- ? "Available on this Host."
36433
- : "Unavailable on this Host. Blank disables Claude explicitly."
37722
+ describeProviderCommandAvailability("claude")
36434
37723
  );
36435
37724
  setText(
36436
37725
  "host-codex-command-status",
36437
- isProviderAvailable("codex")
36438
- ? "Available on this Host."
36439
- : "Unavailable on this Host. Blank disables Codex explicitly."
37726
+ describeProviderCommandAvailability("codex")
36440
37727
  );
36441
37728
  setText(
36442
37729
  "host-default-provider-warning",
@@ -36456,6 +37743,7 @@ Waiting for Claude login output…</pre
36456
37743
  hostPreferencesState.debugHostPreferencesFullDiskAccessBlockEnabled ===
36457
37744
  true;
36458
37745
  renderHostNotificationPreferences({ disabled });
37746
+ renderHostVoiceInputPreferences({ disabled });
36459
37747
  setVisible("host-folder-access-card", folderAccessVisible);
36460
37748
  $("host-public-base-url")?.toggleAttribute(
36461
37749
  "disabled",
@@ -36542,6 +37830,7 @@ Waiting for Claude login output…</pre
36542
37830
  error: "",
36543
37831
  };
36544
37832
  renderHostPreferences();
37833
+ void loadHostVoiceInputNativeAppVersion();
36545
37834
  try {
36546
37835
  const data = await apiWithAuthRetry("GET", "/dev/preferences");
36547
37836
  applyHostPreferencesData(data);
@@ -36975,6 +38264,192 @@ Waiting for Claude login output…</pre
36975
38264
  discardUnsavedHostPreferencesChanges();
36976
38265
  }
36977
38266
 
38267
+ function handleHostVoiceInputTokenInput() {
38268
+ const tokenDraft = $("host-voice-input-token")?.value || "";
38269
+ hostPreferencesState = {
38270
+ ...hostPreferencesState,
38271
+ voiceInputTokenDraft: tokenDraft,
38272
+ voiceInputTestStatus: "",
38273
+ voiceInputTestError: "",
38274
+ voiceInputTestTranscript: "",
38275
+ voiceInputTestLatencyMs: null,
38276
+ };
38277
+ renderHostVoiceInputPreferences({
38278
+ disabled: hostPreferencesState.loading || hostPreferencesState.saving,
38279
+ });
38280
+ }
38281
+
38282
+ function toggleHostVoiceInputEnabled() {
38283
+ const toggle = $("host-voice-input-enabled-toggle");
38284
+ if (!toggle || toggle.disabled) return;
38285
+ const enabled = !getHostVoiceInputEnabledControlValue();
38286
+ setHostVoiceInputEnabledControlValue(enabled);
38287
+ hostPreferencesState = {
38288
+ ...hostPreferencesState,
38289
+ voiceInputEnabled: enabled,
38290
+ };
38291
+ stageHostPreferencesDraftFromControls();
38292
+ renderHostPreferences();
38293
+ }
38294
+
38295
+ function discardHostVoiceInputTokenDraft() {
38296
+ if (
38297
+ hostPreferencesState.loading ||
38298
+ hostPreferencesState.saving ||
38299
+ hostPreferencesState.voiceInputTestRunning ||
38300
+ hostPreferencesState.voiceInputTokenClearing
38301
+ ) {
38302
+ return;
38303
+ }
38304
+ const tokenInput = $("host-voice-input-token");
38305
+ if (tokenInput) tokenInput.value = "";
38306
+ hostPreferencesState = {
38307
+ ...hostPreferencesState,
38308
+ voiceInputTokenDraft: "",
38309
+ voiceInputTestStatus: "",
38310
+ voiceInputTestError: "",
38311
+ voiceInputTestTranscript: "",
38312
+ voiceInputTestLatencyMs: null,
38313
+ };
38314
+ renderHostPreferences();
38315
+ }
38316
+
38317
+ async function removeHostVoiceInputSavedToken() {
38318
+ if (
38319
+ hostPreferencesState.loading ||
38320
+ hostPreferencesState.saving ||
38321
+ hostPreferencesState.voiceInputTestRunning ||
38322
+ hostPreferencesState.voiceInputTokenClearing
38323
+ ) {
38324
+ return;
38325
+ }
38326
+ if (!hostPreferencesState.voiceInputTokenConfigured) {
38327
+ return;
38328
+ }
38329
+ if (
38330
+ !window.confirm(
38331
+ "Remove the saved OpenAI API token and disable Voice Input?"
38332
+ )
38333
+ ) {
38334
+ return;
38335
+ }
38336
+ const tokenInput = $("host-voice-input-token");
38337
+ if (tokenInput) tokenInput.value = "";
38338
+ hostPreferencesState = {
38339
+ ...hostPreferencesState,
38340
+ voiceInputTokenDraft: "",
38341
+ voiceInputTokenClearing: true,
38342
+ voiceInputTestStatus: "Clearing saved token...",
38343
+ voiceInputTestError: "",
38344
+ voiceInputTestTranscript: "",
38345
+ voiceInputTestLatencyMs: null,
38346
+ };
38347
+ renderHostPreferences();
38348
+ try {
38349
+ const data = await apiWithAuthRetry("PUT", "/dev/preferences", {
38350
+ voice_input: {
38351
+ enabled: false,
38352
+ token_action: "clear",
38353
+ },
38354
+ });
38355
+ applyHostVoiceInputStatusPayload(data.preferences?.voice_input);
38356
+ hostPreferencesState = {
38357
+ ...hostPreferencesState,
38358
+ voiceInputTokenClearing: false,
38359
+ voiceInputTokenDraft: "",
38360
+ voiceInputTestStatus: "Saved token cleared.",
38361
+ voiceInputTestError: "",
38362
+ voiceInputTestTranscript: "",
38363
+ voiceInputTestLatencyMs: null,
38364
+ };
38365
+ } catch (err) {
38366
+ if (await handleHostPreferencesAuthFailure(err)) return;
38367
+ hostPreferencesState = {
38368
+ ...hostPreferencesState,
38369
+ voiceInputTokenClearing: false,
38370
+ voiceInputTestStatus: "",
38371
+ voiceInputTestError:
38372
+ err.message || "Could not clear the Voice Input token.",
38373
+ };
38374
+ }
38375
+ renderHostPreferences();
38376
+ }
38377
+
38378
+ async function testHostVoiceInput() {
38379
+ const candidateToken =
38380
+ $("host-voice-input-token")?.value?.trim() ||
38381
+ hostPreferencesState.voiceInputTokenDraft.trim();
38382
+ if (hostPreferencesState.voiceInputTestRunning) {
38383
+ return;
38384
+ }
38385
+ if (
38386
+ candidateToken &&
38387
+ !isHostVoiceInputTokenCandidateTestable(candidateToken)
38388
+ ) {
38389
+ hostPreferencesState = {
38390
+ ...hostPreferencesState,
38391
+ voiceInputTestStatus: "",
38392
+ voiceInputTestError:
38393
+ "Enter a complete OpenAI API token before testing.",
38394
+ };
38395
+ renderHostPreferences();
38396
+ return;
38397
+ }
38398
+ if (
38399
+ !candidateToken &&
38400
+ !hostPreferencesState.voiceInputTokenConfigured
38401
+ ) {
38402
+ return;
38403
+ }
38404
+ hostPreferencesState = {
38405
+ ...hostPreferencesState,
38406
+ voiceInputTestRunning: true,
38407
+ voiceInputTestStatus: "Testing D1.wav...",
38408
+ voiceInputTestError: "",
38409
+ voiceInputTestTranscript: "",
38410
+ voiceInputTestLatencyMs: null,
38411
+ error: "",
38412
+ };
38413
+ renderHostPreferences();
38414
+ try {
38415
+ const data = await apiWithAuthRetry(
38416
+ "POST",
38417
+ "/dev/preferences/voice-input/test",
38418
+ candidateToken ? { openai_api_token: candidateToken } : {}
38419
+ );
38420
+ applyHostVoiceInputStatusPayload(data.voice_input);
38421
+ const tokenInput = $("host-voice-input-token");
38422
+ if (tokenInput) tokenInput.value = "";
38423
+ hostPreferencesState = {
38424
+ ...hostPreferencesState,
38425
+ voiceInputTestRunning: false,
38426
+ voiceInputTokenDraft: "",
38427
+ voiceInputTestStatus:
38428
+ data.sample_exact === true
38429
+ ? "Token saved. Exact sample match."
38430
+ : "Token saved. Sample completed; review the actual transcript.",
38431
+ voiceInputTestError: "",
38432
+ voiceInputTestTranscript: data.transcript || "",
38433
+ voiceInputTestLatencyMs: Number.isFinite(Number(data.duration_ms))
38434
+ ? Number(data.duration_ms)
38435
+ : null,
38436
+ };
38437
+ } catch (err) {
38438
+ if (await handleHostPreferencesAuthFailure(err)) return;
38439
+ hostPreferencesState = {
38440
+ ...hostPreferencesState,
38441
+ voiceInputTestRunning: false,
38442
+ voiceInputTestStatus: "",
38443
+ voiceInputTestError:
38444
+ err.message || "Voice Input token test failed.",
38445
+ voiceInputTestTranscript: "",
38446
+ voiceInputTestLatencyMs: null,
38447
+ };
38448
+ }
38449
+ void loadHostVoiceInputUsage({ force: true });
38450
+ renderHostPreferences();
38451
+ }
38452
+
36978
38453
  async function saveHostPreferences() {
36979
38454
  if (hostPreferencesState.saving) {
36980
38455
  refreshHostPreferencesSaveButton();
@@ -37010,6 +38485,8 @@ Waiting for Claude login output…</pre
37010
38485
  $("host-notification-app-sound-toggle")?.checked !== false;
37011
38486
  const requestedDailyTelemetryEnabled =
37012
38487
  $("host-daily-telemetry-toggle")?.checked === true;
38488
+ const requestedVoiceInputEnabled =
38489
+ getHostVoiceInputEnabledControlValue();
37013
38490
  const claudeCommandInput = $("host-claude-command");
37014
38491
  const requestedClaudeCommand = claudeCommandInput
37015
38492
  ? claudeCommandInput.value.trim()
@@ -37084,6 +38561,7 @@ Waiting for Claude login output…</pre
37084
38561
  notificationSoundWebEnabled: requestedWebNotificationSound,
37085
38562
  notificationSoundAppEnabled: requestedAppNotificationSound,
37086
38563
  dailyTelemetryEnabled: requestedDailyTelemetryEnabled,
38564
+ voiceInputEnabled: requestedVoiceInputEnabled,
37087
38565
  publicBaseUrl: requestedPublicBaseUrl,
37088
38566
  defaultBrowsePath: requestedPath,
37089
38567
  defaultProvider: nextDefaultProvider,
@@ -37113,6 +38591,10 @@ Waiting for Claude login output…</pre
37113
38591
  notification_sound_web_enabled: requestedWebNotificationSound,
37114
38592
  notification_sound_app_enabled: requestedAppNotificationSound,
37115
38593
  daily_telemetry_enabled: requestedDailyTelemetryEnabled,
38594
+ voice_input: {
38595
+ enabled: requestedVoiceInputEnabled,
38596
+ token_action: "retain",
38597
+ },
37116
38598
  direct_host_url: requestedPublicBaseUrl || null,
37117
38599
  public_base_url: requestedPublicBaseUrl || null,
37118
38600
  default_browse_path: requestedPath,
@@ -37739,6 +39221,55 @@ Waiting for Claude login output…</pre
37739
39221
  syncCreateAllowedPathsShortcutState();
37740
39222
  }
37741
39223
 
39224
+ function isCreateWebsiteEligible() {
39225
+ return createWebsiteAuthorityState.eligible === true;
39226
+ }
39227
+
39228
+ function syncCreateWebsiteAuthorityState() {
39229
+ const eligible = isCreateWebsiteEligible();
39230
+ const enabledInput = $("create-website-enabled");
39231
+ const accessSelect = $("create-website-access");
39232
+ const hint = $("create-website-authority-hint");
39233
+ if (enabledInput) {
39234
+ enabledInput.disabled = !eligible;
39235
+ if (!eligible) enabledInput.checked = false;
39236
+ }
39237
+ if (accessSelect) {
39238
+ accessSelect.disabled = !eligible;
39239
+ if (!eligible) accessSelect.value = "owner_only";
39240
+ }
39241
+ if (!eligible) setCreateWebsitePasswordValue("");
39242
+ if (hint) {
39243
+ hint.textContent =
39244
+ createWebsiteAuthorityState.reason ||
39245
+ "Website is available only for agents in the Browser Root.";
39246
+ hint.classList.toggle("hidden", eligible);
39247
+ }
39248
+ syncCreateWebsitePasswordMode();
39249
+ }
39250
+
39251
+ function setCreateWebsiteAuthorityState({
39252
+ folderPath = "",
39253
+ projectId = "",
39254
+ eligible = false,
39255
+ reason = "",
39256
+ } = {}) {
39257
+ createWebsiteAuthorityState = {
39258
+ folderPath: String(folderPath || "").trim(),
39259
+ projectId: String(projectId || "").trim(),
39260
+ eligible: eligible === true,
39261
+ reason:
39262
+ String(reason || "").trim() ||
39263
+ "Website is available only for agents in the Browser Root.",
39264
+ };
39265
+ if (eligible !== true) {
39266
+ setCreateWebsiteEnabledValue(false);
39267
+ setCreateWebsiteAccessValue("owner_only");
39268
+ setCreateWebsitePasswordValue("");
39269
+ }
39270
+ syncCreateWebsiteAuthorityState();
39271
+ }
39272
+
37742
39273
  function setCreateWebsiteAccessValue(value) {
37743
39274
  const select = $("create-website-access");
37744
39275
  if (!select) return;
@@ -37774,16 +39305,19 @@ Waiting for Claude login output…</pre
37774
39305
  const row = $("create-website-password-row");
37775
39306
  const input = $("create-website-password");
37776
39307
  const status = $("create-website-password-status");
39308
+ const eligible = isCreateWebsiteEligible();
37777
39309
  const passwordMode = getCreateWebsiteAccessValue() === "password";
37778
39310
  if (row) row.classList.remove("hidden");
37779
39311
  if (input) {
37780
- input.disabled = !passwordMode;
39312
+ input.disabled = !eligible || !passwordMode;
37781
39313
  }
37782
39314
  if (status) {
37783
- status.textContent = formatOwnerWebsitePasswordHint(
37784
- getCreateWebsiteAccessValue(),
37785
- input?.value || ""
37786
- );
39315
+ status.textContent = eligible
39316
+ ? formatOwnerWebsitePasswordHint(
39317
+ getCreateWebsiteAccessValue(),
39318
+ input?.value || ""
39319
+ )
39320
+ : "Website settings are unavailable outside the Browser Root.";
37787
39321
  }
37788
39322
  }
37789
39323
 
@@ -37889,6 +39423,10 @@ Waiting for Claude login output…</pre
37889
39423
  syncSessionSetupInterfaceStyleVisibility();
37890
39424
  setCreateWebsiteEnabledValue(HOST_DEFAULT_SESSION_DEFAULTS.web.enabled);
37891
39425
  setCreateWebsiteAccessValue(HOST_DEFAULT_SESSION_DEFAULTS.web.access);
39426
+ setCreateWebsiteAuthorityState({
39427
+ folderPath: startFolder,
39428
+ eligible: false,
39429
+ });
37892
39430
  setCreateNotificationsEnabledValue(
37893
39431
  HOST_DEFAULT_SESSION_DEFAULTS.notifications.enabled
37894
39432
  );
@@ -37981,6 +39519,10 @@ Waiting for Claude login output…</pre
37981
39519
  syncSessionSetupInterfaceStyleVisibility();
37982
39520
  setCreateWebsiteEnabledValue(HOST_DEFAULT_SESSION_DEFAULTS.web.enabled);
37983
39521
  setCreateWebsiteAccessValue(HOST_DEFAULT_SESSION_DEFAULTS.web.access);
39522
+ setCreateWebsiteAuthorityState({
39523
+ folderPath: record.agent_folder || "",
39524
+ eligible: false,
39525
+ });
37984
39526
  setCreateNotificationsEnabledValue(
37985
39527
  HOST_DEFAULT_SESSION_DEFAULTS.notifications.enabled
37986
39528
  );
@@ -38389,14 +39931,15 @@ Waiting for Claude login output…</pre
38389
39931
  if (liveSession?.website && typeof liveSession.website === "object") {
38390
39932
  return liveSession.website;
38391
39933
  }
38392
- if (
38393
- selectedAgent?.agent_id &&
38394
- selectedAgent.agent_id === liveSession?.agentId &&
38395
- selectedAgent.website
38396
- ) {
38397
- return selectedAgent.website;
38398
- }
38399
- return null;
39934
+ return {
39935
+ enabled: false,
39936
+ available: false,
39937
+ access: "owner_only",
39938
+ browser_root_authorized: false,
39939
+ reason: "session_folder_outside_browser_root",
39940
+ reason_message:
39941
+ "Website is available only for agents in the Browser Root.",
39942
+ };
38400
39943
  }
38401
39944
 
38402
39945
  function getOwnerVisibleWebsitePassword(website = {}) {
@@ -39038,7 +40581,6 @@ Waiting for Claude login output…</pre
39038
40581
  liveSession = getSelectedLiveSession()
39039
40582
  ) {
39040
40583
  const input = $("agent-profile-rename-input");
39041
- const updateConfig = $("agent-profile-identity-update-config");
39042
40584
  const saveBtn =
39043
40585
  $("agent-profile-identity-save-btn") ||
39044
40586
  $("agent-profile-rename-save-btn");
@@ -39046,10 +40588,6 @@ Waiting for Claude login output…</pre
39046
40588
  if (!liveSession) {
39047
40589
  input.value = "";
39048
40590
  input.disabled = true;
39049
- if (updateConfig) {
39050
- updateConfig.checked = false;
39051
- updateConfig.disabled = true;
39052
- }
39053
40591
  saveBtn.disabled = true;
39054
40592
  saveBtn.textContent = "Save";
39055
40593
  showError("agent-profile-rename-error", "");
@@ -39062,18 +40600,14 @@ Waiting for Claude login output…</pre
39062
40600
  ).trim();
39063
40601
  const draftName = String(agentProfileRenameState.draft || "");
39064
40602
  const normalizedDraft = draftName.trim();
39065
- const persistIdentity = updateConfig?.checked === true;
39066
40603
  input.value = draftName;
39067
40604
  input.disabled = agentProfileRenameState.saving === true;
39068
- if (updateConfig) {
39069
- updateConfig.disabled = agentProfileRenameState.saving === true;
39070
- }
39071
40605
  saveBtn.textContent =
39072
40606
  agentProfileRenameState.saving === true ? "Saving..." : "Save";
39073
40607
  saveBtn.disabled =
39074
40608
  agentProfileRenameState.saving === true ||
39075
40609
  !normalizedDraft ||
39076
- (normalizedDraft === currentName && !persistIdentity);
40610
+ normalizedDraft === currentName;
39077
40611
  showError(
39078
40612
  "agent-profile-rename-error",
39079
40613
  agentProfileRenameState.error || ""
@@ -39091,8 +40625,6 @@ Waiting for Claude login output…</pre
39091
40625
  const currentSessionName = String(
39092
40626
  liveSession.sessionName || liveSession.session_name || ""
39093
40627
  ).trim();
39094
- const updateConfig =
39095
- $("agent-profile-identity-update-config")?.checked === true;
39096
40628
  if (!nextSessionName) {
39097
40629
  agentProfileRenameState = {
39098
40630
  ...agentProfileRenameState,
@@ -39101,7 +40633,7 @@ Waiting for Claude login output…</pre
39101
40633
  renderAgentProfileRenameView(liveSession);
39102
40634
  return;
39103
40635
  }
39104
- if (nextSessionName === currentSessionName && !updateConfig) {
40636
+ if (nextSessionName === currentSessionName) {
39105
40637
  agentProfileRenameState = {
39106
40638
  ...agentProfileRenameState,
39107
40639
  draft: currentSessionName,
@@ -39123,7 +40655,6 @@ Waiting for Claude login output…</pre
39123
40655
  const data = await api("POST", "/session/rename", {
39124
40656
  session_id: liveSession.sessionId,
39125
40657
  session_name: nextSessionName,
39126
- update_config: updateConfig,
39127
40658
  });
39128
40659
  const resolvedSessionName = data.session_name || nextSessionName;
39129
40660
  patchLiveSession(liveSession.sessionId, {
@@ -39138,11 +40669,7 @@ Waiting for Claude login output…</pre
39138
40669
  resolvedSessionName
39139
40670
  );
39140
40671
  renderSelectedAgent();
39141
- showBanner(
39142
- data.config_mutated === true
39143
- ? `Renamed session to ${resolvedSessionName} and updated .oysterun/config.json.`
39144
- : `Renamed session to ${resolvedSessionName}.`
39145
- );
40672
+ showBanner(`Renamed session to ${resolvedSessionName}.`);
39146
40673
  } catch (err) {
39147
40674
  agentProfileRenameState = {
39148
40675
  ...agentProfileRenameState,
@@ -40377,7 +41904,6 @@ Waiting for Claude login output…</pre
40377
41904
  "approval_policy",
40378
41905
  "allow_dangerously_skip_permissions",
40379
41906
  "dangerous_mode",
40380
- "web_access",
40381
41907
  "notifications_enabled",
40382
41908
  "allowed_paths",
40383
41909
  "runtime_capabilities",
@@ -40529,8 +42055,10 @@ Waiting for Claude login output…</pre
40529
42055
  payload.interface_type || getCreateDefaultInterfaceType()
40530
42056
  );
40531
42057
  syncSessionSetupInterfaceStyleVisibility();
40532
- setCreateWebsiteEnabledValue(payload.web_enabled === true);
40533
- setCreateWebsiteAccessValue(payload.web_access || "owner_only");
42058
+ setCreateWebsiteAuthorityState({
42059
+ folderPath: draft.agentFolder || "",
42060
+ eligible: false,
42061
+ });
40534
42062
  setCreateTelegramSettingsValue({});
40535
42063
  $("create-allowed-paths").value = normalizeAllowedPathsForForm(
40536
42064
  payload.allowed_paths
@@ -40603,6 +42131,13 @@ Waiting for Claude login output…</pre
40603
42131
  syncCreateAllowedPathsShortcutState();
40604
42132
  refreshCreateStartButtonEnabled();
40605
42133
  refreshUpdateSessionNameButton();
42134
+ if (draft.agentFolder) {
42135
+ void loadFolderConfigIntoCreateForm(draft.agentFolder, {
42136
+ fillAgentIdIfEmpty: false,
42137
+ applyFormValues: false,
42138
+ showError: false,
42139
+ });
42140
+ }
40606
42141
  }
40607
42142
 
40608
42143
  function readSessionSetupSchedulerDraftForEditor(
@@ -48648,14 +50183,6 @@ Waiting for Claude login output…</pre
48648
50183
  const allowedPaths = isAllowedPathDebugUiVisible()
48649
50184
  ? parseAllowedPathsInput($("create-allowed-paths").value)
48650
50185
  : null;
48651
- const webAccess = getCreateWebsiteAccessValue();
48652
- const webPassword =
48653
- webAccess === "password" ? getCreateWebsitePasswordValue() : "";
48654
- if (webAccess === "password" && !webPassword) {
48655
- throw new Error(
48656
- "Website Password is required when Website Access is Password. Switch Website Access away from Password before clearing it."
48657
- );
48658
- }
48659
50186
 
48660
50187
  const payload = {
48661
50188
  provider: createRuntime,
@@ -48672,8 +50199,6 @@ Waiting for Claude login output…</pre
48672
50199
  createRuntime === "codex" ? codexApproval : undefined,
48673
50200
  allow_dangerously_skip_permissions: allowDanger,
48674
50201
  dangerous_mode: allowDanger,
48675
- web_enabled: getCreateWebsiteEnabledValue(),
48676
- web_access: webAccess,
48677
50202
  notifications_enabled: getCreateNotificationsEnabledValue(),
48678
50203
  install_oysterun_skills: getCreateInstallOysterunSkillsValue(),
48679
50204
  ...readTelegramFieldsPayload("create"),
@@ -48692,9 +50217,6 @@ Waiting for Claude login output…</pre
48692
50217
  ) {
48693
50218
  payload.confirm_beta_provider_permission_mode = true;
48694
50219
  }
48695
- if (webPassword) {
48696
- payload.web_password = webPassword;
48697
- }
48698
50220
  validateTelegramEnabledPayloadOrThrow(
48699
50221
  payload,
48700
50222
  getCreateTelegramConfiguredState()
@@ -48702,6 +50224,23 @@ Waiting for Claude login output…</pre
48702
50224
  return payload;
48703
50225
  }
48704
50226
 
50227
+ function buildCreateWebsitePayload() {
50228
+ if (!isCreateWebsiteEligible()) return null;
50229
+ const webAccess = getCreateWebsiteAccessValue();
50230
+ const webPassword =
50231
+ webAccess === "password" ? getCreateWebsitePasswordValue() : "";
50232
+ if (webAccess === "password" && !webPassword) {
50233
+ throw new Error(
50234
+ "Website Password is required when Website Access is Password. Switch Website Access away from Password before clearing it."
50235
+ );
50236
+ }
50237
+ return {
50238
+ web_enabled: getCreateWebsiteEnabledValue(),
50239
+ web_access: webAccess,
50240
+ ...(webPassword ? { web_password: webPassword } : {}),
50241
+ };
50242
+ }
50243
+
48705
50244
  function readCreateFormRequest() {
48706
50245
  const agent_id = $("create-agent-id").value.trim();
48707
50246
  const agent_folder = $("create-agent-folder").value.trim();
@@ -48724,12 +50263,17 @@ Waiting for Claude login output…</pre
48724
50263
  configToggleChecked
48725
50264
  ),
48726
50265
  sessionPayload: buildCreateSessionPayload(),
50266
+ websitePayload: buildCreateWebsitePayload(),
48727
50267
  };
48728
50268
  }
48729
50269
 
48730
50270
  async function submitCreateAgent() {
48731
50271
  if (!hasRunnableCreateProvider()) {
48732
- const message = createRuntime
50272
+ const message = isProviderCatalogInitialPending()
50273
+ ? "Checking providers. Wait for the Host provider check to finish."
50274
+ : isProviderCatalogRequestFailed()
50275
+ ? "Could not load provider status. Retry the provider check before starting a session."
50276
+ : createRuntime
48733
50277
  ? `${getProviderDisplayLabel(
48734
50278
  createRuntime
48735
50279
  )} is currently unavailable on this Host. Update Host Preferences before starting a session.`
@@ -48754,6 +50298,7 @@ Waiting for Claude login output…</pre
48754
50298
  persistConfig,
48755
50299
  preventConfigCreate,
48756
50300
  sessionPayload,
50301
+ websitePayload,
48757
50302
  } = request;
48758
50303
  const sessionSetupProof = applyCreateSessionSetupProofAttributes(
48759
50304
  buildCreateSessionSetupProviderModelPermissionProof(sessionPayload)
@@ -48791,28 +50336,37 @@ Waiting for Claude login output…</pre
48791
50336
 
48792
50337
  const configPersistenceAction =
48793
50338
  getCreateConfigPersistenceAction(configToggleMode);
48794
- let failureStep = shouldPersistConfig
48795
- ? `Could not ${configPersistenceAction.toLowerCase()} .oysterun/config.json`
48796
- : resumeSessionId
48797
- ? "Could not resume session"
48798
- : "Could not start session";
50339
+ const shouldPersistWebsite = Boolean(websitePayload);
50340
+ let failureStep =
50341
+ shouldPersistConfig || shouldPersistWebsite
50342
+ ? `Could not ${configPersistenceAction.toLowerCase()} .oysterun/config.json`
50343
+ : resumeSessionId
50344
+ ? "Could not resume session"
50345
+ : "Could not start session";
48799
50346
  try {
48800
- if (shouldPersistConfig) {
50347
+ if (shouldPersistConfig || shouldPersistWebsite) {
48801
50348
  await apiWithAuthRetry(
48802
50349
  "PUT",
48803
50350
  "/agent/config",
48804
50351
  {
48805
50352
  agent_id,
48806
50353
  agent_folder,
48807
- ...sessionPayload,
48808
- session_setup_provider_model_permission_proof:
48809
- sessionSetupProof,
50354
+ ...(shouldPersistConfig
50355
+ ? {
50356
+ ...sessionPayload,
50357
+ session_setup_provider_model_permission_proof:
50358
+ sessionSetupProof,
50359
+ }
50360
+ : {}),
50361
+ ...(websitePayload || {}),
48810
50362
  },
48811
50363
  {
48812
50364
  onRetry: () => {
48813
50365
  if (!isCurrentCreateAttempt(attemptId)) return;
48814
50366
  setCreateStatus(
48815
- `${configPersistenceAction} .oysterun/config.json...`,
50367
+ shouldPersistConfig
50368
+ ? `${configPersistenceAction} .oysterun/config.json...`
50369
+ : "Updating Website settings...",
48816
50370
  "Retrying config save after a brief auth settle."
48817
50371
  );
48818
50372
  },
@@ -50190,6 +51744,7 @@ Waiting for Claude login output…</pre
50190
51744
  const folderPath = String(path || "").trim();
50191
51745
  if (!folderPath) {
50192
51746
  hideCreateConfigToggle("");
51747
+ setCreateWebsiteAuthorityState();
50193
51748
  refreshCreateStartButtonEnabled();
50194
51749
  return;
50195
51750
  }
@@ -50207,20 +51762,38 @@ Waiting for Claude login output…</pre
50207
51762
  )}${providerQuery}`
50208
51763
  );
50209
51764
  const resolvedFolderPath = data.agent_folder || folderPath;
51765
+ const currentFolderPath = String(
51766
+ $("create-agent-folder").value || ""
51767
+ ).trim();
51768
+ if (
51769
+ requestId !== latestCreateFolderConfigRequestId ||
51770
+ currentFolderPath !== folderPath
51771
+ ) {
51772
+ return;
51773
+ }
51774
+ setCreateWebsiteAuthorityState({
51775
+ folderPath: resolvedFolderPath,
51776
+ projectId: data.websiteProjectId || "",
51777
+ eligible:
51778
+ data.websiteEligible === true ||
51779
+ data.website?.browser_root_authorized === true,
51780
+ reason:
51781
+ data.website?.reason_message ||
51782
+ "Website is available only for agents in the Browser Root.",
51783
+ });
51784
+ if (opts.applyFormValues === false && isCreateWebsiteEligible()) {
51785
+ setCreateWebsiteEnabledValue(
51786
+ HOST_DEFAULT_SESSION_DEFAULTS.web.enabled
51787
+ );
51788
+ setCreateWebsiteAccessValue(
51789
+ HOST_DEFAULT_SESSION_DEFAULTS.web.access
51790
+ );
51791
+ }
50210
51792
  applyCreateConfigToggleMode(
50211
51793
  resolvedFolderPath,
50212
51794
  data.hasConfig === true
50213
51795
  );
50214
51796
  if (opts.applyFormValues !== false) {
50215
- const currentFolderPath = String(
50216
- $("create-agent-folder").value || ""
50217
- ).trim();
50218
- if (
50219
- requestId !== latestCreateFolderConfigRequestId ||
50220
- currentFolderPath !== folderPath
50221
- ) {
50222
- return;
50223
- }
50224
51797
  const preferencesUntouched =
50225
51798
  createFormPreferenceVersion === requestPreferenceVersion;
50226
51799
  $("create-agent-folder").value = resolvedFolderPath;
@@ -50307,6 +51880,10 @@ Waiting for Claude login output…</pre
50307
51880
  refreshCreateStartButtonEnabled();
50308
51881
  } catch (err) {
50309
51882
  hideCreateConfigToggle(folderPath);
51883
+ setCreateWebsiteAuthorityState({
51884
+ folderPath,
51885
+ eligible: false,
51886
+ });
50310
51887
  setCreateProviderSkillStatus(null);
50311
51888
  setCreateProviderTrustStatus(null);
50312
51889
  refreshCreateStartButtonEnabled();
@@ -50827,6 +52404,10 @@ Waiting for Claude login output…</pre
50827
52404
  markCreatePreferencesEdited();
50828
52405
  syncCreateAllowedPathsShortcutState();
50829
52406
  hideCreateConfigToggle($("create-agent-folder").value);
52407
+ setCreateWebsiteAuthorityState({
52408
+ folderPath: $("create-agent-folder").value,
52409
+ eligible: false,
52410
+ });
50830
52411
  refreshUpdateSessionNameButton();
50831
52412
  void refreshCreateProviderTrustStatus();
50832
52413
  refreshCreateStartButtonEnabled();
@@ -50834,6 +52415,7 @@ Waiting for Claude login output…</pre
50834
52415
  $("create-agent-id").addEventListener("input", () => {
50835
52416
  markCreatePreferencesEdited();
50836
52417
  syncCreateSessionNameDefault();
52418
+ syncCreateWebsiteAuthorityState();
50837
52419
  void refreshCreateProviderSkillStatus();
50838
52420
  void refreshCreateProviderTrustStatus();
50839
52421
  refreshCreateStartButtonEnabled();
@@ -50898,7 +52480,7 @@ Waiting for Claude login output…</pre
50898
52480
  );
50899
52481
  if (
50900
52482
  control instanceof HTMLInputElement &&
50901
- ["text", "url", "search"].includes(control.type)
52483
+ ["text", "url", "search", "password"].includes(control.type)
50902
52484
  ) {
50903
52485
  control.addEventListener(
50904
52486
  "input",
@@ -50906,6 +52488,10 @@ Waiting for Claude login output…</pre
50906
52488
  );
50907
52489
  }
50908
52490
  }
52491
+ $("host-voice-input-token")?.addEventListener(
52492
+ "input",
52493
+ handleHostVoiceInputTokenInput
52494
+ );
50909
52495
  document.addEventListener("change", (event) => {
50910
52496
  if (event.target.closest("[data-host-default-runtime-capability]")) {
50911
52497
  stageHostPreferencesDraftFromControls();
@@ -50914,6 +52500,9 @@ Waiting for Claude login output…</pre
50914
52500
  window.useStartFolderForAllowedPaths = useStartFolderForAllowedPaths;
50915
52501
  window.openPermissionsSettings = openPermissionsSettings;
50916
52502
  window.saveHostPreferences = saveHostPreferences;
52503
+ window.discardHostVoiceInputTokenDraft = discardHostVoiceInputTokenDraft;
52504
+ window.removeHostVoiceInputSavedToken = removeHostVoiceInputSavedToken;
52505
+ window.testHostVoiceInput = testHostVoiceInput;
50917
52506
  window.generateDirectHostLoginQr = generateDirectHostLoginQr;
50918
52507
  window.copyHostPreferencesDirectHostUrl =
50919
52508
  copyHostPreferencesDirectHostUrl;
@@ -51088,10 +52677,6 @@ Waiting for Claude login output…</pre
51088
52677
  refreshAgentProfileWorkspacePolicySaveState();
51089
52678
  return;
51090
52679
  }
51091
- if (event.target.closest("#agent-profile-identity-update-config")) {
51092
- renderAgentProfileRenameView(getSelectedLiveSession());
51093
- return;
51094
- }
51095
52680
  handleSessionLoopChange(event);
51096
52681
  handleHostSchedulerChange(event);
51097
52682
  });