arcane-os 0.3.0 → 0.3.2

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 (153) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +86 -117
  3. package/bin/arcane-test.mjs +170 -46
  4. package/browser-runtime/ai/browser-speech-artifacts.mjs +887 -909
  5. package/browser-runtime/ai/browser-speech-providers.mjs +96 -152
  6. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +627 -819
  7. package/browser-runtime/ai/browser-wasm.mjs +24 -35
  8. package/browser-runtime/ai/browser-wllama-runtime.mjs +64 -316
  9. package/browser-runtime/ai/model-controller.mjs +584 -181
  10. package/browser-runtime/ai/speech-worker-client.mjs +8 -146
  11. package/browser-runtime/ai/speech-worker-runtime.mjs +643 -363
  12. package/browser-runtime/dom-event-instrumentation.mjs +55 -147
  13. package/browser-runtime/event-manager.mjs +239 -624
  14. package/package.json +5 -6
  15. package/runtime/arcane/components/app-bar.html +3 -15
  16. package/runtime/arcane/components/assistant-panel.html +10 -10
  17. package/runtime/arcane/components/calculator.html +1 -1
  18. package/runtime/arcane/components/chat.html +1359 -135
  19. package/runtime/arcane/components/conversation-view.html +2 -2
  20. package/runtime/arcane/components/document-inspector.html +11 -17
  21. package/runtime/arcane/components/file-manager.html +13 -56
  22. package/runtime/arcane/components/markdown-document.html +82 -281
  23. package/runtime/arcane/components/markdown-editor.html +7 -10
  24. package/runtime/arcane/components/media-embed.html +6 -6
  25. package/runtime/arcane/components/screen-capture.html +4 -4
  26. package/runtime/arcane/components/source-explanation.html +2 -2
  27. package/runtime/arcane/components/speech.html +112 -68
  28. package/runtime/arcane/components/terminal-workspace.html +4 -4
  29. package/runtime/arcane/components/theme-editor.html +1 -1
  30. package/runtime/arcane/components/unified-inbox.html +2 -2
  31. package/runtime/arcane/components/voice-transcription.html +31 -21
  32. package/runtime/arcane/entities/Calculation.js +2 -3
  33. package/runtime/arcane/entities/Chat.js +228 -43
  34. package/runtime/arcane/entities/Preference.js +3 -5
  35. package/runtime/arcane/entities/Weather.js +5 -5
  36. package/runtime/arcane/modules/AI.js +1050 -427
  37. package/runtime/arcane/modules/AIProviderRuntime.js +658 -363
  38. package/runtime/arcane/modules/AIResponseLength.js +9 -19
  39. package/runtime/arcane/modules/AIRuntimeState.js +109 -72
  40. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +45 -32
  41. package/runtime/arcane/modules/BrowserTestSuite.js +78 -122
  42. package/runtime/arcane/modules/CalculatorEngine.js +9 -9
  43. package/runtime/arcane/modules/CommunicationAppController.js +3 -7
  44. package/runtime/arcane/modules/ComponentContracts.js +30 -32
  45. package/runtime/arcane/modules/ConfiguredAIChatSession.js +281 -230
  46. package/runtime/arcane/modules/ConversationActionItems.js +26 -59
  47. package/runtime/arcane/modules/ConversationClosingReport.js +34 -61
  48. package/runtime/arcane/modules/ConversationTimebox.js +27 -15
  49. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +152 -344
  50. package/runtime/arcane/modules/DocumentLexicalSearch.js +25 -91
  51. package/runtime/arcane/modules/HTMLImport.js +54 -1
  52. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +40 -203
  53. package/runtime/arcane/modules/LocalAIReadiness.js +40 -60
  54. package/runtime/arcane/modules/LocalAIReadinessController.js +15 -13
  55. package/runtime/arcane/modules/MD.js +1 -45
  56. package/runtime/arcane/modules/Mail.js +51 -103
  57. package/runtime/arcane/modules/MailOutbox.mjs +95 -193
  58. package/runtime/arcane/modules/MailTransport.mjs +36 -57
  59. package/runtime/arcane/modules/ModelDefinition.js +22 -106
  60. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +39 -101
  61. package/runtime/arcane/modules/PersistentAIChatSession.js +281 -18
  62. package/runtime/arcane/modules/PreferenceStore.js +102 -30
  63. package/runtime/arcane/modules/RiskSignalAnalyzer.js +8 -9
  64. package/runtime/arcane/modules/ScopedOPFSCache.js +7 -42
  65. package/runtime/arcane/modules/ScreenCapture.js +175 -128
  66. package/runtime/arcane/modules/SpeechPlayback.js +46 -149
  67. package/runtime/arcane/modules/StaticDocumentCatalog.js +173 -407
  68. package/runtime/arcane/modules/ToolCallRouter.js +25 -12
  69. package/runtime/arcane/modules/YouTubeMedia.js +6 -5
  70. package/schemas/arcane-app-bundle.schema.json +13 -78
  71. package/schemas/arcane-app.schema.json +9 -25
  72. package/schemas/arcane-lock.schema.json +18 -151
  73. package/schemas/arcane-package.schema.json +2 -16
  74. package/schemas/native-build-plan.schema.json +119 -122
  75. package/src/app-descriptor.mjs +75 -132
  76. package/src/application-tests.mjs +200 -0
  77. package/src/cli/main.mjs +27 -46
  78. package/src/constants.mjs +3 -4
  79. package/src/dev-server.mjs +30 -324
  80. package/src/doctor.mjs +92 -154
  81. package/src/dom-event-instrumentation.mjs +55 -147
  82. package/src/errors.mjs +2 -3
  83. package/src/event-manager.mjs +239 -624
  84. package/src/event-queue.mjs +3 -3
  85. package/src/import-map.mjs +273 -1028
  86. package/src/index.mjs +14 -16
  87. package/src/installed-sdk-runtime.mjs +40 -62
  88. package/src/integrated-provider-loader.mjs +53 -382
  89. package/src/mail-api.mjs +0 -2
  90. package/src/mail-server.mjs +224 -580
  91. package/src/mail.mjs +4 -10
  92. package/src/native-plan.mjs +163 -598
  93. package/src/native-provider-loader.mjs +104 -1063
  94. package/src/packager/core.mjs +485 -3229
  95. package/src/process.mjs +5 -10
  96. package/src/release-bundle.mjs +292 -2405
  97. package/src/runtime.mjs +76 -396
  98. package/src/scaffold.mjs +30 -80
  99. package/src/sdk-browser-runtime.mjs +70 -626
  100. package/src/source-server.mjs +588 -0
  101. package/src/targets/index.mjs +78 -188
  102. package/src/templates/workspace-template.mjs +19 -135
  103. package/src/testing-loader.mjs +164 -0
  104. package/src/testing.mjs +1 -1
  105. package/src/toolchain.mjs +131 -544
  106. package/src/update-check.mjs +26 -64
  107. package/src/workspace-operation-lock.mjs +139 -430
  108. package/src/workspace-runtime.mjs +112 -779
  109. package/src/workspace.mjs +40 -302
  110. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +0 -218
  111. package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +0 -203
  112. package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +0 -80
  113. package/browser-runtime/ai/internal/sha256.mjs +0 -166
  114. package/docs/architecture.md +0 -344
  115. package/docs/compatibility.md +0 -36
  116. package/docs/event-manager.md +0 -294
  117. package/docs/platform-targets.md +0 -108
  118. package/docs/publishing.md +0 -201
  119. package/docs/reference/README.md +0 -187
  120. package/docs/reference/ai/browser-speech-package-authority.json +0 -835
  121. package/docs/reference/ai/browser-speech.md +0 -1252
  122. package/docs/reference/ai/browser-wasm.md +0 -530
  123. package/docs/reference/arcane-ollama.md +0 -288
  124. package/docs/reference/availability-and-normalization.md +0 -183
  125. package/docs/reference/behavioral-testing.md +0 -133
  126. package/docs/reference/cli.md +0 -779
  127. package/docs/reference/core/README.md +0 -62
  128. package/docs/reference/core/arcane-ai-contracts.md +0 -907
  129. package/docs/reference/core/arcane-api.md +0 -601
  130. package/docs/reference/core/arcane-entities.md +0 -65
  131. package/docs/reference/core/arcane-events.md +0 -134
  132. package/docs/reference/core/ollama-module.md +0 -181
  133. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +0 -1909
  134. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +0 -1057
  135. package/docs/reference/core/reference/arcane-api/core-and-events.md +0 -320
  136. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +0 -610
  137. package/docs/reference/core/reference/arcane-api/namespaces.md +0 -1157
  138. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +0 -1423
  139. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +0 -315
  140. package/docs/reference/event-manager.md +0 -1511
  141. package/docs/reference/inventory/package-api.json +0 -3284
  142. package/docs/reference/inventory/runtime-components.json +0 -1011
  143. package/docs/reference/inventory/runtime-entities.json +0 -26
  144. package/docs/reference/inventory/runtime-modules.json +0 -1431
  145. package/docs/reference/mail.md +0 -316
  146. package/docs/reference/protocols.md +0 -677
  147. package/docs/reference/runtime-components.md +0 -1366
  148. package/docs/reference/runtime-entities.md +0 -303
  149. package/docs/reference/runtime-modules.md +0 -2960
  150. package/docs/reference/sdk-api.md +0 -6694
  151. package/docs/roadmap.md +0 -79
  152. package/docs/work-amplification.md +0 -129
  153. package/runtime/ARCANE_RUNTIME_RELEASE.json +0 -826
@@ -5,11 +5,8 @@ import {
5
5
  isBrowserSpeechAuthority,
6
6
  isDbopfsSpeechArtifactStore,
7
7
  } from "./browser-speech-artifacts.mjs";
8
- import {
9
- normalizeModelSecurity,
10
- resolveModelSecurity,
11
- sameModelSecurity,
12
- } from "./model-controller.mjs";
8
+
9
+ const completeValue = (value) => value;
13
10
  import {
14
11
  createSpeechWorkerClient,
15
12
  isSpeechWorkerClient,
@@ -18,15 +15,16 @@ import {
18
15
 
19
16
  const AI_PROVIDER_PROTOCOL = "arcane-ai-provider/2";
20
17
  const AI_MODEL_AUTHORITY_PROTOCOL = "arcane-ai-model-authority/1";
21
- const ROLE_OPERATION = Object.freeze({ stt: "transcribe", tts: "synthesize" });
18
+ const ROLE_OPERATION = completeValue({ stt: "transcribe", tts: "synthesize" });
22
19
  const STT_SAMPLE_RATE = 16_000;
23
20
  const TTS_SAMPLE_RATE = 24_000;
24
21
  const TTS_RESPONSE_FORMAT = "wav";
25
- const ROLE_REQUEST_REASON = Object.freeze({
22
+ const ROLE_REQUEST_REASON = completeValue({
26
23
  stt: "stt-transcription-cancelled",
27
24
  tts: "tts-synthesis-cancelled",
28
25
  });
29
26
  const UNMAPPED_PROVIDER_REASON = "browser-speech-provider-error-reason-unmapped";
27
+ const NO_PROVIDER_WARNINGS = completeValue([]);
30
28
  const WORKER_FAILURE_CODES = new Set([
31
29
  "ARCANE_AI_WORKER_CRASHED",
32
30
  "ARCANE_AI_WORKER_MESSAGE_ERROR",
@@ -66,7 +64,9 @@ function trustedLoadFailure(error, role) {
66
64
  if (isTrustedSpeechError(error)) return error;
67
65
  return providerError(
68
66
  "ARCANE_AI_PROVIDER_LOAD_FAILED",
69
- `The browser ${role} provider load was rejected.`,
67
+ typeof error?.message === "string" && error.message
68
+ ? error.message
69
+ : `The browser ${role} provider load was rejected.`,
70
70
  error,
71
71
  `${role}-provider-load-rejected`,
72
72
  );
@@ -76,7 +76,9 @@ function trustedRequestFailure(error, role) {
76
76
  if (isTrustedSpeechError(error)) return error;
77
77
  return providerError(
78
78
  "ARCANE_AI_PROVIDER_REQUEST_FAILED",
79
- `The browser ${role} engine operation was rejected.`,
79
+ typeof error?.message === "string" && error.message
80
+ ? error.message
81
+ : `The browser ${role} engine operation was rejected.`,
80
82
  error,
81
83
  role === "stt"
82
84
  ? "stt-transcription-engine-operation-rejected"
@@ -88,7 +90,9 @@ function trustedWorkerFailure(error, role) {
88
90
  if (isTrustedSpeechError(error)) return error;
89
91
  return providerError(
90
92
  "ARCANE_AI_WORKER_MESSAGE_REJECTED",
91
- `The browser ${role} Worker returned an untrusted error envelope.`,
93
+ typeof error?.message === "string" && error.message
94
+ ? error.message
95
+ : `The browser ${role} Worker returned an unreadable error envelope.`,
92
96
  error,
93
97
  `${role}-worker-error-envelope-rejected`,
94
98
  );
@@ -98,7 +102,9 @@ function trustedLifecycleFailure(error, role, operation) {
98
102
  if (isTrustedSpeechError(error)) return error;
99
103
  return providerError(
100
104
  "ARCANE_AI_INVALID_REQUEST",
101
- `The browser ${role} ${operation} context could not be read.`,
105
+ typeof error?.message === "string" && error.message
106
+ ? error.message
107
+ : `The browser ${role} ${operation} context could not be read.`,
102
108
  error,
103
109
  `${role}-provider-${operation}-context-read-rejected`,
104
110
  );
@@ -129,8 +135,8 @@ function workerFailureCode(error) {
129
135
  }
130
136
 
131
137
  function requiredIdentifier(value, label) {
132
- if (typeof value !== "string" || !value.trim() || value.trim().length > 128) {
133
- throw new TypeError(`${label} must be a trimmed 1-128 character string.`);
138
+ if (typeof value !== "string" || !value.trim()) {
139
+ throw new TypeError(`${label} must be a nonempty trimmed string.`);
134
140
  }
135
141
  return value.trim();
136
142
  }
@@ -141,7 +147,6 @@ function createProviderAuthority({
141
147
  graph,
142
148
  model,
143
149
  runtime,
144
- security,
145
150
  }) {
146
151
  if (graph !== undefined) {
147
152
  if (model !== undefined || runtime !== undefined) {
@@ -158,9 +163,8 @@ function createProviderAuthority({
158
163
  && graph.providerId !== providerId) {
159
164
  throw new TypeError("Browser speech graph providerId must match the provider id.");
160
165
  }
161
- return Object.freeze({
166
+ return completeValue({
162
167
  protocol: AI_MODEL_AUTHORITY_PROTOCOL,
163
- admitted: true,
164
168
  graph,
165
169
  artifactGraphProtocol: graph.protocol,
166
170
  providerId,
@@ -170,13 +174,11 @@ function createProviderAuthority({
170
174
  revision: graph.model.revision,
171
175
  dtype: graph.model.dtype,
172
176
  defaultVoice: graph.model.defaultVoice,
173
- voices: graph.model.voices ?? Object.freeze([]),
177
+ voices: graph.model.voices ?? completeValue([]),
174
178
  inputSampleRate: graph.model.inputSampleRate,
175
179
  outputSampleRate: graph.model.outputSampleRate,
176
180
  runtime: graph.runtime,
177
181
  files: graph.files,
178
- security,
179
- artifactGraphId: graph.identitySha256,
180
182
  });
181
183
  }
182
184
  const authority = createBrowserSpeechAuthority({
@@ -184,7 +186,6 @@ function createProviderAuthority({
184
186
  role,
185
187
  model,
186
188
  runtime,
187
- security,
188
189
  });
189
190
  if (!isBrowserSpeechAuthority(authority)) {
190
191
  throw new TypeError("Browser speech authority construction did not return an SDK authority.");
@@ -207,7 +208,7 @@ function linkSignal(signal) {
207
208
  const forwardExternalAbort = () => controller.abort(signal?.reason);
208
209
  if (signal?.aborted) forwardExternalAbort();
209
210
  else signal?.addEventListener?.("abort", forwardExternalAbort, { once: true });
210
- return Object.freeze({
211
+ return completeValue({
211
212
  controller,
212
213
  abort(reason, code = "ARCANE_AI_REQUEST_ABORTED") {
213
214
  if (controller.signal.aborted) return;
@@ -265,7 +266,7 @@ function isAbortSignal(value) {
265
266
  }
266
267
 
267
268
  function providerContext(context, role, operation) {
268
- if (context === undefined) return Object.freeze({ signal: null });
269
+ if (context === undefined) return completeValue({ signal: null });
269
270
  if (!context || typeof context !== "object" || Array.isArray(context)) {
270
271
  throw providerError(
271
272
  "ARCANE_AI_INVALID_REQUEST",
@@ -291,11 +292,25 @@ function providerContext(context, role, operation) {
291
292
  `${role}-provider-${operation}-signal-not-abort-signal`,
292
293
  );
293
294
  }
294
- return Object.freeze({ context, signal });
295
+ return completeValue({ context, signal });
296
+ }
297
+
298
+ function reportedSecureIntent(...scopes) {
299
+ for (const scope of scopes) {
300
+ try {
301
+ if (scope?.secure === true) return completeValue({ secure: true });
302
+ } catch {
303
+ // Intent reporting never turns a security-shaped value into an ordinary
304
+ // provider admission check. Future hardening requires user review.
305
+ }
306
+ }
307
+ return null;
295
308
  }
296
309
 
297
- function graphSecurity(scope, label) {
298
- return normalizeModelSecurity(scope, label);
310
+ function providerWarnings(runtimeWarnings = NO_PROVIDER_WARNINGS) {
311
+ const warnings = runtimeWarnings;
312
+ if (warnings.length === 0) return NO_PROVIDER_WARNINGS;
313
+ return completeValue([...new Set(warnings)]);
299
314
  }
300
315
 
301
316
  function publicStatus({
@@ -309,10 +324,10 @@ function publicStatus({
309
324
  cache,
310
325
  lifecycleReason,
311
326
  activeOperation,
312
- artifactGraphAdmission,
313
- security,
327
+ secureIntent,
328
+ warnings,
314
329
  }) {
315
- return Object.freeze({
330
+ return completeValue({
316
331
  role,
317
332
  providerId: id,
318
333
  modelId: authority.modelId,
@@ -325,9 +340,8 @@ function publicStatus({
325
340
  generation,
326
341
  errorCode,
327
342
  cache,
328
- security,
329
- artifactGraphId: authority.artifactGraphId ?? null,
330
- artifactGraphAdmission,
343
+ ...(secureIntent ? { security: secureIntent } : {}),
344
+ warnings,
331
345
  });
332
346
  }
333
347
 
@@ -507,9 +521,9 @@ function observeLoadOperation(record, { signal, progress }, role) {
507
521
  settle(reject, abortError(signal, `${role}-load-cancelled`));
508
522
  abandonIfUnobserved(`${role}-load-cancelled`);
509
523
  };
510
- const observer = Object.freeze({
524
+ const observer = completeValue({
511
525
  progress(value) {
512
- if (settled) return;
526
+ if (settled || typeof progress !== "function") return;
513
527
  try {
514
528
  progress(value);
515
529
  } catch (error) {
@@ -543,7 +557,6 @@ function observeLoadOperation(record, { signal, progress }, role) {
543
557
  );
544
558
  return;
545
559
  }
546
- if (record.hasProgress) observer.progress(record.latestProgress);
547
560
  });
548
561
  }
549
562
 
@@ -762,8 +775,8 @@ async function decodeSharedTranscriptionPayload(
762
775
  mono[index] = sample;
763
776
  }
764
777
  throwIfAborted(signal, cancellationReason);
765
- return Object.freeze({
766
- payload: Object.freeze({ audio: mono, sampleRate }),
778
+ return completeValue({
779
+ payload: completeValue({ audio: mono, sampleRate }),
767
780
  shared: true,
768
781
  });
769
782
  }
@@ -812,8 +825,8 @@ function cloneNativeTranscriptionPayload(payload, authority) {
812
825
  );
813
826
  }
814
827
  }
815
- return Object.freeze({
816
- payload: Object.freeze({
828
+ return completeValue({
829
+ payload: completeValue({
817
830
  audio: new Float32Array(descriptors.audio.value),
818
831
  sampleRate,
819
832
  }),
@@ -857,15 +870,13 @@ function normalizeSynthesisPayload(payload, authority) {
857
870
  assertPayloadModel(payload, authority, { operationSubject: "tts-synthesis" });
858
871
  textValue = descriptors.text.value;
859
872
  }
860
- const text = typeof textValue === "string" ? textValue.trim() : "";
873
+ const text = typeof textValue === "string" ? textValue : "";
861
874
  const voiceValue = Object.hasOwn(descriptors, "voice")
862
875
  ? descriptors.voice.value
863
876
  : authority.defaultVoice;
864
- const voice = typeof voiceValue === "string"
865
- ? voiceValue.trim()
866
- : "";
877
+ const voice = typeof voiceValue === "string" ? voiceValue : "";
867
878
  const speed = Object.hasOwn(descriptors, "speed") ? descriptors.speed.value : 1;
868
- if (!text) {
879
+ if (!text.trim()) {
869
880
  throw providerError(
870
881
  "ARCANE_AI_INVALID_REQUEST",
871
882
  "Kokoro requires nonempty text.",
@@ -873,7 +884,7 @@ function normalizeSynthesisPayload(payload, authority) {
873
884
  "tts-synthesis-text-empty",
874
885
  );
875
886
  }
876
- if (!voice) {
887
+ if (!voice.trim()) {
877
888
  throw providerError(
878
889
  "ARCANE_AI_INVALID_REQUEST",
879
890
  "Kokoro requires a nonempty voice id.",
@@ -881,12 +892,12 @@ function normalizeSynthesisPayload(payload, authority) {
881
892
  "tts-synthesis-voice-empty",
882
893
  );
883
894
  }
884
- if (!Number.isFinite(speed) || speed <= 0 || speed > 4) {
895
+ if (!Number.isFinite(speed) || speed <= 0) {
885
896
  throw providerError(
886
897
  "ARCANE_AI_INVALID_REQUEST",
887
- "Kokoro speed must be greater than 0 and at most 4.",
898
+ "Kokoro speed must be greater than 0.",
888
899
  undefined,
889
- "tts-synthesis-speed-out-of-range",
900
+ "tts-synthesis-speed-not-positive",
890
901
  );
891
902
  }
892
903
  if (authority.graph
@@ -898,8 +909,8 @@ function normalizeSynthesisPayload(payload, authority) {
898
909
  "tts-synthesis-voice-not-declared",
899
910
  );
900
911
  }
901
- return Object.freeze({
902
- payload: Object.freeze({ text, voice, speed }),
912
+ return completeValue({
913
+ payload: completeValue({ text, voice, speed }),
903
914
  shared,
904
915
  });
905
916
  }
@@ -1019,7 +1030,7 @@ function encodeSharedSynthesisResult(result, authority) {
1019
1030
  true,
1020
1031
  );
1021
1032
  }
1022
- return Object.freeze({ audio: bytes, contentType: "audio/wav" });
1033
+ return completeValue({ audio: bytes, contentType: "audio/wav" });
1023
1034
  }
1024
1035
 
1025
1036
  function createBrowserSpeechProvider({
@@ -1044,35 +1055,23 @@ function createBrowserSpeechProvider({
1044
1055
  if (!isDbopfsSpeechArtifactStore(store)) {
1045
1056
  throw new TypeError("Browser speech providers require an SDK-created DBOPFS artifact store.");
1046
1057
  }
1047
- const configuredAppSecurity = normalizeModelSecurity(
1048
- appSecurity,
1049
- "Browser speech app security",
1050
- );
1051
- const configuredProviderSecurity = normalizeModelSecurity(
1052
- security,
1053
- "Browser speech provider security",
1054
- );
1055
1058
  const authority = createProviderAuthority({
1056
1059
  providerId,
1057
1060
  role,
1058
1061
  graph,
1059
1062
  model,
1060
1063
  runtime,
1061
- security: configuredProviderSecurity,
1062
- });
1063
- const defaultSecurity = resolveModelSecurity({
1064
- app: configuredAppSecurity,
1065
- binding: configuredProviderSecurity,
1066
1064
  });
1065
+ const defaultSecureIntent = reportedSecureIntent(appSecurity, security);
1067
1066
  const operation = ROLE_OPERATION[role];
1068
1067
  const speech = role === "stt"
1069
- ? Object.freeze({ inputSampleRate: inputSampleRate(authority) })
1070
- : Object.freeze({
1068
+ ? completeValue({ inputSampleRate: inputSampleRate(authority) })
1069
+ : completeValue({
1071
1070
  outputSampleRate: outputSampleRate(authority),
1072
- responseFormats: Object.freeze([TTS_RESPONSE_FORMAT]),
1071
+ responseFormats: completeValue([TTS_RESPONSE_FORMAT]),
1073
1072
  defaultResponseFormat: TTS_RESPONSE_FORMAT,
1074
1073
  });
1075
- const catalogEntry = Object.freeze({
1074
+ const catalogEntry = completeValue({
1076
1075
  id: authority.modelId,
1077
1076
  providerId,
1078
1077
  role,
@@ -1084,7 +1083,6 @@ function createBrowserSpeechProvider({
1084
1083
  runtime: authority.runtime,
1085
1084
  files: authority.files,
1086
1085
  ...(authority.graph ? {
1087
- artifactGraphId: authority.artifactGraphId,
1088
1086
  voices: authority.voices,
1089
1087
  } : {}),
1090
1088
  speech,
@@ -1092,7 +1090,6 @@ function createBrowserSpeechProvider({
1092
1090
  let state = "unloaded";
1093
1091
  let errorCode = null;
1094
1092
  let cache = null;
1095
- let artifactGraphAdmission = null;
1096
1093
  let lifecycleReason = `${role}-provider-created`;
1097
1094
  let activeOperation = null;
1098
1095
  let generation = 0;
@@ -1101,8 +1098,13 @@ function createBrowserSpeechProvider({
1101
1098
  let unloadOperation = null;
1102
1099
  let disposeOperation = null;
1103
1100
  let requestOperation = null;
1101
+ let lastWarnings = NO_PROVIDER_WARNINGS;
1102
+ let secureIntent = defaultSecureIntent;
1104
1103
 
1105
1104
  function status() {
1105
+ const runtimeWarnings = active?.warnings
1106
+ ?? loadOperation?.warnings
1107
+ ?? lastWarnings;
1106
1108
  return publicStatus({
1107
1109
  role,
1108
1110
  id: providerId,
@@ -1114,8 +1116,8 @@ function createBrowserSpeechProvider({
1114
1116
  cache,
1115
1117
  lifecycleReason,
1116
1118
  activeOperation,
1117
- artifactGraphAdmission,
1118
- security: active?.security ?? loadOperation?.security ?? defaultSecurity,
1119
+ secureIntent,
1120
+ warnings: providerWarnings(runtimeWarnings),
1119
1121
  });
1120
1122
  }
1121
1123
 
@@ -1156,7 +1158,7 @@ function createBrowserSpeechProvider({
1156
1158
  localOnly: true,
1157
1159
 
1158
1160
  catalog() {
1159
- return Object.freeze([catalogEntry]);
1161
+ return completeValue([catalogEntry]);
1160
1162
  },
1161
1163
 
1162
1164
  inspect(selection, context = {}) {
@@ -1168,14 +1170,14 @@ function createBrowserSpeechProvider({
1168
1170
  && (!Object.hasOwn(selection ?? {}, "role") || selection.role === role)
1169
1171
  && selection?.localOnly !== false;
1170
1172
  if (!available) {
1171
- return Object.freeze({
1173
+ return completeValue({
1172
1174
  available: false,
1173
1175
  code: "ARCANE_AI_MODEL_AUTHORITY_REQUIRED",
1174
1176
  message: "The selected browser speech model does not match this provider authority.",
1175
1177
  reason: `${role}-provider-inspection-selection-authority-mismatch`,
1176
1178
  });
1177
1179
  }
1178
- return Object.freeze({ available: true, authority });
1180
+ return completeValue({ available: true, authority });
1179
1181
  },
1180
1182
 
1181
1183
  status,
@@ -1234,10 +1236,10 @@ function createBrowserSpeechProvider({
1234
1236
  } catch (error) {
1235
1237
  return Promise.reject(trustedLoadFailure(error, role));
1236
1238
  }
1237
- if (typeof loadProgress !== "function") {
1239
+ if (loadProgress !== undefined && typeof loadProgress !== "function") {
1238
1240
  return Promise.reject(providerError(
1239
1241
  "ARCANE_AI_INVALID_REQUEST",
1240
- "Browser speech load progress must be a function.",
1242
+ "Browser speech load progress must be a function when supplied.",
1241
1243
  undefined,
1242
1244
  `${role}-load-progress-callback-not-function`,
1243
1245
  ));
@@ -1253,62 +1255,20 @@ function createBrowserSpeechProvider({
1253
1255
  `${role}-load-selection-authority-mismatch`,
1254
1256
  ));
1255
1257
  }
1256
- let effectiveSecurity;
1258
+ secureIntent = reportedSecureIntent(appSecurity, security, context.security);
1257
1259
  try {
1258
- if (authority.graph) {
1259
- effectiveSecurity = resolveModelSecurity({
1260
- app: graphSecurity(
1261
- configuredAppSecurity,
1262
- "Browser speech graph app security",
1263
- ),
1264
- binding: graphSecurity(
1265
- configuredProviderSecurity,
1266
- "Browser speech graph provider security",
1267
- ),
1268
- load: graphSecurity(
1269
- context.security,
1270
- "Browser speech graph load security",
1271
- ),
1272
- });
1273
- if (effectiveSecurity.secure !== true) {
1274
- throw providerError(
1275
- "ARCANE_AI_SECURE_MODE_REQUIRED",
1276
- "Authenticated browser speech artifact graphs require explicit secure:true.",
1277
- undefined,
1278
- `${role}-artifact-graph-secure-mode-required`,
1279
- );
1280
- }
1281
- } else {
1282
- effectiveSecurity = resolveModelSecurity({
1283
- app: configuredAppSecurity,
1284
- binding: configuredProviderSecurity,
1285
- load: context.security,
1286
- });
1287
- }
1288
1260
  throwIfAborted(loadSignal, `${role}-load-cancelled`);
1289
1261
  } catch (error) {
1290
1262
  return Promise.reject(trustedLoadFailure(error, role));
1291
1263
  }
1292
1264
  if (state === "ready" && active) {
1293
- if (sameModelSecurity(active.security, effectiveSecurity)) {
1294
- return Promise.resolve(status());
1295
- }
1296
- lifecycleReason = `${role}-load-superseded-by-security-change`;
1297
- return provider.unload().then(() => provider.load(context));
1265
+ return Promise.resolve(status());
1298
1266
  }
1299
1267
  if (loadOperation) {
1300
- if (sameModelSecurity(loadOperation.security, effectiveSecurity)) {
1301
- return observeLoadOperation(loadOperation, {
1302
- signal: loadSignal,
1303
- progress: loadProgress,
1304
- }, role);
1305
- }
1306
- lifecycleReason = `${role}-load-superseded-by-security-change`;
1307
- loadOperation.abort(
1308
- `${role}-load-superseded-by-security-change`,
1309
- "ARCANE_AI_OPERATION_SUPERSEDED",
1310
- );
1311
- return loadOperation.promise.catch(() => undefined).then(() => provider.load(context));
1268
+ return observeLoadOperation(loadOperation, {
1269
+ signal: loadSignal,
1270
+ progress: loadProgress,
1271
+ }, role);
1312
1272
  }
1313
1273
  generation += 1;
1314
1274
  const operationGeneration = generation;
@@ -1319,31 +1279,27 @@ function createBrowserSpeechProvider({
1319
1279
  errorCode = null;
1320
1280
  const record = {
1321
1281
  promise: null,
1322
- security: effectiveSecurity,
1282
+ warnings: NO_PROVIDER_WARNINGS,
1323
1283
  observers: new Set(),
1324
1284
  settled: false,
1325
- hasProgress: false,
1326
- latestProgress: null,
1327
1285
  abort: (
1328
1286
  reason = `${role}-load-cancelled`,
1329
1287
  code = "ARCANE_AI_REQUEST_ABORTED",
1330
1288
  ) => linked.abort(reason, code),
1331
- publishProgress(progress) {
1332
- record.latestProgress = progress;
1333
- record.hasProgress = true;
1334
- for (const observer of [...record.observers]) observer.progress(progress);
1335
- },
1336
1289
  };
1290
+ lastWarnings = NO_PROVIDER_WARNINGS;
1337
1291
  const promise = Promise.resolve().then(async () => {
1338
1292
  let prepared = null;
1339
1293
  let slot = null;
1340
1294
  try {
1341
1295
  prepared = await store.prepare(authority.graph ?? authority, {
1342
1296
  signal: linked.controller.signal,
1343
- onProgress: record.publishProgress,
1344
1297
  offline,
1345
- security: effectiveSecurity,
1346
1298
  });
1299
+ record.warnings = Array.isArray(prepared.warnings)
1300
+ ? completeValue([...prepared.warnings])
1301
+ : NO_PROVIDER_WARNINGS;
1302
+ lastWarnings = record.warnings;
1347
1303
  throwIfAborted(
1348
1304
  linked.controller.signal,
1349
1305
  () => linked.reason(`${role}-load-cancelled`),
@@ -1353,16 +1309,14 @@ function createBrowserSpeechProvider({
1353
1309
  "ARCANE_AI_OPERATION_SUPERSEDED",
1354
1310
  "Browser speech loading was superseded.",
1355
1311
  undefined,
1356
- lifecycleReason === `${role}-load-superseded-by-security-change`
1357
- ? lifecycleReason
1358
- : `${role}-load-superseded-by-unload`,
1312
+ `${role}-load-superseded-by-unload`,
1359
1313
  );
1360
1314
  }
1361
1315
  slot = {
1362
1316
  prepared,
1363
1317
  released: false,
1364
1318
  client: null,
1365
- security: effectiveSecurity,
1319
+ warnings: record.warnings,
1366
1320
  };
1367
1321
  slot.client = createSpeechWorkerClient({
1368
1322
  role,
@@ -1382,7 +1336,6 @@ function createBrowserSpeechProvider({
1382
1336
  ? `${role}-worker-protocol-mismatch`
1383
1337
  : `${role}-worker-crashed`);
1384
1338
  activeOperation = null;
1385
- artifactGraphAdmission = null;
1386
1339
  }
1387
1340
  try {
1388
1341
  releaseSlot(slot);
@@ -1393,7 +1346,6 @@ function createBrowserSpeechProvider({
1393
1346
  errorCode = releaseFailure.code;
1394
1347
  lifecycleReason = releaseFailure.reason;
1395
1348
  activeOperation = null;
1396
- artifactGraphAdmission = null;
1397
1349
  }
1398
1350
  throw releaseFailure;
1399
1351
  }
@@ -1408,19 +1360,16 @@ function createBrowserSpeechProvider({
1408
1360
  `${role}-worker-client-authority-mismatch`,
1409
1361
  );
1410
1362
  }
1411
- const configuration = Object.freeze({
1363
+ const configuration = completeValue({
1412
1364
  role,
1413
1365
  runtime: prepared.runtime,
1414
1366
  model: prepared.model,
1415
- security: effectiveSecurity,
1416
1367
  ...(authority.graph ? {
1417
- artifactGraphId: authority.artifactGraphId,
1418
1368
  artifactGraphProtocol: authority.graph.protocol,
1419
1369
  } : {}),
1420
1370
  });
1421
1371
  await slot.client.request("load", { configuration }, {
1422
1372
  signal: linked.controller.signal,
1423
- progress: record.publishProgress,
1424
1373
  });
1425
1374
  throwIfAborted(
1426
1375
  linked.controller.signal,
@@ -1431,14 +1380,11 @@ function createBrowserSpeechProvider({
1431
1380
  "ARCANE_AI_OPERATION_SUPERSEDED",
1432
1381
  "Browser speech loading was superseded.",
1433
1382
  undefined,
1434
- lifecycleReason === `${role}-load-superseded-by-security-change`
1435
- ? lifecycleReason
1436
- : `${role}-load-superseded-by-unload`,
1383
+ `${role}-load-superseded-by-unload`,
1437
1384
  );
1438
1385
  }
1439
1386
  active = slot;
1440
1387
  cache = prepared.cache;
1441
- artifactGraphAdmission = prepared.artifactGraphAdmission ?? null;
1442
1388
  state = "ready";
1443
1389
  lifecycleReason = `${role}-load-completed`;
1444
1390
  activeOperation = null;
@@ -1576,7 +1522,7 @@ function createBrowserSpeechProvider({
1576
1522
  ? encodeSharedSynthesisResult(result, authority)
1577
1523
  : result;
1578
1524
  })();
1579
- requestOperation = Object.freeze({
1525
+ requestOperation = completeValue({
1580
1526
  promise,
1581
1527
  abort: (
1582
1528
  reason = ROLE_REQUEST_REASON[role],
@@ -1605,7 +1551,6 @@ function createBrowserSpeechProvider({
1605
1551
  failure = trustedWorkerFailure(cleanupError, role);
1606
1552
  }
1607
1553
  state = "unloaded";
1608
- artifactGraphAdmission = null;
1609
1554
  }
1610
1555
  lifecycleReason = failure?.reason
1611
1556
  ?? (failure?.code === "ARCANE_AI_REQUEST_ABORTED"
@@ -1678,7 +1623,6 @@ function createBrowserSpeechProvider({
1678
1623
  `${role}-worker-terminated-by-unload`,
1679
1624
  ));
1680
1625
  cache = null;
1681
- artifactGraphAdmission = null;
1682
1626
  state = "unloaded";
1683
1627
  lifecycleReason = `${role}-unload-completed`;
1684
1628
  activeOperation = null;
@@ -1745,7 +1689,7 @@ function createBrowserSpeechProvider({
1745
1689
  );
1746
1690
  },
1747
1691
  };
1748
- return Object.freeze(provider);
1692
+ return completeValue(provider);
1749
1693
  }
1750
1694
 
1751
1695
  export function createBrowserWhisperProvider(options = {}) {