arcane-os 0.5.9 → 0.5.11

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 (55) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +117 -26
  3. package/browser-runtime/ai/browser-speech-providers.mjs +1 -1
  4. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +63 -39
  5. package/docs/architecture.md +303 -0
  6. package/docs/compatibility.md +38 -0
  7. package/docs/event-manager.md +263 -0
  8. package/docs/platform-targets.md +104 -0
  9. package/docs/publishing.md +126 -0
  10. package/docs/reference/README.md +206 -0
  11. package/docs/reference/ai/browser-speech.md +813 -0
  12. package/docs/reference/ai/browser-wasm.md +637 -0
  13. package/docs/reference/ai/twin-cloud.md +156 -0
  14. package/docs/reference/arcane-ollama.md +288 -0
  15. package/docs/reference/availability-and-normalization.md +224 -0
  16. package/docs/reference/behavioral-testing.md +129 -0
  17. package/docs/reference/cli.md +820 -0
  18. package/docs/reference/core/README.md +61 -0
  19. package/docs/reference/core/arcane-ai-contracts.md +907 -0
  20. package/docs/reference/core/arcane-api.md +601 -0
  21. package/docs/reference/core/arcane-entities.md +59 -0
  22. package/docs/reference/core/arcane-events.md +134 -0
  23. package/docs/reference/core/ollama-module.md +181 -0
  24. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +1909 -0
  25. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +1057 -0
  26. package/docs/reference/core/reference/arcane-api/core-and-events.md +320 -0
  27. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +610 -0
  28. package/docs/reference/core/reference/arcane-api/namespaces.md +1157 -0
  29. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +1423 -0
  30. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +315 -0
  31. package/docs/reference/event-manager.md +1409 -0
  32. package/docs/reference/inventory/package-api.json +3194 -0
  33. package/docs/reference/inventory/runtime-components.json +1015 -0
  34. package/docs/reference/inventory/runtime-entities.json +25 -0
  35. package/docs/reference/inventory/runtime-modules.json +1367 -0
  36. package/docs/reference/mail.md +309 -0
  37. package/docs/reference/protocols.md +749 -0
  38. package/docs/reference/runtime-components.md +1529 -0
  39. package/docs/reference/runtime-entities.md +305 -0
  40. package/docs/reference/runtime-modules.md +3275 -0
  41. package/docs/reference/sdk-api.md +6733 -0
  42. package/docs/roadmap.md +79 -0
  43. package/docs/work-amplification.md +66 -0
  44. package/examples/wasm-ai-demo/README.md +80 -0
  45. package/examples/wasm-ai-demo/app.js +787 -0
  46. package/examples/wasm-ai-demo/index.html +343 -0
  47. package/examples/wasm-ai-demo/profile-tools.js +217 -0
  48. package/examples/wasm-ai-demo/profiles/BOSS.Modelfile +106 -0
  49. package/examples/wasm-ai-demo/profiles/PreCrisis.Modelfile +693 -0
  50. package/examples/wasm-ai-demo/rag/boss-library.json +3006 -0
  51. package/examples/wasm-ai-demo/rag.js +295 -0
  52. package/examples/wasm-ai-demo/server.mjs +71 -0
  53. package/package.json +11 -2
  54. package/runtime/arcane/modules/AI.js +1 -1
  55. package/runtime/arcane/modules/AIProviderRuntime.js +26 -5
@@ -0,0 +1,295 @@
1
+ import DBOPFSDocumentLibrary from "arcane/DBOPFSDocumentLibrary";
2
+
3
+ const DEFAULT_BOSS_BUNDLE_URL = "./rag/boss-library.json";
4
+ const PROFILE_IDS = new Set(["general", "precrisis", "boss"]);
5
+ const libraryEntries = new WeakMap();
6
+
7
+ function normalizeProfileId(profileId) {
8
+ const normalized = String(profileId || "general").trim().toLowerCase();
9
+ if (!PROFILE_IDS.has(normalized)) {
10
+ throw new TypeError(`Unsupported example profile: ${normalized || "empty"}.`);
11
+ }
12
+ return normalized;
13
+ }
14
+
15
+ function reportStatus(onStatus, message) {
16
+ if (typeof onStatus === "function") onStatus(message);
17
+ }
18
+
19
+ function entryMap(dbopfs) {
20
+ let entries = libraryEntries.get(dbopfs);
21
+ if (!entries) {
22
+ entries = new Map();
23
+ libraryEntries.set(dbopfs, entries);
24
+ }
25
+ return entries;
26
+ }
27
+
28
+ function schemaFor(profileId, version = "1") {
29
+ return {
30
+ id: `wasm-ai-demo-${profileId}`,
31
+ table: `wasm_ai_demo_${profileId}_documents`,
32
+ version: String(version),
33
+ };
34
+ }
35
+
36
+ function createLibrary(dbopfs, profileId, version) {
37
+ return new DBOPFSDocumentLibrary({
38
+ db: dbopfs,
39
+ schema: schemaFor(profileId, version),
40
+ });
41
+ }
42
+
43
+ function splitTerms(value) {
44
+ if (Array.isArray(value)) {
45
+ return value.map((entry) => String(entry).trim()).filter(Boolean);
46
+ }
47
+ return String(value || "")
48
+ .split(",")
49
+ .map((entry) => entry.trim())
50
+ .filter(Boolean);
51
+ }
52
+
53
+ function bossRecord(document, index, sourcePath) {
54
+ const record = document && typeof document === "object" && !Array.isArray(document)
55
+ ? document
56
+ : {};
57
+ const id = String(record.id || `boss-library-${index + 1}`).trim();
58
+ const title = String(record.title || id).trim();
59
+ const body = String(record.text ?? record.body ?? "");
60
+ const terms = splitTerms(record.topics);
61
+ return {
62
+ body,
63
+ id,
64
+ kind: "boss-library",
65
+ mediaType: "text/markdown",
66
+ path: id,
67
+ searchTerms: terms,
68
+ sourcePath,
69
+ tags: ["boss", ...terms],
70
+ title,
71
+ };
72
+ }
73
+
74
+ async function fetchBossBundle(bundleUrl) {
75
+ const response = await fetch(bundleUrl, { cache: "no-store" });
76
+ if (!response.ok) {
77
+ throw new Error(`Unable to load the BOSS library: HTTP ${response.status}.`);
78
+ }
79
+ const bundle = await response.json();
80
+ if (!bundle || typeof bundle !== "object" || !Array.isArray(bundle.documents)) {
81
+ throw new TypeError("The BOSS library response must contain a documents array.");
82
+ }
83
+ return bundle;
84
+ }
85
+
86
+ async function readCorpus(library, signal) {
87
+ try {
88
+ return { ...(await library.search("", { signal })), bootstrapped: true };
89
+ } catch (error) {
90
+ if (error?.code === "DBOPFS_DOCUMENT_NOT_BOOTSTRAPPED") {
91
+ return { bootstrapped: false, failures: [], matches: [], total: 0 };
92
+ }
93
+ throw error;
94
+ }
95
+ }
96
+
97
+ async function prepareLibrary({ dbopfs, bundleUrl, initialLibrary, onStatus, profileId, signal }) {
98
+ let version = "1";
99
+ let bossDocuments = [];
100
+ if (profileId === "boss") {
101
+ reportStatus(onStatus, "Loading the BOSS document catalog…");
102
+ const bundle = await fetchBossBundle(bundleUrl);
103
+ version = String(bundle.schema_version ?? "1");
104
+ bossDocuments = bundle.documents.map((document, index) => (
105
+ bossRecord(document, index, bundleUrl)
106
+ ));
107
+ }
108
+
109
+ const library = profileId === "boss"
110
+ ? createLibrary(dbopfs, profileId, version)
111
+ : initialLibrary;
112
+ const existing = await readCorpus(library, signal);
113
+ const userDocuments = existing.matches.filter((document) => document.kind === "user-import");
114
+ const hasBossDocuments = existing.matches.some((document) => document.kind === "boss-library");
115
+ const needsBootstrap = !existing.bootstrapped || (profileId === "boss" && !hasBossDocuments);
116
+
117
+ if (needsBootstrap) {
118
+ const documents = profileId === "boss"
119
+ ? [...bossDocuments, ...userDocuments]
120
+ : userDocuments;
121
+ reportStatus(
122
+ onStatus,
123
+ profileId === "boss"
124
+ ? "Storing the BOSS library through the Arcane document library…"
125
+ : "Opening the Arcane document library…",
126
+ );
127
+ await library.bootstrap({
128
+ files: documents,
129
+ signal,
130
+ onProgress(progress) {
131
+ if (progress?.phase !== "writing") return;
132
+ reportStatus(
133
+ onStatus,
134
+ `Storing local documents ${progress.completed} of ${progress.total}…`,
135
+ );
136
+ },
137
+ });
138
+ }
139
+
140
+ return library;
141
+ }
142
+
143
+ async function libraryFor(profileId, { dbopfs, bundleUrl, onStatus, signal } = {}) {
144
+ const profile = normalizeProfileId(profileId);
145
+ const db = dbopfs ?? globalThis.dbopfs;
146
+ const initialLibrary = createLibrary(db, profile, "1");
147
+ if (db.readyPromise) await db.readyPromise;
148
+ const entries = entryMap(db);
149
+ let entry = entries.get(profile);
150
+ if (!entry) {
151
+ entry = {
152
+ library: null,
153
+ ready: prepareLibrary({
154
+ dbopfs: db,
155
+ bundleUrl: bundleUrl || DEFAULT_BOSS_BUNDLE_URL,
156
+ initialLibrary,
157
+ onStatus,
158
+ profileId: profile,
159
+ signal,
160
+ }),
161
+ };
162
+ entries.set(profile, entry);
163
+ try {
164
+ entry.library = await entry.ready;
165
+ } catch (error) {
166
+ entries.delete(profile);
167
+ throw error;
168
+ }
169
+ } else if (!entry.library) {
170
+ entry.library = await entry.ready;
171
+ }
172
+ return entry.library;
173
+ }
174
+
175
+ function corpusStats(corpus) {
176
+ const documents = corpus.matches || [];
177
+ const bossCount = documents.filter((document) => document.kind === "boss-library").length;
178
+ const userCount = documents.filter((document) => document.kind === "user-import").length;
179
+ return {
180
+ boss: bossCount,
181
+ bossCount,
182
+ bossDocuments: bossCount,
183
+ failures: corpus.failures || [],
184
+ imported: userCount,
185
+ total: documents.length,
186
+ userCount,
187
+ userDocuments: userCount,
188
+ };
189
+ }
190
+
191
+ async function initializeRag({
192
+ dbopfs = globalThis.dbopfs,
193
+ bundleUrl = DEFAULT_BOSS_BUNDLE_URL,
194
+ onStatus,
195
+ profileId = "general",
196
+ signal,
197
+ } = {}) {
198
+ const library = await libraryFor(profileId, {
199
+ dbopfs,
200
+ bundleUrl,
201
+ onStatus,
202
+ signal,
203
+ });
204
+ const corpus = await library.search("", { signal });
205
+ reportStatus(onStatus, "Local Arcane document library ready.");
206
+ return corpusStats(corpus);
207
+ }
208
+
209
+ async function getRagStats(profileId = "general", {
210
+ dbopfs = globalThis.dbopfs,
211
+ signal,
212
+ } = {}) {
213
+ const library = await libraryFor(profileId, { dbopfs, signal });
214
+ return corpusStats(await library.search("", { signal }));
215
+ }
216
+
217
+ async function importedDocument(file, profileId) {
218
+ const fallbackId = `${Date.now()}-${Math.random()}`;
219
+ const id = `user-${profileId}-${globalThis.crypto?.randomUUID?.() || fallbackId}`;
220
+ const body = await file.text();
221
+ return {
222
+ body,
223
+ id,
224
+ kind: "user-import",
225
+ mediaType: file.type === "text/markdown" ? "text/markdown" : "text/plain",
226
+ path: file.name || id,
227
+ searchTerms: splitTerms(file.name),
228
+ sourcePath: file.name || id,
229
+ tags: [profileId, "user-import"],
230
+ title: file.name || "Imported document",
231
+ };
232
+ }
233
+
234
+ async function importRagFiles(files, {
235
+ dbopfs = globalThis.dbopfs,
236
+ onStatus,
237
+ profileId = "general",
238
+ signal,
239
+ } = {}) {
240
+ if (!Array.isArray(files)) throw new TypeError("RAG files must be an array.");
241
+ const profile = normalizeProfileId(profileId);
242
+ const library = await libraryFor(profile, { dbopfs, onStatus, signal });
243
+ const corpus = await library.search("", { signal });
244
+ const imported = [];
245
+ for (let index = 0; index < files.length; index += 1) {
246
+ if (signal?.aborted) throw signal.reason || new DOMException("Aborted", "AbortError");
247
+ const file = files[index];
248
+ if (!file || typeof file.text !== "function") {
249
+ throw new TypeError(`RAG file ${index + 1} is not a browser File.`);
250
+ }
251
+ reportStatus(onStatus, `Reading ${file.name || `document ${index + 1}`}…`);
252
+ imported.push(await importedDocument(file, profile));
253
+ }
254
+ await library.bootstrap({
255
+ files: [...corpus.matches, ...imported],
256
+ signal,
257
+ onProgress(progress) {
258
+ if (progress?.phase === "writing") {
259
+ reportStatus(
260
+ onStatus,
261
+ `Storing local documents ${progress.completed} of ${progress.total}…`,
262
+ );
263
+ }
264
+ },
265
+ });
266
+ return {
267
+ imported: imported.length,
268
+ stats: corpusStats(await library.search("", { signal })),
269
+ };
270
+ }
271
+
272
+ async function retrieveRagContext(query, {
273
+ dbopfs = globalThis.dbopfs,
274
+ profileId = "general",
275
+ signal,
276
+ } = {}) {
277
+ const profile = normalizeProfileId(profileId);
278
+ const library = await libraryFor(profile, { dbopfs, signal });
279
+ const result = await library.buildContext(String(query || ""), { signal });
280
+ const text = result.text && profile === "boss"
281
+ ? ["<boss_library_context>", result.text, "</boss_library_context>"].join("\n\n")
282
+ : result.text;
283
+ return {
284
+ failures: result.failures,
285
+ matches: result.documents,
286
+ text,
287
+ };
288
+ }
289
+
290
+ export {
291
+ getRagStats,
292
+ importRagFiles,
293
+ initializeRag,
294
+ retrieveRagContext,
295
+ };
@@ -0,0 +1,71 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { startSourceExampleServer } from "arcane-os";
5
+
6
+ const root = path.dirname(fileURLToPath(import.meta.url));
7
+ const sdkRoot = path.resolve(root, "..", "..");
8
+ const examplePath = "/examples/wasm-ai-demo";
9
+ const requestedPort = Number.parseInt(process.argv[2] || "4173", 10);
10
+ const port = Number.isFinite(requestedPort) ? requestedPort : 4173;
11
+ const host = process.argv[3] || "0.0.0.0";
12
+ const modelDirectory = process.env.ARCANE_WASM_MODEL_ROOT
13
+ ? path.resolve(process.env.ARCANE_WASM_MODEL_ROOT)
14
+ : path.join(root, "models");
15
+ const tlsDirectory = process.env.ARCANE_WASM_TLS_ROOT
16
+ ? path.resolve(process.env.ARCANE_WASM_TLS_ROOT)
17
+ : path.join(root, "tls");
18
+ const tlsKeyPath = path.join(tlsDirectory, "server-key.pem");
19
+ const tlsCertificatePath = path.join(tlsDirectory, "server.pem");
20
+ const tls = existsSync(tlsKeyPath) && existsSync(tlsCertificatePath)
21
+ ? {
22
+ key: readFileSync(tlsKeyPath),
23
+ cert: readFileSync(tlsCertificatePath),
24
+ }
25
+ : undefined;
26
+
27
+ const running = await startSourceExampleServer({
28
+ crossOriginIsolated: true,
29
+ host,
30
+ mounts: [
31
+ {
32
+ include: [
33
+ "index.html",
34
+ "app.js",
35
+ "rag.js",
36
+ "profile-tools.js",
37
+ "profiles/",
38
+ "rag/",
39
+ ],
40
+ index: "index.html",
41
+ root,
42
+ urlPath: examplePath,
43
+ },
44
+ {
45
+ root: modelDirectory,
46
+ urlPath: `${examplePath}/models`,
47
+ },
48
+ {
49
+ root: path.join(sdkRoot, "src"),
50
+ urlPath: "/src",
51
+ },
52
+ {
53
+ root: path.join(sdkRoot, "browser-runtime"),
54
+ urlPath: "/browser-runtime",
55
+ },
56
+ {
57
+ include: ["arcane/", "strong-type/"],
58
+ root: path.join(sdkRoot, "runtime"),
59
+ urlPath: "/runtime",
60
+ },
61
+ ],
62
+ port,
63
+ startPath: `${examplePath}/`,
64
+ tls,
65
+ });
66
+
67
+ console.log(`Arcane WASM Voice Chat listening on ${running.origin}`);
68
+ console.log(`Example URL: ${running.url}`);
69
+ console.log(`Live Arcane SDK source: ${sdkRoot}`);
70
+
71
+ await running.closed;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.5.9",
3
+ "version": "0.5.11",
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",
@@ -44,6 +44,15 @@
44
44
  "src/",
45
45
  "runtime/",
46
46
  "schemas/",
47
+ "docs/",
48
+ "examples/wasm-ai-demo/README.md",
49
+ "examples/wasm-ai-demo/index.html",
50
+ "examples/wasm-ai-demo/app.js",
51
+ "examples/wasm-ai-demo/server.mjs",
52
+ "examples/wasm-ai-demo/profile-tools.js",
53
+ "examples/wasm-ai-demo/rag.js",
54
+ "examples/wasm-ai-demo/profiles/",
55
+ "examples/wasm-ai-demo/rag/",
47
56
  "README.md",
48
57
  "CHANGELOG.md",
49
58
  "LICENSE",
@@ -54,7 +63,7 @@
54
63
  "test": "npm run test:unit && npm run test:functional && npm run test:integration && npm run test:regression",
55
64
  "test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
56
65
  "test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/contracts.test.mjs test/doctor.test.mjs test/mail-credentials.test.mjs test/mail-outbox.test.mjs test/mail-public-api.test.mjs test/mail-send.test.mjs test/mail-transport.test.mjs test/targets.test.mjs test/workspace-operation-lock.test.mjs",
57
- "test:functional": "node ./bin/arcane-test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-download-resume.test.mjs test/cli.test.mjs test/dbopfs-document-library.test.mjs test/dev-server.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/packaging.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/site.test.mjs test/update-check.test.mjs",
66
+ "test:functional": "node ./bin/arcane-test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/cli.test.mjs test/dbopfs-document-library.test.mjs test/dev-server.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/packaging.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/site.test.mjs test/update-check.test.mjs",
58
67
  "test:integration": "node ./bin/arcane-test.mjs test/integrated-shared.test.mjs test/integrated-workspace.test.mjs test/mail-browser.test.mjs test/native-plan.test.mjs test/native-provider-loader.test.mjs test/npm-release.test.mjs test/release-bundle.test.mjs test/release-capability-smoke.test.mjs test/shared-payload-batch.test.mjs test/tarball.test.mjs test/wllama-webgpu-runtime.test.mjs",
59
68
  "test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/native-provider-generation.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
60
69
  "check": "node tools/check-source.mjs && npm test",
@@ -23,7 +23,7 @@ const DEFAULT_TTS_SEGMENTATION={
23
23
  };
24
24
  const DEFAULT_BROWSER_TTS_EXECUTION={
25
25
  device:'auto',
26
- maxConcurrentRequests:2
26
+ maxConcurrentRequests:4
27
27
  };
28
28
  const BROWSER_TTS_EXECUTION_DEVICES=new Set(['auto','webgpu','wasm']);
29
29
  const MAX_BROWSER_TTS_CONCURRENT_REQUESTS=4;
@@ -2415,12 +2415,33 @@ export class AIProviderRuntime {
2415
2415
  return this.configure(selections);
2416
2416
  }
2417
2417
 
2418
- status(role = null) {
2419
- if (role === null) {
2420
- return getAIRuntimeState();
2418
+ status(role = null, options = {}) {
2419
+ if (role !== null) {
2420
+ assertRole(role);
2421
2421
  }
2422
- assertRole(role);
2423
- return getAIRuntimeState().roles[role];
2422
+ const snapshot = getAIRuntimeState();
2423
+ if (options?.execution !== true) {
2424
+ return role === null ? snapshot : snapshot.roles[role];
2425
+ }
2426
+
2427
+ // Live provider inspection is explicit so ordinary lifecycle reads
2428
+ // retain their sticky snapshot identity and never call a provider.
2429
+ const roles = {...snapshot.roles};
2430
+ for (const selectedRole of role === null ? AI_RUNTIME_ROLES : [role]) {
2431
+ const provider = this.#providerFor(this.#slots[selectedRole]);
2432
+ if (!provider) {
2433
+ continue;
2434
+ }
2435
+ const providerStatus = validateProviderStatus(provider.status());
2436
+ if (providerStatus.execution == null) {
2437
+ continue;
2438
+ }
2439
+ roles[selectedRole] = {
2440
+ ...roles[selectedRole],
2441
+ execution: {...providerStatus.execution}
2442
+ };
2443
+ }
2444
+ return role === null ? {...snapshot, roles} : roles[role];
2424
2445
  }
2425
2446
 
2426
2447
  catalog(role) {