arcane-os 0.5.15 → 0.5.16

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.5.16
6
+
7
+ - Add optional Markdown narration filtering before `AI.streamTTS()`
8
+ segmentation. Omit repeated same formatting marks across streamed chunks,
9
+ preserve single marks and ordinary punctuation, and clear formatting state
10
+ on terminal flush or cancellation. Keep plain speech, displayed and stored
11
+ text, language, voice, synthesis capacity, and audio scheduling unchanged.
12
+ - Select Markdown narration in shared chat and include focused filter test
13
+ source without adding a parser dependency.
14
+
5
15
  ## 0.5.15
6
16
 
7
17
  - Let `SpeechPlayback.prepare()` submit every complete segment immediately when
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.15` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.5.16` 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.15 new hello-speech --path ./hello-speech --target browser
38
+ npx arcane-os@0.5.16 new hello-speech --path ./hello-speech --target browser
39
39
  cd hello-speech
40
40
  npm install
41
41
  npm run dev
@@ -51,6 +51,10 @@ For a first spoken sentence, copy the application-owned
51
51
  beside `App.js`, then use this module. That one configuration file defines the
52
52
  upstream runtime, model, dtype, and voice. This module creates the application's
53
53
  DBOPFS store; the SDK creates and manages its speech providers and Workers.
54
+ The linked automatic/WebGPU-first selection uses `fp32` because
55
+ [Kokoro.js recommends `fp32` when using WebGPU](https://github.com/hexgrad/kokoro/tree/main/kokoro.js#usage).
56
+ Its `selectedDevice` status reports the loaded route, not speech correctness or
57
+ audio quality.
54
58
 
55
59
  ```javascript
56
60
  import arcaneThemeReady from 'arcane/ThemeBootstrap';
@@ -301,6 +305,14 @@ keeping apostrophes, commas, and hyphens that join Unicode letters or numbers
301
305
  inside the same segment.
302
306
  Mute, stop, provider transition, and cancellation still govern the whole queue.
303
307
 
308
+ For raw Markdown narration, use
309
+ `ai.streamTTS(text,false,{textFormat:'markdown'})`. The SDK removes repeated
310
+ same formatting marks (`*`, `#`, `_`, backtick, `~`) before segmentation,
311
+ including runs split across chunks. Single punctuation and ordinary repeated
312
+ punctuation remain literal. The format lasts through `finishTTS()`; new
313
+ streams default to exact plain text. Shared chat selects Markdown mode while
314
+ preserving original messages for display and storage.
315
+
304
316
  The SDK runtime also owns `DBOPFSDocumentLibrary`,
305
317
  `DocumentLexicalSearch`, and `PersistentAIChatSession`. Document bootstrap is
306
318
  schema-driven and explicit; chat never searches a corpus unless the app wires
@@ -322,7 +334,7 @@ uses the same controller for automatic memory extraction.
322
334
  Create a new repository-shaped Arcane application with the exact stable SDK:
323
335
 
324
336
  ```bash
325
- npx arcane-os@0.5.15 new my-app --path ./my-app --target portable --git
337
+ npx arcane-os@0.5.16 new my-app --path ./my-app --target portable --git
326
338
  cd my-app
327
339
  npm install
328
340
  npm run dev
@@ -332,7 +344,7 @@ To enroll an existing repository, install the exact SDK and initialize only
332
344
  missing Arcane files:
333
345
 
334
346
  ```bash
335
- npm install --save-dev --save-exact arcane-os@0.5.15
347
+ npm install --save-dev --save-exact arcane-os@0.5.16
336
348
  npm exec -- arcane init my-app --target portable
337
349
  ```
338
350
 
@@ -348,7 +360,7 @@ npm exec -- arcane-os targets
348
360
  No global SDK install or standalone Arcane CLI is required. The application
349
361
  repository's exact npm dependency and lockfile own the CLI and toolchain version.
350
362
 
351
- Use `npx arcane-os@0.5.15` for the initial bootstrap because it names this npm
363
+ Use `npx arcane-os@0.5.16` for the initial bootstrap because it names this npm
352
364
  package explicitly; bare `npx arcane` outside an installed project could resolve
353
365
  a different package. Both installed commands invoke the same headless toolchain.
354
366
  Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
@@ -368,7 +380,7 @@ node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
368
380
 
369
381
  # From the generated app repository
370
382
  cd ../local-app
371
- npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.5.15.tgz
383
+ npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.5.16.tgz
372
384
  npm ci
373
385
  ```
374
386
 
@@ -377,7 +389,7 @@ same location. The lockfile retains the selected package dependency while
377
389
  Arcane uses the installed package name and version. Local directory `file:` dependencies are not
378
390
  accepted because npm may install them as links; use a packed `.tgz`. A GitHub
379
391
  runner also needs that tarball at the locked path. After publication, replace
380
- the local declaration with the exact `arcane-os@0.5.15` registry package and
392
+ the local declaration with the exact `arcane-os@0.5.16` registry package and
381
393
  commit the regenerated lock.
382
394
 
383
395
  Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
@@ -516,7 +528,7 @@ package installation, or assertions.
516
528
 
517
529
  ## Current target support
518
530
 
519
- Version `0.5.15` exposes one browser target and five explicitly paired
531
+ Version `0.5.16` exposes one browser target and five explicitly paired
520
532
  native development targets: a non-runnable portable directory, a
521
533
  Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
522
534
  unsigned-local-test DEBs, and an Android development-signed APK. The
@@ -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.15` runtime requires Arcane `0.8.12` or newer. Compatibility
282
+ The SDK `0.5.16` 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.15
20
+ npm install --save-exact arcane-os@0.5.16
21
21
  ```
22
22
 
23
23
  For your first AI call, follow the [TWiN Cloud quick start](ai/twin-cloud.md).
@@ -58,9 +58,9 @@ This repository contains explicitly versioned surfaces with different owners:
58
58
 
59
59
  | Surface | Source identity | Meaning |
60
60
  | --- | --- | --- |
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. |
61
+ | SDK and CLI | `arcane-os` `0.5.16` | 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.16`, 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.16`, `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. |
64
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. |
65
65
 
66
66
  The SDK runtime source and Core reference have different owners. A browser
@@ -75,7 +75,7 @@ and the distinction between a documentation snapshot and the selected runtime.
75
75
 
76
76
  ## Installed documentation and release identity
77
77
 
78
- This reference accompanies `arcane-os@0.5.15`. The installed package includes
78
+ This reference accompanies `arcane-os@0.5.16`. The installed package includes
79
79
  the maintained `docs/` tree and `examples/wasm-ai-demo/` source alongside
80
80
  README and CHANGELOG. Open `node_modules/arcane-os/docs/reference/README.md`
81
81
  for the matching local reference. The generated website and test suites remain
@@ -13,7 +13,7 @@ import map resolves `arcane/AI` and `arcane/DBOPFS`. These browser modules are
13
13
  not Node inference APIs. To create an application:
14
14
 
15
15
  ```bash
16
- npx arcane-os@0.5.15 new hello-speech --path ./hello-speech --target browser
16
+ npx arcane-os@0.5.16 new hello-speech --path ./hello-speech --target browser
17
17
  cd hello-speech
18
18
  npm install
19
19
  npm run dev
@@ -37,7 +37,7 @@ export const speechSelection = {
37
37
  id: 'onnx-community/Kokoro-82M-v1.0-ONNX',
38
38
  repository: 'onnx-community/Kokoro-82M-v1.0-ONNX',
39
39
  revision: '1939ad2a8e416c0acfeecc08a694d14ef25f2231',
40
- dtype: 'q8',
40
+ dtype: 'fp32',
41
41
  defaultVoice: 'af_heart'
42
42
  },
43
43
  runtime: {
@@ -55,6 +55,15 @@ export const speechSelection = {
55
55
  };
56
56
  ```
57
57
 
58
+ The omitted execution record below uses the SDK's WebGPU-first automatic
59
+ selection, so this basic configuration uses `fp32` because
60
+ [Kokoro.js recommends `fp32` when using WebGPU](https://github.com/hexgrad/kokoro/tree/main/kokoro.js#usage).
61
+ Automatic fallback carries this same selected model and dtype to WASM; the SDK
62
+ does not rewrite the application selection. If you intentionally choose
63
+ another dtype, evaluate that exact model, browser, and execution route.
64
+ `selectedDevice` reports routing after load, not pronunciation, text fidelity,
65
+ or audio quality.
66
+
58
67
  Then use this **`App.js`**. The application creates and owns the DBOPFS
59
68
  instance. Configuration selects the provider without loading it; the button
60
69
  explicitly loads and unmutes TTS before requesting speech.
@@ -221,8 +230,10 @@ only failed missing segments.
221
230
 
222
231
  The default `{device:'auto',maxConcurrentRequests:4}` attempts the full ONNX
223
232
  Worker/session pool on WebGPU, then recreates that pool on WASM only if WebGPU
224
- loading fails. Use the status example below to read `selectedDevice`; console
225
- node-assignment warnings alone do not identify the selected execution device.
233
+ loading fails. The basic configuration above keeps `fp32` for this WebGPU-first
234
+ path and any automatic WASM fallback. Use the status example below to read
235
+ `selectedDevice`; console node-assignment warnings alone do not identify the
236
+ selected execution device or assess the generated audio.
226
237
 
227
238
  ## Queue complete passages and wait for playback
228
239
 
@@ -275,8 +286,9 @@ all speech owned by that AI instance and settles pending playback results
275
286
  The selected voice and speed are captured for segments extracted by that call.
276
287
  That includes any text left in the same AI instance's partial-stream buffer;
277
288
  finish the previous producer before starting a separate complete passage.
278
- Options are not retained with an unfinished `end:false` remainder. A later
279
- call supplies its own options, and `finishTTS()` uses defaults.
289
+ Voice, speed, pause, and playback options are not retained with an unfinished
290
+ `end:false` remainder. A later call supplies its own options, and `finishTTS()`
291
+ uses their defaults. The optional text format below lasts through that flush.
280
292
  A call extracting no segments returns `true` without waiting for earlier jobs.
281
293
  An already muted call returns `false`.
282
294
 
@@ -337,6 +349,30 @@ boundary; `finishTTS()` flushes any remaining text. It is not a playback-ended
337
349
  notification. Do not mute or dispose immediately after it if playback should
338
350
  continue.
339
351
 
352
+ ## Omit Markdown formatting marks from narration
353
+
354
+ Select `textFormat:'markdown'` for raw model text that contains formatting:
355
+
356
+ ```javascript
357
+ ai.streamTTS('## Heading\n**Hello', false, {textFormat:'markdown'});
358
+ ai.streamTTS('**. Next sentence.');
359
+ await ai.finishTTS();
360
+ ```
361
+
362
+ The SDK omits runs of two or more of the same `*`, `#`, `_`, backtick, or `~`
363
+ before speech segmentation. A candidate run split across chunks is still
364
+ recognized. Single marks, ellipses, quoted endings, and all other text are
365
+ preserved. This is a small narration filter, not a full Markdown parser.
366
+ Ordinary prose is forwarded immediately; only a trailing formatting candidate
367
+ waits for its next character or the final flush.
368
+
369
+ The format stays active until `end:true`, `finishTTS()`, or cancellation. New
370
+ streams default to `plain`, which preserves exact submitted text. The shared
371
+ chat component selects Markdown mode automatically. Applications already
372
+ speaking visible DOM text can keep plain mode. Displayed messages, saved
373
+ history, model input, language, voice, synthesis capacity, and playback timing
374
+ are not changed by the filter.
375
+
340
376
  ## Choose a device or reduce memory use
341
377
 
342
378
  Omitting `tts.execution` selects `{device:'auto',maxConcurrentRequests:4}`.
@@ -401,6 +437,8 @@ until a pool is selected and returns to `null` on unload. Providers without an
401
437
  execution report omit `execution`; do not infer a device from `navigator.gpu`
402
438
  or a configured preference alone. An explicit inspection can throw a provider
403
439
  status error; handle it with the same `error.code` / `error.message` pattern.
440
+ Treat `selectedDevice` as route status only; evaluate actual speech output for
441
+ the model, dtype, browser, and device combinations your application supports.
404
442
 
405
443
  ## Stop, mute, cancel, and release
406
444
 
@@ -875,7 +913,8 @@ cache state, and warnings. Kokoro status also includes an `execution` record
875
913
  with requested and selected device, request limit, and active request count. A
876
914
  successful `selectedDevice:'webgpu'` reports the execution provider selected by
877
915
  the upstream model load; it does not claim that browser, driver, or GPU kernels
878
- overlap physically. A security field is absent in ordinary mode.
916
+ overlap physically or that generated audio has been quality-validated. A
917
+ security field is absent in ordinary mode.
879
918
 
880
919
  The provider/2 load context accepts an optional progress callback for interface
881
920
  compatibility, but the current browser-speech artifact and Worker transport
@@ -9,7 +9,7 @@ same managed browser imports as the [browser speech quick start](browser-speech.
9
9
  Create an application and start its source server:
10
10
 
11
11
  ```bash
12
- npx arcane-os@0.5.15 new hello-twin --path ./hello-twin --target browser
12
+ npx arcane-os@0.5.16 new hello-twin --path ./hello-twin --target browser
13
13
  cd hello-twin
14
14
  npm install
15
15
  npm run dev
@@ -214,7 +214,7 @@ portable runtime subpaths such as `arcane-os/preference-store` and
214
214
  modules. The result reports the complete map written to the selected
215
215
  application; no fixed entry count is a release contract.
216
216
 
217
- SDK `0.5.15` preserves the physical workspace route count and ordered include
217
+ SDK `0.5.16` preserves the physical workspace route count and ordered include
218
218
  list. External and modern integrated routes require `components`, `css`,
219
219
  `dependencies`, `entities`, `img`, `modules`, and `sdk`; a physical workspace
220
220
  may omit only an optional trailing `security` include. The external license
@@ -9,7 +9,7 @@ They are not TypeScript declarations.
9
9
 
10
10
  ## Portable SDK AI and Core AI
11
11
 
12
- The SDK `0.5.15` has two related but separate normalized boundaries:
12
+ The SDK `0.5.16` has two related but separate normalized boundaries:
13
13
 
14
14
  | Boundary | Use | Host |
15
15
  |---|---|---|
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "sdkVersion": "0.5.15",
3
+ "sdkVersion": "0.5.16",
4
4
  "environment": {
5
5
  "runtime": "Node.js for Node entrypoints; browser for browser-only entrypoints",
6
6
  "minimumVersion": "22.23.2 for Node entrypoints",
@@ -5,7 +5,7 @@
5
5
  "repository": "https://github.com/TheWizardNexus/arcane-os-sdk.git",
6
6
  "branch": "main",
7
7
  "path": "runtime/arcane/components",
8
- "sdkVersion": "0.5.15"
8
+ "sdkVersion": "0.5.16"
9
9
  },
10
10
  "componentCount": 39,
11
11
  "loader": "/arcane/modules/HTMLImport.js",
@@ -5,12 +5,12 @@
5
5
  "repository": "https://github.com/TheWizardNexus/arcane-os-sdk.git",
6
6
  "branch": "main",
7
7
  "path": "runtime/arcane",
8
- "sdkVersion": "0.5.15",
8
+ "sdkVersion": "0.5.16",
9
9
  "protocol": "arcane/1"
10
10
  },
11
- "artifactCount": 84,
12
- "javascriptArtifactCount": 82,
13
- "esmExportCount": 353,
11
+ "artifactCount": 85,
12
+ "javascriptArtifactCount": 83,
13
+ "esmExportCount": 354,
14
14
  "artifacts": [
15
15
  {
16
16
  "file": "runtime/arcane/modules/AI.js",
@@ -30,7 +30,7 @@
30
30
  "availability": "Browser + native bridge + TWiN Cloud",
31
31
  "protocol": "arcane-ai-browser-speech-configuration/1, AIProviderRuntime arcane-ai-provider/2 routes, globalThis.arcaneEvents, TWiN Cloud HTTPS, Arcane.ollama, Arcane.speech, Android WebView bridge",
32
32
  "normalization": "Complete mutable caller-owned browser speech authority, SDK-owned provider registration/replacement/disposal, explicit STT/TTS activation, normalized Kokoro TTS execution with default capacity 4, explicit provider-neutral execution snapshots through status(role,{execution:true}), sticky readiness, canonical window.user readiness without compatibility-event object-identity admission or polling, exact ordered structural calls with required arguments.message, atomic nonblank all-ID tool-result sequencing, complete all-choice streaming/data validation, native Ollama structural adaptation, complete-response then all validated tool callbacks then completion ordering, observed async callbacks, Blob/File transcription, immediate exact-segment synthesis, per-call voice/speed capture, optional final-segment pause on the existing audio clock, optional per-call terminal playback completion, ordered audio-clock scheduling, playable audio, active-generation TTS operation-failure routing, mute, and cancellation are normalized; provider/model/runtime/voice policy remains caller-owned.",
33
- "surface": "Browser-speech protocol/event/error/reason constants; default `AI`; read-only `providerRuntime`, `browserSpeechConfiguration`, and `browserSpeechDescriptor`; explicit `providerRuntime.status(role,{execution:true})` snapshots; `streamTTS(text,end,options={})` with optional voice, speed, pauseAfterMs, and waitForPlayback (available in SDK 0.5.12); configure/dispose speech, route lifecycle, declaration-validated chat/stream requests with exact ordered calls, synthesis/transcription, and playback controls; initializes from current canonical user readiness, installs `window.ai`, and projects `ai-ready` plus active-generation `ai-tts-failure`."
33
+ "surface": "Browser-speech protocol/event/error/reason constants; default `AI`; read-only `providerRuntime`, `browserSpeechConfiguration`, and `browserSpeechDescriptor`; explicit `providerRuntime.status(role,{execution:true})` snapshots; `streamTTS(text,end,options={})` with optional voice, speed, pauseAfterMs, waitForPlayback, and speech-only textFormat plain/markdown; configure/dispose speech, route lifecycle, declaration-validated chat/stream requests with exact ordered calls, synthesis/transcription, and playback controls; initializes from current canonical user readiness, installs `window.ai`, and projects `ai-ready` plus active-generation `ai-tts-failure`."
34
34
  },
35
35
  {
36
36
  "file": "runtime/arcane/modules/AIPreferenceRuntime.js",
@@ -824,6 +824,19 @@
824
824
  "normalization": "Endpoint, timeout, cancellation, network, HTTP, and response-contract errors are normalized.",
825
825
  "surface": "`MailTransportError`, `normalizeMailEndpoint()`, `serializeMailReport()`, and `sendMailReport()`."
826
826
  },
827
+ {
828
+ "file": "runtime/arcane/modules/MarkdownSpeech.js",
829
+ "name": "MarkdownSpeech.js",
830
+ "kind": "esm",
831
+ "exports": [
832
+ "MarkdownSpeech"
833
+ ],
834
+ "summary": "Removes repeated same Markdown formatting marks from streamed narration before speech segmentation.",
835
+ "availability": "Cross-host",
836
+ "protocol": "In-process only",
837
+ "normalization": "Speech-only filtering of repeated *, #, _, backtick, and ~ runs across chunks; single marks and all other characters remain literal; terminal flush and reset clear pending formatting state.",
838
+ "surface": "`MarkdownSpeech`; `append(text='',end=false)`, `reset()`."
839
+ },
827
840
  {
828
841
  "file": "runtime/arcane/modules/Marked.min.js",
829
842
  "name": "Marked.min.js",
@@ -159,7 +159,7 @@ imports such as:
159
159
  import ollama from 'arcane/Ollama';
160
160
  ```
161
161
 
162
- The physical-v1 tree lives entirely beneath `arcane/`. SDK `0.5.15` projects the
162
+ The physical-v1 tree lives entirely beneath `arcane/`. SDK `0.5.16` projects the
163
163
  complete canonical runtime and browser runtime selected by the installed SDK
164
164
  package. Runtime dependencies stay under
165
165
  `arcane/dependencies/`; the SDK event and browser-AI closure stays under
@@ -174,7 +174,7 @@ integrated physical route uses the same ordered include list in its one route.
174
174
  Omitting only that final optional entry is compatible. Removing, reordering, or
175
175
  renaming any preceding entry changes the physical contract.
176
176
 
177
- The `0.5.15` map derives its complete entries from the selected runtime graph;
177
+ The `0.5.16` map derives its complete entries from the selected runtime graph;
178
178
  application source imports do not select a fixed entry count. The operation
179
179
  result reports `imports`, `entryCount`, and `excludedModules`; reached-file
180
180
  traversal remains internal. The
@@ -239,8 +239,8 @@ exactly `dependencyName`, `packageSource`,
239
239
  `canonicalPackageRoot`, `packageName`, `packageVersion`, `runtimeRoot`,
240
240
  and `browserRuntimeRoot`. A
241
241
  workspace may use the canonical dependency name or one exact npm alias such as
242
- `npm:arcane-os@0.5.15`; the physical package manifest must still identify
243
- exactly as `arcane-os@0.5.15`. Canonical-plus-alias duplicates, multiple aliases,
242
+ `npm:arcane-os@0.5.16`; the physical package manifest must still identify
243
+ exactly as `arcane-os@0.5.16`. Canonical-plus-alias duplicates, multiple aliases,
244
244
  links/junctions, indirect package roots, or version drift are reported.
245
245
 
246
246
  For external workspaces, `arcane dev` serves the projected `arcane/` root,
@@ -351,7 +351,7 @@ does not silently delete the app-owned cache. A complete whole member supersedes
351
351
  its current resumable fragments. Cleanup failure is warned without hiding the
352
352
  usable model.
353
353
 
354
- SDK `0.5.15` requires WebGPU. Load requests full offload and waits for the runtime
354
+ SDK `0.5.16` requires WebGPU. Load requests full offload and waits for the runtime
355
355
  to report a loaded model. `navigator.gpu` presence alone is
356
356
  not readiness. There is no CPU fallback, partial-offload success mode, or
357
357
  silent switch to native/Core/cloud inference.
@@ -360,17 +360,20 @@ unload.
360
360
  Chat listens for the bound (or current global) AI runtime's `ai-tts-failure`
361
361
  event and forwards its complete Error and exact operation boundary to
362
362
  `speech.reportTTSError()`. Playback-start or playback-resume failures can occur
363
- after Chat's two-argument `streamTTS()` preparation promise has resolved.
363
+ after Chat's `streamTTS()` preparation promise has resolved.
364
364
  SDK `0.5.12` adds a `waitForPlayback:true` mode for the
365
365
  terminal result of the segments submitted by one invocation; Chat continues
366
366
  to feed chunks without waiting for playback. Runtime mute, cancellation,
367
367
  permission waiting, and stale generations remain non-errors.
368
368
 
369
- Each visible model chunk is still forwarded to `AI.streamTTS()` in arrival
370
- order. The AI owner preserves exact segmentation, admits completed segments to
371
- bounded synthesis immediately, and schedules the contiguous ready audio prefix
372
- on the audio clock. Chat neither creates a second queue nor reorders, combines,
373
- or rewrites speech text.
369
+ Each visible model chunk is forwarded to
370
+ `AI.streamTTS(text,false,{textFormat:'markdown'})` in arrival order. The AI
371
+ owner removes repeated Markdown formatting marks from narration before the
372
+ configured segmentation, admits completed segments to bounded synthesis
373
+ immediately, and schedules the contiguous ready audio prefix on the audio
374
+ clock. Chat retains the original Markdown for display and storage and owns no
375
+ second speech queue. Single punctuation, ordinary repeated punctuation,
376
+ language, and voice retain their existing behavior.
374
377
 
375
378
  Events: `chat-ready`, `chat-session-bound`, `chat-session-message`,
376
379
  `chat-session-error`, `chat-send-message`, `chat-send-error`,
@@ -88,6 +88,7 @@ own asynchronous work, cancellation, and backpressure.
88
88
  | [`Mail.js`](#mailjs) | esm | Builds complete reports and prefers the native mail capability with an explicit HTTP transport fallback. | Browser/native hybrid + cloud | Mail inputs/results normalized; transport failures mixed. |
89
89
  | [`MailOutbox.mjs`](#mailoutboxmjs) | esm | Persists complete mail reports before delivery and normalizes idempotent enqueue, retry, reconciliation, and invalid-record maintenance. | Browser/native WebView or compatible injected host | Complete records, full work, cancellation, and lifecycle states normalized; storage, lock, and delivery failures coded. |
90
90
  | [`MailTransport.mjs`](#mailtransportmjs) | esm | Sends one complete mail report to a normalized HTTP(S) endpoint. | Browser/server with fetch + cloud | Normalized endpoint and transport errors; remote detail preserved. |
91
+ | [`MarkdownSpeech.js`](#markdownspeechjs) | esm | Removes repeated Markdown formatting marks from streamed narration. | Cross-host | Speech-only filtering; single marks and ordinary punctuation remain literal. |
91
92
  | [`Marked.min.js`](#markedminjs) | esm | Vendored Marked 18.0.5 Markdown lexer, parser, renderer, extension, and walk-token API. | Cross-host vendor module | Vendor-native Marked contract. |
92
93
  | [`MD.js`](#mdjs) | esm | Renders complete Markdown with Marked and exposes the complete rendered markup. | Browser / native WebView | Complete raw and rendered Marked values; parse errors vendor-native. |
93
94
  | [`MemoryRecords.js`](#memoryrecordsjs) | esm | Normalizes memory content and detects meaningful stored memory. | Cross-host | Fully normalized string/boolean results. |
@@ -274,7 +275,7 @@ rejected. It propagates the caller-owned signal and returns a playable `Blob`;
274
275
  it does not independently choose a provider, cloud fallback, model, runtime, or
275
276
  voice policy for the application. `streamTTS(text='',end=false,options={})` and
276
277
  `finishTTS()` use this same request boundary. The third-argument options below
277
- are available in SDK `0.5.12`:
278
+ are available in SDK `0.5.12`, with `textFormat` added in SDK `0.5.16`:
278
279
 
279
280
  | Field | Default | Meaning |
280
281
  | --- | --- | --- |
@@ -282,12 +283,24 @@ are available in SDK `0.5.12`:
282
283
  | `speed` | Current `ai.voiceSpeed` | A supplied positive speed is captured for those segments and forwarded to `fetchTTS()`. It does not change `ai.voiceSpeed`. |
283
284
  | `pauseAfterMs` | `0` | Finite, nonnegative milliseconds placed after the final extracted segment on the existing audio clock. Invalid values throw `RangeError`; no pause is inserted between this call's other segments. |
284
285
  | `waitForPlayback` | `false` | Omission retains the preparation promise. With `true`, the promise resolves after every extracted segment reaches a terminal playback state: `true` when all naturally end, or `false` after terminal cancellation or failure. |
286
+ | `textFormat` | `plain` for a new stream | `markdown` removes repeated same formatting marks (`*`, `#`, `_`, backtick, `~`) before segmentation. Single marks and ordinary punctuation remain literal. The selected format lasts through this producer's terminal flush. |
285
287
 
286
288
  The voice and speed use the existing `fetchTTS()` validation and error path.
287
- No option rewrites the submitted text. Overrides belong to the segments
289
+ Plain mode preserves the submitted text. Markdown mode changes narration only;
290
+ it leaves displayed, stored, and model content untouched. It is a narrow
291
+ formatting-mark filter, not a full Markdown parser: links, code contents, list
292
+ text, and other characters remain literal. A trailing candidate mark waits for
293
+ the next character so a repeated run split across chunks is still omitted.
294
+ Ordinary prose streams immediately. `end:true`, `finishTTS()`, muted terminal
295
+ calls, and `stopAudio()` clear the formatting state. An explicit format change
296
+ flushes pending formatting marks under the preceding mode before accepting
297
+ the next text. `fetchTTS()` retains its exact-text contract.
298
+
299
+ Voice, speed, pause, and playback overrides belong to the segments
288
300
  extracted in that invocation, including any text buffered by an earlier call.
289
- Options are not retained with an unfinished `end:false` remainder; a later
290
- call supplies its own options, and `finishTTS()` uses defaults. Use `end:true`
301
+ Those overrides are not retained with an unfinished `end:false` remainder; a
302
+ later call supplies its own options, and `finishTTS()` uses their defaults
303
+ while retaining the active text format. Use `end:true`
291
304
  for a complete passage. A call extracting no segments resolves
292
305
  `true` without waiting for earlier jobs; `finishTTS()` remains a preparation
293
306
  flush, not a queue-wide playback barrier. A muted call resolves `false`.
@@ -2244,6 +2257,41 @@ import * as module from '/arcane/modules/MailTransport.mjs';
2244
2257
  console.log(Object.keys(module));
2245
2258
  ```
2246
2259
 
2260
+ ## MarkdownSpeech.js
2261
+
2262
+ ### Overview
2263
+
2264
+ Filters repeated same Markdown formatting marks from narration before speech
2265
+ segmentation. The filter recognizes `*`, `#`, `_`, backtick, and `~` runs of two
2266
+ or more, including runs arriving across separate chunks. Single marks,
2267
+ ellipses, quoted sentence endings, whitespace, and all other text remain
2268
+ literal. It neither interprets links nor changes language or voice.
2269
+
2270
+ ### Public surface
2271
+
2272
+ `MarkdownSpeech`; `append(text='',end=false)`, `reset()`.
2273
+
2274
+ Exact exports: `MarkdownSpeech`.
2275
+
2276
+ ### Availability and normalization
2277
+
2278
+ **Cross-host.** Plain JavaScript with no dependencies. `append()` returns only
2279
+ the newly available narration. Only a trailing candidate marker and whether
2280
+ it repeats are retained; ordinary text is emitted immediately. Terminal
2281
+ `append(text,true)` flushes a single pending mark and resets state. `reset()`
2282
+ clears pending formatting state when its narration is cancelled. Non-string
2283
+ input throws `TypeError`.
2284
+
2285
+ ### Example
2286
+
2287
+ ```javascript
2288
+ import {MarkdownSpeech} from '/arcane/modules/MarkdownSpeech.js';
2289
+
2290
+ const speech = new MarkdownSpeech();
2291
+ const first = speech.append('**Hello'); // Hello
2292
+ const last = speech.append('**...', true); // ...
2293
+ ```
2294
+
2247
2295
  ## Marked.min.js
2248
2296
 
2249
2297
  ### Overview
@@ -18,7 +18,7 @@ This table is the Node `package.json#exports` map: it defines package
18
18
  entrypoints for SDK/tooling code. It is distinct from the generated browser
19
19
  import map that resolves application-facing `arcane/*` modules and the focused
20
20
  EventManager entry. See [browser runtime delivery](protocols.md#browser-runtime-delivery)
21
- for the installed-inventory-derived physical-runtime contract in SDK `0.5.15`.
21
+ for the installed-inventory-derived physical-runtime contract in SDK `0.5.16`.
22
22
 
23
23
  | Specifier | Purpose |
24
24
  | --- | --- |
@@ -751,7 +751,7 @@ deterministic map. The package root also contains the public
751
751
  {
752
752
  schemaVersion: 1,
753
753
  kind: 'arcane-app-runtime-projection',
754
- sdkVersion: '0.5.15',
754
+ sdkVersion: '0.5.16',
755
755
  pathPrefix: 'arcane/',
756
756
  files: [{path}]
757
757
  }
@@ -3463,7 +3463,7 @@ workspace it additionally returns the exact installed package authority:
3463
3463
  packageSource,
3464
3464
  canonicalPackageRoot,
3465
3465
  packageName: 'arcane-os',
3466
- packageVersion: '0.5.15',
3466
+ packageVersion: '0.5.16',
3467
3467
  runtimeRoot,
3468
3468
  browserRuntimeRoot
3469
3469
  }
@@ -3471,9 +3471,9 @@ workspace it additionally returns the exact installed package authority:
3471
3471
  ```
3472
3472
 
3473
3473
  The dependency can be named `arcane-os` or be one exact npm alias for
3474
- `npm:arcane-os@0.5.15`. The selected installation must still be one direct,
3474
+ `npm:arcane-os@0.5.16`. The selected installation must still be one direct,
3475
3475
  physical, non-link package directory whose manifest identifies exactly as
3476
- `arcane-os@0.5.15`; duplicate canonical/alias declarations reject.
3476
+ `arcane-os@0.5.16`; duplicate canonical/alias declarations reject.
3477
3477
  `allowMissingManagedImportMap` is an internal packaging/development seam. An
3478
3478
  ordinary caller should leave it `false`.
3479
3479
 
@@ -8,6 +8,8 @@ For the smallest first request, start with the [browser speech quick start](../.
8
8
 
9
9
  The demo deliberately omits `tts.execution` in `speechConfiguration(dbopfs)` so it consumes the SDK default `{device:'auto',maxConcurrentRequests:4}`. Change that application's TTS record to `execution:{device:'wasm',maxConcurrentRequests:1}` to use less memory, or choose `webgpu` explicitly. Allowed capacities are 1 through 4; Whisper and the LLM retain capacity one.
10
10
 
11
+ The maintained Kokoro selection uses `fp32` because [Kokoro.js recommends `fp32` when using WebGPU](https://github.com/hexgrad/kokoro/tree/main/kokoro.js#usage). Automatic fallback keeps the same dtype on WASM. `selectedDevice` identifies the loaded route; it does not validate speech correctness or audio quality, so evaluate actual output for each browser and device combination your application supports.
12
+
11
13
  Capacity 4 means up to four segments synthesize at once. Segment 5 and later wait in the SDK's FIFO queue; they are not dropped. Synthesis may finish out of order, but playback waits for earlier segments and plays exact input order. Each slot owns a Worker/model session, so raising capacity trades memory for latency.
12
14
 
13
15
  Use the shared Speech component to load/unmute speech, then select **Inspect speech execution** below Chat. The button reads `ai.providerRuntime.status('tts',{execution:true}).execution` and displays requested device, selected device, capacity, active requests, and automatic WASM fallback. It is a snapshot at the moment clicked. `selectedDevice:null` means no pool is selected; `webgpu` reports the upstream execution-provider selection and does not prove physical GPU kernel overlap or audio quality. Auto may fall back to WASM with the same selected model/dtype; explicit WebGPU reports an error when it cannot load.
@@ -39,7 +41,7 @@ Model weights are deliberately excluded from Git. Place the maintained GGUF shar
39
41
 
40
42
  The maintained model descriptors include each shard's known byte length as progress metadata. During a cold install, the SDK reports aggregate loaded and remaining bytes, transfer speed, estimated time, and active file or Range transfers while Chat presents the existing activation progress bar. Completed shards and completed Range parts within a shard remain available after interruption, so retry downloads only missing work.
41
43
 
42
- The app gives the SDK browser-speech owner the existing Whisper tiny.en FP32 and Kokoro 82M Q8 descriptors. The app does not import those runtimes or create a speech worker itself.
44
+ The app gives the SDK browser-speech owner the existing Whisper tiny.en FP32 and Kokoro 82M FP32 descriptors. The app does not import those runtimes or create a speech worker itself.
43
45
 
44
46
  ## Local retrieval
45
47
 
@@ -435,12 +435,13 @@ function speechConfiguration(dbopfs) {
435
435
  },
436
436
  tts: {
437
437
  // Omit execution to use the SDK's auto device and four synthesis slots.
438
+ // Kokoro.js recommends fp32 for the WebGPU route attempted by auto.
438
439
  providerId: "wasm-ai-demo-browser-kokoro",
439
440
  model: {
440
441
  id: "onnx-community/Kokoro-82M-v1.0-ONNX",
441
442
  repository: "onnx-community/Kokoro-82M-v1.0-ONNX",
442
443
  revision: "1939ad2a8e416c0acfeecc08a694d14ef25f2231",
443
- dtype: "q8",
444
+ dtype: "fp32",
444
445
  defaultVoice: "af_heart",
445
446
  },
446
447
  runtime: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.5.15",
3
+ "version": "0.5.16",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -66,7 +66,7 @@
66
66
  "test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/contracts.test.mjs test/doctor.test.mjs test/mail-credentials.test.mjs test/mail-outbox.test.mjs test/mail-public-api.test.mjs test/mail-send.test.mjs test/mail-transport.test.mjs test/targets.test.mjs test/workspace-operation-lock.test.mjs",
67
67
  "test:functional": "node ./bin/arcane-test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/cli.test.mjs test/dbopfs-document-library.test.mjs test/dev-server.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/packaging.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/update-check.test.mjs",
68
68
  "test:integration": "node ./bin/arcane-test.mjs test/integrated-shared.test.mjs test/integrated-workspace.test.mjs test/mail-browser.test.mjs test/native-plan.test.mjs test/native-provider-loader.test.mjs test/npm-release.test.mjs test/release-bundle.test.mjs test/release-capability-smoke.test.mjs test/shared-payload-batch.test.mjs test/tarball.test.mjs test/wllama-webgpu-runtime.test.mjs",
69
- "test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/logging-regression.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
69
+ "test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/logging-regression.test.mjs test/markdown-speech.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
70
70
  "check": "node tools/check-source.mjs && npm test",
71
71
  "check:release": "node tools/check-source.mjs --package-only && npm run test:release",
72
72
  "pack:local": "npm pack --ignore-scripts",
@@ -2817,7 +2817,9 @@
2817
2817
  ){
2818
2818
  const aiRuntime=boundChatAI??globalThis.ai;
2819
2819
  if(typeof aiRuntime?.streamTTS==='function'){
2820
- void Promise.resolve(aiRuntime.streamTTS(text)).catch(
2820
+ void Promise.resolve(aiRuntime.streamTTS(
2821
+ text,false,{textFormat:'markdown'}
2822
+ )).catch(
2821
2823
  reportTTSError
2822
2824
  );
2823
2825
  }else{
@@ -13,6 +13,7 @@ import {
13
13
  } from './AIProviderRuntime.js';
14
14
  import {normalizeOllamaModelIdentifier} from './OllamaModelIdentifier.js';
15
15
  import {arcaneLogging} from 'arcane-os/logging';
16
+ import {MarkdownSpeech} from './MarkdownSpeech.js';
16
17
 
17
18
  const completeValue=(value)=>value;
18
19
 
@@ -2029,6 +2030,8 @@ class AI {
2029
2030
  }
2030
2031
 
2031
2032
  audioMessageChunks='';
2033
+ #speechTextFormat='plain';
2034
+ #markdownSpeech=new MarkdownSpeech();
2032
2035
  sourceNodes=[];
2033
2036
  isSpeaking=false;
2034
2037
  audioContext=null;
@@ -5731,6 +5734,8 @@ class AI {
5731
5734
  if(this.muted){
5732
5735
  if(end){
5733
5736
  this.audioMessageChunks='';
5737
+ this.#speechTextFormat='plain';
5738
+ this.#markdownSpeech.reset();
5734
5739
  }
5735
5740
  this.#traceSpeech('streamTTS.result',{callId,result:false,reason:'muted'});
5736
5741
  return Promise.resolve(false);
@@ -5742,11 +5747,24 @@ class AI {
5742
5747
  throw new RangeError('Speech pauses must be a nonnegative number of milliseconds.');
5743
5748
  }
5744
5749
 
5745
- this.audioMessageChunks+=String(text||'');
5750
+ const textFormat=options.textFormat??this.#speechTextFormat;
5751
+ if(textFormat!=='plain'&&textFormat!=='markdown'){
5752
+ throw new TypeError('Speech textFormat must be plain or markdown.');
5753
+ }
5754
+ let speechText='';
5755
+ if(textFormat!==this.#speechTextFormat){
5756
+ speechText=this.#markdownSpeech.append('',true);
5757
+ }
5758
+ const incomingText=String(text||'');
5759
+ speechText+=textFormat==='markdown'
5760
+ ?this.#markdownSpeech.append(incomingText,end)
5761
+ :incomingText;
5762
+ this.#speechTextFormat=end?'plain':textFormat;
5763
+ this.audioMessageChunks+=speechText;
5746
5764
  const outputs=this.#extractSpeechSegments(end);
5747
5765
  this.#traceSpeech('streamTTS.segments',{
5748
5766
  callId,segments:outputs,remainder:this.audioMessageChunks,
5749
- segmentation:this.ttsSegmentation
5767
+ segmentation:this.ttsSegmentation,textFormat,speechText
5750
5768
  });
5751
5769
 
5752
5770
  if(!outputs.length){
@@ -6414,6 +6432,8 @@ class AI {
6414
6432
  this.speechScheduleContext=null;
6415
6433
  this.speechScheduleTime=0;
6416
6434
  this.audioMessageChunks='';
6435
+ this.#speechTextFormat='plain';
6436
+ this.#markdownSpeech.reset();
6417
6437
  this.#clearSpeechUnlock();
6418
6438
 
6419
6439
  for(const job of this.speechJobs){
@@ -0,0 +1,50 @@
1
+ const FORMATTING_MARKERS=new Set(['*','#','_','`','~']);
2
+
3
+ // Speech-only filtering; single markers and all other text remain literal.
4
+ class MarkdownSpeech {
5
+ #pendingMarker='';
6
+ #repeated=false;
7
+
8
+ append(text='',end=false){
9
+ if(typeof text!=='string'){
10
+ throw new TypeError('Markdown speech input must be text.');
11
+ }
12
+
13
+ let narration='';
14
+
15
+ for(const character of text){
16
+ if(character===this.#pendingMarker){
17
+ this.#repeated=true;
18
+ continue;
19
+ }
20
+
21
+ if(this.#pendingMarker&&!this.#repeated){
22
+ narration+=this.#pendingMarker;
23
+ }
24
+ this.reset();
25
+
26
+ if(FORMATTING_MARKERS.has(character)){
27
+ // Wait for the next character to distinguish one mark from a run.
28
+ this.#pendingMarker=character;
29
+ }else{
30
+ narration+=character;
31
+ }
32
+ }
33
+
34
+ if(end){
35
+ if(this.#pendingMarker&&!this.#repeated){
36
+ narration+=this.#pendingMarker;
37
+ }
38
+ this.reset();
39
+ }
40
+
41
+ return narration;
42
+ }
43
+
44
+ reset(){
45
+ this.#pendingMarker='';
46
+ this.#repeated=false;
47
+ }
48
+ }
49
+
50
+ export {MarkdownSpeech};