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
package/CHANGELOG.md CHANGED
@@ -2,6 +2,38 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.5.15
6
+
7
+ - Let `SpeechPlayback.prepare()` submit every complete segment immediately when
8
+ an `AI.fetchTTS` client advertises positive TTS execution capacity. The AI
9
+ provider queue retains bounded FIFO admission (four synthesis slots by
10
+ default), while indexed Blob URLs keep playback in exact input order even
11
+ when later segments finish first.
12
+ - Preserve the serialized one-segment-lookahead path for native and custom
13
+ speech clients that do not advertise provider execution capacity. Pause and
14
+ Resume keep the same audio element; Stop aborts all owned synthesis; Replay
15
+ retains completed and pending provider segments while retrying only failed
16
+ missing segments, including failures with falsy rejection values.
17
+ - Add basic copyable `SpeechPlayback` examples and synchronize the installed
18
+ reference, current release identities, generated documentation site, and
19
+ focused behavioral contract source for the new admission model.
20
+
21
+ ## 0.5.14
22
+
23
+ - Add the shared `arcane-os/logging` console owner using the existing
24
+ `user.developer` preference. Route first-party runtime diagnostics and
25
+ default model loggers through this owner; preserve warnings and errors
26
+ when developer mode is disabled.
27
+ - Trace complete speech API inputs, generation queues, Worker requests and
28
+ responses, decoded audio, scheduled playback, natural completion,
29
+ cancellation, and failure under that same developer preference. Preserve
30
+ caller text, voice, speed, language selection, and playback behavior.
31
+ - Preserve complete cloud AI error responses and retry HTTP 429 overload
32
+ responses after three seconds with the original request and cancellation
33
+ signal. Keep partial streams and tool callbacks outside the retry path.
34
+ - Preserve exact URL import-map aliases while adding matching versioned
35
+ aliases, including authored imports, scopes, and generated dependency paths.
36
+
5
37
  ## 0.5.13
6
38
 
7
39
  - Derive local browser resource queries from the selected SDK package version
package/README.md CHANGED
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
19
19
  `arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
20
20
  event, cancellation, and browser run contracts.
21
21
 
22
- This checkout defines the `0.5.12` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.5.15` SDK contract. Applications pin one exact npm
23
23
  version and lockfile; registry state is deliberately not baked into application
24
24
  artifacts.
25
25
 
@@ -35,7 +35,7 @@ Create one browser application, install its pinned SDK, and start its source
35
35
  server:
36
36
 
37
37
  ```bash
38
- npx arcane-os@0.5.12 new hello-speech --path ./hello-speech --target browser
38
+ npx arcane-os@0.5.15 new hello-speech --path ./hello-speech --target browser
39
39
  cd hello-speech
40
40
  npm install
41
41
  npm run dev
@@ -103,6 +103,31 @@ order, but playback waits for earlier segments and plays exact input order.
103
103
  Each slot owns a Worker/model session, so raising capacity trades memory for
104
104
  latency.
105
105
 
106
+ If your app already has complete segments in an array, let the shared
107
+ `SpeechPlayback` owner submit them as soon as they are available:
108
+
109
+ ```javascript
110
+ import SpeechPlayback from 'arcane/SpeechPlayback';
111
+
112
+ const audio = document.body.appendChild(document.createElement('audio'));
113
+ audio.controls = true;
114
+ const narration = new SpeechPlayback({audio, speech: ai});
115
+ const speakAll = document.body.appendChild(document.createElement('button'));
116
+ speakAll.textContent = 'Speak all segments';
117
+ speakAll.addEventListener('click', async function speakAllSegments() {
118
+ await ai.setSpeechMuted(false);
119
+ await narration.prepare({
120
+ parts: ['First segment.', 'Second segment.', 'Third segment.'],
121
+ autoplay: true
122
+ });
123
+ });
124
+ ```
125
+
126
+ With browser speech's default `auto` / 4 configuration, all three complete
127
+ segments enter the provider queue immediately, up to four synthesize at once,
128
+ and the audio still plays first, second, third. Native or custom speech without
129
+ advertised provider execution capacity stays serialized with one lookahead.
130
+
106
131
  Continue with [streaming chunks, device selection, cancellation, status, and cleanup](https://github.com/TheWizardNexus/arcane-os-sdk/blob/main/docs/reference/ai/browser-speech.md),
107
132
  the [tiny TWiN Cloud request and saved-preference migration](https://github.com/TheWizardNexus/arcane-os-sdk/blob/main/docs/reference/ai/twin-cloud.md),
108
133
  or the [maintained WASM voice-chat example](https://github.com/TheWizardNexus/arcane-os-sdk/tree/main/examples/wasm-ai-demo).
@@ -297,7 +322,7 @@ uses the same controller for automatic memory extraction.
297
322
  Create a new repository-shaped Arcane application with the exact stable SDK:
298
323
 
299
324
  ```bash
300
- npx arcane-os@0.5.12 new my-app --path ./my-app --target portable --git
325
+ npx arcane-os@0.5.15 new my-app --path ./my-app --target portable --git
301
326
  cd my-app
302
327
  npm install
303
328
  npm run dev
@@ -307,7 +332,7 @@ To enroll an existing repository, install the exact SDK and initialize only
307
332
  missing Arcane files:
308
333
 
309
334
  ```bash
310
- npm install --save-dev --save-exact arcane-os@0.5.12
335
+ npm install --save-dev --save-exact arcane-os@0.5.15
311
336
  npm exec -- arcane init my-app --target portable
312
337
  ```
313
338
 
@@ -323,7 +348,7 @@ npm exec -- arcane-os targets
323
348
  No global SDK install or standalone Arcane CLI is required. The application
324
349
  repository's exact npm dependency and lockfile own the CLI and toolchain version.
325
350
 
326
- Use `npx arcane-os@0.5.12` for the initial bootstrap because it names this npm
351
+ Use `npx arcane-os@0.5.15` for the initial bootstrap because it names this npm
327
352
  package explicitly; bare `npx arcane` outside an installed project could resolve
328
353
  a different package. Both installed commands invoke the same headless toolchain.
329
354
  Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
@@ -343,7 +368,7 @@ node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
343
368
 
344
369
  # From the generated app repository
345
370
  cd ../local-app
346
- npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.5.12.tgz
371
+ npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.5.15.tgz
347
372
  npm ci
348
373
  ```
349
374
 
@@ -352,7 +377,7 @@ same location. The lockfile retains the selected package dependency while
352
377
  Arcane uses the installed package name and version. Local directory `file:` dependencies are not
353
378
  accepted because npm may install them as links; use a packed `.tgz`. A GitHub
354
379
  runner also needs that tarball at the locked path. After publication, replace
355
- the local declaration with the exact `arcane-os@0.5.12` registry package and
380
+ the local declaration with the exact `arcane-os@0.5.15` registry package and
356
381
  commit the regenerated lock.
357
382
 
358
383
  Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
@@ -491,7 +516,7 @@ package installation, or assertions.
491
516
 
492
517
  ## Current target support
493
518
 
494
- Version `0.5.12` exposes one browser target and five explicitly paired
519
+ Version `0.5.15` exposes one browser target and five explicitly paired
495
520
  native development targets: a non-runnable portable directory, a
496
521
  Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
497
522
  unsigned-local-test DEBs, and an Android development-signed APK. The
@@ -1,3 +1,4 @@
1
+ import { arcaneLogging } from '../logging.mjs';
1
2
  import {
2
3
  ARCANE_AI_ADAPTER_PROTOCOL,
3
4
  ArcaneAIError,
@@ -1148,7 +1149,7 @@ export function createDbopfsModelStore({
1148
1149
  try {
1149
1150
  await removeMemberRangeParts(modelName, { total });
1150
1151
  } catch (error) {
1151
- globalThis.console?.warn?.(
1152
+ arcaneLogging.warn?.(
1152
1153
  "Arcane could not remove superseded browser model range parts.",
1153
1154
  error,
1154
1155
  );
@@ -1163,7 +1164,7 @@ export function createDbopfsModelStore({
1163
1164
  total: ranges[0]?.total ?? null,
1164
1165
  });
1165
1166
  } catch (error) {
1166
- globalThis.console?.warn?.(
1167
+ arcaneLogging.warn?.(
1167
1168
  "Arcane could not remove superseded browser model range parts.",
1168
1169
  error,
1169
1170
  );
@@ -1195,7 +1196,7 @@ export function createDbopfsModelStore({
1195
1196
  await removeMemberRangeParts(modelName, { total });
1196
1197
  }
1197
1198
  } catch (error) {
1198
- globalThis.console?.warn?.(
1199
+ arcaneLogging.warn?.(
1199
1200
  "Arcane could not remove superseded browser model range parts.",
1200
1201
  error,
1201
1202
  );
@@ -2404,7 +2405,7 @@ function callbackStreamHandle({ runtime, request, signal, onSettled }) {
2404
2405
  Promise.resolve().then(() => this.cancel(
2405
2406
  "The stream consumer stopped before completion.",
2406
2407
  )).catch(function reportBrowserWasmStreamReturnCancellationFailure(error) {
2407
- console.error("Arcane browser-WASM stream early-return cancellation failed.", error);
2408
+ arcaneLogging.error("Arcane browser-WASM stream early-return cancellation failed.", error);
2408
2409
  });
2409
2410
  return { value, done: true };
2410
2411
  },
@@ -2432,7 +2433,7 @@ function validatedV1StreamHandle(opened, request) {
2432
2433
  Promise.resolve().then(function cancelInvalidV1StreamHandle() {
2433
2434
  return opened.cancel("The v1 provider returned an invalid stream handle.");
2434
2435
  }).catch(function reportInvalidV1StreamCleanupFailure(error) {
2435
- console.error("Arcane invalid v1 stream cleanup failed.", error);
2436
+ arcaneLogging.error("Arcane invalid v1 stream cleanup failed.", error);
2436
2437
  });
2437
2438
  }
2438
2439
  throw fail(
@@ -2447,7 +2448,7 @@ function validatedV1StreamHandle(opened, request) {
2447
2448
  Promise.resolve().then(function cancelRejectedV1Iterator() {
2448
2449
  return opened.cancel(error);
2449
2450
  }).catch(function reportRejectedV1IteratorCleanupFailure(cleanupError) {
2450
- console.error("Arcane rejected v1 stream iterator cleanup failed.", cleanupError);
2451
+ arcaneLogging.error("Arcane rejected v1 stream iterator cleanup failed.", cleanupError);
2451
2452
  });
2452
2453
  throw error;
2453
2454
  }
@@ -2455,7 +2456,7 @@ function validatedV1StreamHandle(opened, request) {
2455
2456
  Promise.resolve().then(function cancelInvalidV1Iterator() {
2456
2457
  return opened.cancel("The v1 provider returned an invalid stream iterator.");
2457
2458
  }).catch(function reportInvalidV1IteratorCleanupFailure(error) {
2458
- console.error("Arcane invalid v1 stream iterator cleanup failed.", error);
2459
+ arcaneLogging.error("Arcane invalid v1 stream iterator cleanup failed.", error);
2459
2460
  });
2460
2461
  throw fail(
2461
2462
  "ARCANE_AI_INVALID_PROVIDER_RESULT",
@@ -2539,13 +2540,13 @@ function validatedV1StreamHandle(opened, request) {
2539
2540
  Promise.resolve().then(function returnUnderlyingV1Stream() {
2540
2541
  return iterator.return(value);
2541
2542
  }).catch(function reportUnderlyingV1StreamReturnFailure(error) {
2542
- console.error("Arcane v1 stream iterator return failed.", error);
2543
+ arcaneLogging.error("Arcane v1 stream iterator return failed.", error);
2543
2544
  });
2544
2545
  }
2545
2546
  Promise.resolve().then(function cancelReturnedV1Stream() {
2546
2547
  return opened.cancel("The stream consumer stopped before completion.");
2547
2548
  }).catch(function reportReturnedV1StreamCancellationFailure(error) {
2548
- console.error("Arcane v1 stream early-return cancellation failed.", error);
2549
+ arcaneLogging.error("Arcane v1 stream early-return cancellation failed.", error);
2549
2550
  });
2550
2551
  return { value, done: true };
2551
2552
  },
@@ -2778,7 +2779,7 @@ export function createBrowserWasmLlmProvider({
2778
2779
  store,
2779
2780
  loadDefaults = {},
2780
2781
  security,
2781
- logger = console,
2782
+ logger = arcaneLogging,
2782
2783
  } = {}) {
2783
2784
  const configuredModels = providerModelSources(sources);
2784
2785
  const modelSources = configuredModels.sources;
@@ -1,3 +1,4 @@
1
+ import { arcaneLogging } from '../logging.mjs';
1
2
  import { Wllama } from "./wllama/index.mjs";
2
3
 
3
4
  const completeValue = (value) => value;
@@ -276,7 +277,7 @@ function initialEvidence() {
276
277
  * network or browser side effects until load() is called. Runtime URLs are
277
278
  * fixed relative to this module for npm and materialized /arcane/sdk trees.
278
279
  */
279
- export function createPackagedWllamaRuntime({ logger = console } = {}) {
280
+ export function createPackagedWllamaRuntime({ logger = arcaneLogging } = {}) {
280
281
  let engine = null;
281
282
  let pending = null;
282
283
  let inferenceActive = false;
@@ -1,3 +1,4 @@
1
+ import { arcaneLogging } from '../logging.mjs';
1
2
  import { createArcaneEventSource } from "arcane-os/event-manager";
2
3
 
3
4
  export const ARCANE_AI_ADAPTER_PROTOCOL = "arcane-ai-adapter/1";
@@ -1372,7 +1373,7 @@ export class ModelController {
1372
1373
  Promise.resolve().then(()=>value.cancel(
1373
1374
  "The provider returned an invalid stream handle.",
1374
1375
  )).catch(function reportInvalidModelStreamCleanupFailure(error){
1375
- console.error("Arcane invalid model stream cleanup failed.",error);
1376
+ arcaneLogging.error("Arcane invalid model stream cleanup failed.",error);
1376
1377
  });
1377
1378
  }
1378
1379
  throw new ArcaneAIError(
@@ -1386,7 +1387,7 @@ export class ModelController {
1386
1387
  }catch(error){
1387
1388
  Promise.resolve().then(()=>value.cancel(error)).catch(
1388
1389
  function reportRejectedModelIteratorCleanupFailure(cleanupError){
1389
- console.error("Arcane rejected model stream iterator cleanup failed.",cleanupError);
1390
+ arcaneLogging.error("Arcane rejected model stream iterator cleanup failed.",cleanupError);
1390
1391
  },
1391
1392
  );
1392
1393
  throw error;
@@ -1395,7 +1396,7 @@ export class ModelController {
1395
1396
  Promise.resolve().then(()=>value.cancel(
1396
1397
  "The provider returned an invalid stream iterator.",
1397
1398
  )).catch(function reportInvalidModelIteratorCleanupFailure(error){
1398
- console.error("Arcane invalid model stream iterator cleanup failed.",error);
1399
+ arcaneLogging.error("Arcane invalid model stream iterator cleanup failed.",error);
1399
1400
  });
1400
1401
  throw new ArcaneAIError(
1401
1402
  "ARCANE_AI_INVALID_PROVIDER_RESULT",
@@ -1468,7 +1469,7 @@ export class ModelController {
1468
1469
  const value = opened ?? await openPromise;
1469
1470
  await value.cancel?.(reason);
1470
1471
  } catch (error) {
1471
- console.error("Arcane model provider cancellation failed.",error);
1472
+ arcaneLogging.error("Arcane model provider cancellation failed.",error);
1472
1473
  }
1473
1474
  try {
1474
1475
  await result;
@@ -1491,7 +1492,7 @@ export class ModelController {
1491
1492
  Promise.resolve().then(()=>this.cancel(
1492
1493
  "The stream consumer stopped before completion.",
1493
1494
  )).catch(function reportModelStreamReturnCancellationFailure(error){
1494
- console.error("Arcane model stream early-return cancellation failed.",error);
1495
+ arcaneLogging.error("Arcane model stream early-return cancellation failed.",error);
1495
1496
  });
1496
1497
  return { value, done: true };
1497
1498
  },
@@ -3,8 +3,10 @@ import {
3
3
  normalizeSpeechWorkerErrorEnvelope,
4
4
  SPEECH_WORKER_PROTOCOL,
5
5
  } from "./speech-worker-runtime.mjs";
6
+ import { arcaneLogging } from "../logging.mjs";
6
7
 
7
8
  const completeValue = (value) => value;
9
+ let nextSpeechWorkerClientId = 0;
8
10
 
9
11
  const PUBLIC_WORKER_OPERATIONS = new Set([
10
12
  "load",
@@ -57,6 +59,7 @@ const WORKER_CLIENTS = new WeakSet();
57
59
  * Workers remain available.
58
60
  */
59
61
  class SpeechWorkerClient {
62
+ #diagnosticId = ++nextSpeechWorkerClientId;
60
63
  #role;
61
64
  #createWorker;
62
65
  #worker = null;
@@ -84,6 +87,29 @@ class SpeechWorkerClient {
84
87
  });
85
88
  this.#onTermination = onTermination;
86
89
  WORKER_CLIENTS.add(this);
90
+ this.#trace("created", { workerUrl: workerUrl.href });
91
+ }
92
+
93
+ #trace(phase, detail = {}, beforeTransfer = false) {
94
+ if (!arcaneLogging.enabled) return;
95
+ let snapshot = detail;
96
+ if (beforeTransfer) {
97
+ try {
98
+ // Preserve complete request content before postMessage transfers it.
99
+ snapshot = structuredClone(detail);
100
+ } catch {
101
+ // Retain the complete original if native cloning cannot represent it.
102
+ }
103
+ }
104
+ arcaneLogging.debug("[Arcane speech worker]", {
105
+ clientId: this.#diagnosticId,
106
+ role: this.#role,
107
+ phase,
108
+ timestamp: new Date().toISOString(),
109
+ timeMs: globalThis.performance?.now?.() ?? Date.now(),
110
+ pendingRequests: this.#pending.size,
111
+ ...snapshot,
112
+ });
87
113
  }
88
114
 
89
115
  #listen(target, type, listener) {
@@ -103,6 +129,7 @@ class SpeechWorkerClient {
103
129
  }
104
130
  const worker = validateWorker(this.#createWorker());
105
131
  this.#worker = worker;
132
+ this.#trace("started");
106
133
  this.#listen(worker, "message", (event) => {
107
134
  this.#handleMessage(event.data);
108
135
  });
@@ -126,6 +153,7 @@ class SpeechWorkerClient {
126
153
  }
127
154
 
128
155
  #handleMessage(message) {
156
+ this.#trace("response", { message });
129
157
  if (message?.protocol !== SPEECH_WORKER_PROTOCOL) {
130
158
  void this.terminate(clientError(
131
159
  "ARCANE_AI_ADAPTER_PROTOCOL_MISMATCH",
@@ -177,7 +205,9 @@ class SpeechWorkerClient {
177
205
  }
178
206
 
179
207
  request(op, payload, { signal = null } = {}) {
208
+ this.#trace("request.call", { op, payload, aborted: signal?.aborted });
180
209
  if (!PUBLIC_WORKER_OPERATIONS.has(op)) {
210
+ this.#trace("request.rejected", { op, reason: "unknown-operation" });
181
211
  return Promise.reject(clientError(
182
212
  "ARCANE_AI_INVALID_REQUEST",
183
213
  "The speech worker operation is not part of its protocol.",
@@ -185,12 +215,16 @@ class SpeechWorkerClient {
185
215
  `${this.#role}-worker-operation-unknown`,
186
216
  ));
187
217
  }
188
- if (signal?.aborted) return Promise.reject(abortError(signal, this.#role, op));
218
+ if (signal?.aborted) {
219
+ this.#trace("request.cancelled", { op, reason: signal.reason });
220
+ return Promise.reject(abortError(signal, this.#role, op));
221
+ }
189
222
  let worker;
190
223
  try {
191
224
  worker = this.#start();
192
225
  this.#transport = worker;
193
226
  } catch (error) {
227
+ this.#trace("request.error", { op, error });
194
228
  return Promise.reject(error);
195
229
  }
196
230
  const id = this.#nextId;
@@ -206,6 +240,7 @@ class SpeechWorkerClient {
206
240
  function onAbort() {
207
241
  const pending = client.#pending.get(id);
208
242
  if (!pending) return;
243
+ client.#trace("request.cancelled", { id, op, reason: signal?.reason });
209
244
  if (client.#role === "tts" && op === "use") {
210
245
  client.#pending.delete(id);
211
246
  pending.cleanup();
@@ -213,13 +248,16 @@ class SpeechWorkerClient {
213
248
  const cancelId = client.#nextId;
214
249
  client.#nextId += 1;
215
250
  try {
216
- client.#transport.postMessage({
251
+ const cancellation = {
217
252
  protocol: SPEECH_WORKER_PROTOCOL,
218
253
  id: cancelId,
219
254
  op: "cancel",
220
255
  payload: { targetId: id },
221
- });
256
+ };
257
+ client.#trace("request.dispatch", { message: cancellation }, true);
258
+ client.#transport.postMessage(cancellation);
222
259
  } catch (error) {
260
+ client.#trace("request.error", { id: cancelId, op: "cancel", error });
223
261
  const failure = clientError(
224
262
  "ARCANE_AI_WORKER_MESSAGE_ERROR",
225
263
  "Unable to cancel an operation in the speech Worker.",
@@ -251,8 +289,10 @@ class SpeechWorkerClient {
251
289
  };
252
290
  const transfers = collectSpeechTransferables(payload);
253
291
  try {
292
+ client.#trace("request.dispatch", { message }, true);
254
293
  client.#transport.postMessage(message, transfers);
255
294
  } catch (error) {
295
+ client.#trace("request.error", { id, op, error });
256
296
  client.#pending.delete(id);
257
297
  cleanup();
258
298
  const failure = clientError(
@@ -272,6 +312,7 @@ class SpeechWorkerClient {
272
312
  }
273
313
 
274
314
  async terminate(reason = null, { intentional = true } = {}) {
315
+ this.#trace("terminate.call", { reason, intentional });
275
316
  if (typeof intentional !== "boolean") {
276
317
  throw new TypeError("Speech Worker termination intent must be a boolean.");
277
318
  }
@@ -303,6 +344,7 @@ class SpeechWorkerClient {
303
344
  if (termination && typeof termination.then === "function") await termination;
304
345
  } finally {
305
346
  for (const pending of pendingOperations) pending.reject(terminationReason);
347
+ this.#trace("terminated", { reason: terminationReason, intentional });
306
348
  this.#onTermination(completeValue({ reason: terminationReason, intentional }));
307
349
  }
308
350
  }
@@ -1,4 +1,5 @@
1
1
  import EventPubSub from 'event-pubsub';
2
+ import {arcaneLogging} from './logging.mjs';
2
3
  import {createDOMInstrumentation} from './dom-event-instrumentation.mjs';
3
4
 
4
5
  export {
@@ -1368,7 +1369,7 @@ function createArcaneEventAuthority(){
1368
1369
  return;
1369
1370
  }
1370
1371
  }catch{}
1371
- try{globalThis.console?.error?.('Arcane event listener failed.',error);}
1372
+ try{arcaneLogging.error('Arcane event listener failed.',error);}
1372
1373
  catch{}
1373
1374
  }
1374
1375
 
@@ -0,0 +1,58 @@
1
+ /** Read the shared user preference; null means the user has not loaded yet. */
2
+ export function readArcaneDeveloperMode(target=globalThis){
3
+ try{
4
+ if(target?.user?.ready!==true){
5
+ return null;
6
+ }
7
+
8
+ return target.user.developer===true;
9
+ }catch{
10
+ return false;
11
+ }
12
+ }
13
+
14
+ function emitArcaneLog(method,diagnostic,args){
15
+ try{
16
+ if(diagnostic&&readArcaneDeveloperMode()!==true){
17
+ return;
18
+ }
19
+ globalThis.console?.[method]?.(...args);
20
+ }catch{
21
+ // Console diagnostics must never change the operation being observed.
22
+ }
23
+ }
24
+
25
+ const loggingOwnerKey=Symbol.for('arcane.logging');
26
+
27
+ /**
28
+ * The shared developer console owner. Diagnostic methods read user.developer
29
+ * for every emission and use info so they remain visible at normal console
30
+ * levels. Warnings, errors, and failure traces remain visible in every mode.
31
+ * Arguments pass through unchanged and are never retained by this owner.
32
+ */
33
+ export const arcaneLogging=globalThis[loggingOwnerKey]??{
34
+ get enabled(){
35
+ return readArcaneDeveloperMode()===true;
36
+ },
37
+ log(...args){
38
+ emitArcaneLog('info',true,args);
39
+ },
40
+ info(...args){
41
+ emitArcaneLog('info',true,args);
42
+ },
43
+ debug(...args){
44
+ emitArcaneLog('info',true,args);
45
+ },
46
+ warn(...args){
47
+ emitArcaneLog('warn',false,args);
48
+ },
49
+ error(...args){
50
+ emitArcaneLog('error',false,args);
51
+ },
52
+ trace(...args){
53
+ emitArcaneLog('trace',false,args);
54
+ }
55
+ };
56
+
57
+ globalThis[loggingOwnerKey]=arcaneLogging;
58
+ globalThis.arcaneLogging=arcaneLogging;
@@ -279,7 +279,7 @@ paths are withheld from the native provider. The provider copies the complete
279
279
  selected release rather than accepting an unrelated source path. Verification
280
280
  is a separate explicit operation for a selected release artifact.
281
281
 
282
- The SDK `0.5.12` runtime requires Arcane `0.8.12` or newer. Compatibility
282
+ The SDK `0.5.15` runtime requires Arcane `0.8.12` or newer. Compatibility
283
283
  is contractual rather than exact-version pinning: the prepared Core must meet
284
284
  the highest minimum declared by the runtime, selected app, and bundled app
285
285
  dependencies; keep each app's Arcane protocol generation; and provide every
@@ -17,7 +17,7 @@ high-level page links to the relevant deep section instead of repeating it.
17
17
  Install the SDK in your application:
18
18
 
19
19
  ```sh
20
- npm install --save-exact arcane-os@0.5.12
20
+ npm install --save-exact arcane-os@0.5.15
21
21
  ```
22
22
 
23
23
  For your first AI call, follow the [TWiN Cloud quick start](ai/twin-cloud.md).
@@ -34,12 +34,14 @@ alone does not make bare module names resolve in a browser.
34
34
  | --- | --- |
35
35
  | Use the Node.js package API | [SDK JavaScript API](sdk-api.md) |
36
36
  | Publish central events, capture complete time-travel history, or observe the DOM | [EventManager and event-stack reference](event-manager.md) |
37
+ | Inspect complete AI and speech calls using the shared developer-mode preference | [Shared logger](sdk-api.md#arcanelogging) and [speech developer diagnostics](ai/browser-speech.md#developer-diagnostics) |
37
38
  | Use the `arcane` command | [CLI reference](cli.md) |
38
39
  | Generate named browser imports or inspect the selected physical runtime | [`arcane import-map`](cli.md#arcane-import-map) and [browser runtime delivery](protocols.md#browser-runtime-delivery) |
39
40
  | Choose browser, native, cloud, or cross-host behavior | [Availability and normalization](availability-and-normalization.md) |
40
41
  | Import a shipped renderer module | [Runtime module catalog](runtime-modules.md) |
41
42
  | Use a shared entity | [Runtime entity modules](runtime-entities.md) and [exact export contracts](core/arcane-entities.md) |
42
43
  | Load a reusable HTML component | [Runtime component catalog](runtime-components.md) |
44
+ | Submit complete speech parts immediately and play them in order | [`SpeechPlayback`](runtime-modules.md#speechplaybackjs) and the [basic browser example](ai/browser-speech.md#play-a-complete-array-with-speechplayback) |
43
45
  | Call `globalThis.Arcane` | [Arcane Core API](core/arcane-api.md) |
44
46
  | Subscribe to native events | [Arcane event reference](core/arcane-events.md) |
45
47
  | Use provider-neutral AI lifecycle, chat, speech, persistence, or document context | [Normalized AI](#normalized-ai) |
@@ -56,9 +58,9 @@ This repository contains explicitly versioned surfaces with different owners:
56
58
 
57
59
  | Surface | Source identity | Meaning |
58
60
  | --- | --- | --- |
59
- | SDK and CLI | `arcane-os` `0.5.12` | The Node.js toolchain, portable `arcane-os/event-manager`, `arcane-os/mail`, `arcane-os/preference-store`, and `arcane-os/speech-playback` entrypoints, plus the browser-only `arcane-os/ai/browser-wasm` and `arcane-os/ai/browser-speech` entrypoints in this checkout. |
60
- | Browser runtime | SDK `0.5.12`, protocol `arcane/1`, `runtime/` | The SDK-canonical runtime tree. `listRuntimeFiles()`, `readRuntimeFile()`, and `loadRuntimeRelease()` derive its current inventory directly from the selected directory. |
61
- | Browser SDK runtime | SDK `0.5.12`, `browser-runtime/` | The browser closure for events, Wllama, and Browser Speech mechanisms. `listSdkBrowserRuntimeFiles()`, `readSdkBrowserRuntimeFile()`, and `loadSdkBrowserRuntimeRelease()` derive its current inventory directly from the selected directory. |
61
+ | SDK and CLI | `arcane-os` `0.5.15` | The Node.js toolchain, portable `arcane-os/event-manager`, `arcane-os/logging`, `arcane-os/mail`, `arcane-os/preference-store`, and `arcane-os/speech-playback` entrypoints, plus the browser-only `arcane-os/ai/browser-wasm` and `arcane-os/ai/browser-speech` entrypoints in this checkout. |
62
+ | Browser runtime | SDK `0.5.15`, protocol `arcane/1`, `runtime/` | The SDK-canonical runtime tree. `listRuntimeFiles()`, `readRuntimeFile()`, and `loadRuntimeRelease()` derive its current inventory directly from the selected directory. |
63
+ | Browser SDK runtime | SDK `0.5.15`, `browser-runtime/` | The browser closure for events, shared logging, Wllama, and Browser Speech mechanisms. `listSdkBrowserRuntimeFiles()`, `readSdkBrowserRuntimeFile()`, and `loadSdkBrowserRuntimeRelease()` derive its current inventory directly from the selected directory. |
62
64
  | Core reference snapshot | Arcane OS commit `567ad110bf57a1c2d4a3daa22ae93716cc5f4d7e`, protocol `arcane/1` | The application-facing Core contract imported into `docs/reference/core/`, with SDK-local links and package-boundary notes added explicitly. |
63
65
 
64
66
  The SDK runtime source and Core reference have different owners. A browser
@@ -73,7 +75,7 @@ and the distinction between a documentation snapshot and the selected runtime.
73
75
 
74
76
  ## Installed documentation and release identity
75
77
 
76
- This reference accompanies `arcane-os@0.5.12`. The installed package includes
78
+ This reference accompanies `arcane-os@0.5.15`. The installed package includes
77
79
  the maintained `docs/` tree and `examples/wasm-ai-demo/` source alongside
78
80
  README and CHANGELOG. Open `node_modules/arcane-os/docs/reference/README.md`
79
81
  for the matching local reference. The generated website and test suites remain
@@ -122,10 +124,10 @@ Public reference entries follow the established Arcane documentation model:
122
124
 
123
125
  ## Public runtime inventory
124
126
 
125
- The package exposes 204 semantic JavaScript records across 16 JavaScript
127
+ The package exposes 198 semantic JavaScript records across 17 JavaScript
126
128
  entrypoints, plus eight JSON Schemas and package metadata. Ten entrypoints are
127
129
  Node.js control-plane surfaces,
128
- `arcane-os/event-manager`, `arcane-os/mail`, `arcane-os/preference-store`, and
130
+ `arcane-os/event-manager`, `arcane-os/logging`, `arcane-os/mail`, `arcane-os/preference-store`, and
129
131
  `arcane-os/speech-playback` run in Node and browsers, and
130
132
  `arcane-os/ai/browser-wasm` plus `arcane-os/ai/browser-speech` are browser-only.
131
133
  The [machine-readable package