arcane-os 0.5.18 → 0.5.19
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 +12 -3
- package/README.md +8 -8
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +31 -11
- package/browser-runtime/ai/browser-wllama-runtime.mjs +78 -0
- package/docs/architecture.md +1 -1
- package/docs/publishing.md +10 -4
- package/docs/reference/ai/browser-wasm.md +29 -6
- package/package.json +1 -1
- package/runtime/arcane/components/chat.html +53 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.5.19
|
|
4
|
+
|
|
5
|
+
- Report observed Wllama initialization stages and runtime activity through
|
|
6
|
+
direct provider loading and the shared AI lifecycle. Keep initialization
|
|
7
|
+
indeterminate when the runtime supplies no meaningful completion total.
|
|
8
|
+
- Show the current initialization stage, stage duration, and time since runtime
|
|
9
|
+
activity in shared chat instead of a completed file count during activation.
|
|
10
|
+
Preserve download progress, cancellation, and complete runtime logging.
|
|
11
|
+
- Remove an adjacent duplicate cancellation check while retaining cancellation
|
|
12
|
+
handling before initialization and after loading.
|
|
13
|
+
|
|
14
|
+
## 0.5.18
|
|
4
15
|
|
|
5
16
|
- Use `strong-type` predicates throughout SDK-owned toolchain, runtime,
|
|
6
17
|
browser-provider, and component code while retaining existing defaults,
|
|
@@ -12,8 +23,6 @@
|
|
|
12
23
|
- Remove `AIResponseLength` exports; applications own response verbosity.
|
|
13
24
|
Parse URL-audit HTML with the native HTML parser.
|
|
14
25
|
|
|
15
|
-
## 0.5.18
|
|
16
|
-
|
|
17
26
|
- Add `AI.prepareTTS()` for detached punctuation-segmented synthesis, optional
|
|
18
27
|
DBOPFS audio storage, complete semantic-input reuse, shared pending work and
|
|
19
28
|
independent preparation cancellation. Persist MIME metadata with each audio
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
|
|
|
19
19
|
`arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
|
|
20
20
|
event, cancellation, and browser run contracts.
|
|
21
21
|
|
|
22
|
-
This checkout defines the `0.5.
|
|
22
|
+
This checkout defines the `0.5.19` SDK contract. Applications pin one exact npm
|
|
23
23
|
version and lockfile; registry state is deliberately not baked into application
|
|
24
24
|
artifacts.
|
|
25
25
|
|
|
@@ -35,7 +35,7 @@ Create one browser application, install its pinned SDK, and start its source
|
|
|
35
35
|
server:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
npx arcane-os@0.5.
|
|
38
|
+
npx arcane-os@0.5.19 new hello-speech --path ./hello-speech --target browser
|
|
39
39
|
cd hello-speech
|
|
40
40
|
npm install
|
|
41
41
|
npm run dev
|
|
@@ -357,7 +357,7 @@ uses the same controller for automatic memory extraction.
|
|
|
357
357
|
Create a new repository-shaped Arcane application with the exact stable SDK:
|
|
358
358
|
|
|
359
359
|
```bash
|
|
360
|
-
npx arcane-os@0.5.
|
|
360
|
+
npx arcane-os@0.5.19 new my-app --path ./my-app --target portable --git
|
|
361
361
|
cd my-app
|
|
362
362
|
npm install
|
|
363
363
|
npm run dev
|
|
@@ -367,7 +367,7 @@ To enroll an existing repository, install the exact SDK and initialize only
|
|
|
367
367
|
missing Arcane files:
|
|
368
368
|
|
|
369
369
|
```bash
|
|
370
|
-
npm install --save-dev --save-exact arcane-os@0.5.
|
|
370
|
+
npm install --save-dev --save-exact arcane-os@0.5.19
|
|
371
371
|
npm exec -- arcane init my-app --target portable
|
|
372
372
|
```
|
|
373
373
|
|
|
@@ -383,7 +383,7 @@ npm exec -- arcane-os targets
|
|
|
383
383
|
No global SDK install or standalone Arcane CLI is required. The application
|
|
384
384
|
repository's exact npm dependency and lockfile own the CLI and toolchain version.
|
|
385
385
|
|
|
386
|
-
Use `npx arcane-os@0.5.
|
|
386
|
+
Use `npx arcane-os@0.5.19` for the initial bootstrap because it names this npm
|
|
387
387
|
package explicitly; bare `npx arcane` outside an installed project could resolve
|
|
388
388
|
a different package. Both installed commands invoke the same headless toolchain.
|
|
389
389
|
Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
|
|
@@ -403,7 +403,7 @@ node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
|
|
|
403
403
|
|
|
404
404
|
# From the generated app repository
|
|
405
405
|
cd ../local-app
|
|
406
|
-
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.5.
|
|
406
|
+
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.5.19.tgz
|
|
407
407
|
npm ci
|
|
408
408
|
```
|
|
409
409
|
|
|
@@ -412,7 +412,7 @@ same location. The lockfile retains the selected package dependency while
|
|
|
412
412
|
Arcane uses the installed package name and version. Local directory `file:` dependencies are not
|
|
413
413
|
accepted because npm may install them as links; use a packed `.tgz`. A GitHub
|
|
414
414
|
runner also needs that tarball at the locked path. After publication, replace
|
|
415
|
-
the local declaration with the exact `arcane-os@0.5.
|
|
415
|
+
the local declaration with the exact `arcane-os@0.5.19` registry package and
|
|
416
416
|
commit the regenerated lock.
|
|
417
417
|
|
|
418
418
|
Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
|
|
@@ -551,7 +551,7 @@ package installation, or assertions.
|
|
|
551
551
|
|
|
552
552
|
## Current target support
|
|
553
553
|
|
|
554
|
-
Version `0.5.
|
|
554
|
+
Version `0.5.19` exposes one browser target and five explicitly paired
|
|
555
555
|
native development targets: a non-runnable portable directory, a
|
|
556
556
|
Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
|
|
557
557
|
unsigned-local-test DEBs, and an Android development-signed APK. The
|
|
@@ -3000,24 +3000,47 @@ export function createBrowserWasmLlmProvider({
|
|
|
3000
3000
|
? context.reportProgress
|
|
3001
3001
|
: options.onProgress ?? null;
|
|
3002
3002
|
const progressStartedAt = Date.now();
|
|
3003
|
+
let progressStageStartedAt = progressStartedAt;
|
|
3004
|
+
let progressUpdatedAt = progressStartedAt;
|
|
3003
3005
|
let currentProgress = null;
|
|
3004
3006
|
let progressHeartbeat = null;
|
|
3005
3007
|
|
|
3006
3008
|
function publishModelLoadProgress(progress) {
|
|
3007
|
-
if (!reportProgress) return;
|
|
3009
|
+
if (!reportProgress || signal.aborted || generation !== lifecycleGeneration || state !== "loading") return;
|
|
3010
|
+
const now = Date.now();
|
|
3011
|
+
if (currentProgress?.phase !== progress.phase || currentProgress?.stage !== progress.stage) {
|
|
3012
|
+
progressStageStartedAt = now;
|
|
3013
|
+
}
|
|
3014
|
+
progressUpdatedAt = now;
|
|
3008
3015
|
currentProgress = { ...progress, heartbeat: false };
|
|
3009
3016
|
reportProgress(completeValue({
|
|
3010
3017
|
...currentProgress,
|
|
3011
|
-
elapsedMs: Math.max(0,
|
|
3018
|
+
elapsedMs: Math.max(0, now - progressStartedAt),
|
|
3019
|
+
...(progress.phase === "initialize" ? {
|
|
3020
|
+
phaseElapsedMs: Math.max(0, now - progressStageStartedAt),
|
|
3021
|
+
activityElapsedMs: 0,
|
|
3022
|
+
} : {}),
|
|
3012
3023
|
}));
|
|
3013
3024
|
}
|
|
3014
3025
|
|
|
3026
|
+
function publishModelInitializationProgress(progress) {
|
|
3027
|
+
if (!reportProgress || signal.aborted || generation !== lifecycleGeneration || state !== "loading") return;
|
|
3028
|
+
progressUpdatedAt = Date.now();
|
|
3029
|
+
if (!progress || (currentProgress?.stage === progress.stage && currentProgress?.message === progress.message)) return;
|
|
3030
|
+
publishModelLoadProgress(progress);
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3015
3033
|
function publishModelLoadHeartbeat() {
|
|
3016
|
-
if (!reportProgress || !currentProgress) return;
|
|
3034
|
+
if (!reportProgress || !currentProgress || signal.aborted || generation !== lifecycleGeneration || state !== "loading") return;
|
|
3035
|
+
const now = Date.now();
|
|
3017
3036
|
reportProgress(completeValue({
|
|
3018
3037
|
...currentProgress,
|
|
3019
3038
|
heartbeat: true,
|
|
3020
|
-
elapsedMs: Math.max(0,
|
|
3039
|
+
elapsedMs: Math.max(0, now - progressStartedAt),
|
|
3040
|
+
...(currentProgress.phase === "initialize" ? {
|
|
3041
|
+
phaseElapsedMs: Math.max(0, now - progressStageStartedAt),
|
|
3042
|
+
activityElapsedMs: Math.max(0, now - progressUpdatedAt),
|
|
3043
|
+
} : {}),
|
|
3021
3044
|
}));
|
|
3022
3045
|
}
|
|
3023
3046
|
|
|
@@ -3047,16 +3070,12 @@ export function createBrowserWasmLlmProvider({
|
|
|
3047
3070
|
if (generation !== lifecycleGeneration || state !== "loading") {
|
|
3048
3071
|
throw fail("ARCANE_AI_OPERATION_SUPERSEDED", "The model load was superseded by unload.");
|
|
3049
3072
|
}
|
|
3050
|
-
throwIfAborted(signal, "load");
|
|
3051
|
-
if (generation !== lifecycleGeneration || state !== "loading") {
|
|
3052
|
-
throw fail("ARCANE_AI_OPERATION_SUPERSEDED", "The model load was superseded by unload.");
|
|
3053
|
-
}
|
|
3054
3073
|
const members = sourceMetadata(activeSource).files;
|
|
3055
3074
|
publishModelLoadProgress({
|
|
3056
3075
|
phase: "initialize",
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3076
|
+
stage: "runtime",
|
|
3077
|
+
message: "Starting the WebAssembly runtime and opening model files",
|
|
3078
|
+
total: null,
|
|
3060
3079
|
heartbeat: false,
|
|
3061
3080
|
});
|
|
3062
3081
|
const modelFiles = admitted.files.map((file, index) => (
|
|
@@ -3074,6 +3093,7 @@ export function createBrowserWasmLlmProvider({
|
|
|
3074
3093
|
...runtimeOptions,
|
|
3075
3094
|
...activeLoadPlan,
|
|
3076
3095
|
signal,
|
|
3096
|
+
onProgress: publishModelInitializationProgress,
|
|
3077
3097
|
});
|
|
3078
3098
|
if (!runtime.isLoaded()) {
|
|
3079
3099
|
throw fail(
|
|
@@ -94,6 +94,59 @@ function createEvidenceLogger(logger) {
|
|
|
94
94
|
let offload = null;
|
|
95
95
|
let invalid = false;
|
|
96
96
|
let completionCapture = null;
|
|
97
|
+
let loadProgress = null;
|
|
98
|
+
let tensorCount = null;
|
|
99
|
+
|
|
100
|
+
function observeLoadLine(line) {
|
|
101
|
+
if (!loadProgress) return;
|
|
102
|
+
let stage;
|
|
103
|
+
let message;
|
|
104
|
+
const metadata = line.match(/^llama_model_loader: loaded meta data with \d+ key-value pairs and (\d+) tensors from /u);
|
|
105
|
+
const layers = line.match(GPU_OFFLOAD_PATTERN);
|
|
106
|
+
if (line.startsWith('Loading "wllama.wasm" from ')) {
|
|
107
|
+
stage = "runtime";
|
|
108
|
+
message = "Loading the WebAssembly runtime";
|
|
109
|
+
} else if (line === "Calling wllamaStart...") {
|
|
110
|
+
stage = "backend";
|
|
111
|
+
message = "Starting the inference engine";
|
|
112
|
+
} else if (line === "Loading model...") {
|
|
113
|
+
stage = "metadata";
|
|
114
|
+
message = "Reading model metadata";
|
|
115
|
+
} else if (WEBGPU_ADAPTER_PATTERN.test(line)) {
|
|
116
|
+
stage = "gpu";
|
|
117
|
+
message = "Graphics device initialized; preparing the model";
|
|
118
|
+
} else if (metadata) {
|
|
119
|
+
tensorCount = Number(metadata[1]);
|
|
120
|
+
stage = "metadata";
|
|
121
|
+
message = `Model metadata read: ${tensorCount} tensors`;
|
|
122
|
+
} else if (line.startsWith("load_tensors: loading model tensors,")) {
|
|
123
|
+
stage = "weights";
|
|
124
|
+
message = tensorCount === null
|
|
125
|
+
? "Loading model weights"
|
|
126
|
+
: `Loading model weights for ${tensorCount} tensors`;
|
|
127
|
+
} else if (layers) {
|
|
128
|
+
// Upstream reports layer assignment before the weight reads finish.
|
|
129
|
+
stage = "weights";
|
|
130
|
+
message = `Loading model weights; ${layers[1]} of ${layers[2]} layers assigned to the GPU`;
|
|
131
|
+
} else if (line === "llama_context: constructing llama_context") {
|
|
132
|
+
stage = "context";
|
|
133
|
+
message = "Preparing the inference context";
|
|
134
|
+
} else if (/^[^:]+:\s+graph (?:nodes|splits)\s+=\s+\d+/u.test(line)) {
|
|
135
|
+
stage = "graph";
|
|
136
|
+
message = "Preparing the inference graph";
|
|
137
|
+
} else if (/^cmn\s+common_init_:\s+warming up the model with an empty run\b/u.test(line)) {
|
|
138
|
+
stage = "warmup";
|
|
139
|
+
message = "Warming up the model with an empty run";
|
|
140
|
+
}
|
|
141
|
+
if (stage) {
|
|
142
|
+
loadProgress({
|
|
143
|
+
phase: "initialize",
|
|
144
|
+
stage,
|
|
145
|
+
message,
|
|
146
|
+
total: null,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
97
150
|
|
|
98
151
|
function same(left, right) {
|
|
99
152
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
@@ -119,6 +172,7 @@ function createEvidenceLogger(logger) {
|
|
|
119
172
|
observeCompletionLine(level, value);
|
|
120
173
|
const line = String(value).trim();
|
|
121
174
|
if (!line) return;
|
|
175
|
+
observeLoadLine(line);
|
|
122
176
|
const adapterMatch = line.match(WEBGPU_ADAPTER_PATTERN);
|
|
123
177
|
if (adapterMatch) {
|
|
124
178
|
const next = completeValue({
|
|
@@ -144,6 +198,8 @@ function createEvidenceLogger(logger) {
|
|
|
144
198
|
}
|
|
145
199
|
|
|
146
200
|
function observe(level, args) {
|
|
201
|
+
// Activity without a new stage updates its age without repainting the UI.
|
|
202
|
+
loadProgress?.(null);
|
|
147
203
|
for (const value of args) {
|
|
148
204
|
if (!is.string(value)) continue;
|
|
149
205
|
for (const line of value.split(/\r?\n/u)) observeLine(level, line);
|
|
@@ -160,6 +216,13 @@ function createEvidenceLogger(logger) {
|
|
|
160
216
|
|
|
161
217
|
return completeValue({
|
|
162
218
|
logger: completeValue(wrapped),
|
|
219
|
+
beginLoadProgress(report) {
|
|
220
|
+
loadProgress = report;
|
|
221
|
+
tensorCount = null;
|
|
222
|
+
return function releaseLoadProgress() {
|
|
223
|
+
loadProgress = null;
|
|
224
|
+
};
|
|
225
|
+
},
|
|
163
226
|
beginCompletionCapture() {
|
|
164
227
|
if (completionCapture) {
|
|
165
228
|
throw runtimeFailure(
|
|
@@ -469,6 +532,18 @@ export function createPackagedWllamaRuntime({ logger = arcaneLogging } = {}) {
|
|
|
469
532
|
cleanup: null,
|
|
470
533
|
});
|
|
471
534
|
const loadController = new AbortController();
|
|
535
|
+
let progressFailure = null;
|
|
536
|
+
const releaseLoadProgress = sessionObservers.get(next).beginLoadProgress(
|
|
537
|
+
function reportRuntimeLoadProgress(progress) {
|
|
538
|
+
if (loadController.signal.aborted || !is.function(options.onProgress)) return;
|
|
539
|
+
try {
|
|
540
|
+
options.onProgress(progress);
|
|
541
|
+
} catch (error) {
|
|
542
|
+
progressFailure = error;
|
|
543
|
+
loadController.abort(error);
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
);
|
|
472
547
|
const loadOperation = Promise.resolve().then(() => (
|
|
473
548
|
next.arcaneLoadModel(files, loadOptions, loadController.signal)
|
|
474
549
|
));
|
|
@@ -484,6 +559,7 @@ export function createPackagedWllamaRuntime({ logger = arcaneLogging } = {}) {
|
|
|
484
559
|
else signal?.addEventListener?.("abort", onAbort, { once: true });
|
|
485
560
|
try {
|
|
486
561
|
await loadOperation;
|
|
562
|
+
if (progressFailure) throw progressFailure;
|
|
487
563
|
if (pending?.engine !== next) throw new Error("Wllama load was cancelled.");
|
|
488
564
|
if (!is.function(next.isModelLoaded) || next.isModelLoaded() !== true) {
|
|
489
565
|
throw runtimeFailure(
|
|
@@ -496,6 +572,7 @@ export function createPackagedWllamaRuntime({ logger = arcaneLogging } = {}) {
|
|
|
496
572
|
engine = next;
|
|
497
573
|
publishEvidence({ state: "ready", webgpu, cancellation: null, cleanup: null });
|
|
498
574
|
} catch (error) {
|
|
575
|
+
releaseLoadProgress();
|
|
499
576
|
let cleanupFailure = null;
|
|
500
577
|
try {
|
|
501
578
|
await exitSession(next);
|
|
@@ -513,6 +590,7 @@ export function createPackagedWllamaRuntime({ logger = arcaneLogging } = {}) {
|
|
|
513
590
|
if (cleanupFailure) throw cleanupFailure;
|
|
514
591
|
throw error;
|
|
515
592
|
} finally {
|
|
593
|
+
releaseLoadProgress();
|
|
516
594
|
signal?.removeEventListener?.("abort", onAbort);
|
|
517
595
|
}
|
|
518
596
|
|
package/docs/architecture.md
CHANGED
|
@@ -279,7 +279,7 @@ paths are withheld from the native provider. The provider copies the complete
|
|
|
279
279
|
selected release rather than accepting an unrelated source path. Verification
|
|
280
280
|
is a separate explicit operation for a selected release artifact.
|
|
281
281
|
|
|
282
|
-
The SDK `0.5.
|
|
282
|
+
The SDK `0.5.19` runtime requires Arcane `0.8.12` or newer. Compatibility
|
|
283
283
|
is contractual rather than exact-version pinning: the prepared Core must meet
|
|
284
284
|
the highest minimum declared by the runtime, selected app, and bundled app
|
|
285
285
|
dependencies; keep each app's Arcane protocol generation; and provide every
|
package/docs/publishing.md
CHANGED
|
@@ -16,7 +16,13 @@ dependency and invoke its local CLI with `npm exec -- arcane`. A separate
|
|
|
16
16
|
global installer, standalone SDK executable, NuGet package, Homebrew formula,
|
|
17
17
|
or OS package is not part of this release surface.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
The user's standing instruction selects publication when a coherent SDK change
|
|
20
|
+
is complete. Publish every ready change that preserves required functionality
|
|
21
|
+
and remains relevant, while excluding unfinished concurrent or explicitly
|
|
22
|
+
deferred work. Default to a patch release and assess whether a new capability
|
|
23
|
+
warrants a minor revision. Honor an explicit no-publish instruction.
|
|
24
|
+
|
|
25
|
+
Publication checks run only for that selected npm release output.
|
|
20
26
|
That selected-release workflow validates package metadata, the executable and
|
|
21
27
|
`.gitattributes` boundary, the complete package inventory, version/channel
|
|
22
28
|
agreement, and required license notices. One unprivileged producer packs one
|
|
@@ -94,9 +100,9 @@ locked installation, runs one selected `arcane package`, creates one selected
|
|
|
94
100
|
identities, receipts, provenance records, or attestation sidecars and does not
|
|
95
101
|
run a second admission job.
|
|
96
102
|
|
|
97
|
-
Stable versioning, the npm `latest` tag, and an official GitHub release
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
Stable versioning, the npm `latest` tag, and an official GitHub release follow
|
|
104
|
+
the selected release decision, including the standing completed-work authority
|
|
105
|
+
above. Unfinished `main` development does not select a release. A stable release
|
|
100
106
|
must publish the exact selected Check artifact under `latest`; GitHub may then
|
|
101
107
|
attach that same package. Its Git
|
|
102
108
|
tag and GitHub release title must both be the same bare numeric
|
|
@@ -101,10 +101,31 @@ active-worker count changes, and the download completes. At known completion, re
|
|
|
101
101
|
and active transfers are zero. Applications format the raw measures into B,
|
|
102
102
|
KB, MB, GB, transfer-rate, and duration labels.
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
Initialization keeps `phase:'initialize'` and reports a `stage` and a readable
|
|
105
|
+
`message` from the packaged runtime's existing log signals: runtime startup,
|
|
106
|
+
engine startup, model metadata, graphics-device initialization, model weights,
|
|
107
|
+
inference context, and inference graph. Warmup is shown only if the runtime
|
|
108
|
+
actually reports it; the default upstream log level may omit that signal.
|
|
109
|
+
These messages do not change the runtime's log level or replace its complete
|
|
110
|
+
diagnostic output.
|
|
111
|
+
|
|
112
|
+
Initialization has `total:null` and omits `completed` and `unit`, so its progress
|
|
113
|
+
bar is indeterminate. Downloaded-file completion is not activation completion.
|
|
114
|
+
The metadata tensor count and GPU layer assignment are descriptive only:
|
|
115
|
+
upstream reports assigned layers before model-weight loading has finished.
|
|
116
|
+
The pinned runtime does not expose a measured overall initialization fraction
|
|
117
|
+
or a shader-compilation completion count. Readiness still requires the load
|
|
118
|
+
operation to resolve and the runtime to confirm the model is loaded.
|
|
119
|
+
|
|
120
|
+
While a load remains active, the provider repeats its current record every five
|
|
121
|
+
seconds with `heartbeat:true` and an updated `elapsedMs`. During initialization,
|
|
122
|
+
`phaseElapsedMs` measures time in the current stage and `activityElapsedMs`
|
|
123
|
+
measures time since the most recent runtime log signal. A signal without a
|
|
124
|
+
recognized stage updates activity time without triggering another UI render.
|
|
125
|
+
The shared chat loading panel shows the observed stage and those timings. A
|
|
126
|
+
heartbeat only shows that the provider's timer ran; it does not establish
|
|
127
|
+
runtime advancement, estimate a completion time, or advance the progress bar.
|
|
128
|
+
Late runtime progress is ignored after cancellation or supersession.
|
|
108
129
|
|
|
109
130
|
The DBOPFS store uses one bounded transfer axis. Ordered multi-file GGUF sets
|
|
110
131
|
download several members concurrently and preserve completed shards across a
|
|
@@ -470,8 +491,10 @@ identity, default model metadata,
|
|
|
470
491
|
`streamChat`, `use`, `probe`, and `dispose`. Direct provider `load()` selects a
|
|
471
492
|
catalog model and returns `{model,status}`;
|
|
472
493
|
the public AI API module's `ai.load()` returns the flat controller status.
|
|
473
|
-
Direct `load({onProgress})` forwards the same
|
|
474
|
-
records used by the controller and provider/2
|
|
494
|
+
Direct `load({onProgress})` forwards the same download progress and observed
|
|
495
|
+
initialization-stage/activity records used by the controller and provider/2
|
|
496
|
+
adapter. Initialization has no completion percentage unless the runtime reports
|
|
497
|
+
a meaningful completion total.
|
|
475
498
|
Provider `security` carries the provider/model-binding `secure` intent. Direct
|
|
476
499
|
`provider.load({security})` and `ai.load({security})` supply the operation
|
|
477
500
|
intent. They do not activate checking in the ordinary development contract.
|
package/package.json
CHANGED
|
@@ -2039,6 +2039,20 @@
|
|
|
2039
2039
|
}
|
|
2040
2040
|
|
|
2041
2041
|
function determinateProgress(){
|
|
2042
|
+
if(role?.progress?.phase==='initialize'){
|
|
2043
|
+
const {completed,total,unit}=role.progress;
|
|
2044
|
+
if(
|
|
2045
|
+
!is.finite(total)
|
|
2046
|
+
||total<=0
|
|
2047
|
+
||!is.finite(completed)
|
|
2048
|
+
||completed<0
|
|
2049
|
+
||!is.string(unit)
|
|
2050
|
+
||!unit
|
|
2051
|
+
){
|
|
2052
|
+
return null;
|
|
2053
|
+
}
|
|
2054
|
+
return {completed,total,unit,byteTelemetry:false};
|
|
2055
|
+
}
|
|
2042
2056
|
const totalBytes=role?.progress?.totalBytes;
|
|
2043
2057
|
const loadedBytes=role?.progress?.loadedBytes;
|
|
2044
2058
|
if(
|
|
@@ -2160,8 +2174,10 @@
|
|
|
2160
2174
|
return `${active} of ${limit} ${noun}${limit===1?'':'s'} active`;
|
|
2161
2175
|
}
|
|
2162
2176
|
|
|
2163
|
-
function progressElapsedText(
|
|
2164
|
-
|
|
2177
|
+
function progressElapsedText(
|
|
2178
|
+
elapsedMilliseconds,
|
|
2179
|
+
suffix='elapsed'
|
|
2180
|
+
){
|
|
2165
2181
|
if(!is.finite(elapsedMilliseconds)||elapsedMilliseconds<0){
|
|
2166
2182
|
return '';
|
|
2167
2183
|
}
|
|
@@ -2170,12 +2186,12 @@
|
|
|
2170
2186
|
const minutes=Math.floor(elapsedSeconds%3600/60);
|
|
2171
2187
|
const seconds=elapsedSeconds%60;
|
|
2172
2188
|
if(hours>0){
|
|
2173
|
-
return `${hours}h ${minutes}m
|
|
2189
|
+
return `${hours}h ${minutes}m ${suffix}`;
|
|
2174
2190
|
}
|
|
2175
2191
|
if(minutes>0){
|
|
2176
|
-
return `${minutes}m ${String(seconds).padStart(2,'0')}s
|
|
2192
|
+
return `${minutes}m ${String(seconds).padStart(2,'0')}s ${suffix}`;
|
|
2177
2193
|
}
|
|
2178
|
-
return `${seconds}s
|
|
2194
|
+
return `${seconds}s ${suffix}`;
|
|
2179
2195
|
}
|
|
2180
2196
|
|
|
2181
2197
|
function progressMessage(){
|
|
@@ -2183,7 +2199,14 @@
|
|
|
2183
2199
|
const phaseValue=is.string(role?.progress?.phase)&&role.progress.phase
|
|
2184
2200
|
?role.progress.phase
|
|
2185
2201
|
:'loading';
|
|
2186
|
-
const
|
|
2202
|
+
const initializing=phaseValue==='initialize';
|
|
2203
|
+
const phase=initializing
|
|
2204
|
+
?is.string(role?.progress?.message)&&role.progress.message
|
|
2205
|
+
?role.progress.message
|
|
2206
|
+
:is.string(role?.progress?.stage)&&role.progress.stage
|
|
2207
|
+
?role.progress.stage.replaceAll('-',' ')
|
|
2208
|
+
:'Initializing the selected model'
|
|
2209
|
+
:phaseValue.replaceAll('-',' ');
|
|
2187
2210
|
const parts=[
|
|
2188
2211
|
`Loading ${modelDisplayName()} through the Arcane SDK`,
|
|
2189
2212
|
phase
|
|
@@ -2211,18 +2234,38 @@
|
|
|
2211
2234
|
:measured.unit;
|
|
2212
2235
|
parts.push(`${measured.completed} of ${measured.total} ${suffix}`);
|
|
2213
2236
|
}
|
|
2214
|
-
const transfers=activeTransferText();
|
|
2237
|
+
const transfers=initializing?'':activeTransferText();
|
|
2215
2238
|
if(transfers){
|
|
2216
2239
|
parts.push(transfers);
|
|
2217
2240
|
}
|
|
2218
|
-
const elapsed=progressElapsedText();
|
|
2241
|
+
const elapsed=progressElapsedText(Number(role?.progress?.elapsedMs));
|
|
2219
2242
|
if(elapsed){
|
|
2220
2243
|
parts.push(elapsed);
|
|
2221
|
-
if(role?.progress?.heartbeat===true){
|
|
2244
|
+
if(!initializing&&role?.progress?.heartbeat===true){
|
|
2222
2245
|
parts.push('Still working');
|
|
2223
2246
|
}
|
|
2224
2247
|
}
|
|
2225
|
-
if(
|
|
2248
|
+
if(initializing){
|
|
2249
|
+
const phaseElapsed=progressElapsedText(
|
|
2250
|
+
role.progress.phaseElapsedMs,
|
|
2251
|
+
'in this stage'
|
|
2252
|
+
);
|
|
2253
|
+
if(phaseElapsed){
|
|
2254
|
+
parts.push(phaseElapsed);
|
|
2255
|
+
}
|
|
2256
|
+
if(role.progress.heartbeat===true){
|
|
2257
|
+
const activityElapsed=progressElapsedText(
|
|
2258
|
+
role.progress.activityElapsedMs,
|
|
2259
|
+
'ago'
|
|
2260
|
+
);
|
|
2261
|
+
if(activityElapsed){
|
|
2262
|
+
parts.push(`Last runtime activity ${activityElapsed}`);
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
parts.push(measured
|
|
2266
|
+
?'Keep this tab open'
|
|
2267
|
+
:'Completion percentage unavailable; keep this tab open');
|
|
2268
|
+
}else if(!measured?.byteTelemetry){
|
|
2226
2269
|
parts.push('The first activation can take several minutes; keep this tab open');
|
|
2227
2270
|
}
|
|
2228
2271
|
return parts.join(' · ');
|