arcane-os 0.63.0 → 0.64.0
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 +22 -0
- package/README.md +1 -1
- package/browser-runtime/core/client.mjs +7 -2
- package/docs/reference/README.md +2 -0
- package/docs/reference/core-client.md +11 -2
- package/docs/reference/core-native-packaging.md +35 -6
- package/docs/reference/core-repositories.md +149 -0
- package/docs/reference/inventory/package-api.json +59 -3
- package/docs/reference/local-ai.md +5 -2
- package/docs/reference/local-image-flux.md +1 -1
- package/docs/reference/local-image-generation.md +6 -0
- package/docs/reference/local-image-sdxl.md +139 -0
- package/docs/reference/local-whisper.md +219 -0
- package/docs/reference/native-speech.md +51 -2
- package/docs/reference/runtime-modules.md +16 -2
- package/docs/reference/sdk-api.md +162 -2
- package/package.json +4 -1
- package/runtime/arcane/components/speech.html +15 -3
- package/runtime/arcane/components/voice-transcription.html +19 -3
- package/runtime/arcane/modules/AI.js +250 -27
- package/runtime/arcane/modules/AIProviderRuntime.js +20 -3
- package/runtime/arcane/modules/AIRuntimeState.js +15 -2
- package/runtime/arcane/modules/ComponentContracts.js +3 -0
- package/schemas/arcane-app.schema.json +53 -2
- package/schemas/arcane-package.schema.json +53 -2
- package/src/core/repositories.mjs +210 -0
- package/src/core/services/speech.mjs +1 -0
- package/src/local-ai/config.mjs +7 -3
- package/src/local-ai/dev.mjs +32 -2
- package/src/local-ai/image-library.mjs +9 -0
- package/src/local-ai/image-models/sdxl.mjs +18 -0
- package/src/local-ai/install.mjs +26 -7
- package/src/local-ai/whisper/build.mjs +48 -0
- package/src/local-ai/whisper/config.mjs +61 -0
- package/src/local-ai/whisper/index.mjs +515 -0
- package/src/local-ai/whisper/install.mjs +274 -0
- package/src/local-ai/whisper/native/CMakeLists.txt +98 -0
- package/src/local-ai/whisper/native/NOTICE +18 -0
- package/src/local-ai/whisper/native/README.md +104 -0
- package/src/local-ai/whisper/native/main.cpp +489 -0
- package/src/native/portable-layout.mjs +37 -7
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Native Whisper transcription
|
|
2
|
+
|
|
3
|
+
Arcane runs `whisper.cpp` through a persistent native helper. A selected model
|
|
4
|
+
stays loaded across recordings. The helper reports actual model readiness,
|
|
5
|
+
native progress and timestamped segments, and completes cancellation before
|
|
6
|
+
the model or recording files are reused. The browser's existing
|
|
7
|
+
`AI.fetchSTT(audioFile, signal)` and Core `speech.transcribe` routes retain the
|
|
8
|
+
application's selected model and cancellation signal.
|
|
9
|
+
|
|
10
|
+
## Select the runtime and model
|
|
11
|
+
|
|
12
|
+
An application's `arcane-app.json` selects native speech alongside its other
|
|
13
|
+
independent local runtimes:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"native": {
|
|
18
|
+
"localAI": {
|
|
19
|
+
"runtimes": [
|
|
20
|
+
{
|
|
21
|
+
"id": "whisper.cpp",
|
|
22
|
+
"backend": "auto",
|
|
23
|
+
"models": ["whisper-small"]
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The equivalent package configuration is `arcane-package.json`'s `localAI`.
|
|
32
|
+
Existing unrelated descriptor fields remain part of the application's file.
|
|
33
|
+
`models` defaults to an empty array, so selecting the runtime alone acquires no
|
|
34
|
+
model. A single selected model becomes `modelId`; multiple models require an
|
|
35
|
+
explicit selected `modelId` before transcription. The built-in `whisper-small`
|
|
36
|
+
descriptor selects the official `ggerganov/whisper.cpp` `ggml-small.bin` model.
|
|
37
|
+
An explicit model descriptor is `{id, path}` or `{id, url, filename?}`.
|
|
38
|
+
|
|
39
|
+
Default Windows x64 preparation acquires the selected complete upstream
|
|
40
|
+
Whisper 1.9.4/b5130 CUDA 12.4 and CPU distributions, FFmpeg 9.0.2 essentials,
|
|
41
|
+
the selected model, and Arcane's helper asset from the matching SDK numeric
|
|
42
|
+
release. The Windows helper includes its selected Visual C++ release runtime
|
|
43
|
+
DLLs beside the executable. Preparation shares matching installation jobs, observes cancellation,
|
|
44
|
+
and preserves the complete selected runtime trees. Model-selection changes
|
|
45
|
+
can reuse the already prepared runtime and matching models.
|
|
46
|
+
|
|
47
|
+
With `backend: "auto"`, accelerated runtimes are attempted before CPU. Failure
|
|
48
|
+
to load an accelerated runtime falls back to the prepared CPU runtime. If
|
|
49
|
+
native accelerated inference fails, Arcane drains that context and retries the
|
|
50
|
+
complete decoded recording once on CPU with the same model. Cancellation,
|
|
51
|
+
input errors and observer errors do not trigger this retry. Explicit `cuda`,
|
|
52
|
+
`metal` or `cpu` selections retain that chosen backend.
|
|
53
|
+
|
|
54
|
+
During that accepted request's internal recovery, the engine reports
|
|
55
|
+
`state: "recovering"`, `loaded: false`, and `busy: true`, with the same selected
|
|
56
|
+
provider and model. It accepts no new transcription while the old context
|
|
57
|
+
drains and the CPU context loads. Only actual CPU readiness restores `ready`.
|
|
58
|
+
An explicitly unloaded, replaced, or closed model remains a cancellation
|
|
59
|
+
boundary; internal recovery preserves the original request and its signal.
|
|
60
|
+
|
|
61
|
+
`requestedBackend` identifies a preference. `observedBackend` and
|
|
62
|
+
`backendEvidence` report native initialization evidence when available; they
|
|
63
|
+
do not claim that every computation runs on the GPU. No driver or compiler is
|
|
64
|
+
installed on application startup.
|
|
65
|
+
|
|
66
|
+
## Core and development integration
|
|
67
|
+
|
|
68
|
+
Development and generated native Core entries compose a
|
|
69
|
+
[`createSpeechService`](./native-speech.md) with the selected Whisper engine.
|
|
70
|
+
Speech preparation and model loading proceed independently of the page,
|
|
71
|
+
chat and image services. Core publishes retained `speech.state` and per-request
|
|
72
|
+
`speech.progress`; transcription availability follows the selected engine's
|
|
73
|
+
actual loaded state. The shared service remains usable when only STT is
|
|
74
|
+
selected, through `transcriptionAvailable` and `roles.stt`.
|
|
75
|
+
|
|
76
|
+
The native bundle includes the helper, runtime libraries, decoder and selected
|
|
77
|
+
model under its runtime directory beside the application executable. Those
|
|
78
|
+
resources stay in the application bundle. They are not embedded into one
|
|
79
|
+
Windows executable. The compiler and matching upstream headers are build-time
|
|
80
|
+
requirements for the first-party helper only.
|
|
81
|
+
|
|
82
|
+
Applications may continue using the existing Core call:
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
const transcript = await client.speech.transcribe({
|
|
86
|
+
audioBase64,
|
|
87
|
+
mimeType: 'audio/webm',
|
|
88
|
+
model: 'whisper-small'
|
|
89
|
+
}, {signal});
|
|
90
|
+
console.log(transcript.text);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
FFmpeg decodes the complete recorded media into mono 16 kHz PCM at the native
|
|
94
|
+
transport boundary. The original recording remains untouched during the
|
|
95
|
+
operation. Arcane imposes no recording-duration cap and does not trim the
|
|
96
|
+
transcript. The upstream inference API's signed-integer sample argument is
|
|
97
|
+
reported honestly if a recording cannot fit that native call; no audio is
|
|
98
|
+
silently shortened. Complete operation files are removed only after decoder
|
|
99
|
+
and native inference cleanup have completed.
|
|
100
|
+
|
|
101
|
+
## Public engine API
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
import {createWhisperRuntime} from 'arcane-os/local-ai/whisper';
|
|
105
|
+
|
|
106
|
+
const stt = createWhisperRuntime({
|
|
107
|
+
runtime: preparedRuntime,
|
|
108
|
+
modelId: 'whisper-small',
|
|
109
|
+
temporaryDirectory: applicationSpeechDirectory,
|
|
110
|
+
onEvent: reportDiagnostic
|
|
111
|
+
});
|
|
112
|
+
const unsubscribe = stt.subscribe(renderModelState);
|
|
113
|
+
await stt.load({signal});
|
|
114
|
+
try {
|
|
115
|
+
const result = await stt.transcribe({audioBase64, mimeType, model: 'whisper-small'}, {
|
|
116
|
+
signal,
|
|
117
|
+
onProgress: renderTranscriptionProgress
|
|
118
|
+
});
|
|
119
|
+
console.log(result.text, result.segments);
|
|
120
|
+
} finally {
|
|
121
|
+
unsubscribe();
|
|
122
|
+
await stt.close();
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`createWhisperRuntime(options)` performs no model load. `options.runtime` is a
|
|
127
|
+
prepared descriptor returned by `ensureLocalAIRuntimes`; alternatively,
|
|
128
|
+
`options.prepare({signal, onEvent})` lazily returns that descriptor during
|
|
129
|
+
`load`. `temporaryDirectory` is required and belongs to the application.
|
|
130
|
+
|
|
131
|
+
| Member | Contract |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| `current()` | Returns `providerId`, selected `modelId`, lifecycle `state`, `loaded`, `busy`, correlated `requestId` or null, requested/observed backend, evidence and any error. |
|
|
134
|
+
| `subscribe(listener, {replay = true, signal} = {})` | Immediately replays current state by default and returns an unsubscribe function. |
|
|
135
|
+
| `load({modelId, signal} = {})` | Prepares the runtime if necessary, then waits for actual native model readiness. With no selected model it remains unloaded. |
|
|
136
|
+
| `transcribe(request, {signal, onProgress, requestId} = {})` | Consumes the complete `audioBase64` recording. Optional `language` defaults to native automatic detection; `translate: true` selects Whisper's translation operation. `model` must match the loaded model. Optional `requestId` carries the caller's existing operation identity through lifecycle snapshots as separate control information; it does not alter the recording or request payload. |
|
|
137
|
+
| `unload()` | Cancels and joins active load/transcription, releases the native context, and permits a later explicit load. |
|
|
138
|
+
| `close()` | Joins shutdown and disposes this engine permanently. Repeated calls share the same shutdown. |
|
|
139
|
+
|
|
140
|
+
One retained model processes one recording at a time; a simultaneous request
|
|
141
|
+
receives `WHISPER_BUSY`. Model changes use `await stt.unload()` followed by
|
|
142
|
+
`stt.load({modelId})`. The `ready` state remains `loaded: true` with `busy: true`
|
|
143
|
+
during ordinary inference. Progress records include the native event plus
|
|
144
|
+
`backend` and `attempt`, so a CPU retry remains distinguishable. Segment text
|
|
145
|
+
is preserved in order. The final result is
|
|
146
|
+
`{text, language, duration, segments: [{index, start, end, text}]}`; times are
|
|
147
|
+
seconds and are native segment timestamps, not invented word timestamps.
|
|
148
|
+
|
|
149
|
+
Full process diagnostics stay with `onEvent` and native process failures.
|
|
150
|
+
Applications should use their selected inspection surface for those details
|
|
151
|
+
and keep ordinary user status concise.
|
|
152
|
+
An error snapshot retains the failed operation's `requestId` after `busy`
|
|
153
|
+
becomes false, allowing the caller to correlate its actual pending response.
|
|
154
|
+
A new request or model load supersedes that identity; unload and close clear
|
|
155
|
+
it. No recording or request payload is retained by this correlation field.
|
|
156
|
+
Progress callbacks may cancel through the supplied controller and return;
|
|
157
|
+
they must not await the same transcription or its shutdown from inside its
|
|
158
|
+
own output callback. Await those operations in the initiating owner instead.
|
|
159
|
+
|
|
160
|
+
## Explicit runtimes and helper builds
|
|
161
|
+
|
|
162
|
+
Windows, Linux and macOS host integration uses the same public engine and
|
|
163
|
+
portable helper source. Automatic archive selection currently provides the
|
|
164
|
+
Windows x64 distribution above. Other targets supply their complete selected
|
|
165
|
+
runtime, decoder and compiled helper paths; their native execution requires
|
|
166
|
+
verification on that actual platform. Android requires a host adapter for
|
|
167
|
+
its process and library lifecycle.
|
|
168
|
+
|
|
169
|
+
An explicit runtime requirement can supply:
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
{
|
|
173
|
+
id: 'whisper.cpp',
|
|
174
|
+
version: '1.9.4',
|
|
175
|
+
backend: 'auto',
|
|
176
|
+
variants: [
|
|
177
|
+
{backend: 'cuda', root: cudaRuntimeDirectory, libraryDirectory: cudaLibraryDirectory},
|
|
178
|
+
{backend: 'cpu', root: cpuRuntimeDirectory, libraryDirectory: cpuLibraryDirectory}
|
|
179
|
+
],
|
|
180
|
+
helperExecutable,
|
|
181
|
+
helperRoot,
|
|
182
|
+
decoderExecutable,
|
|
183
|
+
decoderRoot,
|
|
184
|
+
models: [{id: 'whisper-small', path: selectedModelPath}],
|
|
185
|
+
modelId: 'whisper-small'
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`libraryDirectory` belongs to its complete variant root. `decoderExecutable`
|
|
190
|
+
belongs to `decoderRoot` (which defaults to its containing directory).
|
|
191
|
+
`helperExecutable` similarly belongs to `helperRoot`; its complete directory
|
|
192
|
+
tree is retained, including app-local native dependencies.
|
|
193
|
+
Preparation copies those selected resources into its owned installation, and
|
|
194
|
+
native packaging projects their paths relative to the application bundle.
|
|
195
|
+
`helperUrl`, `helperVersion`, and `decoderUrl` allow an explicitly selected
|
|
196
|
+
published distribution instead of local paths. Dependency permissions remain
|
|
197
|
+
the caller's responsibility.
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
import {buildWhisperHelper} from 'arcane-os/local-ai/whisper/build';
|
|
201
|
+
|
|
202
|
+
const helper = await buildWhisperHelper({
|
|
203
|
+
runtime: {directory: runtimeLibraryDirectory, sourceDirectory: matchingUpstreamSource},
|
|
204
|
+
outputRoot: helperBuildDirectory,
|
|
205
|
+
signal,
|
|
206
|
+
onEvent: reportDiagnostic
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The builder runs CMake configure/build/install against the selected matching
|
|
211
|
+
public upstream headers and runtime. Optional `cmake`, `generator`,
|
|
212
|
+
`cmakeArgs` and `env` select the existing toolchain. It returns the helper
|
|
213
|
+
`executable`, platform, architecture, runtime directory and complete build
|
|
214
|
+
diagnostics. The helper's documented process contract is in
|
|
215
|
+
[`native/README.md`](../../src/local-ai/whisper/native/README.md).
|
|
216
|
+
On MSVC, CMake's installed runtime-library module adds release CRT files to
|
|
217
|
+
the helper's `bin/` directory. `-DMSVC_REDIST_DIR=...` selects an existing
|
|
218
|
+
redistributable directory when needed. Windows provides the Universal CRT;
|
|
219
|
+
compiler tools and debug runtimes stay outside the application bundle.
|
|
@@ -82,8 +82,9 @@ Each supplied engine implements:
|
|
|
82
82
|
function.
|
|
83
83
|
- `load({signal})`, resolving only after the constructor-selected model has
|
|
84
84
|
loaded. Independent engines may load concurrently.
|
|
85
|
-
- `transcribe(request, {signal, onProgress})` for STT, or
|
|
86
|
-
`synthesize(request, {signal, onProgress})` for TTS.
|
|
85
|
+
- `transcribe(request, {signal, onProgress, requestId})` for STT, or
|
|
86
|
+
`synthesize(request, {signal, onProgress, requestId})` for TTS. `requestId` is
|
|
87
|
+
the actual Core request's control metadata, separate from the unchanged payload.
|
|
87
88
|
- `close()`, cancelling and joining its native work and releasing its resources.
|
|
88
89
|
|
|
89
90
|
The service forwards each complete request and result unchanged. It applies
|
|
@@ -112,3 +113,51 @@ share one shutdown promise. Actual engine cleanup failures reject with an
|
|
|
112
113
|
`AggregateError` and remain in role diagnostics. `dispose()` additionally
|
|
113
114
|
releases the service's event subscriptions. New work after shutdown begins
|
|
114
115
|
rejects with `CORE_CLOSING`.
|
|
116
|
+
|
|
117
|
+
## AI built-in native speech readiness
|
|
118
|
+
|
|
119
|
+
The `AI` module's `LOCAL_SPEACH` adapter observes `speech.state` before reading
|
|
120
|
+
`Arcane.speech.status()`. A newer event takes precedence over an outstanding
|
|
121
|
+
initial status response. Each role loads independently: STT does not wait for
|
|
122
|
+
TTS, and unmuting waits for the selected TTS model rather than a callable facade.
|
|
123
|
+
|
|
124
|
+
For this service's role snapshots, readiness requires the exact selected
|
|
125
|
+
`modelId`, its engine identity, `loaded: true`, `available: true`, and a `ready`
|
|
126
|
+
or `running` state. Model loading remains an ordinary cancellable waiting state.
|
|
127
|
+
An engine error, unavailable role, or different selected model rejects the
|
|
128
|
+
adapter load. Later readiness loss or engine/model replacement invalidates the
|
|
129
|
+
AI role and cancels its queued and active requests through the existing provider
|
|
130
|
+
runtime. A new explicit role load or unmute can observe the next ready state.
|
|
131
|
+
|
|
132
|
+
An engine can report `state: 'recovering'`, `loaded: false`, `busy: true`, and
|
|
133
|
+
the continuing Core `requestId` while retrying an already accepted request on
|
|
134
|
+
the same selected provider and model. Availability remains false. The built-in
|
|
135
|
+
STT adapter preserves only its own correlated pending request through this
|
|
136
|
+
state; another caller's recovery supplies no continuation authority. The real
|
|
137
|
+
RPC ID comes from `Arcane.speech.transcribe`'s optional `onRequest` observer.
|
|
138
|
+
The engine retains that ID on a terminal error snapshot until the snapshot is
|
|
139
|
+
superseded, allowing the pending RPC to deliver its actual complete failure
|
|
140
|
+
rather than converting it into cancellation. True unload, close, replacement,
|
|
141
|
+
transport loss and caller cancellation retain their existing ownership.
|
|
142
|
+
|
|
143
|
+
The shared AI observation becomes `recovering` with `loaded: false`, `busy:
|
|
144
|
+
true`, and its existing runtime `operationId`. An error may likewise retain
|
|
145
|
+
`busy: true` until the actual operation settles. New inference still requires
|
|
146
|
+
genuine loaded readiness. The STT provider request context includes a
|
|
147
|
+
`refreshState()` callback which rereads that provider's status only while the
|
|
148
|
+
exact request remains owned; it changes observation, not execution authority.
|
|
149
|
+
Shared speech components retain their own real runtime operation through this
|
|
150
|
+
recovery, keep new dispatch readiness-gated, and preserve native microphone
|
|
151
|
+
capture's separate cancellation and final/interim lifetime.
|
|
152
|
+
|
|
153
|
+
Older fixed-model hosts expose the published aggregate-only `SpeechStatus`
|
|
154
|
+
contract instead of per-model lifecycle. The adapter preserves their independent
|
|
155
|
+
health-based readiness for `whisper-small` with `whisper.cpp` and `kokoro` with
|
|
156
|
+
`kokoro-onnx`, using `status: 'ok'` and the corresponding availability boolean.
|
|
157
|
+
That contract is a one-shot health observation, not evidence of a persistently
|
|
158
|
+
loaded native model or a continuous lifecycle event stream.
|
|
159
|
+
|
|
160
|
+
Cancellation, unload, disposal, and route retirement release adapter-owned
|
|
161
|
+
subscriptions. They do not close the host's shared engines or alter complete
|
|
162
|
+
speech requests and results. Browser and explicitly registered speech providers
|
|
163
|
+
retain their existing lifecycle contracts.
|
|
@@ -1121,6 +1121,14 @@ work. Promise settlement proves only that the provider's exposed request promise
|
|
|
1121
1121
|
completed; provider-specific cancellation acknowledgement remains the selected
|
|
1122
1122
|
provider's boundary.
|
|
1123
1123
|
|
|
1124
|
+
An STT provider's request context also supplies `refreshState()`. An engine
|
|
1125
|
+
lifecycle observer may call it to reread `provider.status()` while that exact
|
|
1126
|
+
request remains active. A recovering or failed pending request is observed as
|
|
1127
|
+
`loaded: false`, `busy: true`, with its existing runtime operation ID; the
|
|
1128
|
+
callback does not make a new request ready or change its supplied payload.
|
|
1129
|
+
After cancellation, unload, disposal, replacement, or settlement it has no
|
|
1130
|
+
effect on the retired request.
|
|
1131
|
+
|
|
1124
1132
|
Direct LLM `request()`, `chat()`, and `stream()` use the same message history,
|
|
1125
1133
|
tool-declaration, emitted-call, all-choice, and ordered parallel-call contracts
|
|
1126
1134
|
as the high-level AI API module, including one nonblank matching result for
|
|
@@ -1229,13 +1237,17 @@ observable without exposing provider transports in application code.
|
|
|
1229
1237
|
|
|
1230
1238
|
Exact exports: `AI_RUNTIME_INTENT_EVENT`, `AI_RUNTIME_PROTOCOL`,
|
|
1231
1239
|
`AI_RUNTIME_ROLES`, `AI_RUNTIME_STARTUP_EVENT`, `AI_RUNTIME_STATES`,
|
|
1232
|
-
`AI_RUNTIME_STATE_EVENT`, `getAIRuntimeState`,
|
|
1240
|
+
`AI_RUNTIME_STATE_EVENT`, `continuesAIRuntimeOperation`, `getAIRuntimeState`,
|
|
1233
1241
|
`publishAIRuntimeRoleState`, `publishAIRuntimeRolesState`,
|
|
1234
1242
|
`requestAIRuntimeIntent`, `startAIRuntime`, `subscribeAIRuntimeIntents`, and
|
|
1235
1243
|
`subscribeAIRuntimeState`.
|
|
1236
1244
|
|
|
1237
1245
|
Each role record is exactly `{role,state,providerId,modelId,localOnly,loaded,
|
|
1238
1246
|
busy,operationId,progress,error}`.
|
|
1247
|
+
`continuesAIRuntimeOperation(previous,current,operationId)` identifies the same
|
|
1248
|
+
already-owned provider/model/runtime operation in a busy, unloaded `recovering`
|
|
1249
|
+
or `error` observation. Pass the actual runtime operation ID, not a component's
|
|
1250
|
+
local counter. It grants neither readiness nor permission to start new work.
|
|
1239
1251
|
`subscribeAIRuntimeState(listener,{signal=null,emitCurrent=true})` installs its
|
|
1240
1252
|
subscription and synchronously replays the current mutable snapshot by default;
|
|
1241
1253
|
`subscribeAIRuntimeIntents(listener,{signal=null})` is future-only. Both return
|
|
@@ -1251,7 +1263,9 @@ transcription-model load.
|
|
|
1251
1263
|
### Availability and normalization
|
|
1252
1264
|
|
|
1253
1265
|
**Cross-host state contract.** States are `unavailable`, `unloaded`, `loading`,
|
|
1254
|
-
`ready`, `unloading`, `error`, and `disposed`.
|
|
1266
|
+
`recovering`, `ready`, `unloading`, `error`, and `disposed`. Recovery remains
|
|
1267
|
+
`loaded: false`; `recovering` and `error` may retain `busy: true` with an actual
|
|
1268
|
+
`operationId` until the pending request settles. Revisions increase monotonically.
|
|
1255
1269
|
The events `arcane-ai-runtime-state`, `arcane-ai-runtime-intent`, and
|
|
1256
1270
|
`arcane-ai-runtime-startup-settled` normalize observation only: receiving one
|
|
1257
1271
|
does not grant a native capability, prove browser support, or load a provider.
|
|
@@ -57,6 +57,7 @@ runtime layouts.
|
|
|
57
57
|
| `arcane-os/core/runtime` | App-neutral native Core dispatcher and service lifecycle. |
|
|
58
58
|
| `arcane-os/core/stdio` | Framed Node stdio transport with graceful runtime drain. |
|
|
59
59
|
| `arcane-os/core/host` | Node-only reusable Core host lifecycle with explicitly supplied services. |
|
|
60
|
+
| `arcane-os/core/repositories` | Native persistent ArcaneData paths and explicitly selected connected working repositories. |
|
|
60
61
|
| `arcane-os/core/packaged-web` | Core-owned packaged web listener with a persisted loopback origin and ready/failure events. |
|
|
61
62
|
| `arcane-os/core/preferences` | Existing preference RPC methods with an application-selected file, ordered writes and owned drain. |
|
|
62
63
|
| `arcane-os/core/diarization` | Lazy native speaker-diarization service, explicit stream methods and lifecycle/result events. |
|
|
@@ -69,6 +70,7 @@ runtime layouts.
|
|
|
69
70
|
| `arcane-os/local-ai/image` | Retained native stable-diffusion.cpp context, complete PNG generation and owned worker lifetime. |
|
|
70
71
|
| `arcane-os/ai/core-image` | Browser image lifecycle and complete PNG Blobs through an available Core service. |
|
|
71
72
|
| `arcane-os/core/image` | Native image Core service with independent runtime preparation and retained model ownership. |
|
|
73
|
+
| `arcane-os/core/speech` | Independent injected STT/TTS engine loading, request cancellation and joined Core service lifetime. |
|
|
72
74
|
| `arcane-os/ai/core-model-assets` | Complete stored browser model members projected through Core into native working files. |
|
|
73
75
|
| `arcane-os/core/model-assets` | Native working-file preparation, engine retain handles and joined cleanup. |
|
|
74
76
|
| `arcane-os/integrated-provider` | Fixed integrated shared-development provider. |
|
|
@@ -391,6 +393,8 @@ The remaining data-only subpaths are eight JSON Schemas and package metadata.
|
|
|
391
393
|
| `arcaneNativeBuilderProvider default export` | singleton | `arcane-os/native/portable-provider` | Portable native provider | Node; produces a portable payload rather than an executable host |
|
|
392
394
|
| `startCoreHost()` | function | `arcane-os/core/host` | Native Core host composition | Node with readable input and writable output streams |
|
|
393
395
|
| `readCoreLaunchContext()` | function | `arcane-os/core/host` | Native Core host composition | Node; reads explicitly selected launch JSON |
|
|
396
|
+
| `resolveArcaneDataPaths()` | function | `arcane-os/core/repositories` | Native repository workspaces | Node on Windows, Linux and macOS; Android supplies its data root |
|
|
397
|
+
| `createRepositoryWorkspace()` | function | `arcane-os/core/repositories` | Native repository workspaces | Node with Git; Android requires host process adaptation |
|
|
394
398
|
| `createWindowsNativeProvider()` | function | `arcane-os/native/windows-provider` | Windows native provider | Node assembly; Windows x64/WebView2 execution |
|
|
395
399
|
| `arcaneNativeBuilderProvider` | singleton | `arcane-os/native/windows-provider` | Windows native provider | Node assembly; Windows x64/WebView2 execution |
|
|
396
400
|
| `arcaneNativeBuilderProvider default export` | singleton | `arcane-os/native/windows-provider` | Windows native provider | Node assembly; Windows x64/WebView2 execution |
|
|
@@ -412,6 +416,8 @@ The remaining data-only subpaths are eight JSON Schemas and package metadata.
|
|
|
412
416
|
| `createCoreImageRuntime()` | function | `arcane-os/ai/core-image` | Core local images | Browser with an available Core image service |
|
|
413
417
|
| `createLocalImageService()` | function | `arcane-os/core/image` | Core local images | Node Core with selected stable-diffusion.cpp requirements |
|
|
414
418
|
| `createLocalImageService default export` | function | `arcane-os/core/image` | Core local images | Same native image service factory |
|
|
419
|
+
| `createSpeechService()` | function | `arcane-os/core/speech` | Core native speech | Node Core with explicitly supplied speech engines |
|
|
420
|
+
| `createSpeechService default export` | function | `arcane-os/core/speech` | Core native speech | Same shared speech service factory |
|
|
415
421
|
| `prepareCoreModelAssets()` | function | `arcane-os/ai/core-model-assets` | Core model assets | Browser with complete Blob/File members and available Core service |
|
|
416
422
|
| `createModelAssetService()` | function | `arcane-os/core/model-assets` | Core model assets | Node Core with an application-selected working directory |
|
|
417
423
|
| `createModelAssetService default export` | function | `arcane-os/core/model-assets` | Core model assets | Same native working-file service factory |
|
|
@@ -7421,12 +7427,23 @@ application builds. The build machine needs its normal Node/npm toolchain and
|
|
|
7421
7427
|
The returned `arcane-native-builder/1` provider exposes `describe`, `doctor`,
|
|
7422
7428
|
`prepare`, `build`, `verify` and `run`. Build returns `{app,target,manifest}`
|
|
7423
7429
|
with the actual directory at `target.rootDir`. Verification reads the artifact;
|
|
7424
|
-
it does not launch the window. Run launches
|
|
7425
|
-
|
|
7430
|
+
it does not launch the window. Run launches the executable recorded in the
|
|
7431
|
+
saved `manifest.host.executable` and observes complete diagnostics and exit.
|
|
7432
|
+
Cancellation closes input and waits for host-owned drain,
|
|
7426
7433
|
including accepted service work, without a forced termination deadline. Missing
|
|
7427
7434
|
prerequisites, download/extraction failures and host failures remain observable
|
|
7428
7435
|
through the operation's error/event boundary.
|
|
7429
7436
|
|
|
7437
|
+
Assembly names the launcher from `appDescriptor.displayName` plus `.exe`,
|
|
7438
|
+
with a matching `.exe.config` file. Low-level compositions without a nonempty
|
|
7439
|
+
string display name use the app ID. Windows-reserved filename characters and
|
|
7440
|
+
control characters become underscores; reserved DOS device basenames receive
|
|
7441
|
+
an underscore prefix. Supported Unicode, spaces and case remain, as do the
|
|
7442
|
+
original descriptor and title. Icon embedding, file inventory, verification
|
|
7443
|
+
and run use this same name. Existing artifacts retain the executable recorded
|
|
7444
|
+
in their manifest; the reusable downloaded host retains `Arcane.exe` and
|
|
7445
|
+
`Arcane.exe.config`, and `runtime/ArcaneCore.exe` remains unchanged.
|
|
7446
|
+
|
|
7430
7447
|
During assembly, `appDescriptor.native.icon` selects the app's existing image.
|
|
7431
7448
|
PNG and ICO inputs produce a derived `runtime/arcane-app.ico` and update only
|
|
7432
7449
|
the copied application's executable icon. Source assets, the selected host
|
|
@@ -7676,6 +7693,63 @@ separate argument; other composing hosts may use the same seam. This JavaScript
|
|
|
7676
7693
|
contract does not establish a compiled host or matching release archive. See
|
|
7677
7694
|
[launch-time locations](core-native-packaging.md#launch-time-locations).
|
|
7678
7695
|
|
|
7696
|
+
## resolveArcaneDataPaths()
|
|
7697
|
+
|
|
7698
|
+
### Overview
|
|
7699
|
+
|
|
7700
|
+
Resolves the native per-user `ArcaneData` directory and its `Repos` child without
|
|
7701
|
+
creating directories or changing application state.
|
|
7702
|
+
|
|
7703
|
+
### Signature and result
|
|
7704
|
+
|
|
7705
|
+
```text
|
|
7706
|
+
resolveArcaneDataPaths({dataRoot}={})
|
|
7707
|
+
```
|
|
7708
|
+
|
|
7709
|
+
Returns absolute `{dataRoot,repositoriesRoot}`. Defaults use Windows LocalAppData,
|
|
7710
|
+
Linux XDG data home or `~/.local/share`, and macOS user Application Support.
|
|
7711
|
+
Android and other adapted hosts supply their persistent `dataRoot`. An explicit
|
|
7712
|
+
root is used directly, with relative paths resolved from the process working
|
|
7713
|
+
directory. See [native repository locations](core-repositories.md#persistent-locations).
|
|
7714
|
+
|
|
7715
|
+
```javascript
|
|
7716
|
+
import {resolveArcaneDataPaths} from 'arcane-os/core/repositories';
|
|
7717
|
+
|
|
7718
|
+
const {repositoriesRoot} = resolveArcaneDataPaths();
|
|
7719
|
+
```
|
|
7720
|
+
|
|
7721
|
+
## createRepositoryWorkspace()
|
|
7722
|
+
|
|
7723
|
+
### Overview
|
|
7724
|
+
|
|
7725
|
+
Owns one native connected working checkout under `ArcaneData/Repos/<name>` or an
|
|
7726
|
+
explicit existing path. The application owns the remote, branch and Core API.
|
|
7727
|
+
|
|
7728
|
+
### Signature and result
|
|
7729
|
+
|
|
7730
|
+
```text
|
|
7731
|
+
createRepositoryWorkspace({name,directory,dataRoot,remote,branch,onEvent,run=runProcess}={})
|
|
7732
|
+
```
|
|
7733
|
+
|
|
7734
|
+
Returns `{directory,open,status,pull,push,close,drain,dispose}`. Construction does
|
|
7735
|
+
no I/O. Operations accept `{signal}`; a missing or empty destination clones once,
|
|
7736
|
+
while existing checkout files, branches, remotes and local changes remain intact.
|
|
7737
|
+
Same-directory calls are ordered within the process. Shutdown drains accepted
|
|
7738
|
+
work and preserves the checkout. No CLI cwd, launch state, preference, snapshot
|
|
7739
|
+
cache or existing user-data location changes. See the [complete inputs, results,
|
|
7740
|
+
errors and lifecycle](core-repositories.md#one-connected-working-checkout).
|
|
7741
|
+
|
|
7742
|
+
```javascript
|
|
7743
|
+
import {createRepositoryWorkspace} from 'arcane-os/core/repositories';
|
|
7744
|
+
|
|
7745
|
+
const repository = createRepositoryWorkspace({
|
|
7746
|
+
name: 'moon-cheese-dispatches',
|
|
7747
|
+
remote: applicationConnection.remote
|
|
7748
|
+
});
|
|
7749
|
+
await repository.open({signal});
|
|
7750
|
+
await repository.close();
|
|
7751
|
+
```
|
|
7752
|
+
|
|
7679
7753
|
## createCoreLocalAIProvider()
|
|
7680
7754
|
|
|
7681
7755
|
### Overview
|
|
@@ -7881,6 +7955,92 @@ selected model and native engine.
|
|
|
7881
7955
|
The default export from `arcane-os/core/image` is the same
|
|
7882
7956
|
`createLocalImageService` factory and lifecycle described above.
|
|
7883
7957
|
|
|
7958
|
+
## createSpeechService()
|
|
7959
|
+
|
|
7960
|
+
### Overview and usage
|
|
7961
|
+
|
|
7962
|
+
`createSpeechService({stt,tts,signal}={})` from `arcane-os/core/speech` composes
|
|
7963
|
+
independently selected STT and TTS engines as one Core service. Supply either
|
|
7964
|
+
role or both. Construction downloads, installs and loads nothing; the host
|
|
7965
|
+
owns engine creation, model/device selection and supported formats.
|
|
7966
|
+
|
|
7967
|
+
```javascript
|
|
7968
|
+
import {createCoreRuntime} from 'arcane-os/core/runtime';
|
|
7969
|
+
import {createSpeechService} from 'arcane-os/core/speech';
|
|
7970
|
+
|
|
7971
|
+
// transcriptionEngine is the host's already constructed, selected engine.
|
|
7972
|
+
const speech = createSpeechService({stt: transcriptionEngine});
|
|
7973
|
+
const core = createCoreRuntime({services: [speech]});
|
|
7974
|
+
core.start();
|
|
7975
|
+
```
|
|
7976
|
+
|
|
7977
|
+
Each supplied engine exposes `current()`, replaying `subscribe(listener)`,
|
|
7978
|
+
`load({signal})`, `close()` and its role's
|
|
7979
|
+
`transcribe(request,{signal,onProgress})` or
|
|
7980
|
+
`synthesize(request,{signal,onProgress})`. The service forwards complete
|
|
7981
|
+
requests and results unchanged. Format decoding, model processing and encoding
|
|
7982
|
+
remain with that engine. This factory supplies no speech engine or automatic
|
|
7983
|
+
browser/native provider selection.
|
|
7984
|
+
|
|
7985
|
+
### Members, results and events
|
|
7986
|
+
|
|
7987
|
+
The returned object has `name: 'speech'`, `current`, `subscribe`, `close`,
|
|
7988
|
+
`start`, `methods`, `drain` and `dispose`:
|
|
7989
|
+
|
|
7990
|
+
- `start(context)` subscribes and starts both configured engine loads
|
|
7991
|
+
independently, returning immediately. Core and status remain responsive
|
|
7992
|
+
during loading; one role's failure leaves the other usable.
|
|
7993
|
+
- `current()` and `methods['speech.status']()` return the same snapshot:
|
|
7994
|
+
`ready`, `transcriptionAvailable`, `synthesisAvailable`, `status`,
|
|
7995
|
+
`sttEngine`, `ttsEngine`, `roles.stt`, `roles.tts` and `closed`.
|
|
7996
|
+
`ready` requires both roles; each independent availability flag requires
|
|
7997
|
+
its model loaded in `ready` or `running` state while the service accepts work.
|
|
7998
|
+
`status` is `created`, `ok`, `closing` or `closed`; per-role state carries
|
|
7999
|
+
model loading, progress and complete errors. An omitted role is unavailable.
|
|
8000
|
+
- `subscribe(listener,{replay=true,signal}={})` replays current state by
|
|
8001
|
+
default and returns an unsubscribe function. Its signal owns only that
|
|
8002
|
+
subscription; a closed service can replay its final snapshot.
|
|
8003
|
+
- `methods['speech.transcribe'](request,context)` and
|
|
8004
|
+
`methods['speech.synthesize'](request,context)` are registered as the matching
|
|
8005
|
+
Core RPC methods. The context supplies `signal`, `requestId` and `emit`.
|
|
8006
|
+
The service publishes synchronous accepted `Thinking` before waiting for
|
|
8007
|
+
its role's load, then forwards the engine's complete progress and result.
|
|
8008
|
+
- `close()` and its `drain()` alias stop acceptance, abort the service lifetime,
|
|
8009
|
+
close both engines concurrently and await startup and request work. Repeated
|
|
8010
|
+
calls share one promise. `dispose()` also releases event subscriptions.
|
|
8011
|
+
|
|
8012
|
+
`speech.state` carries the complete snapshot. `speech.progress` carries
|
|
8013
|
+
`{requestId,role,status:'Thinking',progress}` with initial
|
|
8014
|
+
`progress:{phase:'accepted'}`. The service's subscription replays state; Core's
|
|
8015
|
+
generic runtime replay does not replay this custom event. Browser consumers
|
|
8016
|
+
subscribe before requesting `Arcane.speech.status()` and preserve any newer
|
|
8017
|
+
state event received while that request is pending.
|
|
8018
|
+
|
|
8019
|
+
### Cancellation, errors and availability
|
|
8020
|
+
|
|
8021
|
+
The optional factory signal owns the service lifetime. A request signal is
|
|
8022
|
+
combined with that lifetime and reaches its selected engine. Cancelling one
|
|
8023
|
+
request waiting for a shared load settles that request without cancelling
|
|
8024
|
+
the other requests or role. No later cancelled progress or result is delivered.
|
|
8025
|
+
Actual queued/running native interruption belongs to the engine; it must settle
|
|
8026
|
+
before Core reaches service shutdown, because Core first cancels and awaits
|
|
8027
|
+
active requests.
|
|
8028
|
+
|
|
8029
|
+
Invalid engine interfaces or listeners throw `TypeError`. Missing roles reject
|
|
8030
|
+
their operation with `SPEECH_ENGINE_UNAVAILABLE`; work after shutdown begins
|
|
8031
|
+
rejects with `CORE_CLOSING`. Engine errors remain observable, and engine cleanup
|
|
8032
|
+
failures reject shutdown with `AggregateError`. Asynchronous listener failures
|
|
8033
|
+
reach developer diagnostics. Engine platform support remains its own contract;
|
|
8034
|
+
this Node composition layer adds no engine dependency. Existing AI readiness
|
|
8035
|
+
consumers recognize `whisper.cpp` and `kokoro-onnx` IDs independently of the
|
|
8036
|
+
service's engine-neutral interface. See the [complete engine and lifetime
|
|
8037
|
+
contract](native-speech.md).
|
|
8038
|
+
|
|
8039
|
+
## createSpeechService default export
|
|
8040
|
+
|
|
8041
|
+
The default export from `arcane-os/core/speech` is the same
|
|
8042
|
+
`createSpeechService` factory, with identical inputs, results and lifecycle.
|
|
8043
|
+
|
|
7884
8044
|
## prepareCoreModelAssets()
|
|
7885
8045
|
|
|
7886
8046
|
`prepareCoreModelAssets({client=getInstalledCoreClient(),workingDirectory,members,signal?,onProgress?})`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcane-os",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.64.0",
|
|
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",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"./core/runtime": "./src/core/runtime.mjs",
|
|
62
62
|
"./core/stdio": "./src/core/stdio.mjs",
|
|
63
63
|
"./core/host": "./src/core/host.mjs",
|
|
64
|
+
"./core/repositories": "./src/core/repositories.mjs",
|
|
64
65
|
"./core/packaged-web": "./src/core/services/packaged-web.mjs",
|
|
65
66
|
"./core/preferences": "./src/core/services/preferences.mjs",
|
|
66
67
|
"./core/diarization": "./src/diarization/service.mjs",
|
|
@@ -73,6 +74,8 @@
|
|
|
73
74
|
"./local-ai": "./src/local-ai/install.mjs",
|
|
74
75
|
"./local-ai/onnx": "./src/local-ai/onnx.mjs",
|
|
75
76
|
"./local-ai/image": "./src/local-ai/image-runtime.mjs",
|
|
77
|
+
"./local-ai/whisper": "./src/local-ai/whisper/index.mjs",
|
|
78
|
+
"./local-ai/whisper/build": "./src/local-ai/whisper/build.mjs",
|
|
76
79
|
"./integrated-provider": "./src/integrated-provider-loader.mjs",
|
|
77
80
|
"./packager": "./src/packager/core.mjs",
|
|
78
81
|
"./release-bundle": "./src/release-bundle.mjs",
|
|
@@ -295,6 +295,7 @@
|
|
|
295
295
|
const aiModuleImport = import('../modules/AI.js');
|
|
296
296
|
const providerRuntimeImport = import('../modules/AIProviderRuntime.js');
|
|
297
297
|
const {
|
|
298
|
+
continuesAIRuntimeOperation,
|
|
298
299
|
requestAIRuntimeIntent,
|
|
299
300
|
subscribeAIRuntimeState
|
|
300
301
|
} = await import('../modules/AIRuntimeState.js');
|
|
@@ -366,6 +367,7 @@
|
|
|
366
367
|
let ttsIntentGeneration = 0;
|
|
367
368
|
let eventOperationSequence = 0;
|
|
368
369
|
let transcriptionOperationId = null;
|
|
370
|
+
let transcriptionRuntimeOperationId = null;
|
|
369
371
|
let ttsOperationId = null;
|
|
370
372
|
let destroyed = false;
|
|
371
373
|
let recognitionLanguage = is.string(host.recognitionLanguage)
|
|
@@ -826,8 +828,10 @@
|
|
|
826
828
|
|
|
827
829
|
function applyRoleTransitions(previousSTTRole, previousTTSRole) {
|
|
828
830
|
sttActivationController?.synchronize(sttRole);
|
|
829
|
-
const
|
|
830
|
-
&& sttRole
|
|
831
|
+
const continuingTranscription = transcriptionActive && !nativeCaptureSession
|
|
832
|
+
&& continuesAIRuntimeOperation(previousSTTRole, sttRole, transcriptionRuntimeOperationId);
|
|
833
|
+
const sttBecameUnready = ['ready', 'recovering'].includes(previousSTTRole.state)
|
|
834
|
+
&& sttRole.state !== 'ready' && !continuingTranscription;
|
|
831
835
|
const ttsBecameUnready = previousTTSRole.state === 'ready'
|
|
832
836
|
&& ttsRole.state !== 'ready';
|
|
833
837
|
|
|
@@ -2011,10 +2015,16 @@
|
|
|
2011
2015
|
throw error;
|
|
2012
2016
|
}
|
|
2013
2017
|
|
|
2014
|
-
const
|
|
2018
|
+
const previousRuntimeOperationId = providerRuntime.status('stt').operationId;
|
|
2019
|
+
const pendingTranscription = aiRuntime.fetchSTT(
|
|
2015
2020
|
audioFile,
|
|
2016
2021
|
controller.signal
|
|
2017
2022
|
);
|
|
2023
|
+
const activeRole = providerRuntime.status('stt');
|
|
2024
|
+
transcriptionRuntimeOperationId = activeRole.busy
|
|
2025
|
+
&& activeRole.operationId !== previousRuntimeOperationId
|
|
2026
|
+
? activeRole.operationId : null;
|
|
2027
|
+
const transcription = await pendingTranscription;
|
|
2018
2028
|
if (
|
|
2019
2029
|
generation !== transcriptionGeneration
|
|
2020
2030
|
|| sttRole.state !== 'ready'
|
|
@@ -2044,6 +2054,7 @@
|
|
|
2044
2054
|
} finally {
|
|
2045
2055
|
if (transcriptionAbortController === controller) {
|
|
2046
2056
|
transcriptionAbortController = null;
|
|
2057
|
+
transcriptionRuntimeOperationId = null;
|
|
2047
2058
|
}
|
|
2048
2059
|
if (generation === transcriptionGeneration) {
|
|
2049
2060
|
transcriptionActive = false;
|
|
@@ -2144,6 +2155,7 @@
|
|
|
2144
2155
|
transcriptionGeneration += 1;
|
|
2145
2156
|
transcriptionAbortController?.abort();
|
|
2146
2157
|
transcriptionAbortController = null;
|
|
2158
|
+
transcriptionRuntimeOperationId = null;
|
|
2147
2159
|
recordingRequested = false;
|
|
2148
2160
|
transcriptionActive = false;
|
|
2149
2161
|
clearSilenceTimer();
|