oysterun 1.5.6 → 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 (38) hide show
  1. package/dev/client/web/index.html +1528 -102
  2. package/dev/client/web-chat/dist/assets/{ReactPrism-isi6q-rt.js → ReactPrism-DNQ9nZSp.js} +1 -1
  3. package/dev/client/web-chat/dist/assets/{ReactPrism-isi6q-rt.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-CS0AuJg_.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-Chh0JkSx.js → index-Dt0tJQEo.js} +2 -2
  8. package/dev/client/web-chat/dist/assets/{index-Chh0JkSx.js.map → index-Dt0tJQEo.js.map} +1 -1
  9. package/dev/client/web-chat/dist/assets/{index-CLXxZ4tL.js → index-kph-A9_0.js} +1 -1
  10. package/dev/client/web-chat/dist/assets/{index-CLXxZ4tL.js.map → index-kph-A9_0.js.map} +1 -1
  11. package/dev/client/web-chat/dist/assets/{pdf-BHndlpDv.js → pdf-Dz-8X_5I.js} +3 -3
  12. package/dev/client/web-chat/dist/assets/{pdf-BHndlpDv.js.map → pdf-Dz-8X_5I.js.map} +1 -1
  13. package/dev/client/web-chat/dist/assets/{web-DL4-fcjv.js → web-8cohp67o.js} +1 -1
  14. package/dev/client/web-chat/dist/assets/{web-DL4-fcjv.js.map → web-8cohp67o.js.map} +1 -1
  15. package/dev/client/web-chat/dist/assets/{web-DD_lXOHn.js → web-z_YJW2TC.js} +1 -1
  16. package/dev/client/web-chat/dist/assets/{web-DD_lXOHn.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/host-service/assets/voice-input/D1.wav +0 -0
  23. package/host-service/browser-root-project-index.mjs +0 -2
  24. package/host-service/config.mjs +7 -0
  25. package/host-service/folder-browser.mjs +8 -9
  26. package/host-service/package.json +4 -2
  27. package/host-service/portable-scheduler-definitions.mjs +16 -15
  28. package/host-service/provider-auth.mjs +10 -1
  29. package/host-service/routec-matrix-storage-adapter.mjs +4 -0
  30. package/host-service/server.mjs +482 -122
  31. package/host-service/templates/demo-agents/oysterun-github-tracker/.oysterun/schedulers.json +0 -24
  32. package/host-service/voice-input-owner-capture.mjs +225 -0
  33. package/host-service/voice-input-recording-collector.mjs +369 -0
  34. package/host-service/voice-input-service.mjs +755 -0
  35. package/host-service/voice-input-usage-contract.mjs +63 -0
  36. package/host-service/voice-input-usage-store.mjs +409 -0
  37. package/package.json +2 -1
  38. package/dev/client/web-chat/dist/assets/index-CS0AuJg_.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
@@ -9116,6 +9237,168 @@
9116
9237
  </div>
9117
9238
  </div>
9118
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>
9119
9402
  <section class="table-panel">
9120
9403
  <div class="table-title">
9121
9404
  <div class="table-title-main">
@@ -9792,6 +10075,55 @@
9792
10075
  `;
9793
10076
  }
9794
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
+
9795
10127
  function ChangePasswordView() {
9796
10128
  return html`
9797
10129
  <section id="change-password-view" class="view view-page-scroll">
@@ -10580,19 +10912,6 @@ Terminal output will appear here.</pre
10580
10912
  class="session-profile-action-stack"
10581
10913
  data-session-profile-action-block="session_identity"
10582
10914
  >
10583
- <label
10584
- class="toggle-row"
10585
- data-session-profile-identity-update-config-row="session_identity"
10586
- >
10587
- <span>
10588
- <strong>Update .oysterun/config.json</strong>
10589
- </span>
10590
- <input
10591
- id="agent-profile-identity-update-config"
10592
- type="checkbox"
10593
- data-session-profile-update-config="session_identity"
10594
- />
10595
- </label>
10596
10915
  <button
10597
10916
  class="btn-secondary session-profile-save-row"
10598
10917
  id="agent-profile-identity-save-btn"
@@ -11406,6 +11725,7 @@ Waiting for Claude login output…</pre
11406
11725
  <${SessionLoopView} />
11407
11726
  <${SessionSetupView} />
11408
11727
  <${HostPreferencesView} />
11728
+ <${VoiceInputUsageView} />
11409
11729
  <${ChangePasswordView} />
11410
11730
  <${TerminalView} />
11411
11731
  <${FolderPickerView} />
@@ -13334,7 +13654,7 @@ Waiting for Claude login output…</pre
13334
13654
  let latestCreateFolderConfigRequestId = 0;
13335
13655
  let createWebsiteAuthorityState = {
13336
13656
  folderPath: "",
13337
- projectAgentId: "",
13657
+ projectId: "",
13338
13658
  eligible: false,
13339
13659
  reason: "Website is available only for agents in the Browser Root.",
13340
13660
  };
@@ -13388,8 +13708,37 @@ Waiting for Claude login output…</pre
13388
13708
  preview: null,
13389
13709
  roots: {},
13390
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
+
13391
13739
  function createProviderCatalogState() {
13392
13740
  return {
13741
+ status: PROVIDER_CATALOG_STATUS_UNKNOWN,
13393
13742
  loaded: false,
13394
13743
  loading: false,
13395
13744
  error: "",
@@ -13459,6 +13808,7 @@ Waiting for Claude login output…</pre
13459
13808
  notificationSoundWebEnabled = true,
13460
13809
  notificationSoundAppEnabled = true,
13461
13810
  dailyTelemetryEnabled = false,
13811
+ voiceInputEnabled = false,
13462
13812
  defaultWebsiteAccess = HOST_DEFAULT_SESSION_DEFAULTS.web.access,
13463
13813
  defaultNotificationsEnabled = HOST_DEFAULT_SESSION_DEFAULTS
13464
13814
  .notifications.enabled,
@@ -13485,6 +13835,7 @@ Waiting for Claude login output…</pre
13485
13835
  notificationSoundWebEnabled,
13486
13836
  notificationSoundAppEnabled,
13487
13837
  dailyTelemetryEnabled,
13838
+ voiceInputEnabled,
13488
13839
  defaultWebsiteAccess,
13489
13840
  defaultNotificationsEnabled,
13490
13841
  defaultRuntimeCapabilities: normalizeRuntimeCapabilityState(
@@ -13516,6 +13867,34 @@ Waiting for Claude login output…</pre
13516
13867
  dailyTelemetryEnabled: false,
13517
13868
  dailyTelemetryConsentRecordedAt: "",
13518
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: "",
13519
13898
  defaultWebsiteAccess: HOST_DEFAULT_SESSION_DEFAULTS.web.access,
13520
13899
  defaultNotificationsEnabled:
13521
13900
  HOST_DEFAULT_SESSION_DEFAULTS.notifications.enabled,
@@ -13589,6 +13968,7 @@ Waiting for Claude login output…</pre
13589
13968
  };
13590
13969
  }
13591
13970
  let providerCatalogState = createProviderCatalogState();
13971
+ let providerCatalogLoadPromise = null;
13592
13972
  let providerModelRefreshState = {
13593
13973
  provider: "",
13594
13974
  refreshing: false,
@@ -13614,6 +13994,10 @@ Waiting for Claude login output…</pre
13614
13994
  let browserSurfaceState = createBrowserSurfaceState();
13615
13995
  let browserSurfaceLaunchRequestId = 0;
13616
13996
  let hostPreferencesState = createHostPreferencesState();
13997
+ let hostVoiceInputSampleObjectUrl = "";
13998
+ let hostVoiceInputSampleLoadPromise = null;
13999
+ let hostVoiceInputNativeAppVersionLoadPromise = null;
14000
+ let hostVoiceInputUsageLoadPromise = null;
13617
14001
  let hostRestartHandoffTimer = null;
13618
14002
  let hostRestartLoginGatePollTimer = null;
13619
14003
  let hostRestartLoginGatePollGeneration = 0;
@@ -14039,6 +14423,7 @@ Waiting for Claude login output…</pre
14039
14423
  $("host-notification-app-sound-toggle")?.checked !== false,
14040
14424
  dailyTelemetryEnabled:
14041
14425
  $("host-daily-telemetry-toggle")?.checked === true,
14426
+ voiceInputEnabled: getHostVoiceInputEnabledControlValue(),
14042
14427
  defaultWebsiteAccess: hostPreferencesState.defaultWebsiteAccess,
14043
14428
  defaultNotificationsEnabled:
14044
14429
  $("host-default-notifications-enabled")?.checked !== false,
@@ -14093,6 +14478,7 @@ Waiting for Claude login output…</pre
14093
14478
  notificationSoundWebEnabled: draft.notificationSoundWebEnabled,
14094
14479
  notificationSoundAppEnabled: draft.notificationSoundAppEnabled,
14095
14480
  dailyTelemetryEnabled: draft.dailyTelemetryEnabled,
14481
+ voiceInputEnabled: draft.voiceInputEnabled,
14096
14482
  defaultWebsiteAccess: draft.defaultWebsiteAccess,
14097
14483
  defaultNotificationsEnabled: draft.defaultNotificationsEnabled,
14098
14484
  defaultRuntimeCapabilities: normalizeRuntimeCapabilityState(
@@ -14125,6 +14511,7 @@ Waiting for Claude login output…</pre
14125
14511
  a.notificationSoundWebEnabled === b.notificationSoundWebEnabled &&
14126
14512
  a.notificationSoundAppEnabled === b.notificationSoundAppEnabled &&
14127
14513
  a.dailyTelemetryEnabled === b.dailyTelemetryEnabled &&
14514
+ a.voiceInputEnabled === b.voiceInputEnabled &&
14128
14515
  a.defaultWebsiteAccess === b.defaultWebsiteAccess &&
14129
14516
  a.defaultNotificationsEnabled === b.defaultNotificationsEnabled &&
14130
14517
  JSON.stringify(a.defaultRuntimeCapabilities) ===
@@ -14184,6 +14571,7 @@ Waiting for Claude login output…</pre
14184
14571
  const normalized = normalizeClientRoute(route);
14185
14572
  return (
14186
14573
  normalized.screen === "host-preferences" ||
14574
+ normalized.screen === "voice-input-usage" ||
14187
14575
  (normalized.screen === "folder-picker" &&
14188
14576
  normalized.targetInputId === HOST_PREFERENCES_FOLDER_PICKER_TARGET)
14189
14577
  );
@@ -14199,6 +14587,8 @@ Waiting for Claude login output…</pre
14199
14587
  defaultBrowsePath: savedDraft.defaultBrowsePath,
14200
14588
  notificationSoundWebEnabled: savedDraft.notificationSoundWebEnabled,
14201
14589
  notificationSoundAppEnabled: savedDraft.notificationSoundAppEnabled,
14590
+ dailyTelemetryEnabled: savedDraft.dailyTelemetryEnabled,
14591
+ voiceInputEnabled: savedDraft.voiceInputEnabled,
14202
14592
  defaultWebsiteAccess: savedDraft.defaultWebsiteAccess,
14203
14593
  defaultNotificationsEnabled: savedDraft.defaultNotificationsEnabled,
14204
14594
  defaultRuntimeCapabilities: normalizeRuntimeCapabilityState(
@@ -14234,6 +14624,7 @@ Waiting for Claude login output…</pre
14234
14624
  const normalized = normalizeClientRoute(route);
14235
14625
  return (
14236
14626
  normalized.screen === "host-preferences" ||
14627
+ normalized.screen === "voice-input-usage" ||
14237
14628
  normalized.screen === "agent-profile"
14238
14629
  );
14239
14630
  }
@@ -14242,6 +14633,14 @@ Waiting for Claude login output…</pre
14242
14633
  const current = normalizeClientRoute(currentRoute);
14243
14634
  const next = normalizeClientRoute(nextRoute);
14244
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
+ }
14245
14644
  if (
14246
14645
  current.screen === "host-preferences" &&
14247
14646
  next.screen === "folder-picker" &&
@@ -14353,11 +14752,8 @@ Waiting for Claude login output…</pre
14353
14752
  liveSession.sessionName || liveSession.session_name || ""
14354
14753
  ).trim();
14355
14754
  const draftName = String(agentProfileRenameState.draft || "").trim();
14356
- const updateConfig =
14357
- $("agent-profile-identity-update-config")?.checked === true;
14358
14755
  return (
14359
14756
  agentProfileRenameState.dirty === true ||
14360
- updateConfig === true ||
14361
14757
  (Boolean(draftName) && draftName !== currentName)
14362
14758
  );
14363
14759
  }
@@ -14397,7 +14793,10 @@ Waiting for Claude login output…</pre
14397
14793
 
14398
14794
  function getP75DirtyFormState() {
14399
14795
  const current = normalizeClientRoute(getCurrentRoute());
14400
- if (current.screen === "host-preferences") {
14796
+ if (
14797
+ current.screen === "host-preferences" ||
14798
+ current.screen === "voice-input-usage"
14799
+ ) {
14401
14800
  return hasUnsavedHostPreferencesChanges()
14402
14801
  ? {
14403
14802
  screen: "host-preferences",
@@ -15137,6 +15536,9 @@ Waiting for Claude login output…</pre
15137
15536
  // topbar/back handlers execute a declared route map instead of browser history.
15138
15537
  function getFallbackRoute(route = currentRoute) {
15139
15538
  const normalized = normalizeClientRoute(route);
15539
+ if (normalized.screen === "voice-input-usage") {
15540
+ return buildSettingsRoute();
15541
+ }
15140
15542
  if (normalized.screen === "file-preview") {
15141
15543
  // Source-aware File Preview routes return to their validated source first.
15142
15544
  // Direct or invalid routes still fall back to the containing Explorer.
@@ -15197,6 +15599,7 @@ Waiting for Claude login output…</pre
15197
15599
  const normalized = normalizeClientRoute(route);
15198
15600
  return (
15199
15601
  normalized.screen === "chat" ||
15602
+ normalized.screen === "voice-input-usage" ||
15200
15603
  normalized.screen === "file-preview" ||
15201
15604
  (normalized.screen === "mail" && Boolean(normalized.mailId)) ||
15202
15605
  (normalized.screen === "folder-picker" &&
@@ -19226,12 +19629,13 @@ Waiting for Claude login output…</pre
19226
19629
  entry,
19227
19630
  providerId,
19228
19631
  commandAvailable,
19632
+ authSupported,
19229
19633
  canStartLogin
19230
19634
  ) {
19231
19635
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
19232
19636
  return {
19233
19637
  provider: providerId,
19234
- supported: false,
19638
+ supported: authSupported,
19235
19639
  canStartLogin,
19236
19640
  state: "unknown",
19237
19641
  message: "",
@@ -19329,6 +19733,7 @@ Waiting for Claude login output…</pre
19329
19733
  entry.auth_status,
19330
19734
  providerId,
19331
19735
  entry.command_available === true,
19736
+ entry.auth_supported === true,
19332
19737
  entry.auth_start_supported === true
19333
19738
  ),
19334
19739
  modelCatalogStatus:
@@ -19439,6 +19844,20 @@ Waiting for Claude login output…</pre
19439
19844
  return getProviderCatalogEntry(providerId)?.commandAvailable === true;
19440
19845
  }
19441
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
+
19442
19861
  function getProviderAuthStatus(providerId) {
19443
19862
  return getProviderCatalogEntry(providerId)?.authStatus || null;
19444
19863
  }
@@ -19454,6 +19873,7 @@ Waiting for Claude login output…</pre
19454
19873
  nextStatus,
19455
19874
  providerId,
19456
19875
  entry.commandAvailable === true,
19876
+ entry.authSupported === true,
19457
19877
  entry.authStartSupported === true
19458
19878
  ),
19459
19879
  }
@@ -19491,7 +19911,12 @@ Waiting for Claude login output…</pre
19491
19911
  function buildDefaultProviderWarning(
19492
19912
  savedProvider = hostPreferencesState.defaultProvider
19493
19913
  ) {
19494
- if (!savedProvider || isProviderAvailable(savedProvider)) {
19914
+ if (
19915
+ !savedProvider ||
19916
+ isProviderCatalogInitialPending() ||
19917
+ isProviderCatalogRequestFailed() ||
19918
+ isProviderAvailable(savedProvider)
19919
+ ) {
19495
19920
  return "";
19496
19921
  }
19497
19922
  const effectiveProvider =
@@ -21173,6 +21598,10 @@ Waiting for Claude login output…</pre
21173
21598
  return normalizeClientRoute({ screen: "host-preferences" });
21174
21599
  }
21175
21600
 
21601
+ function buildVoiceInputUsageRoute() {
21602
+ return normalizeClientRoute({ screen: "voice-input-usage" });
21603
+ }
21604
+
21176
21605
  function buildChangePasswordRoute() {
21177
21606
  return normalizeClientRoute({ screen: "change-password" });
21178
21607
  }
@@ -21437,6 +21866,9 @@ Waiting for Claude login output…</pre
21437
21866
  if (screen === "host-preferences") {
21438
21867
  return { screen: "host-preferences", warning };
21439
21868
  }
21869
+ if (screen === "voice-input-usage") {
21870
+ return { screen: "voice-input-usage", warning };
21871
+ }
21440
21872
  if (screen === "change-password") {
21441
21873
  return { screen: "change-password", warning };
21442
21874
  }
@@ -21668,6 +22100,9 @@ Waiting for Claude login output…</pre
21668
22100
  if (pathname === "/app/settings") {
21669
22101
  return buildSettingsRoute();
21670
22102
  }
22103
+ if (pathname === "/app/host-preferences/voice-input-usage") {
22104
+ return buildVoiceInputUsageRoute();
22105
+ }
21671
22106
  if (pathname === "/app/change-password") {
21672
22107
  return buildChangePasswordRoute();
21673
22108
  }
@@ -21795,6 +22230,8 @@ Waiting for Claude login output…</pre
21795
22230
  }
21796
22231
  } else if (normalized.screen === "host-preferences") {
21797
22232
  url.pathname = "/app/settings";
22233
+ } else if (normalized.screen === "voice-input-usage") {
22234
+ url.pathname = "/app/host-preferences/voice-input-usage";
21798
22235
  } else if (normalized.screen === "change-password") {
21799
22236
  url.pathname = "/app/change-password";
21800
22237
  } else if (normalized.screen === "terminal") {
@@ -22026,6 +22463,12 @@ Waiting for Claude login output…</pre
22026
22463
  }
22027
22464
  } else if (normalized.screen === "host-preferences") {
22028
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();
22029
22472
  } else if (normalized.screen === "change-password") {
22030
22473
  renderChangePasswordView();
22031
22474
  } else if (normalized.screen === "terminal") {
@@ -24987,6 +25430,7 @@ Waiting for Claude login output…</pre
24987
25430
  explorer: "file_explorer",
24988
25431
  "session-setup": "session_setup",
24989
25432
  "host-preferences": "host_preferences",
25433
+ "voice-input-usage": "host_preferences",
24990
25434
  terminal: "terminal",
24991
25435
  "folder-picker": "file_explorer",
24992
25436
  "file-preview": "file_preview",
@@ -29609,6 +30053,12 @@ Waiting for Claude login output…</pre
29609
30053
  subtitle: "",
29610
30054
  };
29611
30055
  }
30056
+ if (screen === "voice-input-usage") {
30057
+ return {
30058
+ title: "Voice Input Usage",
30059
+ subtitle: "",
30060
+ };
30061
+ }
29612
30062
  if (screen === "change-password") {
29613
30063
  return {
29614
30064
  title: "Change Password",
@@ -29969,6 +30419,10 @@ Waiting for Claude login output…</pre
29969
30419
  "active",
29970
30420
  screen === "host-preferences"
29971
30421
  );
30422
+ $("voice-input-usage-view").classList.toggle(
30423
+ "active",
30424
+ screen === "voice-input-usage"
30425
+ );
29972
30426
  $("change-password-view").classList.toggle(
29973
30427
  "active",
29974
30428
  screen === "change-password"
@@ -30022,6 +30476,14 @@ Waiting for Claude login output…</pre
30022
30476
  }
30023
30477
  if (screen === "host-preferences") {
30024
30478
  renderHostPreferences();
30479
+ } else if (
30480
+ hostPreferencesState.voiceInputSampleLoaded ||
30481
+ hostVoiceInputSampleObjectUrl
30482
+ ) {
30483
+ releaseHostVoiceInputSampleAudio();
30484
+ }
30485
+ if (screen === "voice-input-usage") {
30486
+ renderHostVoiceInputUsageDetail();
30025
30487
  }
30026
30488
  if (screen === "change-password") {
30027
30489
  renderChangePasswordView();
@@ -30051,6 +30513,10 @@ Waiting for Claude login output…</pre
30051
30513
  void navigateToRoute(buildSettingsRoute());
30052
30514
  return;
30053
30515
  }
30516
+ if (entry.screen === "voice-input-usage") {
30517
+ void navigateToRoute(buildVoiceInputUsageRoute());
30518
+ return;
30519
+ }
30054
30520
  if (entry.screen === "change-password") {
30055
30521
  void navigateToRoute(buildChangePasswordRoute());
30056
30522
  return;
@@ -34317,6 +34783,11 @@ Waiting for Claude login output…</pre
34317
34783
  const notificationSoundAppEnabled =
34318
34784
  data.notification_sound_app_enabled !== false;
34319
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;
34320
34791
  const debugShowCapabilityUi = data.debug_show_capability_ui === true;
34321
34792
  const showInterfaceStyleInSessionSetupProfile =
34322
34793
  data.show_interface_style_in_session_setup_profile === true;
@@ -34353,6 +34824,7 @@ Waiting for Claude login output…</pre
34353
34824
  notificationSoundWebEnabled,
34354
34825
  notificationSoundAppEnabled,
34355
34826
  dailyTelemetryEnabled,
34827
+ voiceInputEnabled,
34356
34828
  defaultWebsiteAccess: normalizeAgentWebAccess(webDefaults.access),
34357
34829
  defaultNotificationsEnabled: notificationDefaults.enabled !== false,
34358
34830
  defaultRuntimeCapabilities: runtimeCapabilityDefaults,
@@ -34409,6 +34881,42 @@ Waiting for Claude login output…</pre
34409
34881
  : "",
34410
34882
  dailyTelemetryHostIdPresent:
34411
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,
34412
34920
  defaultWebsiteAccess: normalizeAgentWebAccess(webDefaults.access),
34413
34921
  defaultNotificationsEnabled: notificationDefaults.enabled !== false,
34414
34922
  defaultRuntimeCapabilities: runtimeCapabilityDefaults,
@@ -34498,17 +35006,6 @@ Waiting for Claude login output…</pre
34498
35006
  });
34499
35007
  syncSessionSetupInterfaceStyleVisibility();
34500
35008
  renderCreateTelegramFeatureVisibility();
34501
- if (!providerCatalogState.loaded) {
34502
- providerCatalogState = {
34503
- ...providerCatalogState,
34504
- providers: KNOWN_PROVIDER_ORDER.map((providerId) => ({
34505
- id: providerId,
34506
- label: getProviderDisplayLabel(providerId),
34507
- commandAvailable: false,
34508
- runtimeSupported: true,
34509
- })),
34510
- };
34511
- }
34512
35009
  renderHostPreferences();
34513
35010
  setCreateRuntime(getEffectiveCreateRuntime(createRuntime), {
34514
35011
  preserveSelection: true,
@@ -34520,11 +35017,18 @@ Waiting for Claude login output…</pre
34520
35017
  const normalizedProviders = Array.isArray(data?.providers)
34521
35018
  ? data.providers.map(normalizeProviderCatalogEntry).filter(Boolean)
34522
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
+ });
34523
35026
  providerCatalogState = {
35027
+ status: getProviderCatalogStatusForProviders(providers),
34524
35028
  loaded: true,
34525
35029
  loading: false,
34526
35030
  error: "",
34527
- providers: normalizedProviders,
35031
+ providers,
34528
35032
  };
34529
35033
  hostPreferencesState = {
34530
35034
  ...hostPreferencesState,
@@ -34545,36 +35049,50 @@ Waiting for Claude login output…</pre
34545
35049
  });
34546
35050
  }
34547
35051
 
34548
- async function loadProviderCatalog(opts = {}) {
34549
- if (providerCatalogState.loading) return;
35052
+ function loadProviderCatalog(opts = {}) {
35053
+ if (providerCatalogLoadPromise) return providerCatalogLoadPromise;
34550
35054
  providerCatalogState = {
34551
35055
  ...providerCatalogState,
35056
+ status: providerCatalogState.loaded
35057
+ ? providerCatalogState.status
35058
+ : PROVIDER_CATALOG_STATUS_PENDING,
34552
35059
  loading: true,
34553
35060
  error: "",
34554
35061
  };
34555
35062
  renderHostPreferences();
34556
- try {
34557
- const data = await readDashboardProviderCatalog({
34558
- force: opts.force === true,
34559
- });
34560
- applyProviderCatalogData(data);
34561
- } catch (err) {
34562
- if (await handleHostPreferencesAuthFailure(err)) return;
34563
- providerCatalogState = {
34564
- ...providerCatalogState,
34565
- loading: false,
34566
- error: err.message || "Could not load provider status.",
34567
- };
34568
- renderHostPreferences();
34569
- if (opts.quiet !== true) {
34570
- 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;
34571
35089
  }
34572
- }
35090
+ })();
35091
+ return providerCatalogLoadPromise;
34573
35092
  }
34574
35093
 
34575
35094
  function retryHostPreferencesProviderSection() {
34576
- if (providerCatalogState.loading) return;
34577
- void loadProviderCatalog({ force: true, quiet: true });
35095
+ return loadProviderCatalog({ force: true, quiet: true });
34578
35096
  }
34579
35097
 
34580
35098
  function clearProviderAuthJobPollTimer() {
@@ -34746,7 +35264,6 @@ Waiting for Claude login output…</pre
34746
35264
  ? response.provider.trim()
34747
35265
  : "claude";
34748
35266
  if (response.provider_auth) {
34749
- invalidateDashboardDataCaches({ providers: true });
34750
35267
  setProviderCatalogAuthStatus(providerId, response.provider_auth);
34751
35268
  }
34752
35269
  providerAuthModalState = {
@@ -34758,6 +35275,7 @@ Waiting for Claude login output…</pre
34758
35275
  response.provider_auth,
34759
35276
  providerId,
34760
35277
  getProviderCatalogEntry(providerId)?.commandAvailable === true,
35278
+ getProviderCatalogEntry(providerId)?.authSupported === true,
34761
35279
  getProviderCatalogEntry(providerId)?.authStartSupported === true
34762
35280
  )
34763
35281
  : getProviderAuthStatus(providerId),
@@ -34829,13 +35347,13 @@ Waiting for Claude login output…</pre
34829
35347
  "GET",
34830
35348
  `/provider-auth/status?provider=${encodeURIComponent(providerId)}`
34831
35349
  );
34832
- invalidateDashboardDataCaches({ providers: true });
34833
35350
  setProviderCatalogAuthStatus(providerId, data.provider_auth || null);
34834
35351
  if (providerAuthModalState.provider === providerId) {
34835
35352
  const normalizedStatus = normalizeProviderAuthStatus(
34836
35353
  data.provider_auth,
34837
35354
  providerId,
34838
35355
  getProviderCatalogEntry(providerId)?.commandAvailable === true,
35356
+ getProviderCatalogEntry(providerId)?.authSupported === true,
34839
35357
  getProviderCatalogEntry(providerId)?.authStartSupported === true
34840
35358
  );
34841
35359
  providerAuthModalState = {
@@ -35004,6 +35522,7 @@ Waiting for Claude login output…</pre
35004
35522
  data.provider_auth,
35005
35523
  providerId,
35006
35524
  getProviderCatalogEntry(providerId)?.commandAvailable === true,
35525
+ getProviderCatalogEntry(providerId)?.authSupported === true,
35007
35526
  getProviderCatalogEntry(providerId)?.authStartSupported === true
35008
35527
  ),
35009
35528
  job,
@@ -35689,7 +36208,11 @@ Waiting for Claude login output…</pre
35689
36208
  if (!availableProviderIds.length) {
35690
36209
  const option = document.createElement("option");
35691
36210
  option.value = "";
35692
- option.textContent = "No available providers";
36211
+ option.textContent = isProviderCatalogInitialPending()
36212
+ ? "Checking providers..."
36213
+ : isProviderCatalogRequestFailed()
36214
+ ? "Provider status unavailable"
36215
+ : "No available providers";
35693
36216
  option.disabled = true;
35694
36217
  option.selected = true;
35695
36218
  select.appendChild(option);
@@ -35738,6 +36261,8 @@ Waiting for Claude login output…</pre
35738
36261
  );
35739
36262
  const runtimeLocked =
35740
36263
  isResumeMode() && Boolean(pendingResumeSourceRuntime);
36264
+ const providerCatalogPending = isProviderCatalogInitialPending();
36265
+ const providerCatalogFailed = isProviderCatalogRequestFailed();
35741
36266
  setVisible("runtime-claude", claudeVisible);
35742
36267
  setVisible("runtime-codex", codexVisible);
35743
36268
  setVisible("runtime-debug-fixture", debugFixtureVisible);
@@ -35783,6 +36308,26 @@ Waiting for Claude login output…</pre
35783
36308
  }
35784
36309
  refreshCreateStartButtonEnabled();
35785
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
+ }
35786
36331
  if (!visibleProviderIds.length) {
35787
36332
  if (modelSelect) {
35788
36333
  modelSelect.innerHTML = "";
@@ -35937,6 +36482,711 @@ Waiting for Claude login output…</pre
35937
36482
  };
35938
36483
  }
35939
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
+
35940
37190
  function renderHostPreferences() {
35941
37191
  const pathInput = $("host-default-browse-path");
35942
37192
  if (
@@ -36067,10 +37317,8 @@ Waiting for Claude login output…</pre
36067
37317
  basicPreferencesUnavailable;
36068
37318
  const providerSectionStatusVisible =
36069
37319
  providerCatalogState.loading || Boolean(providerCatalogState.error);
36070
- const providerSectionInitialPending =
36071
- providerCatalogState.loading && !providerCatalogState.loaded;
36072
- const providerSectionUnavailable =
36073
- !providerCatalogState.loaded && Boolean(providerCatalogState.error);
37320
+ const providerSectionInitialPending = isProviderCatalogInitialPending();
37321
+ const providerSectionUnavailable = isProviderCatalogRequestFailed();
36074
37322
  const providerControlsDisabled =
36075
37323
  disabled ||
36076
37324
  providerSectionInitialPending ||
@@ -36078,12 +37326,12 @@ Waiting for Claude login output…</pre
36078
37326
  let providerSectionStatusCopy = "";
36079
37327
  if (providerCatalogState.loading) {
36080
37328
  providerSectionStatusCopy = providerCatalogState.loaded
36081
- ? "Refreshing provider status..."
36082
- : "Checking provider status...";
37329
+ ? "Refreshing provider availability..."
37330
+ : "Checking provider availability...";
36083
37331
  } else if (providerCatalogState.error) {
36084
37332
  providerSectionStatusCopy = providerCatalogState.loaded
36085
- ? "Could not refresh provider status. Existing values remain available."
36086
- : "Could not load provider status.";
37333
+ ? "Could not refresh provider availability. Existing values remain available."
37334
+ : "Could not load provider availability.";
36087
37335
  }
36088
37336
  setVisible(
36089
37337
  "host-provider-section-status-label",
@@ -36471,15 +37719,11 @@ Waiting for Claude login output…</pre
36471
37719
  );
36472
37720
  setText(
36473
37721
  "host-claude-command-status",
36474
- isProviderAvailable("claude")
36475
- ? "Available on this Host."
36476
- : "Unavailable on this Host. Blank disables Claude explicitly."
37722
+ describeProviderCommandAvailability("claude")
36477
37723
  );
36478
37724
  setText(
36479
37725
  "host-codex-command-status",
36480
- isProviderAvailable("codex")
36481
- ? "Available on this Host."
36482
- : "Unavailable on this Host. Blank disables Codex explicitly."
37726
+ describeProviderCommandAvailability("codex")
36483
37727
  );
36484
37728
  setText(
36485
37729
  "host-default-provider-warning",
@@ -36499,6 +37743,7 @@ Waiting for Claude login output…</pre
36499
37743
  hostPreferencesState.debugHostPreferencesFullDiskAccessBlockEnabled ===
36500
37744
  true;
36501
37745
  renderHostNotificationPreferences({ disabled });
37746
+ renderHostVoiceInputPreferences({ disabled });
36502
37747
  setVisible("host-folder-access-card", folderAccessVisible);
36503
37748
  $("host-public-base-url")?.toggleAttribute(
36504
37749
  "disabled",
@@ -36585,6 +37830,7 @@ Waiting for Claude login output…</pre
36585
37830
  error: "",
36586
37831
  };
36587
37832
  renderHostPreferences();
37833
+ void loadHostVoiceInputNativeAppVersion();
36588
37834
  try {
36589
37835
  const data = await apiWithAuthRetry("GET", "/dev/preferences");
36590
37836
  applyHostPreferencesData(data);
@@ -37018,6 +38264,192 @@ Waiting for Claude login output…</pre
37018
38264
  discardUnsavedHostPreferencesChanges();
37019
38265
  }
37020
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
+
37021
38453
  async function saveHostPreferences() {
37022
38454
  if (hostPreferencesState.saving) {
37023
38455
  refreshHostPreferencesSaveButton();
@@ -37053,6 +38485,8 @@ Waiting for Claude login output…</pre
37053
38485
  $("host-notification-app-sound-toggle")?.checked !== false;
37054
38486
  const requestedDailyTelemetryEnabled =
37055
38487
  $("host-daily-telemetry-toggle")?.checked === true;
38488
+ const requestedVoiceInputEnabled =
38489
+ getHostVoiceInputEnabledControlValue();
37056
38490
  const claudeCommandInput = $("host-claude-command");
37057
38491
  const requestedClaudeCommand = claudeCommandInput
37058
38492
  ? claudeCommandInput.value.trim()
@@ -37127,6 +38561,7 @@ Waiting for Claude login output…</pre
37127
38561
  notificationSoundWebEnabled: requestedWebNotificationSound,
37128
38562
  notificationSoundAppEnabled: requestedAppNotificationSound,
37129
38563
  dailyTelemetryEnabled: requestedDailyTelemetryEnabled,
38564
+ voiceInputEnabled: requestedVoiceInputEnabled,
37130
38565
  publicBaseUrl: requestedPublicBaseUrl,
37131
38566
  defaultBrowsePath: requestedPath,
37132
38567
  defaultProvider: nextDefaultProvider,
@@ -37156,6 +38591,10 @@ Waiting for Claude login output…</pre
37156
38591
  notification_sound_web_enabled: requestedWebNotificationSound,
37157
38592
  notification_sound_app_enabled: requestedAppNotificationSound,
37158
38593
  daily_telemetry_enabled: requestedDailyTelemetryEnabled,
38594
+ voice_input: {
38595
+ enabled: requestedVoiceInputEnabled,
38596
+ token_action: "retain",
38597
+ },
37159
38598
  direct_host_url: requestedPublicBaseUrl || null,
37160
38599
  public_base_url: requestedPublicBaseUrl || null,
37161
38600
  default_browse_path: requestedPath,
@@ -37783,11 +39222,7 @@ Waiting for Claude login output…</pre
37783
39222
  }
37784
39223
 
37785
39224
  function isCreateWebsiteEligible() {
37786
- return (
37787
- createWebsiteAuthorityState.eligible === true &&
37788
- createWebsiteAuthorityState.projectAgentId ===
37789
- String($("create-agent-id")?.value || "").trim()
37790
- );
39225
+ return createWebsiteAuthorityState.eligible === true;
37791
39226
  }
37792
39227
 
37793
39228
  function syncCreateWebsiteAuthorityState() {
@@ -37815,13 +39250,13 @@ Waiting for Claude login output…</pre
37815
39250
 
37816
39251
  function setCreateWebsiteAuthorityState({
37817
39252
  folderPath = "",
37818
- projectAgentId = "",
39253
+ projectId = "",
37819
39254
  eligible = false,
37820
39255
  reason = "",
37821
39256
  } = {}) {
37822
39257
  createWebsiteAuthorityState = {
37823
39258
  folderPath: String(folderPath || "").trim(),
37824
- projectAgentId: String(projectAgentId || "").trim(),
39259
+ projectId: String(projectId || "").trim(),
37825
39260
  eligible: eligible === true,
37826
39261
  reason:
37827
39262
  String(reason || "").trim() ||
@@ -39146,7 +40581,6 @@ Waiting for Claude login output…</pre
39146
40581
  liveSession = getSelectedLiveSession()
39147
40582
  ) {
39148
40583
  const input = $("agent-profile-rename-input");
39149
- const updateConfig = $("agent-profile-identity-update-config");
39150
40584
  const saveBtn =
39151
40585
  $("agent-profile-identity-save-btn") ||
39152
40586
  $("agent-profile-rename-save-btn");
@@ -39154,10 +40588,6 @@ Waiting for Claude login output…</pre
39154
40588
  if (!liveSession) {
39155
40589
  input.value = "";
39156
40590
  input.disabled = true;
39157
- if (updateConfig) {
39158
- updateConfig.checked = false;
39159
- updateConfig.disabled = true;
39160
- }
39161
40591
  saveBtn.disabled = true;
39162
40592
  saveBtn.textContent = "Save";
39163
40593
  showError("agent-profile-rename-error", "");
@@ -39170,18 +40600,14 @@ Waiting for Claude login output…</pre
39170
40600
  ).trim();
39171
40601
  const draftName = String(agentProfileRenameState.draft || "");
39172
40602
  const normalizedDraft = draftName.trim();
39173
- const persistIdentity = updateConfig?.checked === true;
39174
40603
  input.value = draftName;
39175
40604
  input.disabled = agentProfileRenameState.saving === true;
39176
- if (updateConfig) {
39177
- updateConfig.disabled = agentProfileRenameState.saving === true;
39178
- }
39179
40605
  saveBtn.textContent =
39180
40606
  agentProfileRenameState.saving === true ? "Saving..." : "Save";
39181
40607
  saveBtn.disabled =
39182
40608
  agentProfileRenameState.saving === true ||
39183
40609
  !normalizedDraft ||
39184
- (normalizedDraft === currentName && !persistIdentity);
40610
+ normalizedDraft === currentName;
39185
40611
  showError(
39186
40612
  "agent-profile-rename-error",
39187
40613
  agentProfileRenameState.error || ""
@@ -39199,8 +40625,6 @@ Waiting for Claude login output…</pre
39199
40625
  const currentSessionName = String(
39200
40626
  liveSession.sessionName || liveSession.session_name || ""
39201
40627
  ).trim();
39202
- const updateConfig =
39203
- $("agent-profile-identity-update-config")?.checked === true;
39204
40628
  if (!nextSessionName) {
39205
40629
  agentProfileRenameState = {
39206
40630
  ...agentProfileRenameState,
@@ -39209,7 +40633,7 @@ Waiting for Claude login output…</pre
39209
40633
  renderAgentProfileRenameView(liveSession);
39210
40634
  return;
39211
40635
  }
39212
- if (nextSessionName === currentSessionName && !updateConfig) {
40636
+ if (nextSessionName === currentSessionName) {
39213
40637
  agentProfileRenameState = {
39214
40638
  ...agentProfileRenameState,
39215
40639
  draft: currentSessionName,
@@ -39231,7 +40655,6 @@ Waiting for Claude login output…</pre
39231
40655
  const data = await api("POST", "/session/rename", {
39232
40656
  session_id: liveSession.sessionId,
39233
40657
  session_name: nextSessionName,
39234
- update_config: updateConfig,
39235
40658
  });
39236
40659
  const resolvedSessionName = data.session_name || nextSessionName;
39237
40660
  patchLiveSession(liveSession.sessionId, {
@@ -39246,11 +40669,7 @@ Waiting for Claude login output…</pre
39246
40669
  resolvedSessionName
39247
40670
  );
39248
40671
  renderSelectedAgent();
39249
- showBanner(
39250
- data.config_mutated === true
39251
- ? `Renamed session to ${resolvedSessionName} and updated .oysterun/config.json.`
39252
- : `Renamed session to ${resolvedSessionName}.`
39253
- );
40672
+ showBanner(`Renamed session to ${resolvedSessionName}.`);
39254
40673
  } catch (err) {
39255
40674
  agentProfileRenameState = {
39256
40675
  ...agentProfileRenameState,
@@ -48850,7 +50269,11 @@ Waiting for Claude login output…</pre
48850
50269
 
48851
50270
  async function submitCreateAgent() {
48852
50271
  if (!hasRunnableCreateProvider()) {
48853
- 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
48854
50277
  ? `${getProviderDisplayLabel(
48855
50278
  createRuntime
48856
50279
  )} is currently unavailable on this Host. Update Host Preferences before starting a session.`
@@ -50350,7 +51773,7 @@ Waiting for Claude login output…</pre
50350
51773
  }
50351
51774
  setCreateWebsiteAuthorityState({
50352
51775
  folderPath: resolvedFolderPath,
50353
- projectAgentId: data.websiteAgentId || "",
51776
+ projectId: data.websiteProjectId || "",
50354
51777
  eligible:
50355
51778
  data.websiteEligible === true ||
50356
51779
  data.website?.browser_root_authorized === true,
@@ -51057,7 +52480,7 @@ Waiting for Claude login output…</pre
51057
52480
  );
51058
52481
  if (
51059
52482
  control instanceof HTMLInputElement &&
51060
- ["text", "url", "search"].includes(control.type)
52483
+ ["text", "url", "search", "password"].includes(control.type)
51061
52484
  ) {
51062
52485
  control.addEventListener(
51063
52486
  "input",
@@ -51065,6 +52488,10 @@ Waiting for Claude login output…</pre
51065
52488
  );
51066
52489
  }
51067
52490
  }
52491
+ $("host-voice-input-token")?.addEventListener(
52492
+ "input",
52493
+ handleHostVoiceInputTokenInput
52494
+ );
51068
52495
  document.addEventListener("change", (event) => {
51069
52496
  if (event.target.closest("[data-host-default-runtime-capability]")) {
51070
52497
  stageHostPreferencesDraftFromControls();
@@ -51073,6 +52500,9 @@ Waiting for Claude login output…</pre
51073
52500
  window.useStartFolderForAllowedPaths = useStartFolderForAllowedPaths;
51074
52501
  window.openPermissionsSettings = openPermissionsSettings;
51075
52502
  window.saveHostPreferences = saveHostPreferences;
52503
+ window.discardHostVoiceInputTokenDraft = discardHostVoiceInputTokenDraft;
52504
+ window.removeHostVoiceInputSavedToken = removeHostVoiceInputSavedToken;
52505
+ window.testHostVoiceInput = testHostVoiceInput;
51076
52506
  window.generateDirectHostLoginQr = generateDirectHostLoginQr;
51077
52507
  window.copyHostPreferencesDirectHostUrl =
51078
52508
  copyHostPreferencesDirectHostUrl;
@@ -51247,10 +52677,6 @@ Waiting for Claude login output…</pre
51247
52677
  refreshAgentProfileWorkspacePolicySaveState();
51248
52678
  return;
51249
52679
  }
51250
- if (event.target.closest("#agent-profile-identity-update-config")) {
51251
- renderAgentProfileRenameView(getSelectedLiveSession());
51252
- return;
51253
- }
51254
52680
  handleSessionLoopChange(event);
51255
52681
  handleHostSchedulerChange(event);
51256
52682
  });