arcane-os 0.8.1 → 0.10.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.
@@ -2,9 +2,9 @@
2
2
 
3
3
  The npm package exposes a Node.js ESM control plane, the portable
4
4
  `arcane-os/event-manager`, `arcane-os/logging`, `arcane-os/mail`,
5
- `arcane-os/preference-store`, `arcane-os/speech-playback`, and
6
- `arcane-os/speech-text` entrypoints, and the browser-only
7
- `arcane-os/ai/browser-wasm` and `arcane-os/ai/browser-speech` entrypoints.
5
+ `arcane-os/preference-store`, `arcane-os/speech-playback`,
6
+ `arcane-os/speech-text`, and `arcane-os/browser-device` entrypoints, and the browser-only
7
+ `arcane-os/pwa`, `arcane-os/ai/browser-wasm` and `arcane-os/ai/browser-speech` entrypoints.
8
8
  Those package subpaths are distinct from application-facing projection modules
9
9
  in the managed browser map, such as `arcane/AIProviderRuntime`,
10
10
  `arcane/AIRuntimeState`, and `arcane/ThemeBootstrap`. Applications use those
@@ -38,6 +38,8 @@ for the installed-inventory-derived physical-runtime contract in SDK `0.5.18`.
38
38
  | `arcane-os/preference-store` | Portable preference records and injected storage adapters. |
39
39
  | `arcane-os/speech-playback` | Portable speech preparation, playback state, and injected media adapters. |
40
40
  | `arcane-os/speech-text` | Speech-input formatting cleanup for complete text and streamed chunks. |
41
+ | `arcane-os/browser-device` | Synchronous mobile or desktop identity hints for application-owned settings. |
42
+ | `arcane-os/pwa` | Nonblocking PWA registration, native worker updates and observable lifecycle state. |
41
43
  | `arcane-os/ai/browser-wasm` | Caller-selected browser-local Wllama inference, complete DBOPFS model storage, streaming, cancellation, and structural tool-call results. |
42
44
  | `arcane-os/ai/browser-speech` | Caller-selected browser-local Whisper STT and Kokoro TTS provider mechanisms, ordinary upstream assets, materialized/native routing, Workers, and cancellation. |
43
45
  | `arcane-os/mail` | Portable Mail runtime, durable outbox, complete transport responses, and provider-neutral acceptance contracts. |
@@ -124,6 +126,9 @@ browser map are cataloged separately in [Runtime modules](runtime-modules.md).
124
126
  | `createBrowserWhisperProvider()` | function | `arcane-os/ai/browser-speech` | Browser speech providers | Browser with Workers, object URLs, caller-selected Whisper runtime/model artifacts, and an SDK DBOPFS speech store |
125
127
  | `createCanonicalUstarHeader()` | function | `arcane-os` | Packaging and release bundles | Node |
126
128
  | `createDbopfsModelStore()` | function | `arcane-os/ai/browser-wasm` | Browser-WASM local AI | Browser with a ready DBOPFS instance and OPFS |
129
+ | `getBrowserDeviceClass()` | function | `arcane-os/browser-device` | Browser device settings | Node and Browser; synchronous identity hint with no model, storage, or GPU operation |
130
+ | `PWA_STATE_EVENT` | constant | `arcane-os/pwa` | Progressive web applications | Browser lifecycle event name; importable without registration |
131
+ | `registerPwa()` | function | `arcane-os/pwa` | Progressive web applications | Browser service workers; synchronous unsupported state when unavailable |
127
132
  | `createDbopfsSpeechArtifactStore()` | function | `arcane-os/ai/browser-speech` | Browser speech providers | Browser with ready DBOPFS, Web Locks, Fetch, File/Blob, and object URLs |
128
133
  | `removeBrowserSpeechModelCache()` | function | `arcane-os/ai/browser-speech` | Browser speech providers | Browser CacheStorage; explicit removal of one selected upstream model |
129
134
  | `createNativeBuildPlan()` | function | `arcane-os` | Targets, native plans, and providers | Node; selected browser/native target or provider as documented |
@@ -6007,6 +6012,84 @@ const source = createBrowserModelSource({
6007
6012
  });
6008
6013
  ```
6009
6014
 
6015
+ ## getBrowserDeviceClass()
6016
+
6017
+ ### Overview
6018
+
6019
+ Returns `mobile` or `desktop` to select application-owned settings. Phones and
6020
+ tablets use `mobile` when browser client hints, platform, user-agent information,
6021
+ or the iPadOS Mac-platform/touch combination identifies them. Missing or
6022
+ unrecognized identity uses `desktop`.
6023
+
6024
+ ### Signature and result
6025
+
6026
+ ```text
6027
+ getBrowserDeviceClass(navigatorObject = globalThis.navigator)
6028
+ ```
6029
+
6030
+ The optional navigator-like object supplies `userAgentData`, `userAgent`,
6031
+ `platform`, and `maxTouchPoints`. The synchronous string result reports a
6032
+ settings class, not hardware capability or readiness.
6033
+
6034
+ ### Availability and normalization
6035
+
6036
+ Import from `arcane-os/browser-device` in Node or a browser. The dependency-free
6037
+ entrypoint reads only the supplied or global browser identity and does not load
6038
+ an AI runtime. No model, GPU, storage, network, or listener operation runs.
6039
+ Missing or unrecognized identity returns `desktop`; browser identity masking
6040
+ can affect classification. Existing provider defaults remain unchanged unless
6041
+ the caller selects and supplies its own profile.
6042
+
6043
+ ### Example
6044
+
6045
+ ```javascript
6046
+ import {getBrowserDeviceClass} from 'arcane-os/browser-device';
6047
+
6048
+ const deviceClass = getBrowserDeviceClass();
6049
+ console.log(deviceClass); // mobile or desktop
6050
+ ```
6051
+
6052
+ ## PWA_STATE_EVENT
6053
+
6054
+ ### Overview
6055
+
6056
+ The event name `arcane.pwa.state` identifies PWA registration and native worker
6057
+ lifecycle state published through the existing Arcane event owner.
6058
+
6059
+ ### Example
6060
+
6061
+ ```javascript
6062
+ import {PWA_STATE_EVENT} from 'arcane-os/pwa';
6063
+ console.log(PWA_STATE_EVENT);
6064
+ ```
6065
+
6066
+ See the [PWA state contract](pwa.md#registerpwa) for complete payload fields and
6067
+ current-state subscriptions. Importing the constant does not register a worker.
6068
+
6069
+ ## registerPwa()
6070
+
6071
+ ### Overview
6072
+
6073
+ `registerPwa({workerUrl = './arcane-sw.js', scope} = {})` starts native browser
6074
+ registration and returns a synchronous owner with `ready`, `state`, `subscribe`,
6075
+ `update` and `dispose`. It does not block page rendering or load models.
6076
+ Unsupported environments receive an explicit unsupported state.
6077
+
6078
+ ### Example
6079
+
6080
+ ```javascript
6081
+ import {registerPwa} from 'arcane-os/pwa';
6082
+ const pwa = registerPwa();
6083
+ pwa.ready.catch(function reportRegistrationError(error) {
6084
+ console.error(error);
6085
+ });
6086
+ ```
6087
+
6088
+ The [PWA guide](pwa.md) defines inputs, native registration results, error state,
6089
+ current-state replay, disposal, offline resource ownership and update behavior.
6090
+ The generated bootstrap registers automatically; a manual caller owns a separate
6091
+ entry point and must observe its `ready` rejection.
6092
+
6010
6093
  ## createBrowserWasmLlmProvider()
6011
6094
 
6012
6095
  ### Overview
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.8.1",
3
+ "version": "0.10.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",
@@ -14,6 +14,8 @@
14
14
  "./events": "./src/events.mjs",
15
15
  "./event-manager": "./src/event-manager.mjs",
16
16
  "./logging": "./browser-runtime/logging.mjs",
17
+ "./browser-device": "./browser-runtime/browser-device.mjs",
18
+ "./pwa": "./browser-runtime/pwa.mjs",
17
19
  "./preference-store": "./runtime/arcane/modules/PreferenceStore.js",
18
20
  "./speech-playback": "./runtime/arcane/modules/SpeechPlayback.js",
19
21
  "./speech-text": "./browser-runtime/speech-text.mjs",
@@ -65,7 +67,7 @@
65
67
  "test": "npm run test:unit && npm run test:functional && npm run test:integration && npm run test:regression",
66
68
  "test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
67
69
  "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",
68
- "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/speech-playback.test.mjs test/site.test.mjs test/update-check.test.mjs",
70
+ "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/dev-pwa.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/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-worker.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/speech-playback.test.mjs test/site.test.mjs test/update-check.test.mjs",
69
71
  "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",
70
72
  "test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/html-import-registration.test.mjs test/logging-regression.test.mjs test/markdown-speech.test.mjs test/prepared-speech.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
71
73
  "check": "node tools/check-source.mjs && npm test",
@@ -19,8 +19,13 @@ let htmlImportScriptId=0;
19
19
  const htmlImportModuleURL=new URL(import.meta.url);
20
20
  const htmlImportAssetVersion=htmlImportModuleURL.searchParams.get('arcaneVersion');
21
21
 
22
+ function pwaResourceUrlsEnabled(){
23
+ return Boolean(document.querySelector?.('script[data-arcane-pwa]'));
24
+ }
25
+
22
26
  function versionComponentResource(value,baseHref){
23
- if(!htmlImportAssetVersion||!is.string(value)||!value||value.startsWith('#')){
27
+ const clean=pwaResourceUrlsEnabled();
28
+ if((!htmlImportAssetVersion&&!clean)||!is.string(value)||!value||value.startsWith('#')){
24
29
  return value;
25
30
  }
26
31
  let resolvedURL;
@@ -46,7 +51,7 @@ function versionComponentResource(value,baseHref){
46
51
  const parameter=new URLSearchParams(field);
47
52
  if(parameter.has('v'))return null;
48
53
  if(!parameter.has('arcaneVersion'))return field;
49
- if(replaced)return null;
54
+ if(clean||replaced)return null;
50
55
  replaced=true;
51
56
  const equals=field.indexOf('=');
52
57
  const key=equals<0?field:field.slice(0,equals);
@@ -54,6 +59,11 @@ function versionComponentResource(value,baseHref){
54
59
  }).filter(function retainQueryField(field){
55
60
  return field!==null;
56
61
  }):[];
62
+ if(clean){
63
+ return fields.some(function hasRemainingField(field){return field!=='';})
64
+ ?`${pathname}?${fields.join('&')}${fragment}`
65
+ :`${pathname}${fragment}`;
66
+ }
57
67
  if(!replaced){
58
68
  if(fields.at(-1)==='')fields[fields.length-1]=versionField;
59
69
  else fields.push(versionField);
@@ -81,7 +91,7 @@ function resolveComponentResource(value,runtimeRoot){
81
91
  }
82
92
 
83
93
  function resolveComponentStyleResources(styleText,runtimeRoot){
84
- if(!runtimeRoot&&!htmlImportAssetVersion)return styleText;
94
+ if(!runtimeRoot&&!htmlImportAssetVersion&&!pwaResourceUrlsEnabled())return styleText;
85
95
  const parts=[];
86
96
  let copiedThrough=0;
87
97
  let index=0;
@@ -40,6 +40,7 @@
40
40
  "entry": { "$ref": "#/$defs/relativePath" },
41
41
  "strategy": { "enum": ["static", "adapter"] },
42
42
  "localAIModelPolicy": { "$ref": "#/$defs/localAIModelPolicy" },
43
+ "pwa": { "$ref": "#/$defs/pwa" },
43
44
  "include": { "$ref": "#/$defs/nonemptyPathList" },
44
45
  "exclude": { "$ref": "#/$defs/pathList" },
45
46
  "shared": {
@@ -224,6 +225,33 @@
224
225
  }
225
226
  ],
226
227
  "$defs": {
228
+ "pwa": {
229
+ "type": "object",
230
+ "additionalProperties": false,
231
+ "properties": {
232
+ "enabled": { "type": "boolean", "default": false },
233
+ "manifest": {
234
+ "type": "object",
235
+ "description": "Application-owned Web App Manifest members, including branding, icons, and presentation."
236
+ },
237
+ "offline": {
238
+ "type": "object",
239
+ "additionalProperties": false,
240
+ "properties": {
241
+ "include": {
242
+ "type": "array",
243
+ "description": "Literal package paths or directories to cache. Omitted or empty selects all emitted files.",
244
+ "items": { "type": "string", "minLength": 1 }
245
+ },
246
+ "exclude": {
247
+ "type": "array",
248
+ "description": "Literal package paths or directories excluded from offline assets. Generated shell records and the entry remain available.",
249
+ "items": { "type": "string", "minLength": 1 }
250
+ }
251
+ }
252
+ }
253
+ }
254
+ },
227
255
  "appId": { "type": "string", "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$" },
228
256
  "presentationText": {
229
257
  "type": "string",
@@ -43,6 +43,9 @@
43
43
  "localAIModelPolicy": {
44
44
  "$ref": "#/$defs/localAIModelPolicy"
45
45
  },
46
+ "pwa": {
47
+ "$ref": "#/$defs/pwa"
48
+ },
46
49
  "security": {
47
50
  "type": "object",
48
51
  "additionalProperties": false,
@@ -117,6 +120,33 @@
117
120
  }
118
121
  ],
119
122
  "$defs": {
123
+ "pwa": {
124
+ "type": "object",
125
+ "additionalProperties": false,
126
+ "properties": {
127
+ "enabled": { "type": "boolean", "default": false },
128
+ "manifest": {
129
+ "type": "object",
130
+ "description": "Application-owned Web App Manifest members, including branding, icons, and presentation."
131
+ },
132
+ "offline": {
133
+ "type": "object",
134
+ "additionalProperties": false,
135
+ "properties": {
136
+ "include": {
137
+ "type": "array",
138
+ "description": "Literal package paths or directories to cache. Omitted or empty selects all emitted files.",
139
+ "items": { "type": "string", "minLength": 1 }
140
+ },
141
+ "exclude": {
142
+ "type": "array",
143
+ "description": "Literal package paths or directories excluded from offline assets. Generated shell records and the entry remain available.",
144
+ "items": { "type": "string", "minLength": 1 }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ },
120
150
  "appId": {
121
151
  "type": "string",
122
152
  "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$"
@@ -2,6 +2,7 @@ import Is from 'strong-type';
2
2
  import {isDeepStrictEqual} from 'node:util';
3
3
  import {readFile} from 'node:fs/promises';
4
4
  import path from 'node:path';
5
+ import {normalizePwaConfig} from './pwa.mjs';
5
6
  import {
6
7
  ARCANE_MACHINE_BUNDLE_VERSION,
7
8
  ARCANE_PROTOCOL,
@@ -107,7 +108,7 @@ function validateLocalAIModelPolicy(value,label){
107
108
 
108
109
  function validatePackage(value,appId){
109
110
  assertOnlyKeys(value,new Set([
110
- 'entry','strategy','include','exclude','shared','adapter','localAIModelPolicy'
111
+ 'entry','strategy','include','exclude','shared','adapter','localAIModelPolicy','pwa'
111
112
  ]),'descriptor.package');
112
113
  const entry=normalizeRelativePath(value.entry,'descriptor.package.entry');
113
114
  if(!['static','adapter'].includes(value.strategy))fail('descriptor.package.strategy must be static or adapter.');
@@ -135,6 +136,7 @@ function validatePackage(value,appId){
135
136
  include,
136
137
  exclude,
137
138
  shared,
139
+ ...(value.pwa===undefined?{}:{pwa:normalizePwaConfig(value.pwa)}),
138
140
  ...(adapter?{adapter}:{}),
139
141
  ...(value.localAIModelPolicy===undefined?{}:{
140
142
  localAIModelPolicy:validateLocalAIModelPolicy(
@@ -320,6 +322,7 @@ export function projectPackageManifest(descriptor){
320
322
  mediaOrigins:[...value.security.mediaOrigins]
321
323
  }}:{}),
322
324
  ...(value.package.localAIModelPolicy?{localAIModelPolicy:value.package.localAIModelPolicy}:{}),
325
+ ...(value.package.pwa===undefined?{}:{pwa:value.package.pwa}),
323
326
  include:[...value.package.include],
324
327
  exclude:[...value.package.exclude],
325
328
  shared:[...value.package.shared],
@@ -378,6 +381,7 @@ function synthesizedDescriptor(packageManifest,nativeDescriptor){
378
381
  entry:packageManifest.entry,
379
382
  strategy:packageManifest.strategy,
380
383
  ...(packageManifest.localAIModelPolicy?{localAIModelPolicy:packageManifest.localAIModelPolicy}:{}),
384
+ ...(packageManifest.pwa===undefined?{}:{pwa:packageManifest.pwa}),
381
385
  include:[...packageManifest.include],
382
386
  exclude:[...(packageManifest.exclude??[])],
383
387
  shared:[...packageManifest.shared],
@@ -426,7 +430,11 @@ export async function loadAppDescriptor({workspaceRoot,appRoot,appId,packageMani
426
430
  if(authored){
427
431
  const descriptor=validateAppDescriptor(authored,{appId});
428
432
  const projection=projectPackageManifest(authored);
429
- if(!isDeepStrictEqual(projection,packageManifest)){
433
+ const packageProjection=packageManifest.pwa===undefined?packageManifest:{
434
+ ...packageManifest,
435
+ pwa:normalizePwaConfig(packageManifest.pwa)
436
+ };
437
+ if(!isDeepStrictEqual(projection,packageProjection)){
430
438
  fail(`${APP_DESCRIPTOR_NAME} does not project exactly to arcane-package.json.`);
431
439
  }
432
440
  return completeValue({descriptor,source:'authored',descriptorPath});