arcane-os 0.52.0 → 0.53.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,42 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.53.0
4
+
5
+ - Add `acceptWebSocket` through `arcane-os/websocket` and the Node root export.
6
+ Accept a caller-selected native HTTP/1.1 Upgrade and exchange complete text
7
+ and binary messages with fragmentation, Ping/Pong, ordered writable
8
+ backpressure, close/abort handling and observable connection settlement.
9
+ Buffered Upgrade input reaches synchronously registered listeners in order.
10
+ - Preserve valid text, leading Unicode markers and binary view boundaries.
11
+ Text that cannot be represented exactly as UTF-8 reports an explicit error
12
+ before transmission or a close-state change. Browser clients use native
13
+ `WebSocket`; existing HTTP/TLS listeners, routes and application messages
14
+ remain caller-owned.
15
+ - No third-party WebSocket runtime, client sharing, reconnect, application
16
+ envelope, storage, HTTP/2 extended CONNECT or extension negotiation is added.
17
+ Existing SDK behavior and dependencies remain unchanged. Ordinary consumers
18
+ update through their public package workflow; optional server integration
19
+ belongs to the consuming application.
20
+
21
+ ## 0.52.1
22
+
23
+ - Preserve an authored host `index.html` at `/` independently of application
24
+ entry and offline selection. Root import-map/PWA generation no longer aliases
25
+ that public homepage to the app entry. Existing app-only roots retain their
26
+ launcher behavior; excluded homepage content and assets stay outside the
27
+ application's managed documents and offline inventory.
28
+ - Source development follows root-file creation/removal without a descriptor
29
+ edit, serves an unselected host root unchanged, and avoids stale conditional
30
+ responses when generated aliases change within one HTTP second. Ordinary
31
+ selected routes, startup entry, manifest identity and worker lifecycle remain
32
+ unchanged.
33
+ - Packaged authored roots carry optional generated `app.rootDocument` metadata
34
+ so preview serves them at `/`; older and app-only packages retain their
35
+ existing redirects and queries. Worker directory navigation uses only indexes
36
+ already selected in its current offline inventory, with exact-query priority.
37
+ No dependency, configuration option, saved-data migration or cache reset is
38
+ introduced. Consumers regenerate their normal public SDK outputs after update.
39
+
3
40
  ## 0.52.0
4
41
 
5
42
  - Add optional `package.documents` to select managed application HTML separately
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.52.0` SDK contract. Applications pin one exact npm
23
+ This checkout defines the `0.53.0` SDK contract. Applications pin one exact npm
24
24
  version and lockfile; registry state is deliberately not baked into application
25
25
  artifacts.
26
26
 
@@ -145,6 +145,19 @@ one metadata task; unchanged files reuse their parsed selection. Shared runtime
145
145
  requests do not wait for that refresh. Full offline inventories are enumerated
146
146
  only for worker/offline-manifest requests, with separate state for each selected
147
147
  app snapshot so an older inventory cannot overwrite newer generated metadata.
148
+
149
+ Authored host-root `index.html` presence is refreshed independently of descriptor
150
+ edits. Development serves that host index at `/` instead of redirecting to the
151
+ app entry and suppresses the root PWA alias. An index outside the selected app
152
+ mapping uses an unchanged exact-root response; `/index.html` and other resource
153
+ URLs retain their existing selection rules. A selected index keeps its existing
154
+ document classification. This broadens no app or offline inventory and adds no
155
+ managed maps or bootstrap. With no host index, the app-entry redirect remains.
156
+ A worker serves a directory's cached index body only when that index is already
157
+ in its current offline selection; otherwise the host owns the unchanged request.
158
+ The selected app launch and installation identity remain independent of this
159
+ host-root navigation.
160
+
148
161
  Changed resource requests return the complete current saved source without
149
162
  packaging, copying files into `dist`, or restarting the server. Conditional
150
163
  requests for unchanged resources return `304`. Enabled PWAs check on page load
@@ -253,9 +266,12 @@ separate. See the [complete output contract](reference/protocols.md#app-selected
253
266
 
254
267
  Packaging keeps standalone app files at the release root and places an explicitly
255
268
  nested app beneath `apps/<id>/`. It preserves every shared route destination.
256
- When selected content supplies no root `index.html`, the SDK generates one that
257
- opens the selected app entry. Authored document bases, app URLs, relative module
258
- imports and shared runtime paths therefore resolve through the same layout in
269
+ A selected authored output-root `index.html` is preserved and suppresses the
270
+ deployment-root PWA navigation alias. When selected content supplies no root
271
+ index, the SDK generates a launcher and retains the selected app-entry alias;
272
+ an excluded source-host index does not alter that package result. Authored
273
+ document bases, app URLs, relative module imports and shared runtime paths
274
+ therefore resolve through the same layout in
259
275
  source development and packaged delivery. Packaging consumes the saved source
260
276
  and managed import maps; use `arcane import-map` to refresh those maps, or the
261
277
  ordinary `arcane dev` startup that performs that refresh.
@@ -266,6 +282,13 @@ entry, or `./apps/hello-world/index.html` for a nested app. Its complete `files`
266
282
  inventory follows the selected layout and includes the root `index.html`.
267
283
  Packaged browser serving opens `app.start`; a previously generated flat release without `app.start` retains
268
284
  its `app.entry` launch path.
285
+ Only selected records supplying an authored output-root index add the optional
286
+ generated `app.rootDocument: "./index.html"` release field. Preview serves that
287
+ document at `/` without inferring ownership from HTML content. When the field is
288
+ absent, older releases and generated-launcher packages retain their existing
289
+ `302` root redirect to `app.start` or the older `app.entry` fallback, preserving
290
+ the request query. This release fact adds no app configuration or API option and
291
+ does not change `app.start`.
269
292
 
270
293
  The authored schema-2 descriptor is canonical for new apps and projects an
271
294
  exact schema-1 `arcane-package.json` for current consumers. Existing Arcane
@@ -245,6 +245,12 @@ files remain unchanged. The same layout applies to `arcane dev` and
245
245
  `arcane package`. Generated and offline app files are committed; GitHub Actions
246
246
  consume those committed files. See [root generated output](pwa.md#root-generated-output).
247
247
 
248
+ An existing authored source-host `index.html` suppresses the generated `/`
249
+ navigation alias even when the page is outside the application or offline
250
+ selection. It is not automatically added to either inventory or given managed
251
+ maps or PWA bootstrap. The selected app entry and installation identity remain
252
+ unchanged; see [authored host roots](pwa.md#authored-host-root-and-app-entry).
253
+
248
254
  The supported physical layout preserves its route count and ordered include
249
255
  list. External and modern integrated physical routes require `components`, `css`,
250
256
  `dependencies`, `entities`, `img`, `modules`, and `sdk`; a physical workspace
@@ -373,6 +379,16 @@ not package the app or produce `dist` output. With PWA enabled, the server
373
379
  generates the installation and offline manifests directly; see
374
380
  [PWA development and versioning](pwa.md#development-and-hosting).
375
381
 
382
+ The startup URL remains the selected app entry. At `/`, the server serves an
383
+ existing authored host-root `index.html` and suppresses the app-entry redirect
384
+ and PWA alias. A root outside the selected app mapping is served unchanged only
385
+ through this exact-root route; `/index.html` and other resource URLs retain their
386
+ existing selection rules. A selected root follows its existing document
387
+ classification. Root presence does not expand either inventory or cause
388
+ bootstrap injection. With no host index, the app-entry redirect remains.
389
+ Root-file creation or removal is reflected in subsequent root-navigation and
390
+ generated-PWA requests without a restart or a descriptor edit.
391
+
376
392
  The command reports acceptance before bind/start work, emits the final URL,
377
393
  owns the server until cancellation, and restores failure to the process exit.
378
394
  The default host is `127.0.0.1`. `--public` selects `0.0.0.0` (all IPv4
@@ -550,8 +566,11 @@ defaulting to `dist/<id>/`, and preserves the prior output until the replacement
550
566
  is complete. It consumes saved source and managed
551
567
  import maps, keeps standalone app files at the output root, and retains the
552
568
  configured shared route destinations. Explicit multi-app workspaces retain
553
- their selected app beneath `apps/<id>/`. When selected shared content supplies no root
554
- `index.html`, the SDK generates one that opens the selected app entry.
569
+ their selected app beneath `apps/<id>/`. A selected authored output-root
570
+ `index.html` is preserved and suppresses the deployment-root PWA navigation
571
+ alias. When selected content supplies no root index, the SDK generates a launcher
572
+ and retains the app-entry alias. An excluded source-host root does not change
573
+ that package selection.
555
574
  Source document bases and resource URLs therefore retain their development
556
575
  layout. Packaging does not run tests or checks automatically.
557
576
 
@@ -584,6 +603,10 @@ The result includes the release root, manifest, and complete selected inventory.
584
603
  the package launch URL in `app.start`: `./index.html` for a root app with that
585
604
  entry, or `./apps/hello-world/index.html` for a nested app. The complete file
586
605
  inventory follows the selected layout and includes the root `index.html`.
606
+ Only selected records supplying an authored output-root index add
607
+ `app.rootDocument: "./index.html"`. This optional generated release fact selects
608
+ root-document serving in preview; it is not an app configuration or CLI option
609
+ and does not change `app.start`.
587
610
  `--dry-run` plans the package without refreshing source, running tests, or
588
611
  replacing output.
589
612
 
@@ -749,6 +772,11 @@ For `--target browser`, starts the existing release in the app's configured
749
772
  does not package, rebuild, test, check, or verify that release automatically.
750
773
  It opens the release manifest's `app.start` URL. Older flat releases without
751
774
  that field continue to open their `app.entry` path.
775
+ Requests to `/` serve the explicit `app.rootDocument` when that generated
776
+ release field is present. With no field, including older releases and app-only
777
+ packages with a generated launcher, `/` retains its `302` redirect to
778
+ `app.start` or the older `app.entry` fallback, preserving the request query.
779
+ Preview does not infer root ownership from HTML content.
752
780
  The preview always uses HTTPS with the workspace certificate pair. Supply
753
781
  `--cert <file> --key <file>` together to use another pair; see
754
782
  [development HTTPS setup](#development-https-setup).
@@ -6,12 +6,26 @@
6
6
  "minimumVersion": "22.23.2 for Node entrypoints",
7
7
  "moduleSystem": "ESM"
8
8
  },
9
- "memberCount": 223,
9
+ "memberCount": 224,
10
10
  "runtimeSubpathPatterns": {
11
11
  "arcane-os/modules/*": "./runtime/arcane/modules/*",
12
12
  "arcane-os/entities/*": "./runtime/arcane/entities/*"
13
13
  },
14
14
  "members": [
15
+ {
16
+ "id": "root:acceptWebSocket",
17
+ "name": "acceptWebSocket",
18
+ "displayName": "acceptWebSocket()",
19
+ "kind": "function",
20
+ "signature": "acceptWebSocket({request,socket,head,protocol,signal})",
21
+ "entrypoints": ["arcane-os", "arcane-os/websocket"],
22
+ "primaryImport": "arcane-os/websocket",
23
+ "group": "WebSocket protocol",
24
+ "summary": "Accept one caller-selected HTTP/1.1 Upgrade and exchange complete native WebSocket messages.",
25
+ "availability": "Node; interoperates with browser native WebSocket clients",
26
+ "protocol": "RFC6455 version 13, without extensions or HTTP/2 extended CONNECT",
27
+ "normalization": "Native EventTarget message/error/close events and owned send/close promises; no application envelopes or storage"
28
+ },
15
29
  {
16
30
  "id": "root:APP_BUNDLE_DESCRIPTOR_NAME",
17
31
  "name": "APP_BUNDLE_DESCRIPTOR_NAME",
@@ -251,15 +251,34 @@ The declared application ID remains unchanged. `appsRoot: "apps"` continues
251
251
  to discover `apps/<id>` and supports the existing integrated and multi-app
252
252
  layouts. Entries and include/exclude paths remain relative to the application.
253
253
 
254
- Root HTML uses `<base href="./">`; nested navigable documents use their actual
255
- depth back to the workspace. Managed bare imports remain the public interface;
256
- their targets follow the selected npm routes. `arcane import-map` generates
257
- enabled static PWA records at the app root, so normal static hosting needs no
254
+ SDK-managed root HTML uses `<base href="./">`; nested navigable documents use
255
+ their actual depth back to the workspace. Managed bare imports remain the
256
+ public interface; their targets follow the selected npm routes. `arcane import-map`
257
+ generates enabled static PWA records at the app root, so normal static hosting needs no
258
258
  SDK request handler or runtime copy. Managed refresh, development serving,
259
259
  inspection, dry run and packaging add no nested `apps/<id>/` navigation pages,
260
260
  redirects, or duplicate PWA worker/inventory files. There is no output-retention
261
261
  switch for those retired records.
262
262
 
263
+ An authored source-host `index.html` suppresses the root app-entry redirect and
264
+ generated PWA navigation alias even when that file is outside the application's
265
+ include or offline selection. Development serves that host index at `/`. An
266
+ index outside the selected app mapping uses an unchanged exact-root response;
267
+ `/index.html` and other resource URLs retain their existing selection rules.
268
+ A selected index follows the existing document classification. Root presence
269
+ adds no page, assets, managed maps, or PWA bootstrap to the application. With no
270
+ host index, development retains the app-entry redirect. In a package, only a
271
+ selected authored output-root index suppresses the alias; an absent or excluded
272
+ root retains the generated launcher and app-entry alias. Selected records that
273
+ supply an authored output-root index add the optional generated release field
274
+ `app.rootDocument: "./index.html"`. Preview serves that document at `/`; absence
275
+ retains the existing `302` to `app.start` or the older `app.entry` fallback with
276
+ the request query preserved. This is a release fact, not an app setting or HTML
277
+ content inference, and `app.start` remains unchanged. The worker can answer a
278
+ directory navigation from an index only when that index already belongs to its
279
+ current offline selection and has a cached response; otherwise the unchanged
280
+ request remains host-owned. See [authored host roots](pwa.md#authored-host-root-and-app-entry).
281
+
263
282
  Application identity, stored data, the existing default installation ID and an
264
283
  authored `pwa.manifest.id` remain unchanged. Authored resources continue to
265
284
  follow the app's include/exclude selection; generation does not delete existing
@@ -102,6 +102,38 @@ committed files rather than generating them. Actual multi-app workspaces keep
102
102
  their explicit `appsRoot: "apps"` layout. This source/package contract does not
103
103
  establish any particular installed application's browser lifecycle.
104
104
 
105
+ ### Authored host root and app entry
106
+
107
+ An authored `index.html` at the source host root keeps `/` separate from the
108
+ selected application entry. Root import-map generation and development PWA
109
+ generation omit the `/`-to-entry navigation alias whenever that file exists,
110
+ including when it is outside `package.include` or the offline selection.
111
+ Development serving answers `/` with that host index. An index outside the app's
112
+ selected source mapping is served unchanged through this exact-root route;
113
+ `/index.html` and other resource URLs retain their existing selection rules.
114
+ A selected index follows its existing managed-document classification. Root
115
+ presence alone adds no page or assets to the application or offline inventory
116
+ and adds no managed maps or PWA bootstrap. The host owns a root response outside
117
+ the worker's selected offline resources. With no host index, development retains
118
+ its app-entry redirect.
119
+
120
+ Packaging considers the selected output instead of unrelated source-host files.
121
+ A selected authored output-root `index.html` is preserved and suppresses the
122
+ deployment-root navigation alias. When selected content supplies no output-root
123
+ index, the package retains its generated launcher and app-entry alias. An
124
+ authored source root excluded from the package does not replace that launcher.
125
+ Only when selected records supply an authored output-root index does packaging
126
+ write `app.rootDocument: "./index.html"` in `ARCANE_APP_RELEASE.json`.
127
+ Packaged preview uses that generated field to serve the authored document at
128
+ `/`; it does not inspect HTML to infer root ownership. Without the field,
129
+ including older releases and generated-launcher packages, preview retains its
130
+ `302` redirect to `app.start` (or the older `app.entry` fallback), preserving the
131
+ request query. The startup URL remains the selected app launch URL.
132
+
133
+ This behavior requires no new setting. The application entry, default install
134
+ start URL, manifest identity and scope, worker registration scope, cache
135
+ identity, and saved application data keep their existing contracts.
136
+
105
137
  ### Offline resource selection
106
138
 
107
139
  `offline.include` and `offline.exclude` select literal paths or directory
@@ -304,8 +336,10 @@ therefore matters even when an offline cache exists. Updating files in place
304
336
  while installation fetches them is not an atomic release snapshot; the host owns
305
337
  consistent deployment of the selected output.
306
338
 
307
- An offline navigation alias redirects to its selected entry page, preserving the
308
- document URL used to resolve relative modules and styles.
339
+ Where an app-entry navigation alias remains, it redirects to the selected entry
340
+ page, preserving the document URL used to resolve relative modules and styles.
341
+ An authored host root suppresses that alias as described above; merely retaining
342
+ an old cached root resource does not add it to the current offline selection.
309
343
 
310
344
  For a same-origin navigation to a selected static `.html` or `.htm` document,
311
345
  the worker can reuse that document's cached body while leaving the complete
@@ -319,6 +353,14 @@ document. This does not change query matching for assets, API calls, ordinary
319
353
  fetch requests, unselected documents or other origins. Cache refresh and the
320
354
  Update prompt keep their existing lifecycle.
321
355
 
356
+ For a same-origin directory navigation without an app-entry alias, the worker
357
+ can likewise reuse that directory's `index.html` body only when the index is
358
+ already in the current offline selection and has a retained cached response.
359
+ The directory URL and its query remain unchanged, including beneath a deployment
360
+ subpath. An exact query-specific selected resource takes precedence. Otherwise,
361
+ the original request passes to the host unchanged. This adds no page or asset to
362
+ the inventory and does not save a host response over the selected index.
363
+
322
364
  Browser storage eviction can remove an offline cache. A missing release cache
323
365
  entry falls back to the network, so offline availability still depends on the
324
366
  browser retaining the selected resources.
@@ -41,6 +41,7 @@ runtime layouts.
41
41
  | `arcane-os` | Complete high-level SDK surface. |
42
42
  | `arcane-os/toolchain` | Headless application operations. |
43
43
  | `arcane-os/events` | CLI/event reporter. |
44
+ | `arcane-os/websocket` | Node HTTP/1.1 WebSocket Upgrade, complete messages and connection lifecycle. Browser clients use native `WebSocket`. |
44
45
  | `arcane-os/testing` | Isolated test registration and execution. |
45
46
  | `arcane-os/targets` | Target adapters and dispatch. |
46
47
  | `arcane-os/native` | Native plan and builder protocol. |
@@ -130,6 +131,7 @@ The remaining data-only subpaths are eight JSON Schemas and package metadata.
130
131
 
131
132
  | Member | Kind | Import | Group | Availability |
132
133
  | --- | --- | --- | --- | --- |
134
+ | `acceptWebSocket()` | function | `arcane-os`, `arcane-os/websocket` | WebSocket protocol | Node; browser-native client interoperability |
133
135
  | `fetchRequest()` | function | `arcane-os/ai/twin-cloud` | TWiN Cloud requests | Node and Browser; remote HTTPS provider |
134
136
  | `fetchSystemOneRequest()` | function | `arcane-os/ai/twin-cloud` | TWiN Cloud state-and-questions requests | Node and Browser; remote HTTPS provider |
135
137
  | `generateImages()` | function | `arcane-os/ai/twin-cloud` | TWiN Cloud image generation | Node and Browser with Fetch and Blob; remote HTTPS provider |
@@ -882,7 +884,14 @@ generates one that opens the selected app page. The result
882
884
  contains `outputRoot`, `manifest`, and the complete `files` inventory.
883
885
  `manifest.app.entry` stays app-relative, while `manifest.app.start`
884
886
  is the package launch URL, such as `./index.html` for a root app or
885
- `./apps/hello-world/index.html` for a nested app. Release
887
+ `./apps/hello-world/index.html` for a nested app. Only when selected records
888
+ supply an authored output-root index does the release add
889
+ `manifest.app.rootDocument: "./index.html"`. Preview serves that explicit
890
+ document at `/`; without the field, older releases and generated-launcher
891
+ packages retain the `302` redirect to `app.start` or the older `app.entry`
892
+ fallback with the request query preserved. This generated release fact adds no
893
+ app configuration or API option, changes no `app.start`, and requires no HTML
894
+ content inference. Release
886
895
  schema `1`, kind `arcane-app-release`, and packager identity
887
896
  `arcane-app-packager-v1` remain unchanged. A dry run returns the planned output
888
897
  and file inventory without writing source or output.
@@ -3746,6 +3755,154 @@ async function useselectApp(...arguments_) {
3746
3755
  }
3747
3756
  ```
3748
3757
 
3758
+ ## acceptWebSocket()
3759
+
3760
+ ### Overview
3761
+
3762
+ Accepts one caller-selected RFC6455 HTTP/1.1 Upgrade using Node's native
3763
+ duplex stream. The caller owns HTTP routing, TLS, listeners, application
3764
+ messages and shutdown. Use the raw `server` returned by `startDevServer`, or
3765
+ the public `.server` / `.secureServer` of `node-http-server`. There is no
3766
+ listener creation, default port, application envelope, message persistence,
3767
+ client sharing, reconnect loop or third-party WebSocket runtime.
3768
+
3769
+ Browser clients use native `WebSocket` directly. Node's global `WebSocket` is
3770
+ also a client, not a server. This server adapter supports version 13 over an
3771
+ HTTP/1.1 `upgrade` event, including HTTP/1.1 negotiated on an HTTPS listener.
3772
+ It does not implement HTTP/2 extended CONNECT or negotiate extensions such as
3773
+ compression. TLS setup remains with the existing server owner.
3774
+
3775
+ ### Signature, modes, and result
3776
+
3777
+ ```text
3778
+ acceptWebSocket({request,socket,head,protocol,signal})
3779
+ ```
3780
+
3781
+ Import from `arcane-os/websocket` or `arcane-os`. Supply the exact `request`,
3782
+ `socket` and `head` from the selected Node `upgrade` event. Do not substitute
3783
+ `request.socket` for that event's stream. `head` defaults to an empty buffer.
3784
+ Optional `protocol` is one exact client-offered token; the default empty string
3785
+ selects no subprotocol. A browser that offers subprotocols expects one to be
3786
+ selected. Optional `signal` owns this connection's lifetime.
3787
+
3788
+ The synchronous result is a native `EventTarget` with:
3789
+
3790
+ | Member | Contract |
3791
+ | --- | --- |
3792
+ | `readyState` | `1` open, `2` closing, `3` closed. Acceptance queues the handshake before any messages. |
3793
+ | `protocol` | Selected subprotocol, or the empty string. |
3794
+ | `send(data)` | `Promise<void>` for a complete string, `ArrayBuffer`, or view. A view sends only its selected region. Input is snapshotted at submission; writes stay in submission order. The promise waits for the native write callback and any required drain; it is not a peer/application acknowledgement. |
3795
+ | `close(code=1000,reason='')` | Starts the close handshake after already accepted sends; returns `closed`. Repeated calls return the same promise. A peer Close rejects unsent application messages and retains the close reply. |
3796
+ | `terminate(reason?)` | Destroys only this connection's stream immediately, rejects pending sends and returns `closed`. An optional reason is reported as an error. |
3797
+ | `closed` | Resolves once the stream closes to `{code,reason,wasClean,error}`. It never rejects. Code/reason come from the peer Close; without one, code is `1006`. An empty peer Close reports `1005`. `wasClean` requires both close frames and no recorded failure. |
3798
+
3799
+ Register listeners synchronously after acceptance. The handshake write and
3800
+ initial `head` processing begin in a microtask so those listeners receive
3801
+ buffered messages. `head` is processed once, before later stream chunks.
3802
+
3803
+ | Native event | Payload |
3804
+ | --- | --- |
3805
+ | `message` | `MessageEvent.data`: complete text as a string, or complete binary as `Uint8Array`. Fragment boundaries never replace message boundaries. |
3806
+ | `error` | `CustomEvent.detail`: the complete original transport Error or coded protocol/abort Error. |
3807
+ | `close` | `CustomEvent.detail`: the same result as `closed`. |
3808
+
3809
+ These are transport-local native events, not an application bus or retained
3810
+ event history. Native `EventTarget` owns listener exceptions; an async listener
3811
+ must observe its own promises. Listen for errors or inspect `closed.error`.
3812
+ Ping receives a matching Pong internally; Pong is not an application message.
3813
+ Text decoding preserves a leading U+FEFF and validates UTF-8 across the complete
3814
+ fragmented message. Binary data is never interpreted or wrapped as JSON.
3815
+
3816
+ ### Errors and cancellation
3817
+
3818
+ Before changing the socket, an invalid handshake throws
3819
+ `ARCANE_WEBSOCKET_HANDSHAKE_INVALID` with `statusCode:400,headers:{}`;
3820
+ an unsupported WebSocket version throws `ARCANE_WEBSOCKET_VERSION_UNSUPPORTED`
3821
+ with `statusCode:426` and `headers:{'Sec-WebSocket-Version':'13'}`. The host
3822
+ owns sending that HTTP response and ending the rejected stream. Invalid socket,
3823
+ data or close arguments report `TypeError` / `RangeError`; a failed `send`
3824
+ rejects its promise without changing the payload.
3825
+ An unpaired UTF-16 surrogate cannot be represented as UTF-8: sending such text
3826
+ rejects with `ARCANE_WEBSOCKET_TEXT_INVALID`, and using it as a close reason
3827
+ throws that error before changing connection state. Valid strings remain exact.
3828
+
3829
+ After acceptance, invalid framing reports `ARCANE_WEBSOCKET_PROTOCOL_INVALID`
3830
+ and close status `1002`; invalid text reports `ARCANE_WEBSOCKET_TEXT_INVALID`
3831
+ and `1007`. Complete platform errors remain observable. Framing lengths,
3832
+ masking, the handshake's SHA-1 accept calculation and control-frame bounds are
3833
+ RFC transport requirements only. There are no application message limits,
3834
+ byte progress, content identities, or optional security controls.
3835
+
3836
+ A pre-aborted signal throws before taking ownership. Later abort destroys the
3837
+ stream with an `AbortError` coded `ARCANE_WEBSOCKET_ABORTED`, preserving the
3838
+ signal reason as its cause. Closing rejects new sends with
3839
+ `ARCANE_WEBSOCKET_CONNECTION_CLOSED` (or the existing failure).
3840
+ No timer silently terminates a slow close handshake; use the caller's signal or
3841
+ `terminate()` when that owner decides to stop. Server listener closure alone,
3842
+ including Node `closeAllConnections()`, does not own upgraded sockets. Track
3843
+ connections or provide a shared lifetime signal, then await their `closed`
3844
+ promises separately. None of this changes chat storage or `persist:false`.
3845
+
3846
+ ### Example
3847
+
3848
+ This example attaches to an already-owned listener and returns a separate
3849
+ connection cleanup function. Its route and echo policy belong to the caller.
3850
+ Call it with `publicServer.server`, `publicServer.secureServer`, or an SDK
3851
+ development server's returned `server`; it creates no additional HTTP server.
3852
+
3853
+ ```javascript
3854
+ import {acceptWebSocket} from 'arcane-os/websocket';
3855
+
3856
+ function attachEcho(server,signal){
3857
+ const connections=new Set();
3858
+ function upgrade(request,socket,head){
3859
+ if(request.url!=='/socket'){
3860
+ socket.end('HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n');
3861
+ return;
3862
+ }
3863
+ let connection;
3864
+ try{connection=acceptWebSocket({request,socket,head,signal});}
3865
+ catch(error){
3866
+ console.error(error);
3867
+ const status=error.statusCode??400;
3868
+ const version=status===426?'Sec-WebSocket-Version: 13\r\n':'';
3869
+ socket.end(`HTTP/1.1 ${status} Upgrade rejected\r\n${version}Connection: close\r\n\r\n`);
3870
+ return;
3871
+ }
3872
+ connections.add(connection);
3873
+ connection.addEventListener('error',function failed(event){console.error(event.detail);});
3874
+ connection.addEventListener('message',function echo(event){
3875
+ connection.send(event.data).catch(function sendFailed(error){console.error(error);});
3876
+ });
3877
+ connection.addEventListener('close',function remove(){connections.delete(connection);});
3878
+ }
3879
+ server.on('upgrade',upgrade);
3880
+ return async function detach(){
3881
+ server.off('upgrade',upgrade);
3882
+ await Promise.all([...connections].map(function stop(connection){return connection.terminate();}));
3883
+ };
3884
+ }
3885
+
3886
+ // In browser code, after the host has selected its ws: or wss: URL:
3887
+ function connectEcho(url){
3888
+ const client=new WebSocket(url);
3889
+ client.addEventListener('open',function greet(){client.send('The moon needs a coffee break.');});
3890
+ client.addEventListener('message',function received(event){console.log(event.data);});
3891
+ return client;
3892
+ }
3893
+ ```
3894
+
3895
+ Protocol references: [RFC6455](https://www.rfc-editor.org/rfc/rfc6455.html),
3896
+ [Node upgrade event](https://nodejs.org/api/http.html#event-upgrade), and
3897
+ [native browser WebSocket](https://websockets.spec.whatwg.org/).
3898
+ Work is connection-local and linear in actual framing/content. Input is
3899
+ assembled once per complete message; advertised lengths do not preallocate
3900
+ payloads. Outbound framing snapshots each submitted message and uses native
3901
+ writable backpressure. Callers await `send` when producing a sustained stream.
3902
+ There is no history scan, app loop, diagnostic payload store or automatic retry.
3903
+ The authored `test/websocket.test.mjs` fixtures cover this boundary; source
3904
+ inspection is distinct from executing them or verifying a browser deployment.
3905
+
3749
3906
  ## startDevServer()
3750
3907
 
3751
3908
  ### Overview
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.52.0",
3
+ "version": "0.53.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",
@@ -12,6 +12,7 @@
12
12
  ".": "./src/index.mjs",
13
13
  "./toolchain": "./src/toolchain.mjs",
14
14
  "./events": "./src/events.mjs",
15
+ "./websocket": "./src/websocket.mjs",
15
16
  "./event-manager": "./src/event-manager.mjs",
16
17
  "./modules/*": "./runtime/arcane/modules/*",
17
18
  "./entities/*": "./runtime/arcane/entities/*",
@@ -86,7 +87,7 @@
86
87
  "test": "npm run test:unit && npm run test:functional && npm run test:integration && npm run test:regression",
87
88
  "test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
88
89
  "test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/app-selection.test.mjs test/contracts.test.mjs test/conversation-closing-report.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",
89
- "test:functional": "node ./bin/arcane-test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-input.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-png.test.mjs test/dbopfs.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/file-preview.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-media.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/rendered-print.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/theme-manager.test.mjs test/twin-cloud-images.test.mjs test/twin-cloud.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
90
+ "test:functional": "node ./bin/arcane-test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-input.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-png.test.mjs test/dbopfs.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/file-preview.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-media.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/rendered-print.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/theme-manager.test.mjs test/twin-cloud-images.test.mjs test/twin-cloud.test.mjs test/websocket.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
90
91
  "test:integration": "node ./bin/arcane-test.mjs test/installed-package-runtime.test.mjs test/root-app-layout.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/browser-wasm-cpu.test.mjs test/wllama-webgpu-runtime.test.mjs",
91
92
  "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",
92
93
  "check": "node tools/check-source.mjs && npm test",