arcane-os 0.5.13 → 0.5.15

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 (55) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +33 -8
  3. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +11 -10
  4. package/browser-runtime/ai/browser-wllama-runtime.mjs +2 -1
  5. package/browser-runtime/ai/model-controller.mjs +6 -5
  6. package/browser-runtime/ai/speech-worker-client.mjs +45 -3
  7. package/browser-runtime/event-manager.mjs +2 -1
  8. package/browser-runtime/logging.mjs +58 -0
  9. package/docs/architecture.md +1 -1
  10. package/docs/reference/README.md +9 -7
  11. package/docs/reference/ai/browser-speech.md +90 -1
  12. package/docs/reference/ai/twin-cloud.md +1 -1
  13. package/docs/reference/cli.md +1 -1
  14. package/docs/reference/core/arcane-ai-contracts.md +1 -1
  15. package/docs/reference/inventory/package-api.json +34 -6
  16. package/docs/reference/inventory/runtime-components.json +1 -1
  17. package/docs/reference/inventory/runtime-modules.json +4 -4
  18. package/docs/reference/protocols.md +5 -5
  19. package/docs/reference/runtime-modules.md +70 -15
  20. package/docs/reference/sdk-api.md +120 -11
  21. package/package.json +4 -3
  22. package/runtime/arcane/components/app-bar.html +3 -1
  23. package/runtime/arcane/components/chat.html +22 -20
  24. package/runtime/arcane/components/dashboard-config.html +3 -1
  25. package/runtime/arcane/components/data-maintenance.html +4 -2
  26. package/runtime/arcane/components/data-view.html +3 -1
  27. package/runtime/arcane/components/directory-picker.html +3 -1
  28. package/runtime/arcane/components/file-manager.html +11 -9
  29. package/runtime/arcane/components/header.html +5 -3
  30. package/runtime/arcane/components/markdown-document.html +3 -1
  31. package/runtime/arcane/components/markdown-editor.html +4 -2
  32. package/runtime/arcane/components/modal.html +3 -1
  33. package/runtime/arcane/components/screen-capture.html +4 -2
  34. package/runtime/arcane/components/speech.html +10 -8
  35. package/runtime/arcane/components/table.html +3 -1
  36. package/runtime/arcane/components/voice-transcription.html +8 -6
  37. package/runtime/arcane/entities/Chat.js +5 -4
  38. package/runtime/arcane/entities/User.js +2 -1
  39. package/runtime/arcane/modules/AI.js +442 -292
  40. package/runtime/arcane/modules/AIProviderRuntime.js +359 -82
  41. package/runtime/arcane/modules/CommunicationAppController.js +2 -1
  42. package/runtime/arcane/modules/ComponentContracts.js +3 -2
  43. package/runtime/arcane/modules/ConversationTimebox.js +2 -1
  44. package/runtime/arcane/modules/DBOPFS.js +5 -4
  45. package/runtime/arcane/modules/Errors.js +3 -14
  46. package/runtime/arcane/modules/HTMLImport.js +4 -3
  47. package/runtime/arcane/modules/LocalAIReadinessController.js +2 -1
  48. package/runtime/arcane/modules/MD.js +3 -2
  49. package/runtime/arcane/modules/MailOutbox.mjs +2 -1
  50. package/runtime/arcane/modules/PersistentAIChatSession.js +2 -1
  51. package/runtime/arcane/modules/ScreenCapture.js +2 -1
  52. package/runtime/arcane/modules/SpeechPlayback.js +206 -40
  53. package/runtime/arcane/modules/ThemeBootstrap.js +3 -2
  54. package/runtime/arcane/modules/ToolCallRouter.js +2 -1
  55. package/src/import-map.mjs +65 -10
@@ -1,3 +1,4 @@
1
+ import {arcaneLogging} from 'arcane-os/logging';
1
2
  import {
2
3
  AI_RUNTIME_ROLES,
3
4
  getAIRuntimeState,
@@ -1708,6 +1709,7 @@ export class AIProviderRuntime {
1708
1709
  }
1709
1710
 
1710
1711
  get speechMuted() {
1712
+ arcaneLogging.debug('[Arcane speech runtime] speechMuted', this.#speechMuted);
1711
1713
  return this.#speechMuted;
1712
1714
  }
1713
1715
 
@@ -1719,6 +1721,9 @@ export class AIProviderRuntime {
1719
1721
  this.#assertOpen();
1720
1722
  this.#assertNotConfiguring();
1721
1723
  const admitted = validateProvider(provider);
1724
+ if (SPEECH_ROLES.includes(admitted.role)) {
1725
+ arcaneLogging.debug('[Arcane speech runtime] register', provider);
1726
+ }
1722
1727
  const key = providerKey(admitted.role, admitted.id);
1723
1728
  const existing = this.#providers.get(key);
1724
1729
  if (existing && existing !== admitted) {
@@ -1785,10 +1790,17 @@ export class AIProviderRuntime {
1785
1790
  }
1786
1791
  }
1787
1792
 
1788
- return this.#createProviderUnregisterHandle(admitted);
1793
+ const unregister = this.#createProviderUnregisterHandle(admitted);
1794
+ if (SPEECH_ROLES.includes(admitted.role)) {
1795
+ arcaneLogging.debug('[Arcane speech runtime] register.result', admitted.role, admitted.id, unregister);
1796
+ }
1797
+ return unregister;
1789
1798
  }
1790
1799
 
1791
1800
  unregister(role, providerId, expectedProvider = null) {
1801
+ if (SPEECH_ROLES.includes(role)) {
1802
+ arcaneLogging.debug('[Arcane speech runtime] unregister', role, providerId, expectedProvider);
1803
+ }
1792
1804
  this.#assertOpen();
1793
1805
  this.#assertNotConfiguring();
1794
1806
  assertRole(role);
@@ -1796,6 +1808,9 @@ export class AIProviderRuntime {
1796
1808
  const key = providerKey(role, providerId);
1797
1809
  const provider = this.#providers.get(key);
1798
1810
  if (!provider || (expectedProvider && provider !== expectedProvider)) {
1811
+ if (SPEECH_ROLES.includes(role)) {
1812
+ arcaneLogging.debug('[Arcane speech runtime] unregister.result', role, providerId, false);
1813
+ }
1799
1814
  return false;
1800
1815
  }
1801
1816
 
@@ -1809,16 +1824,29 @@ export class AIProviderRuntime {
1809
1824
  }
1810
1825
  this.#providers.delete(key);
1811
1826
  this.#disposeAllCompletedProviders.delete(provider);
1827
+ if (SPEECH_ROLES.includes(role)) {
1828
+ arcaneLogging.debug('[Arcane speech runtime] unregister.result', role, providerId, true);
1829
+ }
1812
1830
  return true;
1813
1831
  }
1814
1832
 
1815
1833
  hasProvider(role, providerId) {
1834
+ if (SPEECH_ROLES.includes(role)) {
1835
+ arcaneLogging.debug('[Arcane speech runtime] hasProvider', role, providerId);
1836
+ }
1816
1837
  assertRole(role);
1817
1838
  assertIdentifier(providerId, 'AI provider id');
1818
- return this.#providers.has(providerKey(role, providerId));
1839
+ const result = this.#providers.has(providerKey(role, providerId));
1840
+ if (SPEECH_ROLES.includes(role)) {
1841
+ arcaneLogging.debug('[Arcane speech runtime] hasProvider.result', role, providerId, result);
1842
+ }
1843
+ return result;
1819
1844
  }
1820
1845
 
1821
1846
  ownsProvider(role, expectedProvider) {
1847
+ if (SPEECH_ROLES.includes(role)) {
1848
+ arcaneLogging.debug('[Arcane speech runtime] ownsProvider', role, expectedProvider);
1849
+ }
1822
1850
  if (!ROLE_SET.has(role)) {
1823
1851
  throw speechProviderReplacementError(
1824
1852
  'ARCANE_AI_PROVIDER_IDENTITY_ROLE_INVALID',
@@ -1846,17 +1874,27 @@ export class AIProviderRuntime {
1846
1874
  );
1847
1875
  }
1848
1876
 
1849
- return this.#providers.get(providerKey(role, admitted.id)) === admitted;
1877
+ const result = this.#providers.get(providerKey(role, admitted.id)) === admitted;
1878
+ if (SPEECH_ROLES.includes(role)) {
1879
+ arcaneLogging.debug('[Arcane speech runtime] ownsProvider.result', role, admitted.id, result);
1880
+ }
1881
+ return result;
1850
1882
  }
1851
1883
 
1852
1884
  providerIdentity(role, providerId) {
1885
+ if (SPEECH_ROLES.includes(role)) {
1886
+ arcaneLogging.debug('[Arcane speech runtime] providerIdentity', role, providerId);
1887
+ }
1853
1888
  assertRole(role);
1854
1889
  assertIdentifier(providerId, 'AI provider id');
1855
1890
  const provider = this.#providers.get(providerKey(role, providerId)) ?? null;
1856
1891
  if (!provider) {
1892
+ if (SPEECH_ROLES.includes(role)) {
1893
+ arcaneLogging.debug('[Arcane speech runtime] providerIdentity.result', role, providerId, null);
1894
+ }
1857
1895
  return null;
1858
1896
  }
1859
- return completeValue(
1897
+ const result = completeValue(
1860
1898
  {
1861
1899
  protocol: provider.protocol,
1862
1900
  role: provider.role,
@@ -1864,9 +1902,16 @@ export class AIProviderRuntime {
1864
1902
  localOnly: provider.localOnly
1865
1903
  }
1866
1904
  );
1905
+ if (SPEECH_ROLES.includes(role)) {
1906
+ arcaneLogging.debug('[Arcane speech runtime] providerIdentity.result', role, providerId, result);
1907
+ }
1908
+ return result;
1867
1909
  }
1868
1910
 
1869
1911
  selection(role, options = {}) {
1912
+ if (SPEECH_ROLES.includes(role)) {
1913
+ arcaneLogging.debug('[Arcane speech runtime] selection', role, options);
1914
+ }
1870
1915
  assertRole(role);
1871
1916
  assertPlainObject(options, 'AI provider route options');
1872
1917
  for (const key of Reflect.ownKeys(options)) {
@@ -1881,16 +1926,28 @@ export class AIProviderRuntime {
1881
1926
  fail('AI provider route localOnly must be a boolean.');
1882
1927
  }
1883
1928
  const slot = this.#slots[role];
1884
- return localOnly ? slot.routes.localOnly : slot.routes.default;
1929
+ const selection = localOnly ? slot.routes.localOnly : slot.routes.default;
1930
+ if (SPEECH_ROLES.includes(role)) {
1931
+ arcaneLogging.debug('[Arcane speech runtime] selection.result', role, selection);
1932
+ }
1933
+ return selection;
1885
1934
  }
1886
1935
 
1887
1936
  ownsSelection(role, providerId, options = {}) {
1937
+ if (SPEECH_ROLES.includes(role)) {
1938
+ arcaneLogging.debug('[Arcane speech runtime] ownsSelection', role, providerId, options);
1939
+ }
1888
1940
  assertRole(role);
1889
1941
  assertIdentifier(providerId, 'AI provider id');
1890
- return this.selection(role, options)?.providerId === providerId;
1942
+ const result = this.selection(role, options)?.providerId === providerId;
1943
+ if (SPEECH_ROLES.includes(role)) {
1944
+ arcaneLogging.debug('[Arcane speech runtime] ownsSelection.result', role, providerId, result);
1945
+ }
1946
+ return result;
1891
1947
  }
1892
1948
 
1893
1949
  validateConfiguration(value) {
1950
+ arcaneLogging.debug('[Arcane speech runtime] validateConfiguration', value);
1894
1951
  this.#assertOpen();
1895
1952
  const selections = immutableSelections(value);
1896
1953
  for (const role of AI_RUNTIME_ROLES) {
@@ -1910,10 +1967,12 @@ export class AIProviderRuntime {
1910
1967
  }
1911
1968
  }
1912
1969
  }
1970
+ arcaneLogging.debug('[Arcane speech runtime] validateConfiguration.result', selections);
1913
1971
  return selections;
1914
1972
  }
1915
1973
 
1916
1974
  validateSpeechConfiguration(value) {
1975
+ arcaneLogging.debug('[Arcane speech runtime] validateSpeechConfiguration', value);
1917
1976
  this.#assertOpen();
1918
1977
  const selections = immutableSpeechSelections(value);
1919
1978
  for (const role of SPEECH_ROLES) {
@@ -1933,10 +1992,12 @@ export class AIProviderRuntime {
1933
1992
  }
1934
1993
  }
1935
1994
  }
1995
+ arcaneLogging.debug('[Arcane speech runtime] validateSpeechConfiguration.result', selections);
1936
1996
  return selections;
1937
1997
  }
1938
1998
 
1939
1999
  configure(value) {
2000
+ arcaneLogging.debug('[Arcane speech runtime] configure', value);
1940
2001
  this.#assertOpen();
1941
2002
  if (this.#configuring) {
1942
2003
  throw operationError(
@@ -1977,10 +2038,12 @@ export class AIProviderRuntime {
1977
2038
  } finally {
1978
2039
  this.#configuring = false;
1979
2040
  }
2041
+ arcaneLogging.debug('[Arcane speech runtime] configure.result', selections);
1980
2042
  return selections;
1981
2043
  }
1982
2044
 
1983
2045
  configureSpeech(value) {
2046
+ arcaneLogging.debug('[Arcane speech runtime] configureSpeech', value);
1984
2047
  this.#assertOpen();
1985
2048
  if (this.#configuring) {
1986
2049
  throw operationError(
@@ -2022,10 +2085,12 @@ export class AIProviderRuntime {
2022
2085
  } finally {
2023
2086
  this.#configuring = false;
2024
2087
  }
2088
+ arcaneLogging.debug('[Arcane speech runtime] configureSpeech.result', selections);
2025
2089
  return selections;
2026
2090
  }
2027
2091
 
2028
2092
  replaceSpeechProvider(role, value) {
2093
+ arcaneLogging.debug('[Arcane speech runtime] replaceSpeechProvider', role, value);
2029
2094
  this.#assertOpen();
2030
2095
  if (this.#configuring) {
2031
2096
  throw speechProviderReplacementError(
@@ -2036,7 +2101,9 @@ export class AIProviderRuntime {
2036
2101
  }
2037
2102
  this.#configuring = true;
2038
2103
  try {
2039
- return this.#commitSpeechProviderRoleReplacement(role, value);
2104
+ const result = this.#commitSpeechProviderRoleReplacement(role, value);
2105
+ arcaneLogging.debug('[Arcane speech runtime] replaceSpeechProvider.result', role, result);
2106
+ return result;
2040
2107
  } finally {
2041
2108
  this.#configuring = false;
2042
2109
  }
@@ -2191,6 +2258,7 @@ export class AIProviderRuntime {
2191
2258
  }
2192
2259
 
2193
2260
  replaceSpeechProviders(value) {
2261
+ arcaneLogging.debug('[Arcane speech runtime] replaceSpeechProviders', value);
2194
2262
  this.#assertOpen();
2195
2263
  if (this.#configuring) {
2196
2264
  throw speechProviderReplacementError(
@@ -2201,7 +2269,9 @@ export class AIProviderRuntime {
2201
2269
  }
2202
2270
  this.#configuring = true;
2203
2271
  try {
2204
- return this.#commitSpeechProviderReplacement(value);
2272
+ const result = this.#commitSpeechProviderReplacement(value);
2273
+ arcaneLogging.debug('[Arcane speech runtime] replaceSpeechProviders.result', result);
2274
+ return result;
2205
2275
  } finally {
2206
2276
  this.#configuring = false;
2207
2277
  }
@@ -2366,6 +2436,7 @@ export class AIProviderRuntime {
2366
2436
  }
2367
2437
 
2368
2438
  configureFromTuple(tuple) {
2439
+ arcaneLogging.debug('[Arcane speech runtime] configureFromTuple', tuple);
2369
2440
  if (!Array.isArray(tuple) || tuple.length !== 6) {
2370
2441
  fail('AI preference tuple must contain exactly six entries.');
2371
2442
  }
@@ -2416,12 +2487,19 @@ export class AIProviderRuntime {
2416
2487
  }
2417
2488
 
2418
2489
  status(role = null, options = {}) {
2490
+ if (role === null || SPEECH_ROLES.includes(role)) {
2491
+ arcaneLogging.debug('[Arcane speech runtime] status', role, options);
2492
+ }
2419
2493
  if (role !== null) {
2420
2494
  assertRole(role);
2421
2495
  }
2422
2496
  const snapshot = getAIRuntimeState();
2423
2497
  if (options?.execution !== true) {
2424
- return role === null ? snapshot : snapshot.roles[role];
2498
+ const result = role === null ? snapshot : snapshot.roles[role];
2499
+ if (role === null || SPEECH_ROLES.includes(role)) {
2500
+ arcaneLogging.debug('[Arcane speech runtime] status.result', role, result);
2501
+ }
2502
+ return result;
2425
2503
  }
2426
2504
 
2427
2505
  // Live provider inspection is explicit so ordinary lifecycle reads
@@ -2433,6 +2511,9 @@ export class AIProviderRuntime {
2433
2511
  continue;
2434
2512
  }
2435
2513
  const providerStatus = validateProviderStatus(provider.status());
2514
+ if (SPEECH_ROLES.includes(selectedRole)) {
2515
+ arcaneLogging.debug('[Arcane speech runtime] provider.status.result', selectedRole, providerStatus);
2516
+ }
2436
2517
  if (providerStatus.execution == null) {
2437
2518
  continue;
2438
2519
  }
@@ -2441,10 +2522,17 @@ export class AIProviderRuntime {
2441
2522
  execution: {...providerStatus.execution}
2442
2523
  };
2443
2524
  }
2444
- return role === null ? {...snapshot, roles} : roles[role];
2525
+ const result = role === null ? {...snapshot, roles} : roles[role];
2526
+ if (role === null || SPEECH_ROLES.includes(role)) {
2527
+ arcaneLogging.debug('[Arcane speech runtime] status.result', role, result);
2528
+ }
2529
+ return result;
2445
2530
  }
2446
2531
 
2447
2532
  catalog(role) {
2533
+ if (SPEECH_ROLES.includes(role)) {
2534
+ arcaneLogging.debug('[Arcane speech runtime] catalog', role);
2535
+ }
2448
2536
  assertRole(role);
2449
2537
  const entries = [];
2450
2538
  for (const provider of this.#providers.values()) {
@@ -2452,6 +2540,9 @@ export class AIProviderRuntime {
2452
2540
  continue;
2453
2541
  }
2454
2542
  const providerCatalog = provider.catalog();
2543
+ if (SPEECH_ROLES.includes(role)) {
2544
+ arcaneLogging.debug('[Arcane speech runtime] provider.catalog.result', role, provider.id, providerCatalog);
2545
+ }
2455
2546
  if (!Array.isArray(providerCatalog)) {
2456
2547
  fail('AI provider.catalog() must synchronously return an array.');
2457
2548
  }
@@ -2465,10 +2556,16 @@ export class AIProviderRuntime {
2465
2556
  )
2466
2557
  );
2467
2558
  }
2559
+ if (SPEECH_ROLES.includes(role)) {
2560
+ arcaneLogging.debug('[Arcane speech runtime] catalog.result', role, entries);
2561
+ }
2468
2562
  return completeValue(entries);
2469
2563
  }
2470
2564
 
2471
2565
  async inspect(role, options = {}) {
2566
+ if (SPEECH_ROLES.includes(role)) {
2567
+ arcaneLogging.debug('[Arcane speech runtime] inspect', role, options);
2568
+ }
2472
2569
  this.#assertOpen();
2473
2570
  this.#assertNotConfiguring();
2474
2571
  assertRole(role);
@@ -2482,25 +2579,29 @@ export class AIProviderRuntime {
2482
2579
  const generation = slot.generation;
2483
2580
  const selection = this.selection(role, {localOnly});
2484
2581
  if (!selection) {
2485
- return completeValue(
2486
- {
2487
- available: false,
2488
- code: 'ARCANE_AI_ROLE_NOT_SELECTED',
2489
- message: `No ${role} provider and model are selected.`
2490
- }
2491
- );
2582
+ const result = completeValue({
2583
+ available: false,
2584
+ code: 'ARCANE_AI_ROLE_NOT_SELECTED',
2585
+ message: `No ${role} provider and model are selected.`
2586
+ });
2587
+ if (SPEECH_ROLES.includes(role)) {
2588
+ arcaneLogging.debug('[Arcane speech runtime] inspect.result', role, result);
2589
+ }
2590
+ return result;
2492
2591
  }
2493
2592
  const provider = this.#providers.get(
2494
2593
  providerKey(role, selection.providerId)
2495
2594
  ) ?? null;
2496
2595
  if (!provider) {
2497
- return completeValue(
2498
- {
2499
- available: false,
2500
- code: 'ARCANE_AI_PROVIDER_UNAVAILABLE',
2501
- message: `The selected ${role} provider is not registered.`
2502
- }
2503
- );
2596
+ const result = completeValue({
2597
+ available: false,
2598
+ code: 'ARCANE_AI_PROVIDER_UNAVAILABLE',
2599
+ message: `The selected ${role} provider is not registered.`
2600
+ });
2601
+ if (SPEECH_ROLES.includes(role)) {
2602
+ arcaneLogging.debug('[Arcane speech runtime] inspect.result', role, result);
2603
+ }
2604
+ return result;
2504
2605
  }
2505
2606
 
2506
2607
  let inspection;
@@ -2512,11 +2613,20 @@ export class AIProviderRuntime {
2512
2613
  selection,
2513
2614
  {role, signal}
2514
2615
  );
2616
+ if (SPEECH_ROLES.includes(role)) {
2617
+ arcaneLogging.debug('[Arcane speech runtime] provider.inspect.result', role, selection, inspection);
2618
+ }
2515
2619
  if (signal?.aborted) {
2516
2620
  throw normalizedAbort();
2517
2621
  }
2518
2622
  } catch (error) {
2623
+ if (SPEECH_ROLES.includes(role)) {
2624
+ arcaneLogging.debug('[Arcane speech runtime] inspect.error', role, error);
2625
+ }
2519
2626
  if (isAbort(error, signal)) {
2627
+ if (SPEECH_ROLES.includes(role)) {
2628
+ arcaneLogging.debug('[Arcane speech runtime] inspect.cancelled', role, error);
2629
+ }
2520
2630
  throw normalizedAbort(error);
2521
2631
  }
2522
2632
  this.#assertOpen();
@@ -2526,36 +2636,47 @@ export class AIProviderRuntime {
2526
2636
  error,
2527
2637
  'ARCANE_AI_PROVIDER_AUTHORITY_BLOCKED'
2528
2638
  );
2529
- return completeValue(
2530
- {
2531
- available: false,
2532
- code: unavailable.code,
2533
- message: unavailable.message
2534
- }
2535
- );
2639
+ const result = completeValue({
2640
+ available: false,
2641
+ code: unavailable.code,
2642
+ message: unavailable.message
2643
+ });
2644
+ if (SPEECH_ROLES.includes(role)) {
2645
+ arcaneLogging.debug('[Arcane speech runtime] inspect.result', role, result);
2646
+ }
2647
+ return result;
2536
2648
  }
2537
2649
  this.#assertOpen();
2538
2650
  this.#assertNotConfiguring();
2539
2651
  this.#assertCurrentOperation(slot, generation, signal);
2540
2652
  try {
2541
2653
  validateInspection(inspection, selection);
2654
+ if (SPEECH_ROLES.includes(role)) {
2655
+ arcaneLogging.debug('[Arcane speech runtime] inspect.result', role, inspection);
2656
+ }
2542
2657
  return inspection;
2543
2658
  } catch (error) {
2659
+ if (SPEECH_ROLES.includes(role)) {
2660
+ arcaneLogging.debug('[Arcane speech runtime] inspect.error', role, error);
2661
+ }
2544
2662
  const unavailable = stateError(
2545
2663
  error,
2546
2664
  'ARCANE_AI_PROVIDER_AUTHORITY_BLOCKED'
2547
2665
  );
2548
- return completeValue(
2549
- {
2550
- available: false,
2551
- code: unavailable.code,
2552
- message: unavailable.message
2553
- }
2554
- );
2666
+ const result = completeValue({
2667
+ available: false,
2668
+ code: unavailable.code,
2669
+ message: unavailable.message
2670
+ });
2671
+ if (SPEECH_ROLES.includes(role)) {
2672
+ arcaneLogging.debug('[Arcane speech runtime] inspect.result', role, result);
2673
+ }
2674
+ return result;
2555
2675
  }
2556
2676
  }
2557
2677
 
2558
2678
  async start(options) {
2679
+ arcaneLogging.debug('[Arcane speech runtime] start', options);
2559
2680
  this.#assertOpen();
2560
2681
  this.#assertNotConfiguring();
2561
2682
  if (!this.#configured) {
@@ -2589,6 +2710,9 @@ export class AIProviderRuntime {
2589
2710
  }
2590
2711
 
2591
2712
  load(role, options = {}) {
2713
+ if (SPEECH_ROLES.includes(role)) {
2714
+ arcaneLogging.debug('[Arcane speech runtime] load', role, options);
2715
+ }
2592
2716
  this.#assertOpen();
2593
2717
  this.#assertNotConfiguring();
2594
2718
  assertRole(role);
@@ -2747,25 +2871,36 @@ export class AIProviderRuntime {
2747
2871
  slot.selection,
2748
2872
  {role, signal: controller.signal}
2749
2873
  );
2874
+ if (SPEECH_ROLES.includes(role)) {
2875
+ arcaneLogging.debug('[Arcane speech runtime] load.inspection', role, slot.selection, inspection);
2876
+ }
2750
2877
  validateInspection(inspection, slot.selection);
2751
2878
  runtime.#assertCurrentOperation(slot, generation, controller.signal);
2752
- await provider.load(
2753
- {
2754
- role,
2755
- selection: slot.selection,
2756
- signal: controller.signal,
2757
- progress: function publishAIProviderLoadProgress(progress) {
2758
- runtime.#publishLoadProgress(
2759
- slot,
2760
- generation,
2761
- operationId,
2762
- progress
2763
- );
2764
- }
2879
+ const loadRequest = {
2880
+ role,
2881
+ selection: slot.selection,
2882
+ signal: controller.signal,
2883
+ progress: function publishAIProviderLoadProgress(progress) {
2884
+ runtime.#publishLoadProgress(
2885
+ slot,
2886
+ generation,
2887
+ operationId,
2888
+ progress
2889
+ );
2765
2890
  }
2766
- );
2891
+ };
2892
+ if (SPEECH_ROLES.includes(role)) {
2893
+ arcaneLogging.debug('[Arcane speech runtime] provider.load', {generation, operationId}, loadRequest);
2894
+ }
2895
+ const loadResult = await provider.load(loadRequest);
2896
+ if (SPEECH_ROLES.includes(role)) {
2897
+ arcaneLogging.debug('[Arcane speech runtime] provider.load.result', {role, generation, operationId}, loadResult);
2898
+ }
2767
2899
  runtime.#assertCurrentOperation(slot, generation, controller.signal);
2768
2900
  const providerStatus = validateProviderStatus(provider.status());
2901
+ if (SPEECH_ROLES.includes(role)) {
2902
+ arcaneLogging.debug('[Arcane speech runtime] load.status', {role, generation, operationId}, providerStatus);
2903
+ }
2769
2904
  if (providerStatus.state !== 'ready'
2770
2905
  || providerStatus.loaded !== true
2771
2906
  || providerStatus.busy !== false) {
@@ -2794,6 +2929,9 @@ export class AIProviderRuntime {
2794
2929
  const normalized = isAbort(error, controller.signal)
2795
2930
  ? normalizedAbort(error)
2796
2931
  : error;
2932
+ if (SPEECH_ROLES.includes(role)) {
2933
+ arcaneLogging.debug('[Arcane speech runtime] load.error', {role, generation, operationId}, error);
2934
+ }
2797
2935
  if (slot.generation === generation && !slot.unloadPromise) {
2798
2936
  slot.ready = false;
2799
2937
  if (role === 'tts') {
@@ -2834,6 +2972,9 @@ export class AIProviderRuntime {
2834
2972
  }
2835
2973
 
2836
2974
  unload(role, options = {}) {
2975
+ if (SPEECH_ROLES.includes(role)) {
2976
+ arcaneLogging.debug('[Arcane speech runtime] unload', role, options);
2977
+ }
2837
2978
  this.#assertNotConfiguring();
2838
2979
  assertRole(role);
2839
2980
  assertPlainObject(options, 'AI provider unload options');
@@ -2931,14 +3072,18 @@ export class AIProviderRuntime {
2931
3072
  throw normalizedAbort();
2932
3073
  }
2933
3074
  if (provider) {
2934
- await provider.unload(
2935
- {
2936
- role,
2937
- selection: slot.selection,
2938
- signal
2939
- }
2940
- );
3075
+ const unloadRequest = {role, selection: slot.selection, signal};
3076
+ if (SPEECH_ROLES.includes(role)) {
3077
+ arcaneLogging.debug('[Arcane speech runtime] provider.unload', {generation, operationId: unloadOperationId}, unloadRequest);
3078
+ }
3079
+ const unloadResult = await provider.unload(unloadRequest);
3080
+ if (SPEECH_ROLES.includes(role)) {
3081
+ arcaneLogging.debug('[Arcane speech runtime] provider.unload.result', {role, generation, operationId: unloadOperationId}, unloadResult);
3082
+ }
2941
3083
  const unloadedStatus = validateProviderStatus(provider.status());
3084
+ if (SPEECH_ROLES.includes(role)) {
3085
+ arcaneLogging.debug('[Arcane speech runtime] unload.status', role, unloadedStatus);
3086
+ }
2942
3087
  if (unloadedStatus.loaded || unloadedStatus.busy) {
2943
3088
  throw operationError(
2944
3089
  `The selected ${role} provider remained loaded after unload.`,
@@ -2954,6 +3099,9 @@ export class AIProviderRuntime {
2954
3099
  }
2955
3100
  return runtime.status(role);
2956
3101
  } catch (error) {
3102
+ if (SPEECH_ROLES.includes(role)) {
3103
+ arcaneLogging.debug('[Arcane speech runtime] unload.error', {role, generation, operationId: unloadOperationId}, error);
3104
+ }
2957
3105
  if (slot.generation === generation) {
2958
3106
  runtime.#publishRoleError(slot, error, providerOwned);
2959
3107
  }
@@ -2982,6 +3130,9 @@ export class AIProviderRuntime {
2982
3130
  }
2983
3131
 
2984
3132
  dispose(role, options = {}) {
3133
+ if (SPEECH_ROLES.includes(role)) {
3134
+ arcaneLogging.debug('[Arcane speech runtime] dispose', role, options);
3135
+ }
2985
3136
  this.#assertNotConfiguring();
2986
3137
  assertRole(role);
2987
3138
  assertPlainObject(options, 'AI provider dispose options');
@@ -3006,14 +3157,18 @@ export class AIProviderRuntime {
3006
3157
  try {
3007
3158
  await runtime.unload(role, {signal});
3008
3159
  if (provider) {
3009
- await provider.dispose(
3010
- {
3011
- role,
3012
- selection: slot.selection,
3013
- signal
3014
- }
3015
- );
3160
+ const disposeRequest = {role, selection: slot.selection, signal};
3161
+ if (SPEECH_ROLES.includes(role)) {
3162
+ arcaneLogging.debug('[Arcane speech runtime] provider.dispose', disposeRequest);
3163
+ }
3164
+ const disposeResult = await provider.dispose(disposeRequest);
3165
+ if (SPEECH_ROLES.includes(role)) {
3166
+ arcaneLogging.debug('[Arcane speech runtime] provider.dispose.result', role, disposeResult);
3167
+ }
3016
3168
  const disposedStatus = validateProviderStatus(provider.status());
3169
+ if (SPEECH_ROLES.includes(role)) {
3170
+ arcaneLogging.debug('[Arcane speech runtime] dispose.status', role, disposedStatus);
3171
+ }
3017
3172
  if (disposedStatus.loaded || disposedStatus.busy) {
3018
3173
  throw operationError(
3019
3174
  `The selected ${role} provider remained active after disposal.`,
@@ -3035,6 +3190,9 @@ export class AIProviderRuntime {
3035
3190
  );
3036
3191
  return runtime.status(role);
3037
3192
  } catch (error) {
3193
+ if (SPEECH_ROLES.includes(role)) {
3194
+ arcaneLogging.debug('[Arcane speech runtime] dispose.error', role, error);
3195
+ }
3038
3196
  runtime.#publishRoleError(slot, error, runtime.status(role).loaded);
3039
3197
  throw error;
3040
3198
  } finally {
@@ -3046,6 +3204,7 @@ export class AIProviderRuntime {
3046
3204
  }
3047
3205
 
3048
3206
  disposeAll(options = {}) {
3207
+ arcaneLogging.debug('[Arcane speech runtime] disposeAll', options);
3049
3208
  this.#assertNotConfiguring();
3050
3209
  assertPlainObject(options, 'AI provider dispose-all options');
3051
3210
  for (const key of Reflect.ownKeys(options)) {
@@ -3171,10 +3330,12 @@ export class AIProviderRuntime {
3171
3330
  })();
3172
3331
  this.#disposeAllPromise = disposing.then(
3173
3332
  function releaseCompletedAIProviderRuntimeDisposal(result) {
3333
+ arcaneLogging.debug('[Arcane speech runtime] disposeAll.result', result);
3174
3334
  runtime.#disposeAllPromise = null;
3175
3335
  return result;
3176
3336
  },
3177
3337
  function releaseFailedAIProviderRuntimeDisposal(error) {
3338
+ arcaneLogging.debug('[Arcane speech runtime] disposeAll.error', error);
3178
3339
  runtime.#disposeAllPromise = null;
3179
3340
  throw error;
3180
3341
  }
@@ -3183,6 +3344,9 @@ export class AIProviderRuntime {
3183
3344
  }
3184
3345
 
3185
3346
  request(role, options = {}) {
3347
+ if (SPEECH_ROLES.includes(role)) {
3348
+ arcaneLogging.debug('[Arcane speech runtime] request', role, options);
3349
+ }
3186
3350
  return this.#requestRole(role, options, false);
3187
3351
  }
3188
3352
 
@@ -3286,6 +3450,18 @@ export class AIProviderRuntime {
3286
3450
  );
3287
3451
  }
3288
3452
  const maxConcurrentRequests = this.#providerRequestCapacity(provider);
3453
+ if (SPEECH_ROLES.includes(role)) {
3454
+ arcaneLogging.debug('[Arcane speech runtime] request.state', {
3455
+ role,
3456
+ generation: slot.generation,
3457
+ selection: slot.selection,
3458
+ ready: slot.ready,
3459
+ activeRequests: slot.activeRequests.size,
3460
+ queuedRequests: slot.requestQueue.length,
3461
+ maxConcurrentRequests,
3462
+ queued
3463
+ }, providerStatus);
3464
+ }
3289
3465
  if ((!queued && slot.requestQueue.length)
3290
3466
  || slot.activeRequests.size >= maxConcurrentRequests) {
3291
3467
  return this.#enqueueRoleRequest(slot, options);
@@ -3369,6 +3545,7 @@ export class AIProviderRuntime {
3369
3545
  const projectedStreamChunks = [];
3370
3546
  const projectedStreamReaders = [];
3371
3547
  let projectedStreamClosed = false;
3548
+ let projectedStreamFailed = false;
3372
3549
  let projectedStreamError = null;
3373
3550
  let terminalSettled = false;
3374
3551
  let resolveTerminal;
@@ -3394,7 +3571,7 @@ export class AIProviderRuntime {
3394
3571
  }
3395
3572
  while (projectedStreamReaders.length) {
3396
3573
  const reader = projectedStreamReaders.shift();
3397
- if (projectedStreamError) {
3574
+ if (projectedStreamFailed) {
3398
3575
  reader.reject(projectedStreamError);
3399
3576
  } else {
3400
3577
  reader.resolve({value: undefined, done: true});
@@ -3407,11 +3584,12 @@ export class AIProviderRuntime {
3407
3584
  drainProjectedAIProviderStreamReaders();
3408
3585
  }
3409
3586
 
3410
- function closeProjectedAIProviderStream(error = null) {
3587
+ function closeProjectedAIProviderStream(error) {
3411
3588
  if (projectedStreamClosed) {
3412
3589
  return;
3413
3590
  }
3414
3591
  projectedStreamClosed = true;
3592
+ projectedStreamFailed = arguments.length > 0;
3415
3593
  projectedStreamError = error;
3416
3594
  drainProjectedAIProviderStreamReaders();
3417
3595
  }
@@ -3424,7 +3602,7 @@ export class AIProviderRuntime {
3424
3602
  });
3425
3603
  }
3426
3604
  if (projectedStreamClosed) {
3427
- return projectedStreamError
3605
+ return projectedStreamFailed
3428
3606
  ? Promise.reject(projectedStreamError)
3429
3607
  : Promise.resolve({value: undefined, done: true});
3430
3608
  }
@@ -3449,7 +3627,7 @@ export class AIProviderRuntime {
3449
3627
  slot.activeRequests.delete(requestSequence);
3450
3628
  }
3451
3629
  runtime.#drainRoleRequestQueue(slot);
3452
- if (error) {
3630
+ if (arguments.length === 1) {
3453
3631
  closeProjectedAIProviderStream(error);
3454
3632
  restoreAIProviderRoleAfterRequest(error);
3455
3633
  rejectTerminal(error);
@@ -3527,8 +3705,8 @@ export class AIProviderRuntime {
3527
3705
  );
3528
3706
  }
3529
3707
 
3530
- async function cancelAIProviderStream(reason, terminalError = null) {
3531
- const terminalOutcome = terminalError ?? normalizedAbort(
3708
+ async function cancelAIProviderStream(reason, terminalError) {
3709
+ const terminalOutcome = arguments.length > 1 ? terminalError : normalizedAbort(
3532
3710
  reason instanceof Error
3533
3711
  ? reason
3534
3712
  : operationError(
@@ -3744,7 +3922,7 @@ export class AIProviderRuntime {
3744
3922
  )
3745
3923
  ).catch(
3746
3924
  function reportReturnedAIProviderStreamCancellationFailure(error) {
3747
- console.error(
3925
+ arcaneLogging.error(
3748
3926
  'Arcane AI provider stream early-return cancellation failed.',
3749
3927
  error
3750
3928
  );
@@ -3799,6 +3977,9 @@ export class AIProviderRuntime {
3799
3977
  }
3800
3978
 
3801
3979
  requestRecord.cancel = async function cancelAIProviderRequest(reason) {
3980
+ if (SPEECH_ROLES.includes(role)) {
3981
+ arcaneLogging.debug('[Arcane speech runtime] provider.request.cancel', {role, generation, requestSequence, operationId}, reason);
3982
+ }
3802
3983
  controller.abort(reason);
3803
3984
  await requestRecord.promise?.catch(
3804
3985
  function retainCancelledAIProviderRequest() {}
@@ -3810,15 +3991,20 @@ export class AIProviderRuntime {
3810
3991
  if (controller.signal.aborted) {
3811
3992
  throw normalizedAbort();
3812
3993
  }
3813
- const result = await provider.request(
3814
- {
3815
- role,
3816
- selection: slot.selection,
3817
- operation: options.operation,
3818
- payload: options.payload,
3819
- signal: controller.signal
3820
- }
3821
- );
3994
+ const providerRequest = {
3995
+ role,
3996
+ selection: slot.selection,
3997
+ operation: options.operation,
3998
+ payload: options.payload,
3999
+ signal: controller.signal
4000
+ };
4001
+ if (SPEECH_ROLES.includes(role)) {
4002
+ arcaneLogging.debug('[Arcane speech runtime] provider.request', {generation, requestSequence, operationId}, providerRequest);
4003
+ }
4004
+ const result = await provider.request(providerRequest);
4005
+ if (SPEECH_ROLES.includes(role)) {
4006
+ arcaneLogging.debug('[Arcane speech runtime] provider.request.result', {role, generation, requestSequence, operationId}, result);
4007
+ }
3822
4008
  runtime.#assertCurrentRequest(
3823
4009
  slot,
3824
4010
  generation,
@@ -3836,6 +4022,9 @@ export class AIProviderRuntime {
3836
4022
  const normalized = isAbort(error, controller.signal)
3837
4023
  ? normalizedAbort(error)
3838
4024
  : error;
4025
+ if (SPEECH_ROLES.includes(role)) {
4026
+ arcaneLogging.debug('[Arcane speech runtime] provider.request.error', {role, generation, requestSequence, operationId}, error);
4027
+ }
3839
4028
  requestError = normalized;
3840
4029
  throw normalized;
3841
4030
  } finally {
@@ -3843,6 +4032,17 @@ export class AIProviderRuntime {
3843
4032
  if (slot.activeRequests.get(requestSequence) === requestRecord) {
3844
4033
  slot.activeRequests.delete(requestSequence);
3845
4034
  }
4035
+ if (SPEECH_ROLES.includes(role)) {
4036
+ arcaneLogging.debug('[Arcane speech runtime] request.settled', {
4037
+ role,
4038
+ generation,
4039
+ currentGeneration: slot.generation,
4040
+ requestSequence,
4041
+ operationId,
4042
+ activeRequests: slot.activeRequests.size,
4043
+ queuedRequests: slot.requestQueue.length
4044
+ }, requestError);
4045
+ }
3846
4046
  runtime.#drainRoleRequestQueue(slot);
3847
4047
  if (slot.generation === generation && !slot.unloadPromise) {
3848
4048
  restoreAIProviderRoleAfterRequest(requestError);
@@ -3861,18 +4061,26 @@ export class AIProviderRuntime {
3861
4061
  }
3862
4062
 
3863
4063
  transcribe(payload, options = {}) {
4064
+ arcaneLogging.debug('[Arcane speech runtime] transcribe', payload, options);
3864
4065
  return this.#roleRequestAlias('stt', 'transcribe', payload, options);
3865
4066
  }
3866
4067
 
3867
4068
  synthesize(payload, options = {}) {
4069
+ arcaneLogging.debug('[Arcane speech runtime] synthesize', payload, options);
3868
4070
  return this.#roleRequestAlias('tts', 'synthesize', payload, options);
3869
4071
  }
3870
4072
 
3871
4073
  cancel(role) {
4074
+ if (SPEECH_ROLES.includes(role)) {
4075
+ arcaneLogging.debug('[Arcane speech runtime] cancel', role);
4076
+ }
3872
4077
  assertRole(role);
3873
4078
  const slot = this.#slots[role];
3874
4079
  const requestRecord = slot.activeRequests.values().next().value ?? null;
3875
4080
  if (!requestRecord) {
4081
+ if (SPEECH_ROLES.includes(role)) {
4082
+ arcaneLogging.debug('[Arcane speech runtime] cancel.result', role, false);
4083
+ }
3876
4084
  return false;
3877
4085
  }
3878
4086
  const reason = operationError(
@@ -3882,10 +4090,14 @@ export class AIProviderRuntime {
3882
4090
  requestRecord?.controller.abort(reason);
3883
4091
  const cancellation = requestRecord?.cancel?.(reason);
3884
4092
  cancellation?.catch(function retainAIProviderCancelFailureInState() {});
4093
+ if (SPEECH_ROLES.includes(role)) {
4094
+ arcaneLogging.debug('[Arcane speech runtime] cancel.result', role, true, requestRecord.operationId);
4095
+ }
3885
4096
  return true;
3886
4097
  }
3887
4098
 
3888
4099
  async setSpeechMuted(muted) {
4100
+ arcaneLogging.debug('[Arcane speech runtime] setSpeechMuted', muted);
3889
4101
  this.#assertOpen();
3890
4102
  this.#assertNotConfiguring();
3891
4103
  if (typeof muted !== 'boolean') {
@@ -3924,6 +4136,7 @@ export class AIProviderRuntime {
3924
4136
  }
3925
4137
  if (runtime.#speechDesiredMuted === desiredMuted) {
3926
4138
  runtime.#speechMuted = desiredMuted;
4139
+ arcaneLogging.debug('[Arcane speech runtime] setSpeechMuted.settled', desiredMuted);
3927
4140
  return runtime.status('tts');
3928
4141
  }
3929
4142
  }
@@ -3971,6 +4184,15 @@ export class AIProviderRuntime {
3971
4184
  }
3972
4185
 
3973
4186
  #enqueueRoleRequest(slot, options) {
4187
+ if (SPEECH_ROLES.includes(slot.role)) {
4188
+ arcaneLogging.debug('[Arcane speech runtime] queue.enqueue', {
4189
+ role: slot.role,
4190
+ generation: slot.generation,
4191
+ selection: slot.selection,
4192
+ activeRequests: slot.activeRequests.size,
4193
+ queuedRequests: slot.requestQueue.length
4194
+ }, options);
4195
+ }
3974
4196
  const runtime = this;
3975
4197
  return new Promise(function queueAIProviderRoleRequest(resolve, reject) {
3976
4198
  const entry = {
@@ -3986,6 +4208,13 @@ export class AIProviderRuntime {
3986
4208
  }
3987
4209
  slot.requestQueue.splice(index, 1);
3988
4210
  entry.detachSignal?.();
4211
+ if (SPEECH_ROLES.includes(slot.role)) {
4212
+ arcaneLogging.debug('[Arcane speech runtime] queue.cancelled', {
4213
+ role: slot.role,
4214
+ generation: slot.generation,
4215
+ queuedRequests: slot.requestQueue.length
4216
+ }, options, options.signal?.reason);
4217
+ }
3989
4218
  reject(normalizedAbort(options.signal?.reason));
3990
4219
  }
3991
4220
  if (options.signal) {
@@ -4024,6 +4253,15 @@ export class AIProviderRuntime {
4024
4253
  && slot.activeRequests.size < maxConcurrentRequests) {
4025
4254
  const entry = slot.requestQueue.shift();
4026
4255
  entry.detachSignal?.();
4256
+ if (SPEECH_ROLES.includes(slot.role)) {
4257
+ arcaneLogging.debug('[Arcane speech runtime] queue.dispatch', {
4258
+ role: slot.role,
4259
+ generation: slot.generation,
4260
+ activeRequests: slot.activeRequests.size,
4261
+ queuedRequests: slot.requestQueue.length,
4262
+ maxConcurrentRequests
4263
+ }, entry.options);
4264
+ }
4027
4265
  if (entry.options.signal?.aborted) {
4028
4266
  entry.reject(normalizedAbort(entry.options.signal.reason));
4029
4267
  continue;
@@ -4032,6 +4270,9 @@ export class AIProviderRuntime {
4032
4270
  try {
4033
4271
  operation = runtime.#requestRole(slot.role, entry.options, true);
4034
4272
  } catch (error) {
4273
+ if (SPEECH_ROLES.includes(slot.role)) {
4274
+ arcaneLogging.debug('[Arcane speech runtime] queue.error', slot.role, entry.options, error);
4275
+ }
4035
4276
  entry.reject(error);
4036
4277
  continue;
4037
4278
  }
@@ -4043,6 +4284,9 @@ export class AIProviderRuntime {
4043
4284
  const queued = slot.requestQueue.splice(0);
4044
4285
  for (const entry of queued) {
4045
4286
  entry.detachSignal?.();
4287
+ if (SPEECH_ROLES.includes(slot.role)) {
4288
+ arcaneLogging.debug('[Arcane speech runtime] queue.rejected', slot.role, entry.options, error);
4289
+ }
4046
4290
  entry.reject(error);
4047
4291
  }
4048
4292
  }
@@ -4156,6 +4400,16 @@ export class AIProviderRuntime {
4156
4400
 
4157
4401
  #publishRoleRequestState(slot) {
4158
4402
  const requestRecord = slot.activeRequests.values().next().value ?? null;
4403
+ if (SPEECH_ROLES.includes(slot.role)) {
4404
+ arcaneLogging.debug('[Arcane speech runtime] queue.state', {
4405
+ role: slot.role,
4406
+ generation: slot.generation,
4407
+ selection: slot.selection,
4408
+ operationId: requestRecord?.operationId ?? null,
4409
+ activeRequests: slot.activeRequests.size,
4410
+ queuedRequests: slot.requestQueue.length
4411
+ });
4412
+ }
4159
4413
  if (!requestRecord && slot.requestQueue.length === 0) {
4160
4414
  return false;
4161
4415
  }
@@ -4193,6 +4447,15 @@ export class AIProviderRuntime {
4193
4447
  }
4194
4448
 
4195
4449
  #publishLoadProgress(slot, generation, operationId, progress) {
4450
+ if (SPEECH_ROLES.includes(slot.role)) {
4451
+ arcaneLogging.debug('[Arcane speech runtime] load.progress', {
4452
+ role: slot.role,
4453
+ generation,
4454
+ currentGeneration: slot.generation,
4455
+ operationId,
4456
+ aborted: slot.loadController?.signal.aborted
4457
+ }, progress);
4458
+ }
4196
4459
  if (slot.generation !== generation || slot.loadController?.signal.aborted) {
4197
4460
  return false;
4198
4461
  }
@@ -4213,6 +4476,17 @@ export class AIProviderRuntime {
4213
4476
 
4214
4477
  #publishRoleError(slot, error, loaded) {
4215
4478
  const requestRecord = slot.activeRequests.values().next().value ?? null;
4479
+ if (SPEECH_ROLES.includes(slot.role)) {
4480
+ arcaneLogging.debug('[Arcane speech runtime] role.error', {
4481
+ role: slot.role,
4482
+ generation: slot.generation,
4483
+ selection: slot.selection,
4484
+ operationId: requestRecord?.operationId ?? null,
4485
+ loaded,
4486
+ activeRequests: slot.activeRequests.size,
4487
+ queuedRequests: slot.requestQueue.length
4488
+ }, error);
4489
+ }
4216
4490
  publishAIRuntimeRoleState(
4217
4491
  slot.role,
4218
4492
  roleRecord(
@@ -4237,6 +4511,9 @@ export class AIProviderRuntime {
4237
4511
  return;
4238
4512
  }
4239
4513
  const slot = this.#slots[intent.role];
4514
+ if (SPEECH_ROLES.includes(intent.role)) {
4515
+ arcaneLogging.debug('[Arcane speech runtime] intent', intent);
4516
+ }
4240
4517
  if (!slot.selection) {
4241
4518
  return;
4242
4519
  }