arcane-os 0.72.0 → 0.74.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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.74.0
4
+
5
+ - Add optional `longPaths` to `createRepositoryWorkspace()` for newly cloned
6
+ Windows repositories. Explicit true or false selects clone-local
7
+ `core.longpaths` before the first checkout and persists it in the new target.
8
+ - Preserve inherited and template configuration when the option is omitted,
9
+ existing repositories, non-Windows commands, and the ordinary single-clone
10
+ identity, progress, cancellation and hook lifecycle. No global setting,
11
+ repository migration, filename transformation or dependency change is added.
12
+
13
+ ## 0.73.0
14
+
15
+ - Add public `resolvePwaRetirementResponse({workspaceRoot, appId})` for existing
16
+ custom Node hosts. It supplies the shared retirement worker response only for
17
+ an explicitly disabled standalone PWA whose authored root worker is absent.
18
+ - Preserve raw `enabled: false` selection before descriptor normalization in
19
+ custom-host and SDK source serving. Omitted settings, enabled PWAs, authored
20
+ workers and other routes keep their existing behavior.
21
+ - Retire only the worker's own registration through the existing browser
22
+ lifecycle. Saved data, caches, DBOPFS and open pages remain untouched; no
23
+ forced reload, polling or replacement server is introduced.
24
+
3
25
  ## 0.72.0
4
26
 
5
27
  - Add optional `native.launchContext` defaults for ordinary packaged launch and
package/README.md CHANGED
@@ -20,7 +20,7 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
20
20
  share the theme, packaging, event, cancellation,
21
21
  and browser run contracts while retaining their selected application layout.
22
22
 
23
- This checkout defines the `0.72.0` SDK contract. Development applications declare
23
+ This checkout defines the `0.74.0` SDK contract. Development applications declare
24
24
  `arcane-os` as `latest` and retain their lockfile. Exact SDK pins begin when Roshi
25
25
  designates a production build. Follow [Consumer updates](docs/consumer-updates.md)
26
26
  to refresh and compare the installed SDK and lockfile with the published channel;
@@ -44,6 +44,7 @@ alone does not make bare module names resolve in a browser.
44
44
  | Install an app and cache selected resources offline | [Progressive web applications](pwa.md) |
45
45
  | Disable source PWA generation or explicitly retire confirmed generated output | [Source PWA retirement](pwa.md#disable-source-pwa-generation-and-retire-generated-output) |
46
46
  | Retire the prior worker after explicitly disabling standalone source PWA | [Disabled-worker browser lifecycle](pwa.md#disabled-worker-retirement-on-the-source-server) |
47
+ | Retire a disabled worker through an existing custom source host | [Public PWA retirement response](pwa.md#retirement-through-an-existing-custom-host) |
47
48
  | Generate named browser imports or inspect the selected physical runtime | [`arcane import-map`](cli.md#arcane-import-map) and [browser runtime delivery](protocols.md#browser-runtime-delivery) |
48
49
  | Connect an application through the shared WebSocket transport | [Shared WebSocket clients](websocket-client.md) |
49
50
  | Choose browser, native, cloud, or cross-host behavior | [Availability and normalization](availability-and-normalization.md) |
@@ -54,7 +55,7 @@ alone does not make bare module names resolve in a browser.
54
55
  | Choose theme-switcher corner radii, label font and group background through host CSS | [Theme-switcher styling properties](runtime-components.md#theme-switcherhtml) |
55
56
  | Use optional Core preferences and appearance without masking real failures | [PreferenceStore](runtime-modules.md#preferencestorejs), [SystemAppearance](runtime-modules.md#systemappearancejs), and [Core lookup errors](core-runtime.md#state-and-frames) |
56
57
  | Submit complete speech parts immediately and play them in order | [`SpeechPlayback`](runtime-modules.md#speechplaybackjs) and the [basic browser example](ai/browser-speech.md#play-a-complete-array-with-speechplayback) |
57
- | Preserve exact text in an explicit complete synthesis call | [Exact complete synthesis input](ai/browser-speech.md#exact-complete-synthesis-input) and [managed AI narration](sdk-api.md#managed-ai-narration) |
58
+ | Preserve exact speech text and complete browser Kokoro audio | [Exact complete synthesis input](ai/browser-speech.md#exact-complete-synthesis-input) and [managed AI narration](sdk-api.md#managed-ai-narration) |
58
59
  | Select initial native window dimensions and resizing behavior | [Initial native window size](core-native-packaging.md#initial-native-window-size) and [Windows provider](sdk-api.md#createwindowsnativeprovider) |
59
60
  | Call `globalThis.Arcane` | [Arcane Core API](core/arcane-api.md) |
60
61
  | Connect a browser to one Core transport | [Core browser client](core-client.md) |
@@ -62,8 +63,8 @@ alone does not make bare module names resolve in a browser.
62
63
  | Compose native Core dispatch, service lifecycle, and stdio hosting | [Native Core runtime](core-runtime.md) |
63
64
  | Reuse dependency-ready native services without a renderer | [Core service composition](core-runtime.md#native-service-composition) and [existing ONNX owner](local-ai.md#native-service-owners) |
64
65
  | Compose application Core services during source development, independently of local AI | [Development Core services](core-development.md) |
65
- | Share one application service runtime between its native UI and MCP process | [Shared Core host](core-shared-host.md) |
66
- | Store complete browser model resources through one DBOPFS owner | [Model resources](ai/browser-decisions.md#shared-dbopfs-model-resources) and [speech routing](ai/browser-speech.md#ordinary-module-routing) |
66
+ | Share one application service runtime between its ordinary native launch and MCP process | [Shared Core host](core-shared-host.md) and [native launch defaults](core-native-packaging.md#launch-time-locations) |
67
+ | Store complete browser model resources, including the selected decision-runtime entry, through one DBOPFS owner | [Model resources](ai/browser-decisions.md#shared-dbopfs-model-resources) and [speech routing](ai/browser-speech.md#ordinary-module-routing) |
67
68
  | Observe committed storage changes across live application documents | [DBOPFS change subscriptions](runtime-modules.md#dbopfsjs) |
68
69
  | Cancel cold speech preparation while preserving other activation interests | [Prepared narration](ai/browser-speech.md#prepare-narration-once-and-replay-stored-audio) |
69
70
  | Own an explicitly selected native Codex App Server session | [Codex App Server](codex-app-server.md) |
@@ -86,7 +87,7 @@ alone does not make bare module names resolve in a browser.
86
87
  | Write, commit and push complete selected repository text | [Selected-text repository writing](core-repositories.md#write-commit-and-push-selected-text) |
87
88
  | Observe existing checkout configuration and capture an explicit remote/ref | [Repository configuration and selected targets](core-repositories.md#existing-checkout-configuration-and-selected-targets) |
88
89
  | Build an installed-SDK portable Core payload | [Portable Core packaging](core-native-packaging.md) |
89
- | Pass host-selected state defaults and unchanged explicit launch values to services | [Launch-time locations](core-native-packaging.md#launch-time-locations) |
90
+ | Resolve matching native state/workspace/endpoint defaults and preserve explicit launch values | [Launch-time locations](core-native-packaging.md#launch-time-locations) and [resolveNativeLaunchContext()](sdk-api.md#resolvenativelaunchcontext) |
90
91
  | Build a Windows executable without an OS checkout | [Windows executable packaging](core-native-packaging.md#windows-executable) |
91
92
  | Compose a Mac application with an architecture-matched host | [macOS application composition](core-native-packaging.md#macos-application-composition) |
92
93
  | Subscribe to native events | [Arcane event reference](core/arcane-events.md) |
@@ -47,7 +47,7 @@ const status = await repository.status({signal});
47
47
  await repository.close();
48
48
  ```
49
49
 
50
- `createRepositoryWorkspace({name,directory,dataRoot,remote,branch,gitIdentity,onEvent,run}={})`
50
+ `createRepositoryWorkspace({name,directory,dataRoot,remote,branch,longPaths,gitIdentity,onEvent,run}={})`
51
51
  returns `{directory,open,status,configuration,pull,push,write,close,drain,dispose}`. The `directory`
52
52
  property is the absolute selected working path.
53
53
 
@@ -63,6 +63,11 @@ property is the absolute selected working path.
63
63
  `branch`, when supplied, is passed unchanged to Git's `clone --branch` option;
64
64
  otherwise Git selects the remote's default branch. Existing checkouts keep
65
65
  their own branch, remote, tracked files, untracked files and local changes.
66
+ - Optional `longPaths` is a boolean for newly cloned Windows checkouts. Explicit
67
+ `true` or `false` supplies `clone --config core.longpaths=<value>`, so Git
68
+ writes that choice to the new repository before its first checkout. Omission
69
+ leaves Git's configuration unchanged. The option does not rewrite an existing
70
+ checkout, change a global setting, or alter non-Windows clone commands.
66
71
  - `onEvent` and optional `run` use the existing SDK process owner. The default
67
72
  adapter is `runProcess`; Git must be available on PATH. Credentials and
68
73
  authentication remain with Git and the native host. The SDK adds no download,
@@ -90,6 +95,38 @@ A failed or cancelled clone reports the actual failure; any remaining directory
90
95
  is retained for application-owned inspection. There is no automatic deletion or
91
96
  destructive retry. Filesystem failures and complete Git diagnostics propagate.
92
97
 
98
+ ### Windows long paths for a new checkout
99
+
100
+ Select the option in the application's native repository connection:
101
+
102
+ ```javascript
103
+ const repository = createRepositoryWorkspace({
104
+ name: 'moon-cheese-dispatches',
105
+ remote: applicationConnection.remote,
106
+ longPaths: true
107
+ });
108
+ await repository.open({signal});
109
+ ```
110
+
111
+ This remains one ordinary Git clone, including its branch/tag selection,
112
+ progress, hooks, cancellation and empty-remote behavior. It does not clone
113
+ without checkout and run a second initialization sequence. The boolean is
114
+ captured when the owner is constructed; later edits to the options object
115
+ cannot change an accepted connection.
116
+
117
+ An explicit value intentionally selects the new repository's local
118
+ `core.longpaths` setting. Git's ordinary configuration precedence still applies,
119
+ including higher-priority command-scope settings supplied by the native host.
120
+ Omit the option to retain inherited or template configuration without an SDK
121
+ override. Existing repositories retain their settings even if this option is
122
+ supplied; there is no configuration migration or repair pass.
123
+
124
+ [Git documents clone-local configuration before the initial checkout](https://git-scm.com/docs/git-clone#OPTIONS).
125
+ [Git for Windows describes long-path support](https://gitforwindows.org/git-cannot-create-a-file-or-directory-with-a-long-path.html)
126
+ for its native commands; unrelated editors, scripts and host tools may have
127
+ their own path limitations. This option changes no filenames or payloads and
128
+ does not change the SDK's persistent directory selection.
129
+
93
130
  ## Existing checkout configuration and selected targets
94
131
 
95
132
  `configuration({signal}={})` observes an existing checkout without invoking
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "sdkVersion": "0.71.1",
3
+ "sdkVersion": "0.73.0",
4
4
  "environment": {
5
5
  "runtime": "Node.js for Node entrypoints; browser for browser-only entrypoints",
6
6
  "minimumVersion": "22.23.2 for Node entrypoints",
7
7
  "moduleSystem": "ESM"
8
8
  },
9
- "memberCount": 297,
9
+ "memberCount": 299,
10
10
  "runtimeSubpathPatterns": {
11
11
  "arcane-os/modules/*": "./runtime/arcane/modules/*",
12
12
  "arcane-os/entities/*": "./runtime/arcane/entities/*"
@@ -199,14 +199,14 @@
199
199
  "name": "createRepositoryWorkspace",
200
200
  "displayName": "createRepositoryWorkspace()",
201
201
  "kind": "function",
202
- "signature": "createRepositoryWorkspace({name,directory,dataRoot,remote,branch,gitIdentity,onEvent,run=runProcess}={})",
202
+ "signature": "createRepositoryWorkspace({name,directory,dataRoot,remote,branch,longPaths,gitIdentity,onEvent,run=runProcess}={})",
203
203
  "entrypoints": ["arcane-os/core/repositories"],
204
204
  "primaryImport": "arcane-os/core/repositories",
205
205
  "group": "Native repository workspaces",
206
206
  "summary": "Owns one selected native working checkout with ArcaneData/Repos defaults and existing Git/process lifecycle.",
207
207
  "availability": "Node with Git on Windows, Linux and macOS; Android requires host process adaptation",
208
208
  "protocol": "Git working repository and SDK process adapter",
209
- "normalization": "Returns directory, open, status, configuration, pull, push, write and identical close/drain/dispose hooks. Only missing or empty destinations clone; existing roots are preserved. configuration observes the existing root, symbolic HEAD, origin URL/pushURL arrays and branch remote/merge/upstream URL arrays without clone/init/fetch, preserving unset, empty and repeated values. Explicit paths remain selected. Same-directory operations serialize within the process; cancellation and shutdown join accepted process work. pull/push/write accept a captured target {remote,ref} for current-branch fast-forward pull or non-force current-HEAD selected-ref push; omission retains ordinary configured behavior. write snapshots exact text and paths, stages/commits only literal owned paths and preserves unrelated staged files and Git filters. Complete outputs and causal failures retain confirmed local/committed/pushed and uncertain outcomes plus the supplied target. Optional gitIdentity captures name/email/username for child Git operations only; omitted fields remain inherited and no config file is written."
209
+ "normalization": "Returns directory, open, status, configuration, pull, push, write and identical close/drain/dispose hooks. Only missing or empty destinations clone; existing roots are preserved. Optional longPaths captures a boolean: explicit Windows true/false supplies clone-local core.longpaths before the first checkout, persisting only in the new repository under ordinary Git configuration precedence. Omission preserves configuration; existing repositories and non-Windows clone commands remain unchanged. No global setting or migration. configuration observes the existing root, symbolic HEAD, origin URL/pushURL arrays and branch remote/merge/upstream URL arrays without clone/init/fetch, preserving unset, empty and repeated values. Explicit paths remain selected. Same-directory operations serialize within the process; cancellation and shutdown join accepted process work. pull/push/write accept a captured target {remote,ref} for current-branch fast-forward pull or non-force current-HEAD selected-ref push; omission retains ordinary configured behavior. write snapshots exact text and paths, stages/commits only literal owned paths and preserves unrelated staged files and Git filters. Complete outputs and causal failures retain confirmed local/committed/pushed and uncertain outcomes plus the supplied target. Optional gitIdentity captures name/email/username for child Git operations only; omitted fields remain inherited and identity selection writes no config file."
210
210
  },
211
211
  {
212
212
  "id": "core-repositories:readGitIdentity",
@@ -542,7 +542,7 @@
542
542
  "summary": "Explicit-load Laya and Julia-1 decisions with caller-selected precision, complete inputs and graph results.",
543
543
  "availability": "Browser module Workers and the selected Transformers.js backend; import/construction starts no model load",
544
544
  "protocol": "Caller-owned state/question/options rows; five-input decision graph; native Worker request/result lifecycle",
545
- "normalization": "Full field tokenization, raw logits and unrounded softmax; dtype defaults to fp16 for Laya and fp32 for Julia, with explicit values forwarded unchanged. Optional existing SDK store routes actual loader resources through fetchResource outside cloned Worker configuration; stored mode disables competing model caches, preserves complete HTTP responses and joins resource cleanup before outstanding calls settle. Omission retains upstream loading; no decision-result persistence or precision fallback"
545
+ "normalization": "Full field tokenization, raw logits and unrounded softmax; dtype defaults to fp16 for Laya and fp32 for Julia, with explicit values forwarded unchanged. Optional existing SDK store routes the complete selected runtime entry and actual loader resources through fetchResource outside cloned Worker configuration. Each activation materializes unchanged entry source with JavaScript MIME and retains its original configured URL for later loads; its object URL is revoked after Worker termination. Stored mode disables competing model caches, preserves complete HTTP responses and joins runtime-entry/resource cleanup before outstanding calls settle. Runtime-entry HTTP failures retain error.response. Custom stored entries must support an object URL and changed import.meta.url; native absolute dependencies and custom factory imports outside upstream preloads remain browser-owned. Omission retains direct native module and upstream model loading; no decision-result persistence or precision fallback"
546
546
  },
547
547
  {
548
548
  "id": "root:acceptWebSocket",
@@ -2208,6 +2208,20 @@
2208
2208
  "protocol": "arcane-target-adapter/1, arcane-native-build-plan/1, or provider protocol",
2209
2209
  "normalization": "Normalized complete plan and result; target-specific artifact detail preserved"
2210
2210
  },
2211
+ {
2212
+ "id": "root:resolvePwaRetirementResponse",
2213
+ "name": "resolvePwaRetirementResponse",
2214
+ "displayName": "resolvePwaRetirementResponse()",
2215
+ "kind": "function",
2216
+ "signature": "async resolvePwaRetirementResponse({workspaceRoot=process.cwd(), appId}={})",
2217
+ "entrypoints": ["arcane-os"],
2218
+ "primaryImport": "arcane-os",
2219
+ "group": "Workspace, doctor, repository, and server",
2220
+ "summary": "Resolves the shared disabled-PWA root worker response for an existing custom source host, preserving authored workers and browser saved data.",
2221
+ "availability": "Node; browser service-worker lifecycle remains browser-owned",
2222
+ "protocol": "Node ESM and native service-worker lifecycle",
2223
+ "normalization": "Current standalone descriptor and absent-root-worker selection; complete status/headers/body or null"
2224
+ },
2211
2225
  {
2212
2226
  "id": "root:resolveWorkspace",
2213
2227
  "name": "resolveWorkspace",
@@ -3708,7 +3722,7 @@
3708
3722
  "summary": "Creates a local-only Kokoro text-to-speech provider for the provider-neutral AI runtime.",
3709
3723
  "availability": "Browser with DBOPFS, Web Locks, Workers, object URLs, and caller-selected kokoro-js runtime/model content",
3710
3724
  "protocol": "arcane-ai-provider/2 tts/synthesize",
3711
- "normalization": "Independent load, status, request, unload, cancellation and disposal; optional ordinary model.voices records remain complete in the synchronous catalog without loading or constraining synthesis. Provider-native requests return 24000 Hz Float32Array PCM, while shared AI.js requests use wav and return complete Uint8Array audio/wav data; never selects a cloud fallback"
3725
+ "normalization": "Independent load, status, request, unload, cancellation and disposal; optional ordinary model.voices records remain complete in the synchronous catalog without loading or constraining synthesis. Upstream text normalization and phonemization remain selected-runtime behavior. Tokenization disables truncation; short inputs use one inference and longer inputs send every content token through ordered engine-capacity segments, retaining required outer padding and all generated audio in order. Segments share the slot's existing model and disposal owner. Request-local cancellation is observed between segments and before returning audio; an already-running upstream inference may finish. Provider-native requests return 24000 Hz Float32Array PCM, while shared AI.js requests use wav and return complete Uint8Array audio/wav data; never selects a cloud fallback"
3712
3726
  },
3713
3727
  {
3714
3728
  "id": "browser-speech:createBrowserSpeechArtifactGraph",
@@ -4530,19 +4544,33 @@
4530
4544
  "protocol": "arcane/1 preferences methods and schemaVersion 1 entries file",
4531
4545
  "normalization": "Binding identity is exactly equal to the named createPreferencesService export"
4532
4546
  },
4547
+ {
4548
+ "id": "core-host:resolveNativeLaunchContext",
4549
+ "name": "resolveNativeLaunchContext",
4550
+ "displayName": "resolveNativeLaunchContext()",
4551
+ "kind": "function",
4552
+ "signature": "resolveNativeLaunchContext({appId,context={}}={})",
4553
+ "entrypoints": ["arcane-os/core/host"],
4554
+ "primaryImport": "arcane-os/core/host",
4555
+ "group": "Native Core host composition",
4556
+ "summary": "Resolves matching per-app native state, workspace and optional shared endpoint defaults for packaged UI and external MCP.",
4557
+ "availability": "Node native locations on Windows/Linux/macOS; Android and other hosts supply stateRoot and native adaptation",
4558
+ "protocol": "Pure native launch-context resolution and existing local IPC",
4559
+ "normalization": "Returns a new complete context with only omitted stateRoot/workspaceRoot and selected sharedHost endpoint/log fields filled; preserves explicit values and additional payloads. Native launchContext presence opts generated entry into resolution; omitted sharedHost retains stdio. No I/O, saved-state discovery, process startup, migration or ArcaneData/Repos change. Malformed selected appId/stateRoot/sharedHost inputs report TypeError; platform IPC errors remain operation-owned."
4560
+ },
4533
4561
  {
4534
4562
  "id": "core-host:readCoreLaunchContext",
4535
4563
  "name": "readCoreLaunchContext",
4536
4564
  "displayName": "readCoreLaunchContext()",
4537
4565
  "kind": "function",
4538
- "signature": "async readCoreLaunchContext({argv=process.argv.slice(2)}={})",
4566
+ "signature": "async readCoreLaunchContext({argv=process.argv.slice(2),defaults={},appId}={})",
4539
4567
  "entrypoints": ["arcane-os/core/host"],
4540
4568
  "primaryImport": "arcane-os/core/host",
4541
4569
  "group": "Native Core host composition",
4542
- "summary": "Reads explicit launch JSON with the separately supplied native host stateRoot default for service launch context.",
4570
+ "summary": "Combines packaged defaults, actual native host stateRoot and complete explicit launch JSON, with optional native location resolution.",
4543
4571
  "availability": "Node, including the selected external-entry SEA launcher",
4544
4572
  "protocol": "Explicit launch arguments and JSON configuration",
4545
- "normalization": "Optional --arcane-host-state-root <directory> supplies the stateRoot default; every explicit --arcane-launch-config JSON field takes precedence unchanged, including relative or null stateRoot and application-owned preferencesFile. Returns an empty record with neither argument; surfaces missing argument values, file and JSON failures; performs no directory selection, path resolution, configuration search, rewrite, migration or environment mutation"
4573
+ "normalization": "Precedence is defaults, then --arcane-host-state-root, then complete top-level --arcane-launch-config JSON fields; nested values replace without deep merging or payload rewriting. Explicit appId invokes resolveNativeLaunchContext; ordinary calls retain relative/null/custom state values and return an empty record with no inputs. Missing arguments, file/JSON errors and malformed records reject. No configuration search, file rewrite, migration or environment mutation; generated native.launchContext explicitly selects the resolver."
4546
4574
  },
4547
4575
  {
4548
4576
  "id": "core-host:startCoreHost",
@@ -207,6 +207,66 @@ The server path uses the existing portable Node serving owner, and browser
207
207
  retirement requires the browser's native service-worker support and an existing
208
208
  registration eligible for that update.
209
209
 
210
+ ### Retirement through an existing custom host
211
+
212
+ An application that already serves its source through `node-http-server` can
213
+ keep that server and delegate only its root worker route to the public SDK
214
+ representation owner:
215
+
216
+ ```js
217
+ import {Server} from 'node-http-server';
218
+ import {resolvePwaRetirementResponse} from 'arcane-os';
219
+
220
+ // This is the application's existing subclass and workspace root.
221
+ class DragonMailServer extends Server {
222
+ async onRequest(request, response) {
223
+ const pathname = new URL(request.originalUrl || request.url, 'http://localhost').pathname;
224
+ if ((request.method === 'GET' || request.method === 'HEAD') && pathname === '/arcane-sw.js') {
225
+ const retirement = await resolvePwaRetirementResponse({
226
+ workspaceRoot: this.workspaceRoot,
227
+ appId: 'dragon-mail'
228
+ });
229
+ if (retirement) {
230
+ response.statusCode = retirement.statusCode;
231
+ for (const [name, value] of Object.entries(retirement.headers)) {
232
+ response.setHeader(name, value);
233
+ }
234
+ await this.serve(request, response, retirement.body);
235
+ return true;
236
+ }
237
+ }
238
+ // Keep the application's other routes here before ordinary static serving.
239
+ return false;
240
+ }
241
+ }
242
+ ```
243
+
244
+ Use the application's actual root and ID. The SDK reads the current
245
+ `arcane-packager.json` and, for standalone `appsRoot:"."`, the authored root
246
+ `arcane-app.json`; only an absent authored descriptor selects
247
+ `arcane-package.json`. Reads use the existing descriptor projection and package
248
+ contract without rewriting either file or requiring a stale generated projection
249
+ to equal a newly edited authored descriptor. `appId` is optional; when supplied,
250
+ it must match that descriptor's ID.
251
+
252
+ The result is `null` for multi-app workspaces, omitted or enabled PWA, an omitted
253
+ `pwa.enabled` field (including `{pwa:{}}`), and any existing root `arcane-sw.js`
254
+ entry. The author's explicit `pwa.enabled:false` choice and an `ENOENT`
255
+ worker result produce `{statusCode, headers, body}`: status `200`, the shared
256
+ JavaScript content type, `Cache-Control: no-cache`, and the complete existing
257
+ retirement script. Malformed configuration and other filesystem failures reject
258
+ instead of selecting another descriptor or treating a file as absent.
259
+
260
+ The host applies those headers directly; it must not replace `no-cache` with
261
+ its unrelated response defaults or treat a former enabled worker's validator as
262
+ validation of this response. The existing `serve()` method owns response
263
+ completion and HEAD body omission. Keep this exact worker route at the serving
264
+ origin that owns the registration; application HTTPS redirects and other routes
265
+ remain application-owned. This API starts no server, writes no file, performs
266
+ no offline-inventory scan, and touches no browser data. It shares the same
267
+ response policy and worker generator as the SDK source server. The native
268
+ activation and existing-page lifetime limits above also apply here.
269
+
210
270
  ### Authored host root and app entry
211
271
 
212
272
  An authored `index.html` at the source host root keeps `/` separate from the
@@ -57,7 +57,7 @@ runtime layouts.
57
57
  | `arcane-os/core/runtime` | App-neutral native Core dispatcher and service lifecycle. |
58
58
  | `arcane-os/core/development` | App-service composition and document-owned Core transport during source development, independently of local AI. |
59
59
  | `arcane-os/core/stdio` | Framed Node stdio transport with graceful runtime drain. |
60
- | `arcane-os/core/host` | Node-only reusable Core host lifecycle with explicitly supplied services. |
60
+ | `arcane-os/core/host` | Node-only reusable Core host lifecycle, app-selected native launch defaults and shared native location resolution. |
61
61
  | `arcane-os/codex/app-server` | Explicit native Codex App Server session, complete live turns, server requests and recovery. |
62
62
  | `arcane-os/mcp/stdio` | Node MCP STDIO server for app-owned tools and static resources. |
63
63
  | `arcane-os/core/repositories` | Native persistent ArcaneData paths, connected working repositories and scoped non-secret Git identity configuration. |
@@ -319,6 +319,7 @@ The remaining data-only subpaths are eight JSON Schemas and package metadata.
319
319
  | `repositoryPush()` | function | `arcane-os` | Workspace, doctor, repository, and server | Node; network-assisted Git |
320
320
  | `repositoryStatus()` | function | `arcane-os` | Workspace, doctor, repository, and server | Node |
321
321
  | `resolveNativeBuildOutputRoot()` | function | `arcane-os` | Targets, native plans, and providers | Node; selected browser/native target or provider as documented |
322
+ | `resolvePwaRetirementResponse()` | function | `arcane-os` | Workspace, doctor, repository, and server | Node; browser service-worker lifecycle remains browser-owned |
322
323
  | `resolveWorkspace()` | function | `arcane-os` | Workspace, doctor, repository, and server | Node |
323
324
  | `ROOT_CONFIG_NAME` | constant | `arcane-os/packager` | Packaging and release bundles | Node |
324
325
  | `discoverApps()` | function | `arcane-os` | Workspace, doctor, repository, and server | Node |
@@ -429,7 +430,8 @@ The remaining data-only subpaths are eight JSON Schemas and package metadata.
429
430
  | `connectSharedCoreHost()` | function | `arcane-os/core/host` | Shared Core host | Node local IPC and optional explicit headless startup |
430
431
  | `runSharedCoreHost()` | function | `arcane-os/core/host` | Shared Core host | Node headless entry and optional parent startup IPC |
431
432
  | `startSharedCoreBridge()` | function | `arcane-os/core/host` | Shared Core host | Node readable/writable streams and local IPC |
432
- | `readCoreLaunchContext()` | function | `arcane-os/core/host` | Native Core host composition | Node; reads explicitly selected launch JSON |
433
+ | `readCoreLaunchContext()` | function | `arcane-os/core/host` | Native Core host composition | Node; combines packaged defaults, host state and explicitly selected launch JSON |
434
+ | `resolveNativeLaunchContext()` | function | `arcane-os/core/host` | Native Core host composition | Node native paths; Android supplies its adapted state root |
433
435
  | `resolveArcaneDataPaths()` | function | `arcane-os/core/repositories` | Native repository workspaces | Node on Windows, Linux and macOS; Android supplies its data root |
434
436
  | `createRepositoryWorkspace()` | function | `arcane-os/core/repositories` | Native repository workspaces | Node with Git; Android requires host process adaptation |
435
437
  | `readGitIdentity()` | function | `arcane-os/core/repositories` | Native repository workspaces | Node with Git; Android requires host process adaptation |
@@ -4205,6 +4207,39 @@ There is no history scan, app loop, diagnostic payload store or automatic retry.
4205
4207
  The authored `test/websocket.test.mjs` fixtures cover this boundary; source
4206
4208
  inspection is distinct from executing them or verifying a browser deployment.
4207
4209
 
4210
+ ## resolvePwaRetirementResponse()
4211
+
4212
+ ```text
4213
+ async resolvePwaRetirementResponse({workspaceRoot=process.cwd(), appId}={})
4214
+ ```
4215
+
4216
+ Import from `arcane-os`. This Node API supplies the shared disabled-PWA worker
4217
+ response to an application's existing server without starting or replacing it.
4218
+ It reads the current standalone workspace configuration and root authored
4219
+ descriptor, using the package descriptor only when the authored file is absent.
4220
+ The optional `appId` must match the selected descriptor. It uses the existing
4221
+ configuration contracts, performs no writes and rereads current configuration
4222
+ on each call; there is no retained cache, timer, subscription or dispose handle.
4223
+
4224
+ It returns `null` for a non-standalone workspace, omitted or enabled PWA, an
4225
+ omitted `pwa.enabled` field, or an existing root `arcane-sw.js` entry. The author's
4226
+ explicit `pwa.enabled:false` choice plus an `ENOENT` root
4227
+ worker produces `{statusCode:200, headers, body}`, with the existing JavaScript
4228
+ content type, `Cache-Control: no-cache`, and the complete retirement script.
4229
+ Configuration/JSON errors and other filesystem failures reject. The result
4230
+ carries no prior worker `Last-Modified` validator.
4231
+
4232
+ The caller owns its exact GET/HEAD `/arcane-sw.js` route and applies the supplied
4233
+ status, headers and body through its existing `node-http-server` public
4234
+ `serve()` path. A `null` result leaves normal routing intact. There is no request
4235
+ payload transformation, server listener, generated file, cache/storage cleanup,
4236
+ client enumeration or reload. The worker skips waiting on installation and
4237
+ unregisters its own registration on activation; existing-page controller timing
4238
+ remains browser-owned. The [custom-host PWA guide](pwa.md#retirement-through-an-existing-custom-host)
4239
+ shows the complete hook and preservation contract. The Node filesystem path is
4240
+ portable across Windows, Linux and macOS; Android needs its normal Node host
4241
+ adaptation, and browser retirement requires native service-worker support.
4242
+
4208
4243
  ## startDevServer()
4209
4244
 
4210
4245
  ### Overview
@@ -7925,29 +7960,90 @@ The default binding from `arcane-os/core/diarization` is exactly the named
7925
7960
  import createDiarizationService from 'arcane-os/core/diarization';
7926
7961
  ```
7927
7962
 
7963
+ ## resolveNativeLaunchContext()
7964
+
7965
+ ### Overview
7966
+
7967
+ Resolves one application's native state, workspace and optional shared endpoint
7968
+ through the same public owner used by its packaged executable and external MCP.
7969
+ This synchronous function performs no I/O, saved-state discovery, process start
7970
+ or migration.
7971
+
7972
+ ### Signature and result
7973
+
7974
+ ```text
7975
+ resolveNativeLaunchContext({appId,context={}}={})
7976
+ ```
7977
+
7978
+ Import from `arcane-os/core/host`. The result is a new complete context with
7979
+ omitted fields filled. Windows defaults to `%LOCALAPPDATA%/Arcane/<appId>`
7980
+ (home `AppData/Local` fallback), macOS to
7981
+ `~/Library/Application Support/Arcane/<appId>`, and portable Linux Node to
7982
+ absolute `$XDG_DATA_HOME/Arcane/<appId>` or `~/.local/share/Arcane/<appId>`.
7983
+ Android and other adapted hosts provide their actual `stateRoot` and own native
7984
+ IPC/process adaptation.
7985
+
7986
+ An omitted `workspaceRoot` becomes `<absolute selected stateRoot>/Workspace`.
7987
+ Only a selected `sharedHost` object receives a default endpoint and log file:
7988
+ Windows uses a named pipe containing the URL-encoded complete absolute state
7989
+ path; Linux/macOS use `<absolute selected stateRoot>/core.sock`. The log is
7990
+ `<absolute selected stateRoot>/Core.log`. Explicit fields, additional payloads
7991
+ and explicit workspace values remain unchanged. Relative explicit paths retain
7992
+ the caller's working-directory meaning. `appId` and the selected state path
7993
+ must be nonempty strings; a selected `sharedHost` must be an object. Malformed
7994
+ inputs report `TypeError`; native endpoint failures belong to the IPC operation.
7995
+
7996
+ ```javascript
7997
+ import {resolveNativeLaunchContext} from 'arcane-os/core/host';
7998
+
7999
+ const context = resolveNativeLaunchContext({
8000
+ appId: 'moon-cheese-post',
8001
+ context: {sharedHost: {}}
8002
+ });
8003
+ // Use context.sharedHost.endpoint for the native UI and external MCP.
8004
+ // An explicit context.workspaceRoot or context.stateRoot remains selected.
8005
+ ```
8006
+
8007
+ In a complete app descriptor, `native.launchContext:{sharedHost:{}}` selects
8008
+ these defaults for ordinary packaged executable launch. Omitting `sharedHost`
8009
+ keeps stdio-owned lifetime; omitting `native.launchContext` preserves the prior
8010
+ generated-entry path. The selected shared entry creates its state directory
8011
+ for socket/log use; application services own workspace I/O. MCP-first startup
8012
+ prepares that returned state directory before the existing `start` option opens
8013
+ its log. No caller needs to reconstruct platform paths. The separate
8014
+ `resolveArcaneDataPaths()` repository-location contract remains unchanged.
8015
+ See [launch-time locations](core-native-packaging.md#launch-time-locations) and
8016
+ [shared native/MCP hosting](core-shared-host.md#native-launch-and-platforms).
8017
+
7928
8018
  ## readCoreLaunchContext()
7929
8019
 
7930
8020
  ### Overview
7931
8021
 
7932
- Reads the complete JSON object explicitly selected by
7933
- `--arcane-launch-config <path>`, with an optional separately supplied native host
7934
- state-directory default. It does not search for application configuration.
8022
+ Combines complete packaged defaults, a separately supplied native host state
8023
+ directory and the complete JSON object selected by `--arcane-launch-config`.
8024
+ An explicit `appId` selects the native resolver; ordinary calls remain a
8025
+ configuration reader without automatic location selection.
7935
8026
 
7936
8027
  ### Signature and result
7937
8028
 
7938
8029
  ```text
7939
- async readCoreLaunchContext({argv=process.argv.slice(2)}={})
8030
+ async readCoreLaunchContext({argv=process.argv.slice(2),defaults={},appId}={})
7940
8031
  ```
7941
8032
 
7942
- Import it from `arcane-os/core/host`. `--arcane-host-state-root <directory>`
7943
- supplies a `stateRoot` default; every field from the explicit launch JSON object
7944
- then takes precedence unchanged, including a relative, null or other explicitly
7945
- present `stateRoot`. With neither argument, the promise resolves to `{}`.
7946
- A missing argument value, unreadable launch file, malformed JSON or non-object
7947
- value rejects. Relative launch-file paths use the process working directory.
7948
- The reader does not choose, resolve or validate the state directory, rewrite
7949
- content, discover or migrate stored data, or mutate the environment. An
7950
- application-selected `preferencesFile` remains a separate unchanged field.
8033
+ Import from `arcane-os/core/host`. Precedence is `defaults`, then
8034
+ `--arcane-host-state-root <directory>`, then every complete top-level field from
8035
+ the explicit launch JSON object. Nested fields replace rather than deep-merge.
8036
+ The original file and its payload remain unchanged. Supplying `appId` applies
8037
+ `resolveNativeLaunchContext` to that merged record; its defaults and input
8038
+ contract then apply. Without `appId`, relative, null and other explicit state
8039
+ values retain the prior reader semantics. With no arguments or defaults, the
8040
+ ordinary call still resolves to `{}`.
8041
+
8042
+ Missing argument values, unreadable files, malformed JSON, non-object defaults
8043
+ or launch records reject. Relative launch-file paths use the working directory.
8044
+ The reader searches no other configuration, rewrites no content, migrates no
8045
+ data and changes no environment. Application-owned fields such as
8046
+ `preferencesFile` retain their complete values.
7951
8047
 
7952
8048
  ```javascript
7953
8049
  import {readCoreLaunchContext} from 'arcane-os/core/host';
@@ -7956,12 +8052,13 @@ const launchContext = await readCoreLaunchContext();
7956
8052
  ```
7957
8053
 
7958
8054
  The generated entry supplies artifact-derived `appRoot`, then applies the whole
7959
- explicit context and passes the same object as every service factory's second
7960
- argument. Authored service options remain the first argument. Workspace/state
7961
- locations and additional fields belong to the launcher/application. Windows and
7962
- Mac launchers forward their actual host-selected state directory through the
7963
- separate argument; other composing hosts may use the same seam. This JavaScript
7964
- contract does not establish a compiled host or matching release archive. See
8055
+ context and passes the same object as every service factory's second argument.
8056
+ Authored service options remain the first argument. It supplies the descriptor's
8057
+ `native.launchContext` as `defaults` and the app ID only when that optional
8058
+ field is present. Matching Windows and macOS launchers apply the same packaged
8059
+ defaults before explicit launch JSON and forward their actual state directory;
8060
+ other composing hosts can use the same public seam. Compiled host availability
8061
+ and native execution are separate from this JavaScript contract. See
7965
8062
  [launch-time locations](core-native-packaging.md#launch-time-locations).
7966
8063
 
7967
8064
  ## resolveArcaneDataPaths()
@@ -7999,7 +8096,7 @@ explicit existing path. The application owns the remote, branch and Core API.
7999
8096
  ### Signature and result
8000
8097
 
8001
8098
  ```text
8002
- createRepositoryWorkspace({name,directory,dataRoot,remote,branch,gitIdentity,onEvent,run=runProcess}={})
8099
+ createRepositoryWorkspace({name,directory,dataRoot,remote,branch,longPaths,gitIdentity,onEvent,run=runProcess}={})
8003
8100
  ```
8004
8101
 
8005
8102
  Returns `{directory,open,status,configuration,pull,push,write,close,drain,dispose}`. Construction does
@@ -8010,6 +8107,14 @@ work and preserves the checkout. No CLI cwd, launch state, preference, snapshot
8010
8107
  cache or existing user-data location changes. See the [complete inputs, results,
8011
8108
  errors and lifecycle](core-repositories.md#one-connected-working-checkout).
8012
8109
 
8110
+ Optional `longPaths` captures a boolean for new Windows clones. Explicit true
8111
+ or false supplies clone-local `core.longpaths` before the first checkout and
8112
+ persists it only in the new repository. Omission preserves inherited/template
8113
+ configuration. Existing repositories and non-Windows clone commands remain
8114
+ unchanged; no global setting or data migration is performed. Git's ordinary
8115
+ configuration precedence, hooks and single-clone lifecycle remain in effect.
8116
+ See [Windows long paths](core-repositories.md#windows-long-paths-for-a-new-checkout).
8117
+
8013
8118
  Optional `gitIdentity:{name?,email?,username?}` captures explicit non-secret
8014
8119
  settings for child Git operations without writing global/local configuration.
8015
8120
  Selected author fields override inherited child author/committer fields;
@@ -9550,13 +9655,26 @@ No decisions are inserted into chat history or durable storage.
9550
9655
 
9551
9656
  Optional `store` is an existing SDK DBOPFS model or speech store exposing
9552
9657
  `fetchResource(input, options)`. It remains outside cloned Worker configuration.
9553
- The selected loader routes its actual model/configuration/tokenizer resources
9554
- through that owner and disables competing model caches in this explicit mode.
9555
- Omission preserves ordinary upstream loading. Native ESM imports retain their
9556
- platform loader. Resource progress counts closed shards; cancellation joins
9557
- resource cleanup before outstanding `load()`/`evaluate()` calls settle.
9658
+ Explicit activation first stores the complete selected `runtime.moduleUrl`
9659
+ entry and materializes it as a JavaScript object URL without rewriting source.
9660
+ The configured original URL remains the resource selection for later loads.
9661
+ The selected loader also routes its actual model/configuration/tokenizer
9662
+ resources through that owner and disables competing model caches in this mode.
9663
+ Each activation revokes its runtime object URL after terminating its Worker on
9664
+ cancellation, failure, unload or disposal. Resource progress counts closed
9665
+ shards; cancellation joins runtime-entry and resource cleanup before outstanding
9666
+ `load()`/`evaluate()` calls settle. Runtime-entry HTTP failures report
9667
+ `ARCANE_DECISION_RUNTIME_DOWNLOAD_FAILED` with the complete stored response in
9668
+ `error.response` for developer diagnostics.
9558
9669
  `unload()` and `dispose()` acknowledge their state transition synchronously.
9559
9670
 
9671
+ The default runtime entry is self-contained. Custom stored entries must work
9672
+ from an object URL: entry materialization changes `import.meta.url` and does not
9673
+ rewrite relative imports or original-location support-file references. Native
9674
+ absolute ESM dependencies and custom factory imports outside upstream preloads
9675
+ remain browser-owned. Omitting `store` preserves direct native module loading
9676
+ and ordinary upstream model loading. See [shared model resources](ai/browser-decisions.md#shared-dbopfs-model-resources).
9677
+
9560
9678
  ### Example
9561
9679
 
9562
9680
  ```js
@@ -10442,6 +10560,18 @@ mono 16-bit PCM. Unsupported formats fail
10442
10560
  `ARCANE_AI_UNSUPPORTED_RESPONSE_FORMAT`; malformed adapter audio fails
10443
10561
  `ARCANE_AI_INVALID_PROVIDER_RESULT`. Unknown fields and accessors reject as malformed.
10444
10562
 
10563
+ The Kokoro adapter retains upstream text normalization and phonemization, then
10564
+ tokenizes without truncation and sends every content token through the selected
10565
+ engine. Short inputs use one inference; longer inputs use ordered segments
10566
+ within that engine's capacity and return all generated audio in order. Each
10567
+ segment retains the engine's required outer padding, voice-style selection and
10568
+ speed. The segments share the slot's existing loaded model and disposal owner;
10569
+ they do not create another model or provider-pool slot. Cancellation is checked
10570
+ between inference segments and before returning audio; already-running upstream
10571
+ inference may finish. This engine transport is distinct from the SDK's optional
10572
+ `textFormat:'plain'` input treatment. See [complete synthesis input](ai/browser-speech.md#exact-complete-synthesis-input)
10573
+ for the selected tokenizer/model contract and upstream revision behavior.
10574
+
10445
10575
  Automatic execution tries a complete WebNN NPU pool when
10446
10576
  `navigator.ml.createContext` is exposed, then WebGPU when `navigator.gpu` is
10447
10577
  exposed, then CPU through WASM. A failed candidate is cleaned up before a fresh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.72.0",
3
+ "version": "0.74.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",
@@ -45,7 +45,7 @@ export function resolveArcaneDataPaths({dataRoot} = {}) {
45
45
 
46
46
  /** One connected checkout, composed into an application's own Core service. */
47
47
  export function createRepositoryWorkspace(
48
- {name, directory, dataRoot, remote, branch, gitIdentity, onEvent, run = runProcess} = {}
48
+ {name, directory, dataRoot, remote, branch, longPaths, gitIdentity, onEvent, run = runProcess} = {}
49
49
  ) {
50
50
  let selected = directory;
51
51
  if (selected === undefined) {
@@ -58,6 +58,9 @@ export function createRepositoryWorkspace(
58
58
  requireString(selected, 'directory');
59
59
  if (remote !== undefined) requireString(remote, 'remote');
60
60
  if (branch !== undefined) requireString(branch, 'branch');
61
+ if (longPaths !== undefined && !is.boolean(longPaths)) {
62
+ throw new TypeError('longPaths must be a boolean when supplied.');
63
+ }
61
64
  if (!is.function(run)) throw new TypeError('run must implement the SDK process adapter.');
62
65
  const execute = createGitIdentityRunner(run, gitIdentity);
63
66
  const repositoryDirectory = path.resolve(selected);
@@ -100,6 +103,11 @@ export function createRepositoryWorkspace(
100
103
  await mkdir(path.dirname(repositoryDirectory), {recursive: true});
101
104
  throwIfAborted(signal);
102
105
  const arguments_ = ['clone', '--progress'];
106
+ // Clone-local configuration persists before the first checkout. Omission
107
+ // leaves inherited settings alone; existing repositories never reach here.
108
+ if (process.platform === 'win32' && longPaths !== undefined) {
109
+ arguments_.push('--config', `core.longpaths=${longPaths}`);
110
+ }
103
111
  if (branch !== undefined) arguments_.push('--branch', branch);
104
112
  arguments_.push('--', remote, repositoryDirectory);
105
113
  const result = await prepareRun(
@@ -17,7 +17,7 @@ import {
17
17
  readWorkspaceAssetVersion,resolveAssetReference,rewriteAssetReferences,versionAssetUrl
18
18
  } from './import-map.mjs';
19
19
  import {createPwaArtifacts,selectPwaFiles} from './pwa.mjs';
20
- import {createPwaRetirementWorkerScript} from './pwa-worker.mjs';
20
+ import {createPwaRetirementResponse} from './pwa-retirement.mjs';
21
21
 
22
22
  const is = new Is(false);
23
23
 
@@ -1074,6 +1074,7 @@ async function startOwnedDevServer({
1074
1074
  currentSourceRoutes = {
1075
1075
  ...routeSet,
1076
1076
  app: manifest,
1077
+ pwaExplicitlyDisabled: (authored ? value.package?.pwa : value.pwa)?.enabled === false,
1077
1078
  rootDocument,
1078
1079
  startPath: applicationSourcePath(routeSet.config,routeSet.appId,manifest.entry),
1079
1080
  mappings: mappings.map(
@@ -1303,26 +1304,18 @@ async function startOwnedDevServer({
1303
1304
  return;
1304
1305
  }
1305
1306
  if (mode === 'source' && selectedRoutes.config.appsRoot === '.'
1306
- && selectedRoutes.app?.pwa?.enabled === false && target.path === '/arcane-sw.js') {
1307
- let rootWorkerExists = true;
1308
- try {
1309
- await lstat(path.join(selectedRoutes.workspaceRoot, 'arcane-sw.js'));
1310
- } catch (error) {
1311
- if (error?.code !== 'ENOENT') throw error;
1312
- rootWorkerExists = false;
1313
- }
1314
- if (!rootWorkerExists) {
1315
- response.setHeader('Cache-Control', 'no-cache');
1316
- await serveGeneratedRepresentation(
1317
- fileServer,
1318
- request,
1319
- response,
1320
- {
1321
- // A former enabled worker's date cannot validate this retirement script.
1322
- contentType: contentTypes.js,
1323
- body: createPwaRetirementWorkerScript
1324
- }
1325
- );
1307
+ && selectedRoutes.pwaExplicitlyDisabled && target.path === '/arcane-sw.js') {
1308
+ const retirement = await createPwaRetirementResponse({
1309
+ workspaceRoot: selectedRoutes.workspaceRoot,
1310
+ pwa: selectedRoutes.app.pwa
1311
+ });
1312
+ if (retirement) {
1313
+ response.statusCode = retirement.statusCode;
1314
+ for (const [name, value] of Object.entries(retirement.headers)) {
1315
+ response.setHeader(name, value);
1316
+ }
1317
+ // A former enabled worker's date cannot validate this retirement script.
1318
+ await fileServer.serve(request, response, retirement.body);
1326
1319
  return;
1327
1320
  }
1328
1321
  }
package/src/index.mjs CHANGED
@@ -146,6 +146,7 @@ export {
146
146
  } from './workspace.mjs';
147
147
  export {materializeInstalledSdkRuntime} from './installed-sdk-runtime.mjs';
148
148
  export {startDevServer} from './dev-server.mjs';
149
+ export {resolvePwaRetirementResponse} from './pwa-retirement.mjs';
149
150
  export {createDevelopmentCore} from './core/development.mjs';
150
151
  export {startSourceExampleServer} from './source-server.mjs';
151
152
  export {acceptWebSocket} from './websocket.mjs';
@@ -0,0 +1,53 @@
1
+ import {lstat, readFile} from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import contentTypes from 'node-http-server/mime-types';
4
+ import {APP_DESCRIPTOR_NAME, projectPackageManifest} from './app-descriptor.mjs';
5
+ import {APP_CONFIG_NAME, validateAppConfig} from './packager/core.mjs';
6
+ import {inspectWorkspaceProfile} from './workspace.mjs';
7
+ import {createPwaRetirementWorkerScript} from './pwa-worker.mjs';
8
+
9
+ export async function resolvePwaRetirementResponse({workspaceRoot = process.cwd(), appId} = {}) {
10
+ const profile = await inspectWorkspaceProfile(workspaceRoot);
11
+ if (profile.config.appsRoot !== '.') return null;
12
+
13
+ let manifestPath = path.join(profile.workspaceRoot, APP_DESCRIPTOR_NAME);
14
+ let source;
15
+ let authored = true;
16
+ try {
17
+ source = await readFile(manifestPath, 'utf8');
18
+ } catch (error) {
19
+ if (error?.code !== 'ENOENT') throw error;
20
+ authored = false;
21
+ manifestPath = path.join(profile.workspaceRoot, APP_CONFIG_NAME);
22
+ source = await readFile(manifestPath, 'utf8');
23
+ }
24
+ const value = JSON.parse(source);
25
+ const manifest = authored ? projectPackageManifest(value) : value;
26
+ validateAppConfig(
27
+ manifest, appId ?? manifest.id, profile.config, manifestPath
28
+ );
29
+ // Normalization defaults an omitted enabled field to false. Retirement
30
+ // instead consumes the author's explicit choice from the original record.
31
+ const pwa = authored ? value.package.pwa : value.pwa;
32
+ return createPwaRetirementResponse({workspaceRoot: profile.workspaceRoot, pwa});
33
+ }
34
+
35
+ // Source serving already owns a current descriptor snapshot. Reuse that snapshot
36
+ // without introducing another descriptor read or changing its refresh lifetime.
37
+ export async function createPwaRetirementResponse({workspaceRoot, pwa}) {
38
+ if (pwa?.enabled !== false) return null;
39
+ try {
40
+ await lstat(path.join(workspaceRoot, 'arcane-sw.js'));
41
+ return null;
42
+ } catch (error) {
43
+ if (error?.code !== 'ENOENT') throw error;
44
+ }
45
+ return {
46
+ statusCode: 200,
47
+ headers: {
48
+ 'Content-Type': contentTypes.js,
49
+ 'Cache-Control': 'no-cache'
50
+ },
51
+ body: createPwaRetirementWorkerScript()
52
+ };
53
+ }