arcane-os 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +35 -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 +331 -25
  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 +30 -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 +19 -6
  15. package/docs/reference/inventory/runtime-modules.json +113 -9
  16. package/docs/reference/protocols.md +260 -38
  17. package/docs/reference/runtime-components.md +108 -15
  18. package/docs/reference/runtime-modules.md +422 -8
  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 +288 -52
  23. package/runtime/arcane/components/speech.html +339 -15
  24. package/runtime/arcane/modules/AI.js +1245 -136
  25. package/runtime/arcane/modules/AIProviderRuntime.js +299 -30
  26. package/runtime/arcane/modules/AIRuntimeState.js +23 -4
  27. package/runtime/arcane/modules/ConfiguredAIChatSession.js +93 -8
  28. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +448 -24
  29. package/runtime/arcane/modules/LocalAIReadinessController.js +1 -1
  30. package/schemas/arcane-lock.schema.json +6 -4
  31. package/src/dev-server.mjs +29 -13
  32. package/src/doctor.mjs +1 -3
  33. package/src/import-map.mjs +134 -83
  34. package/src/packager/core.mjs +311 -39
  35. package/src/scaffold.mjs +45 -17
  36. package/src/templates/workspace-template.mjs +23 -4
  37. package/src/toolchain.mjs +10 -2
  38. package/src/workspace.mjs +177 -24
@@ -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. |
@@ -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,72 @@ 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.1`
263
+ ships the browser-WASM LLM and browser Whisper/Kokoro adapters but predates the
264
+ legacy speech adapters described above. Current source also supplies the narrow
265
+ AI.js legacy OpenAI/Ollama/Core-speech adapters; other native, Core, or cloud
266
+ adapters may be supplied externally only when they implement the same
267
+ `arcane-ai-provider/2` boundary. A
268
+ provider must prove a matching `arcane-ai-model-authority/1` inspection before load.
269
+ `localOnly` routing fails closed; it never selects a cloud or non-local route as
270
+ a fallback. Role lifecycle and stream cleanup are normalized, while the
271
+ selected provider retains its own capability, permission, download, and model
272
+ requirements. [Deep protocol details](protocols.md#portable-ai-provider-runtime).
273
+
274
+ ### Example
275
+
276
+ ```javascript
277
+ import {getAIProviderRuntime} from '/arcane/modules/AIProviderRuntime.js';
278
+
279
+ const runtime = getAIProviderRuntime();
280
+ console.log(runtime.protocol, runtime.status());
281
+ ```
282
+
173
283
  ## AIResponseLength.js
174
284
 
175
285
  ### Overview
@@ -218,6 +328,57 @@ import * as module from '/arcane/modules/AIResponseURLPolicy.js';
218
328
  console.log(Object.keys(module));
219
329
  ```
220
330
 
331
+ ## AIRuntimeState.js
332
+
333
+ ### Overview
334
+
335
+ Publishes one sticky immutable state tree for `llm`, `stt`, and `tts`, transient
336
+ load/unload/dispose intents, and a startup-settlement report. It makes lifecycle
337
+ observable without exposing provider transports in application code.
338
+
339
+ ### Public surface
340
+
341
+ Exact exports: `AI_RUNTIME_PROTOCOL`, `AI_RUNTIME_STATE_EVENT`,
342
+ `AI_RUNTIME_INTENT_EVENT`, `AI_RUNTIME_STARTUP_EVENT`, `AI_RUNTIME_ROLES`,
343
+ `AI_RUNTIME_STATES`, `aiRuntimeEvents`, `getAIRuntimeState()`,
344
+ `subscribeAIRuntimeState()`, `publishAIRuntimeRoleState()`,
345
+ `publishAIRuntimeRolesState()`, `requestAIRuntimeIntent()`,
346
+ `subscribeAIRuntimeIntents()`, and `startAIRuntime()`.
347
+
348
+ Each role record is exactly `{role,state,providerId,modelId,localOnly,loaded,
349
+ busy,operationId,progress,error}`.
350
+ `startAIRuntime({startMuted=true,startTranscription=false,signal})` returns
351
+ `{barrier,settled,cancel}`: `barrier` settles for text chat, while `settled`
352
+ covers every requested role. Muted startup does not request TTS, and STT startup
353
+ is opt-in so selection and state observation do not begin a transcription-model
354
+ load.
355
+
356
+ ### Availability and normalization
357
+
358
+ **Cross-host state contract.** States are `unavailable`, `unloaded`, `loading`,
359
+ `ready`, `unloading`, `error`, and `disposed`. Revisions increase monotonically.
360
+ The events `arcane-ai-runtime-state`, `arcane-ai-runtime-intent`, and
361
+ `arcane-ai-runtime-startup-settled` normalize observation only: receiving one
362
+ does not grant a native capability, prove browser support, or load a provider.
363
+ `arcane-ai-runtime-startup-settled` reports the LLM/text-chat `barrier`.
364
+ Await the returned `handle.settled` promise for every role requested by that
365
+ startup; the all-role settlement has no separate public event.
366
+
367
+ ### Example
368
+
369
+ ```javascript
370
+ import {
371
+ getAIRuntimeState,
372
+ subscribeAIRuntimeState
373
+ } from '/arcane/modules/AIRuntimeState.js';
374
+
375
+ const unsubscribe = subscribeAIRuntimeState(snapshot => {
376
+ console.log(snapshot.roles.llm.state);
377
+ });
378
+ console.log(getAIRuntimeState().protocol);
379
+ unsubscribe();
380
+ ```
381
+
221
382
  ## AnsiText.js
222
383
 
223
384
  ### Overview
@@ -659,7 +820,40 @@ Owns bounded in-memory AI turns, context construction, response-length instructi
659
820
 
660
821
  ### Public surface
661
822
 
662
- default `ConfiguredAIChatSession`; `history()`, `clear()`, `send()`.
823
+ default `ConfiguredAIChatSession`; `history()`, `clear()`, `prepare()`, `send()`.
824
+
825
+ `new ConfiguredAIChatSession(options={})` admits exactly `chat`,
826
+ `contextBuilder`, `initialMessages`, `maxContextCharacters`,
827
+ `maxMessageCharacters`, `maxMessages`, `request`, `responseLength`, and
828
+ `systemPrompt`. `initialMessages` is an array of closed `user`, `assistant`, or
829
+ `tool` messages under the same message/context bounds. It excludes `system`,
830
+ allows exactly one structural assistant tool call, and requires a matching tool
831
+ result before another user turn or tool-call sequence; `systemPrompt` owns the
832
+ separate system message.
833
+
834
+ `prepare(input,{signal})` performs the complete bounded request but does not
835
+ commit history immediately. It returns frozen `{response,commit,rollback}`;
836
+ exactly one terminal settlement is permitted. `send()` is the convenience path
837
+ that prepares and then commits the turn.
838
+
839
+ An optional async `contextBuilder({input,history,signal})` receives a frozen
840
+ request snapshot and the same cancellation signal. Its returned context is
841
+ framed as untrusted data for only the current request and is never committed to
842
+ history.
843
+
844
+ An injected `chat(request)` may return the prior normalized session result or
845
+ exactly one non-stream OpenAI-compatible choice. The prior form preserves its
846
+ explicit `done` boolean; OpenAI-compatible choice normalization sets
847
+ `done:true`. Both return frozen
848
+ `{provider,model,message:{role:'assistant',content,tool_calls?},done,
849
+ doneReason,promptEvalCount,evalCount}`. Tool calls remain structural data and
850
+ are never executed. When `tool_calls` is present it must contain exactly one
851
+ valid structural call. A malformed response fails `AI_CHAT_INVALID_RESPONSE`;
852
+ caller cancellation is `AbortError` with code `AI_CHAT_ABORTED`. A new user
853
+ turn cannot bypass a pending structural tool call
854
+ (`AI_CHAT_TOOL_RESULT_REQUIRED`), a mismatched tool result fails
855
+ `AI_CHAT_INVALID_TOOL_MESSAGE`, and a second terminal settlement of one
856
+ prepared transaction fails `AI_CHAT_TRANSACTION_SETTLED`.
663
857
 
664
858
  Exact exports: `default`.
665
859
 
@@ -670,9 +864,19 @@ Exact exports: `default`.
670
864
  ### Example
671
865
 
672
866
  ```javascript
673
- import * as module from '/arcane/modules/ConfiguredAIChatSession.js';
867
+ import ConfiguredAIChatSession from '/arcane/modules/ConfiguredAIChatSession.js';
674
868
 
675
- console.log(Object.keys(module));
869
+ const session = new ConfiguredAIChatSession({
870
+ chat: async request => ({
871
+ provider: 'demo',
872
+ model: 'echo',
873
+ message: {
874
+ role: 'assistant',
875
+ content: `Received ${request.messages.length} messages.`
876
+ }
877
+ })
878
+ });
879
+ console.log(await session.send('Hello'));
676
880
  ```
677
881
 
678
882
  ## ConversationActionItems.js
@@ -843,6 +1047,96 @@ import * as module from '/arcane/modules/DBOPFS.js';
843
1047
  console.log(Object.keys(module));
844
1048
  ```
845
1049
 
1050
+ ## DBOPFSDocumentLibrary.js
1051
+
1052
+ ### Overview
1053
+
1054
+ Stores one application-defined document corpus through an existing DBOPFS-style
1055
+ adapter, searches only a completed generation, and builds bounded context that
1056
+ is explicitly labeled untrusted. Construction performs no read, write, fetch,
1057
+ or search; applications call `bootstrap()` deliberately.
1058
+
1059
+ ### Public surface
1060
+
1061
+ Exact exports: default and named `DBOPFSDocumentLibrary`,
1062
+ `createDBOPFSDocumentLibrary()`, and `normalizeDBOPFSDocumentSchema()`.
1063
+
1064
+ `new DBOPFSDocumentLibrary({concurrency,db,maxCorpusCharacters,
1065
+ maxDocumentCharacters,maxSearchCharacters,schema})` exposes `schema`,
1066
+ `bootstrap({files,onProgress,read,readFailurePolicy,signal})`,
1067
+ `search(query,{kinds,limit,signal,tags})`,
1068
+ `evaluate(query,{sources,read,maxCharacters,maxCorpusCharacters,
1069
+ maxScoringCharacters,maxDocumentCharacters?,kinds?,tags?,readFailurePolicy?,
1070
+ onProgress?,signal?})`,
1071
+ `buildContext(query,{limit,maxCharacters,maxDocumentCharacters,signal})`, and
1072
+ `createContextBuilder({limit,maxCharacters,maxDocumentCharacters})`.
1073
+
1074
+ `evaluate()` requires `sources`, `read`, `maxCharacters`,
1075
+ `maxCorpusCharacters`, and `maxScoringCharacters`. It filters source metadata
1076
+ before calling
1077
+ `read(source,{maxCharacters,maxCorpusCharacters,ordinal,signal})`, never accepts a
1078
+ source body as implicit authority, and never persists a caller-owned body.
1079
+ `maxDocumentCharacters` defaults to the smaller instance/output bound.
1080
+
1081
+ ### Availability and normalization
1082
+
1083
+ **Browser or compatible host with an injected DBOPFS adapter.** The adapter
1084
+ keeps the existing `get`, `set`, `getAllKeys`, and `delete` method names; Node
1085
+ can use the same class only through an explicitly imported runtime module and a
1086
+ compatible storage adapter; SDK `0.2.1` publishes no Node package subpath or
1087
+ Node storage implementation for it. Bootstrap uses a bounded concurrent
1088
+ generation, commits its manifest last, cleans partial data on failure, and
1089
+ rejects case-colliding IDs. Search
1090
+ returns `{failures,matches,total}` so one corrupt record does not become a false
1091
+ complete result. `bootstrap()` defaults to rejecting read failure; the explicit
1092
+ `readFailurePolicy:'preserve-readable'` mode returns partial-success
1093
+ `readCoverage`. `evaluate()` also defaults to rejecting a source-read failure;
1094
+ its explicit `preserve-readable` mode instead ranks the readable records and
1095
+ returns partial `failures` plus `coverage` in the evaluation result (not
1096
+ bootstrap's `readCoverage`). It reads a caller-owned source list without
1097
+ persisting its bodies and returns frozen `{authority:'sources',characters,
1098
+ coverage,documents,failures,limits,query,scoringTruncated,text,truncated}`.
1099
+ Read failure remains `DBOPFS_DOCUMENT_READ_FAILED`; invalid public input uses
1100
+ `DBOPFS_DOCUMENT_INVALID`, invalid integer budgets use
1101
+ `DBOPFS_DOCUMENT_INVALID_LIMIT`, and a preserved read failure without a usable
1102
+ source code is reported as `failures[].code:'DBOPFS_DOCUMENT_ERROR'`.
1103
+ Cancellation is `AbortError` with code `DBOPFS_DOCUMENT_ABORTED`. Construction
1104
+ does not search.
1105
+ When an application explicitly supplies the library's context builder, each
1106
+ prepared chat send performs that bounded retrieval.
1107
+
1108
+ ### Example
1109
+
1110
+ ```javascript
1111
+ import {
1112
+ createDBOPFSDocumentLibrary
1113
+ } from '/arcane/modules/DBOPFSDocumentLibrary.js';
1114
+
1115
+ const documents = createDBOPFSDocumentLibrary({
1116
+ db: globalThis.dbopfs,
1117
+ schema: {id: 'help', version: '1', table: 'help_documents'}
1118
+ });
1119
+ async function replaceHelpCorpusAfterUserChoice() {
1120
+ await documents.bootstrap({files: [{
1121
+ id: 'welcome',
1122
+ path: 'welcome.md',
1123
+ title: 'Welcome',
1124
+ body: 'Arcane applications are portable.'
1125
+ }]});
1126
+ console.log(await documents.search('portable'));
1127
+
1128
+ const preview = await documents.evaluate('portable', {
1129
+ sources: [{id:'draft', path:'draft.md', title:'Draft'}],
1130
+ read: async source => source.id === 'draft' ? 'Portable app notes.' : '',
1131
+ maxCharacters: 2048,
1132
+ maxCorpusCharacters: 4096,
1133
+ maxDocumentCharacters: 512,
1134
+ maxScoringCharacters: 512
1135
+ });
1136
+ console.log(preview.coverage, preview.text);
1137
+ }
1138
+ ```
1139
+
846
1140
  ## DBOPFSWorker.js
847
1141
 
848
1142
  ### Overview
@@ -913,6 +1207,48 @@ import * as module from '/arcane/modules/DirectoryPicker.js';
913
1207
  console.log(Object.keys(module));
914
1208
  ```
915
1209
 
1210
+ ## DocumentLexicalSearch.js
1211
+
1212
+ ### Overview
1213
+
1214
+ Provides deterministic, dependency-free metadata/body ranking and bounded
1215
+ context excerpts for caller-owned document records.
1216
+
1217
+ ### Public surface
1218
+
1219
+ Exact exports: `DOCUMENT_SEARCH_FIELD_ORDER`, default and named
1220
+ `DocumentLexicalSearch`, `createDocumentLexicalIndex()`,
1221
+ `documentContextExcerpt()`, `documentSearchTokens()`,
1222
+ `normalizedDocumentSearchText()`, `scoreDocumentBody()`, and
1223
+ `scoreDocumentLexicalIndex()`.
1224
+
1225
+ `new DocumentLexicalSearch(records,{maxResults=20})` exposes
1226
+ `rank(query,{kinds,tags})` and `search(query,{kinds,limit,tags})`.
1227
+
1228
+ ### Availability and normalization
1229
+
1230
+ **Cross-host.** Indexing and search are in-process only. Text, tags, kinds,
1231
+ scores, field ordering, truncation, and tie-breaking are normalized into frozen
1232
+ records. This module performs no storage, network, model, Core, or DOM action.
1233
+ The caller decides whether a result is merely displayed or explicitly injected
1234
+ as untrusted AI context.
1235
+
1236
+ ### Example
1237
+
1238
+ ```javascript
1239
+ import DocumentLexicalSearch from '/arcane/modules/DocumentLexicalSearch.js';
1240
+
1241
+ const search = new DocumentLexicalSearch([{
1242
+ id: 'welcome',
1243
+ path: 'welcome.md',
1244
+ title: 'Welcome',
1245
+ body: 'Arcane applications are portable.',
1246
+ kind: 'guide',
1247
+ tags: ['intro']
1248
+ }]);
1249
+ console.log(search.search('portable', {limit: 5}));
1250
+ ```
1251
+
916
1252
  ## DocumentNavigation.js
917
1253
 
918
1254
  ### Overview
@@ -1095,7 +1431,15 @@ Exact exports: `availabilityFromReport`, `createLocalAIReadinessController`.
1095
1431
 
1096
1432
  ### Availability and normalization
1097
1433
 
1098
- **Browser/native hybrid.** Normalized controller state and change events. Transport: LocalAIReadiness + component events. [Deep protocol details](protocols.md).
1434
+ `availabilityFromReport()` returns `true` only for a slot whose local
1435
+ requirement is explicitly `required:true` and whose report is explicitly
1436
+ `ready:true`. Missing and non-local-required slots remain false: this projection
1437
+ does not attest provider registration, selection, credentials, browser speech
1438
+ authority, or model load state. Components must preserve selected sticky
1439
+ `AIRuntimeState` roles as the readiness authority.
1440
+
1441
+ **Browser/native hybrid.** Normalized controller state and change events.
1442
+ Transport: LocalAIReadiness + component events. [Deep protocol details](protocols.md).
1099
1443
 
1100
1444
  ### Example
1101
1445
 
@@ -1369,6 +1713,64 @@ import * as module from '/arcane/modules/OpenMeteoWeatherProvider.js';
1369
1713
  console.log(Object.keys(module));
1370
1714
  ```
1371
1715
 
1716
+ ## PersistentAIChatSession.js
1717
+
1718
+ ### Overview
1719
+
1720
+ Composes `ConfiguredAIChatSession` with one `ChatEntity` so every user,
1721
+ assistant, and structural tool-result turn has an explicit durable-persistence
1722
+ choice. It preserves the existing DBOPFS method names and ChatEntity memory
1723
+ semantics; it does not define a new storage protocol.
1724
+
1725
+ ### Public surface
1726
+
1727
+ Exact exports: default and named `PersistentAIChatSession` plus
1728
+ `createPersistentAIChatSession()`.
1729
+
1730
+ Constructor and factory options are `{chat,chatEntity,chatFileName,
1731
+ contextBuilder,loadExisting,maxContextCharacters,maxMessageCharacters,
1732
+ maxMessages,memory,request,responseLength,systemPrompt}`. Public members are
1733
+ static `create()`, getters `chatEntity` and `fileName`, and `ready()`,
1734
+ `history()`, `settleMemory()`, and `send(input)`.
1735
+ `ready()` waits for initialization and resolves the same session instance.
1736
+
1737
+ `send()` accepts `{message:{content,role:'user'|'tool',tool_call_id?,persist},
1738
+ response:{persist},signal?}`. Message and response persistence must match.
1739
+ `persist:false` still commits the coherent turn to live bounded model context,
1740
+ but not to durable ChatEntity history or memory. A structural tool result must
1741
+ use the persistence choice captured by its matching assistant tool call.
1742
+
1743
+ ### Availability and normalization
1744
+
1745
+ **Browser or native WebView with ChatEntity/DBOPFS and a configured chat
1746
+ function.** The default chat calls normalized `Arcane.ai.chat()`; callers can
1747
+ inject the browser-WASM controller, another provider-neutral adapter, or a
1748
+ cloud chat function. There is no automatic provider or storage fallback.
1749
+ Context builders are request-only, and document context remains explicitly
1750
+ untrusted. Errors include `AI_CHAT_BUSY`, `AI_CHAT_TOOL_RESULT_REQUIRED`,
1751
+ `AI_CHAT_INVALID_TOOL_MESSAGE`, and `AI_CHAT_INCOHERENT_PERSISTENCE`.
1752
+
1753
+ ### Example
1754
+
1755
+ ```javascript
1756
+ import {
1757
+ createPersistentAIChatSession
1758
+ } from '/arcane/modules/PersistentAIChatSession.js';
1759
+
1760
+ async function sendPersistentSupportTurnAfterUserChoice(documents) {
1761
+ const session = await createPersistentAIChatSession({
1762
+ chatFileName: 'support.jsonl',
1763
+ loadExisting: true,
1764
+ contextBuilder: documents.createContextBuilder()
1765
+ });
1766
+ const response = await session.send({
1767
+ message: {role: 'user', content: 'Summarize the documents.', persist: true},
1768
+ response: {persist: true}
1769
+ });
1770
+ console.log(response.message.content);
1771
+ }
1772
+ ```
1773
+
1372
1774
  ## PreferenceStore.js
1373
1775
 
1374
1776
  ### Overview
@@ -1650,9 +2052,21 @@ Exact exports: `MAX_SPEECH_CHARACTERS`, `MAX_SPEECH_CHUNKS`, `MAX_SPEECH_INPUT`,
1650
2052
  ### Example
1651
2053
 
1652
2054
  ```javascript
1653
- import * as module from '/arcane/modules/SpeechPlayback.js';
2055
+ import SpeechPlayback from '/arcane/modules/SpeechPlayback.js';
1654
2056
 
1655
- console.log(Object.keys(module));
2057
+ const audio = document.body.appendChild(document.createElement('audio'));
2058
+ audio.controls = true;
2059
+ const speech = new SpeechPlayback({audio});
2060
+ const speakButton = document.body.appendChild(document.createElement('button'));
2061
+ speakButton.type = 'button';
2062
+ speakButton.textContent = 'Speak';
2063
+ speakButton.addEventListener('click', async () => {
2064
+ await speech.prepare({
2065
+ key: 'ready',
2066
+ parts: ['Arcane is ready.'],
2067
+ autoplay: true
2068
+ });
2069
+ });
1656
2070
  ```
1657
2071
 
1658
2072
  ## StaticDocumentCatalog.js