arcane-os 0.2.1 → 0.2.3

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 (30) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +8 -8
  3. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +5 -5
  4. package/browser-runtime/ai/browser-speech-providers.mjs +1 -0
  5. package/docs/architecture.md +2 -2
  6. package/docs/reference/README.md +79 -13
  7. package/docs/reference/ai/browser-speech.md +336 -0
  8. package/docs/reference/ai/browser-wasm.md +207 -82
  9. package/docs/reference/availability-and-normalization.md +33 -4
  10. package/docs/reference/behavioral-testing.md +4 -1
  11. package/docs/reference/cli.md +29 -10
  12. package/docs/reference/core/arcane-ai-contracts.md +43 -9
  13. package/docs/reference/inventory/package-api.json +110 -14
  14. package/docs/reference/inventory/runtime-components.json +42 -14
  15. package/docs/reference/inventory/runtime-modules.json +118 -13
  16. package/docs/reference/protocols.md +264 -38
  17. package/docs/reference/runtime-components.md +177 -21
  18. package/docs/reference/runtime-modules.md +449 -13
  19. package/docs/reference/sdk-api.md +626 -85
  20. package/package.json +1 -1
  21. package/runtime/ARCANE_RUNTIME_RELEASE.json +19 -19
  22. package/runtime/arcane/components/chat.html +17 -52
  23. package/runtime/arcane/components/speech.html +109 -29
  24. package/runtime/arcane/components/voice-transcription.html +437 -68
  25. package/runtime/arcane/modules/AI.js +556 -128
  26. package/runtime/arcane/modules/AIProviderRuntime.js +299 -30
  27. package/runtime/arcane/modules/AIRuntimeState.js +23 -4
  28. package/runtime/arcane/modules/ComponentContracts.js +272 -0
  29. package/runtime/arcane/modules/LocalAIReadinessController.js +1 -1
  30. package/schemas/arcane-lock.schema.json +2 -2
@@ -20,8 +20,10 @@ Apps import renderer ESM from `/arcane/modules/<file>`. Classic scripts, the OPF
20
20
  | [`AI.js`](#aijs) | esm | Provider-selectable chat, speech-to-text, text-to-speech, tool calling, structured output, streaming, and queued audio playback. | Browser + native bridge + cloud | High-level chat/speech behavior is normalized; provider diagnostics and media errors remain mixed. |
21
21
  | [`AIPreferenceRuntime.js`](#aipreferenceruntimejs) | esm | Applies and reads non-persistent per-user AI preference overrides. | Cross-host | Normalized six-slot preference state. |
22
22
  | [`AIPreferenceTuple.js`](#aipreferencetuplejs) | esm | Normalizes and compares the six provider/model preference slots. | Cross-host | Fully normalized frozen tuple. |
23
+ | [`AIProviderRuntime.js`](#aiproviderruntimejs) | esm | Owns provider-neutral selection, lifecycle, routing, startup, requests, streaming, cancellation, and independent LLM/STT/TTS state. | Cross-host runtime; provider-specific availability | Normalized required provider members plus closed route/status contracts, with fail-closed local-only selection and no implicit fallback. |
23
24
  | [`AIResponseLength.js`](#airesponselengthjs) | esm | Normalizes concise/short/medium/long response preferences and applies the matching system instruction. | Cross-host | Fully normalized string/instruction contract. |
24
25
  | [`AIResponseURLPolicy.js`](#airesponseurlpolicyjs) | esm | Extracts and audits links from AI Markdown, rendered HTML, CSS, srcset, bare URLs, and email text. | Cross-host | Normalized frozen allowlist audit. |
26
+ | [`AIRuntimeState.js`](#airuntimestatejs) | esm | Publishes sticky immutable role snapshots, lifecycle intents, and startup-settlement barriers. | Cross-host state contract | Closed monotonic state records; events report state but grant no authority. |
25
27
  | [`AnsiText.js`](#ansitextjs) | esm | Parses terminal ANSI sequences into display spans or strips them to plain text. | Cross-host | Normalized text/span output. |
26
28
  | [`ApiModelDatabase.js`](#apimodeldatabasejs) | esm | Fetches an injectable HTTP JSON model with parser, cache, redacted public endpoint records, and request lifecycle events. | Browser / native WebView / server with fetch | Request records are normalized; fetch/provider failures remain mixed. |
27
29
  | [`AppDataScope.js`](#appdatascopejs) | esm | Reconciles declared and native application identity and scopes OPFS/localStorage ownership fail-closed. | Browser / native WebView hybrid | Strict normalized identifiers and coded mismatch failures. |
@@ -39,7 +41,7 @@ Apps import renderer ESM from `/arcane/modules/<file>`. Classic scripts, the OPF
39
41
  | [`CommunicationHub.js`](#communicationhubjs) | esm | Fans out provider refresh/send operations and aggregates normalized threads/messages. | Cross-host with injected providers | Normalized aggregates; refresh contains per-provider failures. |
40
42
  | [`CommunicationPreferences.js`](#communicationpreferencesjs) | esm | Stores app-scoped, non-secret communication provider preferences. | Browser / native WebView hybrid | Normalized preference record; storage failures mixed. |
41
43
  | [`CommunicationProviderRegistry.js`](#communicationproviderregistryjs) | esm | Registers and queries validated provider definitions, channels, and required methods. | Cross-host | Strict normalized registry. |
42
- | [`ComponentContracts.js`](#componentcontractsjs) | esm | Owns normalized configuration/value contracts shared by chart, dashboard, Markdown, and voice components. | Cross-host | Fully normalized labels, rows, definitions, visibility, formats, editor and voice options. |
44
+ | [`ComponentContracts.js`](#componentcontractsjs) | esm | Owns normalized configuration/value contracts and shared explicit STT activation behavior for chart, dashboard, Markdown, and voice components. | Cross-host | Fully normalized labels, rows, definitions, visibility, formats, editor and voice options, plus capability-neutral STT activation intent and presentation state. |
43
45
  | [`ConfiguredAIChatSession.js`](#configuredaichatsessionjs) | esm | Owns bounded in-memory AI turns, context construction, response-length instruction, and atomic history commit. | Native bridge by default; cross-host with injected chat | Normalized session/result; provider rejection preserved. |
44
46
  | [`ConversationActionItems.js`](#conversationactionitemsjs) | esm | Normalizes, creates, updates, remembers, selects, and formats bounded conversation action items. | Cross-host | Fully normalized status/base/presentation contract. |
45
47
  | [`ConversationClosingReport.js`](#conversationclosingreportjs) | esm | Defines the closing-report tool, instruction, result normalizer, call classifier, and formatter. | Cross-host | Fully normalized report contract. |
@@ -48,9 +50,11 @@ Apps import renderer ESM from `/arcane/modules/<file>`. Classic scripts, the OPF
48
50
  | [`DataMaintenance.js`](#datamaintenancejs) | esm | Deletes empty chats and associated/empty memory records inside the current app data scope. | Browser / native WebView | Normalized counts; destructive storage failures preserved. |
49
51
  | [`DBLS.js`](#dblsjs) | esm | Provides app-scoped localStorage tables, batch reads/writes, filtering, deletion, and counts. | Browser / native WebView | Scoped keys and values normalized; storage failures mixed. |
50
52
  | [`DBOPFS.js`](#dbopfsjs) | esm | Provides app-scoped OPFS tables, worker I/O, backup/restore, compression, and CRUD/batch APIs. | Browser / native WebView | App scope normalized; DOM/storage errors preserved. |
53
+ | [`DBOPFSDocumentLibrary.js`](#dbopfsdocumentlibraryjs) | esm | Bootstraps and searches an app-defined DBOPFS corpus and builds explicitly untrusted chat context. | Browser or compatible DBOPFS host | Existing DBOPFS semantics; manifest-last generations and bounded search only after the app calls it or wires its context builder. |
51
54
  | [`DBOPFSWorker.js`](#dbopfsworkerjs) | worker | Serializes OPFS sync-handle read/write requests from a MessagePort. | Dedicated worker | Responses normalize to `{success,fileData?}` or `{error:{name,message}}`. |
52
55
  | [`DevelopmentWorkspace.js`](#developmentworkspacejs) | esm | Provides bounded workspace inspection, context, setup task, and Node installer clients without arbitrary command execution. | Native bridge | Inputs normalized; provider result/error preserved. |
53
56
  | [`DirectoryPicker.js`](#directorypickerjs) | esm | Wraps the provider-owned native directory chooser and normalizes selected/cancelled/error results. | Native bridge | Strict normalized selection and coded errors. |
57
+ | [`DocumentLexicalSearch.js`](#documentlexicalsearchjs) | esm | Provides dependency-free deterministic metadata/body ranking and bounded excerpts. | Cross-host | Frozen stable results with no storage, provider, or network side effects. |
54
58
  | [`DocumentNavigation.js`](#documentnavigationjs) | esm | Binds document navigation, filtering, history, current-item reveal, and load initialization. | Browser / native WebView | Normalized filter/navigation state; DOM effects preserved. |
55
59
  | [`Errors.js`](#errorsjs) | esm | Normalizes global errors/rejections, fingerprints and deduplicates incidents, persists a ledger, and performs bounded delivery. | Browser / native WebView hybrid | Incident records normalized; storage/mail failures isolated. |
56
60
  | [`GifEncoder.js`](#gifencoderjs) | esm | Encodes indexed frames into a bounded animated GIF using palette mapping and LZW. | Cross-host | Normalized byte output and bounds. |
@@ -70,6 +74,7 @@ Apps import renderer ESM from `/arcane/modules/<file>`. Classic scripts, the OPF
70
74
  | [`OllamaModelIdentifier.js`](#ollamamodelidentifierjs) | esm | Validates and canonicalizes the syntax of Ollama model identifiers without granting model admission. | Cross-host | Fully normalized string/boolean result. |
71
75
  | [`OllamaSettings.js`](#ollamasettingsjs) | esm | Defines bounded runtime/service preference schemas and deterministic Arcane brain alias names. | Cross-host | Fully normalized settings/name contract. |
72
76
  | [`OpenMeteoWeatherProvider.js`](#openmeteoweatherproviderjs) | esm | Searches and loads Open-Meteo data into frozen Arcane weather entities. | Browser / native WebView / server with fetch + cloud | Provider data normalized to entities; transport errors mixed. |
77
+ | [`PersistentAIChatSession.js`](#persistentaichatsessionjs) | esm | Adds explicit durable history/memory policy to bounded configured chat without changing DBOPFS or ChatEntity semantics. | Browser / native WebView with DBOPFS and configured chat | Live context commits atomically; persistence stays coherent across user/assistant/tool turns. |
73
78
  | [`PreferenceStore.js`](#preferencestorejs) | esm | Loads and updates schema-defined app preferences through native storage with a narrow browser fallback. | Browser/native hybrid | Values normalized; only exact unsupported capability falls back. |
74
79
  | [`QRCode.min.js`](#qrcodeminjs) | classic-script | Vendored QRCode generator for DOM, canvas, SVG, and image output. | Browser vendor script | Vendor-native. |
75
80
  | [`Questionnaire.js`](#questionnairejs) | esm | Evaluates whether a one-time questionnaire prompt is due without performing the prompt. | Cross-host | Normalized fail-closed boolean. |
@@ -106,13 +111,52 @@ Provider-selectable chat, speech-to-text, text-to-speech, tool calling, structur
106
111
 
107
112
  ### Public surface
108
113
 
109
- default `AI`; `setAI()`, `streamRequest()`, `streamMessage()`, `fetchRequest()`, `fetch()`, `streamTTS()`, `finishTTS()`, `fetchSTT()`, `stopAudio()`, `resumeAudio()`, `playAudio()`; installs `window.ai` and emits `ai-ready`.
114
+ default `AI`; read-only `providerRuntime`; `setAI()`, `configureProviders()`, `transitionAI()`,
115
+ `transitionProviders()`, `startProviders()`, `setSpeechMuted()`,
116
+ `streamRequest()`, `streamMessage()`, `fetchRequest()`, `fetch()`,
117
+ `streamTTS()`, `finishTTS()`, `fetchSTT()`, `stopAudio()`, `resumeAudio()`,
118
+ `playAudio()`; consumes `user-entity-loaded` and `arcane-ollama-ready`,
119
+ installs `window.ai`, and emits `ai-ready`.
120
+
121
+ The provider-runtime methods keep LLM, STT, and TTS selection explicit. They do
122
+ not reinterpret one provider's failure as permission to select another
123
+ provider. `transitionAI()` and `transitionProviders()` are deliberate
124
+ cross-role transitions: each stops queued audio, unloads the current LLM, STT,
125
+ and TTS roles, then applies the replacement configuration. `transitionAI()`
126
+ returns aggregate runtime status; `transitionProviders()` returns the admitted
127
+ three-role route configuration. Selected `OPENAI` LLM/STT/TTS, `OLLAMA` LLM,
128
+ and admitted Core `LOCAL_SPEACH` STT/TTS legacy routes expose truthful
129
+ capability-only readiness through internal provider/2 adapters without probing,
130
+ downloading, or hiding a load. Cloud speech admission requires the selected
131
+ route, its model, a credential, and `fetch`; Core speech admission requires the
132
+ exact selected `Arcane.speech.transcribe` or `synthesize` method. `fetchRequest()`
133
+ keeps the selected provider's public response shape. Browser speech routes
134
+ translate the existing AI.js STT `{audio:Blob|File,mimeType,model}` and TTS
135
+ `{model,input,responseFormat,voice?,speed?}` requests at the provider boundary;
136
+ only WAV is accepted for the shared TTS result. TTS voice selection comes from
137
+ the exact selected provider/model catalog `defaultVoice`; a saved OpenAI voice
138
+ is used only by the selected OpenAI adapter and is never forwarded to another
139
+ provider route.
140
+
141
+ `startProviders({startMuted=true,startTranscription=false,signal=null}={})`
142
+ starts text chat without requesting an STT load by default; it does not undo an
143
+ already ready or independently loading role. Callers must opt into eager STT
144
+ startup with `startTranscription:true` or publish the explicit user activation
145
+ intent exposed by the shared speech component. `setSpeechMuted(false)` records
146
+ the shared unmuted lifecycle preference before loading TTS, while
147
+ `setSpeechMuted(true)` cancels active TTS work and unloads that role.
148
+ `fetchSTT(audioFile,responseHandler,signal)` propagates the caller-owned signal;
149
+ delivery suppression is guaranteed after abort, while underlying provider-stop
150
+ claims remain limited to that provider's cancellation contract.
110
151
 
111
152
  Exact exports: `default`.
112
153
 
113
154
  ### Availability and normalization
114
155
 
115
- **Browser + native bridge + cloud.** High-level chat/speech behavior is normalized; provider diagnostics and media errors remain mixed. Transport: OpenAI HTTPS, Arcane.ollama, Arcane.speech, Android WebView bridge. [Deep protocol details](protocols.md).
156
+ **Browser + native bridge + cloud.** High-level chat/speech behavior is
157
+ normalized; provider diagnostics and media errors remain mixed. Transport:
158
+ AIProviderRuntime `arcane-ai-provider/2` routes, OpenAI HTTPS, Arcane.ollama,
159
+ Arcane.speech, and the Android WebView bridge. [Deep protocol details](protocols.md).
116
160
 
117
161
  ### Example
118
162
 
@@ -170,6 +214,71 @@ import * as module from '/arcane/modules/AIPreferenceTuple.js';
170
214
  console.log(Object.keys(module));
171
215
  ```
172
216
 
217
+ ## AIProviderRuntime.js
218
+
219
+ ### Overview
220
+
221
+ Owns the portable provider-neutral runtime for independently selected LLM,
222
+ speech-to-text, and text-to-speech providers. The exported class documents the
223
+ shape, but application code uses the exported singleton returned by
224
+ `getAIProviderRuntime()`; direct construction fails with
225
+ `ARCANE_AI_RUNTIME_SINGLETON_REQUIRED`.
226
+
227
+ ### Public surface
228
+
229
+ Exact exports: `AI_PROVIDER_PROTOCOL`, `AI_PROVIDER_RUNTIME_PROTOCOL`,
230
+ `AI_MODEL_AUTHORITY_PROTOCOL`, `AIProviderRuntime`, `aiProviderRuntime`, and
231
+ `getAIProviderRuntime`.
232
+
233
+ The singleton exposes provider registration, closed three-role configuration,
234
+ catalog and status inspection, `start()`, independent `load()`, `unload()`,
235
+ `dispose()`, and `cancel()` operations, plus `chat()`, `stream()`,
236
+ `transcribe()`, `synthesize()`, and `setSpeechMuted()`. Provider payloads must
237
+ be data-only; callbacks, accessors, symbols, cycles, and excessive nesting are
238
+ rejected at the provider boundary.
239
+
240
+ `start({startMuted=true,startTranscription=false,signal=null}={})` waits for
241
+ prior speech-state and role unload work, applies the requested initial mute
242
+ state, and returns the `startAIRuntime()` control handle
243
+ `{barrier,settled,cancel}`. Startup does not request selected STT unless the
244
+ caller explicitly opts in; it does not force an independently active STT role
245
+ back to unloaded. The barrier and settled promises describe provider-startup
246
+ readiness; cancellation remains cooperative through the supplied signal and
247
+ returned control.
248
+
249
+ Interactive requests are latest-request-wins per role. A newer valid request
250
+ that reaches admission aborts the active request, waits for its provider promise
251
+ to settle (or for bounded stream cleanup to be confirmed), and revalidates
252
+ ready/loaded/not-busy state before it starts. Rapid intermediate requests are
253
+ superseded, and their late results cannot restore or overwrite newer role state.
254
+ Promise settlement proves only that the provider's exposed request promise
255
+ completed; it does not by itself prove that underlying provider work stopped.
256
+ Provider-specific positive cancellation acknowledgement remains
257
+ provider-specific. Load and reconfiguration guards stay fail-closed while
258
+ request ownership is active.
259
+
260
+ ### Availability and normalization
261
+
262
+ **Cross-host runtime with provider-specific execution.** Published SDK `0.2.2`
263
+ ships the browser-WASM LLM and browser Whisper/Kokoro adapters and supplies the
264
+ narrow AI.js legacy OpenAI/Ollama/Core-speech adapters; other native, Core, or
265
+ cloud adapters may be supplied externally only when they implement the same
266
+ `arcane-ai-provider/2` boundary. A
267
+ provider must prove a matching `arcane-ai-model-authority/1` inspection before load.
268
+ `localOnly` routing fails closed; it never selects a cloud or non-local route as
269
+ a fallback. Role lifecycle and stream cleanup are normalized, while the
270
+ selected provider retains its own capability, permission, download, and model
271
+ requirements. [Deep protocol details](protocols.md#portable-ai-provider-runtime).
272
+
273
+ ### Example
274
+
275
+ ```javascript
276
+ import {getAIProviderRuntime} from '/arcane/modules/AIProviderRuntime.js';
277
+
278
+ const runtime = getAIProviderRuntime();
279
+ console.log(runtime.protocol, runtime.status());
280
+ ```
281
+
173
282
  ## AIResponseLength.js
174
283
 
175
284
  ### Overview
@@ -218,6 +327,57 @@ import * as module from '/arcane/modules/AIResponseURLPolicy.js';
218
327
  console.log(Object.keys(module));
219
328
  ```
220
329
 
330
+ ## AIRuntimeState.js
331
+
332
+ ### Overview
333
+
334
+ Publishes one sticky immutable state tree for `llm`, `stt`, and `tts`, transient
335
+ load/unload/dispose intents, and a startup-settlement report. It makes lifecycle
336
+ observable without exposing provider transports in application code.
337
+
338
+ ### Public surface
339
+
340
+ Exact exports: `AI_RUNTIME_PROTOCOL`, `AI_RUNTIME_STATE_EVENT`,
341
+ `AI_RUNTIME_INTENT_EVENT`, `AI_RUNTIME_STARTUP_EVENT`, `AI_RUNTIME_ROLES`,
342
+ `AI_RUNTIME_STATES`, `aiRuntimeEvents`, `getAIRuntimeState()`,
343
+ `subscribeAIRuntimeState()`, `publishAIRuntimeRoleState()`,
344
+ `publishAIRuntimeRolesState()`, `requestAIRuntimeIntent()`,
345
+ `subscribeAIRuntimeIntents()`, and `startAIRuntime()`.
346
+
347
+ Each role record is exactly `{role,state,providerId,modelId,localOnly,loaded,
348
+ busy,operationId,progress,error}`.
349
+ `startAIRuntime({startMuted=true,startTranscription=false,signal})` returns
350
+ `{barrier,settled,cancel}`: `barrier` settles for text chat, while `settled`
351
+ covers every requested role. Muted startup does not request TTS, and STT startup
352
+ is opt-in so selection and state observation do not begin a transcription-model
353
+ load.
354
+
355
+ ### Availability and normalization
356
+
357
+ **Cross-host state contract.** States are `unavailable`, `unloaded`, `loading`,
358
+ `ready`, `unloading`, `error`, and `disposed`. Revisions increase monotonically.
359
+ The events `arcane-ai-runtime-state`, `arcane-ai-runtime-intent`, and
360
+ `arcane-ai-runtime-startup-settled` normalize observation only: receiving one
361
+ does not grant a native capability, prove browser support, or load a provider.
362
+ `arcane-ai-runtime-startup-settled` reports the LLM/text-chat `barrier`.
363
+ Await the returned `handle.settled` promise for every role requested by that
364
+ startup; the all-role settlement has no separate public event.
365
+
366
+ ### Example
367
+
368
+ ```javascript
369
+ import {
370
+ getAIRuntimeState,
371
+ subscribeAIRuntimeState
372
+ } from '/arcane/modules/AIRuntimeState.js';
373
+
374
+ const unsubscribe = subscribeAIRuntimeState(snapshot => {
375
+ console.log(snapshot.roles.llm.state);
376
+ });
377
+ console.log(getAIRuntimeState().protocol);
378
+ unsubscribe();
379
+ ```
380
+
221
381
  ## AnsiText.js
222
382
 
223
383
  ### Overview
@@ -631,17 +791,40 @@ console.log(Object.keys(module));
631
791
 
632
792
  ### Overview
633
793
 
634
- Owns normalized configuration/value contracts shared by chart, dashboard, Markdown, and voice components.
794
+ Owns normalized configuration/value contracts and shared explicit STT activation
795
+ behavior for chart, dashboard, Markdown, and voice components.
635
796
 
636
797
  ### Public surface
637
798
 
638
- Six constant sets and twelve normalization/formatting helpers.
799
+ Constant sets plus normalization, formatting, and explicit STT activation
800
+ helpers. `createSTTActivationController({host,button,onChange,EventClass=CustomEvent})`
801
+ consumes only normalized
802
+ [`AIRuntimeState`](#airuntimestatejs) `stt` role records. Its frozen controller
803
+ exposes `action`, `error`, `label`, `pending`, `selected`, `status`, `title`, and
804
+ `visible` getters plus `request(action)`, `synchronize(role)`, and `destroy()`.
805
+ `host` supplies `dispatchEvent(event)` and `requestSTTActivation(intent)`;
806
+ `button` supplies `addEventListener()` and `removeEventListener()`; and
807
+ `onChange()` is called whenever presentation should be rendered again. Browser
808
+ callers use the default `CustomEvent`; non-DOM callers must inject a compatible
809
+ `EventClass` constructor.
639
810
 
640
- Exact exports: `CHART_LABELS`, `DASHBOARD_LABELS`, `MARKDOWN_FORMATS`, `MARKDOWN_LABELS`, `VOICE_LABELS`, `VOICE_MESSAGES`, `appendTranscription`, `applyMarkdownFormat`, `effectiveDashboardVisibility`, `normalizeChartOptions`, `normalizeChartRows`, `normalizeDashboardDefinitions`, `normalizeDashboardOptions`, `normalizeDashboardVisibility`, `normalizeMarkdownFormats`, `normalizeMarkdownOptions`, `normalizeVoiceOptions`.
811
+ `request('load'|'unload')` emits the cancelable
812
+ `speech-stt-activation-request` event with frozen `{intent,state}` before it
813
+ invokes `host.requestSTTActivation(intent)`. Callback failure emits
814
+ `speech-stt-activation-error` with frozen `{request,error,message}`. Syncing
815
+ sticky state only changes the controller's observation and presentation; it
816
+ never emits a lifecycle intent, chooses a provider, or starts a download.
817
+ `destroy()` removes its button listener and suppresses late callback effects.
818
+
819
+ Exact exports: `CHART_LABELS`, `DASHBOARD_LABELS`, `MARKDOWN_FORMATS`, `MARKDOWN_LABELS`, `VOICE_LABELS`, `VOICE_MESSAGES`, `appendTranscription`, `applyMarkdownFormat`, `createSTTActivationController`, `effectiveDashboardVisibility`, `normalizeChartOptions`, `normalizeChartRows`, `normalizeDashboardDefinitions`, `normalizeDashboardOptions`, `normalizeDashboardVisibility`, `normalizeMarkdownFormats`, `normalizeMarkdownOptions`, `normalizeVoiceOptions`.
641
820
 
642
821
  ### Availability and normalization
643
822
 
644
- **Cross-host.** Fully normalized labels, rows, definitions, visibility, formats, editor and voice options. Transport: In-process only. [Deep protocol details](protocols.md).
823
+ **Cross-host with an injected event constructor outside DOM hosts.** Fully
824
+ normalized labels, rows, definitions, visibility, formats, editor and voice
825
+ options, plus capability-neutral STT activation intent and presentation state.
826
+ Provider authority and lifecycle execution remain with the configured runtime
827
+ owner. Transport: In-process only. [Deep protocol details](protocols.md).
645
828
 
646
829
  ### Example
647
830
 
@@ -659,7 +842,40 @@ Owns bounded in-memory AI turns, context construction, response-length instructi
659
842
 
660
843
  ### Public surface
661
844
 
662
- default `ConfiguredAIChatSession`; `history()`, `clear()`, `send()`.
845
+ default `ConfiguredAIChatSession`; `history()`, `clear()`, `prepare()`, `send()`.
846
+
847
+ `new ConfiguredAIChatSession(options={})` admits exactly `chat`,
848
+ `contextBuilder`, `initialMessages`, `maxContextCharacters`,
849
+ `maxMessageCharacters`, `maxMessages`, `request`, `responseLength`, and
850
+ `systemPrompt`. `initialMessages` is an array of closed `user`, `assistant`, or
851
+ `tool` messages under the same message/context bounds. It excludes `system`,
852
+ allows exactly one structural assistant tool call, and requires a matching tool
853
+ result before another user turn or tool-call sequence; `systemPrompt` owns the
854
+ separate system message.
855
+
856
+ `prepare(input,{signal})` performs the complete bounded request but does not
857
+ commit history immediately. It returns frozen `{response,commit,rollback}`;
858
+ exactly one terminal settlement is permitted. `send()` is the convenience path
859
+ that prepares and then commits the turn.
860
+
861
+ An optional async `contextBuilder({input,history,signal})` receives a frozen
862
+ request snapshot and the same cancellation signal. Its returned context is
863
+ framed as untrusted data for only the current request and is never committed to
864
+ history.
865
+
866
+ An injected `chat(request)` may return the prior normalized session result or
867
+ exactly one non-stream OpenAI-compatible choice. The prior form preserves its
868
+ explicit `done` boolean; OpenAI-compatible choice normalization sets
869
+ `done:true`. Both return frozen
870
+ `{provider,model,message:{role:'assistant',content,tool_calls?},done,
871
+ doneReason,promptEvalCount,evalCount}`. Tool calls remain structural data and
872
+ are never executed. When `tool_calls` is present it must contain exactly one
873
+ valid structural call. A malformed response fails `AI_CHAT_INVALID_RESPONSE`;
874
+ caller cancellation is `AbortError` with code `AI_CHAT_ABORTED`. A new user
875
+ turn cannot bypass a pending structural tool call
876
+ (`AI_CHAT_TOOL_RESULT_REQUIRED`), a mismatched tool result fails
877
+ `AI_CHAT_INVALID_TOOL_MESSAGE`, and a second terminal settlement of one
878
+ prepared transaction fails `AI_CHAT_TRANSACTION_SETTLED`.
663
879
 
664
880
  Exact exports: `default`.
665
881
 
@@ -670,9 +886,19 @@ Exact exports: `default`.
670
886
  ### Example
671
887
 
672
888
  ```javascript
673
- import * as module from '/arcane/modules/ConfiguredAIChatSession.js';
889
+ import ConfiguredAIChatSession from '/arcane/modules/ConfiguredAIChatSession.js';
674
890
 
675
- console.log(Object.keys(module));
891
+ const session = new ConfiguredAIChatSession({
892
+ chat: async request => ({
893
+ provider: 'demo',
894
+ model: 'echo',
895
+ message: {
896
+ role: 'assistant',
897
+ content: `Received ${request.messages.length} messages.`
898
+ }
899
+ })
900
+ });
901
+ console.log(await session.send('Hello'));
676
902
  ```
677
903
 
678
904
  ## ConversationActionItems.js
@@ -843,6 +1069,96 @@ import * as module from '/arcane/modules/DBOPFS.js';
843
1069
  console.log(Object.keys(module));
844
1070
  ```
845
1071
 
1072
+ ## DBOPFSDocumentLibrary.js
1073
+
1074
+ ### Overview
1075
+
1076
+ Stores one application-defined document corpus through an existing DBOPFS-style
1077
+ adapter, searches only a completed generation, and builds bounded context that
1078
+ is explicitly labeled untrusted. Construction performs no read, write, fetch,
1079
+ or search; applications call `bootstrap()` deliberately.
1080
+
1081
+ ### Public surface
1082
+
1083
+ Exact exports: default and named `DBOPFSDocumentLibrary`,
1084
+ `createDBOPFSDocumentLibrary()`, and `normalizeDBOPFSDocumentSchema()`.
1085
+
1086
+ `new DBOPFSDocumentLibrary({concurrency,db,maxCorpusCharacters,
1087
+ maxDocumentCharacters,maxSearchCharacters,schema})` exposes `schema`,
1088
+ `bootstrap({files,onProgress,read,readFailurePolicy,signal})`,
1089
+ `search(query,{kinds,limit,signal,tags})`,
1090
+ `evaluate(query,{sources,read,maxCharacters,maxCorpusCharacters,
1091
+ maxScoringCharacters,maxDocumentCharacters?,kinds?,tags?,readFailurePolicy?,
1092
+ onProgress?,signal?})`,
1093
+ `buildContext(query,{limit,maxCharacters,maxDocumentCharacters,signal})`, and
1094
+ `createContextBuilder({limit,maxCharacters,maxDocumentCharacters})`.
1095
+
1096
+ `evaluate()` requires `sources`, `read`, `maxCharacters`,
1097
+ `maxCorpusCharacters`, and `maxScoringCharacters`. It filters source metadata
1098
+ before calling
1099
+ `read(source,{maxCharacters,maxCorpusCharacters,ordinal,signal})`, never accepts a
1100
+ source body as implicit authority, and never persists a caller-owned body.
1101
+ `maxDocumentCharacters` defaults to the smaller instance/output bound.
1102
+
1103
+ ### Availability and normalization
1104
+
1105
+ **Browser or compatible host with an injected DBOPFS adapter.** The adapter
1106
+ keeps the existing `get`, `set`, `getAllKeys`, and `delete` method names; Node
1107
+ can use the same class only through an explicitly imported runtime module and a
1108
+ compatible storage adapter; SDK `0.2.2` publishes no Node package subpath or
1109
+ Node storage implementation for it. Bootstrap uses a bounded concurrent
1110
+ generation, commits its manifest last, cleans partial data on failure, and
1111
+ rejects case-colliding IDs. Search
1112
+ returns `{failures,matches,total}` so one corrupt record does not become a false
1113
+ complete result. `bootstrap()` defaults to rejecting read failure; the explicit
1114
+ `readFailurePolicy:'preserve-readable'` mode returns partial-success
1115
+ `readCoverage`. `evaluate()` also defaults to rejecting a source-read failure;
1116
+ its explicit `preserve-readable` mode instead ranks the readable records and
1117
+ returns partial `failures` plus `coverage` in the evaluation result (not
1118
+ bootstrap's `readCoverage`). It reads a caller-owned source list without
1119
+ persisting its bodies and returns frozen `{authority:'sources',characters,
1120
+ coverage,documents,failures,limits,query,scoringTruncated,text,truncated}`.
1121
+ Read failure remains `DBOPFS_DOCUMENT_READ_FAILED`; invalid public input uses
1122
+ `DBOPFS_DOCUMENT_INVALID`, invalid integer budgets use
1123
+ `DBOPFS_DOCUMENT_INVALID_LIMIT`, and a preserved read failure without a usable
1124
+ source code is reported as `failures[].code:'DBOPFS_DOCUMENT_ERROR'`.
1125
+ Cancellation is `AbortError` with code `DBOPFS_DOCUMENT_ABORTED`. Construction
1126
+ does not search.
1127
+ When an application explicitly supplies the library's context builder, each
1128
+ prepared chat send performs that bounded retrieval.
1129
+
1130
+ ### Example
1131
+
1132
+ ```javascript
1133
+ import {
1134
+ createDBOPFSDocumentLibrary
1135
+ } from '/arcane/modules/DBOPFSDocumentLibrary.js';
1136
+
1137
+ const documents = createDBOPFSDocumentLibrary({
1138
+ db: globalThis.dbopfs,
1139
+ schema: {id: 'help', version: '1', table: 'help_documents'}
1140
+ });
1141
+ async function replaceHelpCorpusAfterUserChoice() {
1142
+ await documents.bootstrap({files: [{
1143
+ id: 'welcome',
1144
+ path: 'welcome.md',
1145
+ title: 'Welcome',
1146
+ body: 'Arcane applications are portable.'
1147
+ }]});
1148
+ console.log(await documents.search('portable'));
1149
+
1150
+ const preview = await documents.evaluate('portable', {
1151
+ sources: [{id:'draft', path:'draft.md', title:'Draft'}],
1152
+ read: async source => source.id === 'draft' ? 'Portable app notes.' : '',
1153
+ maxCharacters: 2048,
1154
+ maxCorpusCharacters: 4096,
1155
+ maxDocumentCharacters: 512,
1156
+ maxScoringCharacters: 512
1157
+ });
1158
+ console.log(preview.coverage, preview.text);
1159
+ }
1160
+ ```
1161
+
846
1162
  ## DBOPFSWorker.js
847
1163
 
848
1164
  ### Overview
@@ -913,6 +1229,48 @@ import * as module from '/arcane/modules/DirectoryPicker.js';
913
1229
  console.log(Object.keys(module));
914
1230
  ```
915
1231
 
1232
+ ## DocumentLexicalSearch.js
1233
+
1234
+ ### Overview
1235
+
1236
+ Provides deterministic, dependency-free metadata/body ranking and bounded
1237
+ context excerpts for caller-owned document records.
1238
+
1239
+ ### Public surface
1240
+
1241
+ Exact exports: `DOCUMENT_SEARCH_FIELD_ORDER`, default and named
1242
+ `DocumentLexicalSearch`, `createDocumentLexicalIndex()`,
1243
+ `documentContextExcerpt()`, `documentSearchTokens()`,
1244
+ `normalizedDocumentSearchText()`, `scoreDocumentBody()`, and
1245
+ `scoreDocumentLexicalIndex()`.
1246
+
1247
+ `new DocumentLexicalSearch(records,{maxResults=20})` exposes
1248
+ `rank(query,{kinds,tags})` and `search(query,{kinds,limit,tags})`.
1249
+
1250
+ ### Availability and normalization
1251
+
1252
+ **Cross-host.** Indexing and search are in-process only. Text, tags, kinds,
1253
+ scores, field ordering, truncation, and tie-breaking are normalized into frozen
1254
+ records. This module performs no storage, network, model, Core, or DOM action.
1255
+ The caller decides whether a result is merely displayed or explicitly injected
1256
+ as untrusted AI context.
1257
+
1258
+ ### Example
1259
+
1260
+ ```javascript
1261
+ import DocumentLexicalSearch from '/arcane/modules/DocumentLexicalSearch.js';
1262
+
1263
+ const search = new DocumentLexicalSearch([{
1264
+ id: 'welcome',
1265
+ path: 'welcome.md',
1266
+ title: 'Welcome',
1267
+ body: 'Arcane applications are portable.',
1268
+ kind: 'guide',
1269
+ tags: ['intro']
1270
+ }]);
1271
+ console.log(search.search('portable', {limit: 5}));
1272
+ ```
1273
+
916
1274
  ## DocumentNavigation.js
917
1275
 
918
1276
  ### Overview
@@ -1095,7 +1453,15 @@ Exact exports: `availabilityFromReport`, `createLocalAIReadinessController`.
1095
1453
 
1096
1454
  ### Availability and normalization
1097
1455
 
1098
- **Browser/native hybrid.** Normalized controller state and change events. Transport: LocalAIReadiness + component events. [Deep protocol details](protocols.md).
1456
+ `availabilityFromReport()` returns `true` only for a slot whose local
1457
+ requirement is explicitly `required:true` and whose report is explicitly
1458
+ `ready:true`. Missing and non-local-required slots remain false: this projection
1459
+ does not attest provider registration, selection, credentials, browser speech
1460
+ authority, or model load state. Components must preserve selected sticky
1461
+ `AIRuntimeState` roles as the readiness authority.
1462
+
1463
+ **Browser/native hybrid.** Normalized controller state and change events.
1464
+ Transport: LocalAIReadiness + component events. [Deep protocol details](protocols.md).
1099
1465
 
1100
1466
  ### Example
1101
1467
 
@@ -1369,6 +1735,64 @@ import * as module from '/arcane/modules/OpenMeteoWeatherProvider.js';
1369
1735
  console.log(Object.keys(module));
1370
1736
  ```
1371
1737
 
1738
+ ## PersistentAIChatSession.js
1739
+
1740
+ ### Overview
1741
+
1742
+ Composes `ConfiguredAIChatSession` with one `ChatEntity` so every user,
1743
+ assistant, and structural tool-result turn has an explicit durable-persistence
1744
+ choice. It preserves the existing DBOPFS method names and ChatEntity memory
1745
+ semantics; it does not define a new storage protocol.
1746
+
1747
+ ### Public surface
1748
+
1749
+ Exact exports: default and named `PersistentAIChatSession` plus
1750
+ `createPersistentAIChatSession()`.
1751
+
1752
+ Constructor and factory options are `{chat,chatEntity,chatFileName,
1753
+ contextBuilder,loadExisting,maxContextCharacters,maxMessageCharacters,
1754
+ maxMessages,memory,request,responseLength,systemPrompt}`. Public members are
1755
+ static `create()`, getters `chatEntity` and `fileName`, and `ready()`,
1756
+ `history()`, `settleMemory()`, and `send(input)`.
1757
+ `ready()` waits for initialization and resolves the same session instance.
1758
+
1759
+ `send()` accepts `{message:{content,role:'user'|'tool',tool_call_id?,persist},
1760
+ response:{persist},signal?}`. Message and response persistence must match.
1761
+ `persist:false` still commits the coherent turn to live bounded model context,
1762
+ but not to durable ChatEntity history or memory. A structural tool result must
1763
+ use the persistence choice captured by its matching assistant tool call.
1764
+
1765
+ ### Availability and normalization
1766
+
1767
+ **Browser or native WebView with ChatEntity/DBOPFS and a configured chat
1768
+ function.** The default chat calls normalized `Arcane.ai.chat()`; callers can
1769
+ inject the browser-WASM controller, another provider-neutral adapter, or a
1770
+ cloud chat function. There is no automatic provider or storage fallback.
1771
+ Context builders are request-only, and document context remains explicitly
1772
+ untrusted. Errors include `AI_CHAT_BUSY`, `AI_CHAT_TOOL_RESULT_REQUIRED`,
1773
+ `AI_CHAT_INVALID_TOOL_MESSAGE`, and `AI_CHAT_INCOHERENT_PERSISTENCE`.
1774
+
1775
+ ### Example
1776
+
1777
+ ```javascript
1778
+ import {
1779
+ createPersistentAIChatSession
1780
+ } from '/arcane/modules/PersistentAIChatSession.js';
1781
+
1782
+ async function sendPersistentSupportTurnAfterUserChoice(documents) {
1783
+ const session = await createPersistentAIChatSession({
1784
+ chatFileName: 'support.jsonl',
1785
+ loadExisting: true,
1786
+ contextBuilder: documents.createContextBuilder()
1787
+ });
1788
+ const response = await session.send({
1789
+ message: {role: 'user', content: 'Summarize the documents.', persist: true},
1790
+ response: {persist: true}
1791
+ });
1792
+ console.log(response.message.content);
1793
+ }
1794
+ ```
1795
+
1372
1796
  ## PreferenceStore.js
1373
1797
 
1374
1798
  ### Overview
@@ -1650,9 +2074,21 @@ Exact exports: `MAX_SPEECH_CHARACTERS`, `MAX_SPEECH_CHUNKS`, `MAX_SPEECH_INPUT`,
1650
2074
  ### Example
1651
2075
 
1652
2076
  ```javascript
1653
- import * as module from '/arcane/modules/SpeechPlayback.js';
2077
+ import SpeechPlayback from '/arcane/modules/SpeechPlayback.js';
1654
2078
 
1655
- console.log(Object.keys(module));
2079
+ const audio = document.body.appendChild(document.createElement('audio'));
2080
+ audio.controls = true;
2081
+ const speech = new SpeechPlayback({audio});
2082
+ const speakButton = document.body.appendChild(document.createElement('button'));
2083
+ speakButton.type = 'button';
2084
+ speakButton.textContent = 'Speak';
2085
+ speakButton.addEventListener('click', async () => {
2086
+ await speech.prepare({
2087
+ key: 'ready',
2088
+ parts: ['Arcane is ready.'],
2089
+ autoplay: true
2090
+ });
2091
+ });
1656
2092
  ```
1657
2093
 
1658
2094
  ## StaticDocumentCatalog.js