@ours.network/cowork 0.4.0 → 0.4.1-nightly.20260816.4aaf940

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.
@@ -1,5 +1,5 @@
1
1
  # Prerequisites
2
2
 
3
- ours-cowork requires Node.js 20 or newer and access to an ours/ADAPT broker over WebSocket. The daemon is a standalone process with its own package, configuration, state directory, packet host, and operator socket.
3
+ ours-cowork requires Node.js 20 or newer and access to an ours/ADAPT broker over WebSocket. The daemon is a standalone process with its own package, configuration, state directory, embedded standard-SDK host, and operator socket.
4
4
 
5
5
  The account running it must be able to create a private `0700` state directory. The Unix management socket is local and owner-only. systemd user services are supported on Linux and launchd agents on macOS.
@@ -15,4 +15,4 @@ ours-cowork web
15
15
 
16
16
  This starts the background daemon when it is absent, waits for `http://127.0.0.1:3052/`, and opens the system browser. Use `ours-cowork start` when no browser should open, or `ours-cowork serve` in the foreground while diagnosing startup. `ours-cowork --json web` verifies readiness and returns the URL without opening a browser.
17
17
 
18
- The localhost HTTP console has no authentication. Do not proxy, forward, or expose its port to another host. The daemon hosts its own room packets and does not require another local ours process.
18
+ The localhost HTTP console has no authentication. Do not proxy, forward, or expose its port to another host. The daemon embeds the public ours SDK and hosts its own room identities; it does not require another local ours process.
@@ -11,7 +11,37 @@ The default config file is `~/.ours-cowork/config.json`. It is a strict document
11
11
  }
12
12
  ```
13
13
 
14
- Environment overrides are `OURS_COWORK_CONFIG`, `OURS_COWORK_BROKER_URL`, `OURS_COWORK_STATE_DIR`, and `OURS_COWORK_REST_PORT`. The default console URL is `http://127.0.0.1:3052/`. Setting the REST port enables the loopback listener on that port. Explicitly setting `rest.enabled` to `false` disables both the web console and HTTP room RPC. Configuration, private files, and the complete state directory must remain owned by the daemon account with the modes enforced at startup.
14
+ Environment overrides are `OURS_COWORK_CONFIG`, `OURS_COWORK_BROKER_URL`, `OURS_COWORK_STATE_DIR`, `OURS_COWORK_REST_PORT`, `OURS_COWORK_DAEMON_MODE`, `OURS_COWORK_DAEMON_ENDPOINT`, and `OURS_COWORK_DAEMON_STATE_DIR`. The default console URL is `http://127.0.0.1:3052/`. Setting the REST port enables the loopback listener on that port. Explicitly setting `rest.enabled` to `false` disables both the web console and HTTP room RPC. Configuration, private files, and the complete state directory must remain owned by the daemon account with the modes enforced at startup.
15
+
16
+ ## Which ours daemon hosts the rooms
17
+
18
+ Omit the `daemon` block and nothing changes: cowork runs its own ours runtime below `stateDir`, which is what every existing installation does. There is no migration.
19
+
20
+ To host the room identities on an ours daemon you already run, add the block and name both fields:
21
+
22
+ ```json
23
+ {
24
+ "version": 1,
25
+ "brokerUrl": "wss://broker1.ours.network",
26
+ "stateDir": "/absolute/private/path",
27
+ "rest": { "enabled": true, "port": 3052 },
28
+ "daemon": {
29
+ "mode": "external",
30
+ "endpoint": "http://127.0.0.1:3050",
31
+ "stateDir": "/home/operator/.ours"
32
+ }
33
+ }
34
+ ```
35
+
36
+ `endpoint` is the daemon's base URL — an `http`/`https` origin with no path, query, or credentials — and `stateDir` is the state directory that daemon owns. Point both at your common daemon (`http://127.0.0.1:3050` with `~/.ours`), or at a dedicated daemon on its own port with its own isolated state directory and service. Both fields are required together: an API token belongs to one state directory, and the SDK refuses to offer it to an endpoint that was not chosen just as deliberately.
37
+
38
+ `http://` is accepted only for a daemon on this host — `localhost`, any `127.0.0.0/8` address, or `[::1]`. Any other host requires `https://`, and a plaintext remote endpoint is rejected when the configuration is read, before a token is looked up. The API token is a bearer credential; cowork will not put one on the wire in the clear.
39
+
40
+ Cowork never asks for that token. It reads the daemon's own `daemon-token` file inside the state directory you named, or `OURS_API_TOKEN` when you set one. Tokens are never written into cowork's config file, its logs, or a generated service definition.
41
+
42
+ At startup cowork asks the endpoint which state directory it owns before sending any credential. If the endpoint is unreachable, is not an ours daemon, or owns a different state directory, startup fails and cowork stays stopped. It does not start a runtime of its own instead. `mode: "embedded"` states the default explicitly and rejects the two other fields.
43
+
44
+ The equivalent environment selection is `OURS_COWORK_DAEMON_ENDPOINT` plus `OURS_COWORK_DAEMON_STATE_DIR`, which imply external mode; `OURS_COWORK_DAEMON_MODE` sets it directly. `install-service` copies whichever selection is in effect into the generated unit.
15
45
 
16
46
  CLI room commands always use `management.sock`; they do not switch to REST.
17
47
 
@@ -13,6 +13,10 @@ ours-cowork web
13
13
 
14
14
  `start` detaches the standalone CLI in `serve` mode and waits for the authenticated control session on `management.sock`. `serve` keeps the supervisor in the foreground. Status asks the responding worker to prove that its supervisor capability handshake completed. Stop sends a session-bound shutdown request to that worker; the worker asks its own supervisor over their existing authenticated IPC channel to enter the same bounded shutdown path used for signals. The CLI never signals a numeric PID. Stop reports success only after the accepted control session disappears. An occupied socket without this protocol blocks stop/restart with invalid state and is left untouched.
15
15
 
16
+ In external daemon mode (see Configuration) the same commands manage only the cowork daemon. Start that ours daemon first: cowork verifies the endpoint at boot and refuses to start while it is unreachable, so `start` reports an internal failure and `status` then reports the daemon as unavailable. Under the installed service that refusal is retried rather than final — see Service management. `stop` and `restart` leave the external daemon running, and room state stays where it already is — room metadata and archives under cowork's `stateDir`, room identities inside the external daemon's state directory.
17
+
18
+ If the external daemon restarts underneath a running cowork, the room notification watches reconnect on their own. A reconnected watch starts from the daemon's current position, so cowork also refreshes each hosted room once per reconnect; anything that arrived while the watch was down is picked up by that refresh rather than skipped.
19
+
16
20
  `web` uses the same safe start path: an already-running daemon is retained, an absent daemon is started, and readiness is checked with `GET /` before a browser opens. It never retries a room mutation. With `--json`, it returns the URL with `opened: false` and has no browser side effect. If HTTP is explicitly disabled, `web` exits `1` and explains how to enable it.
17
21
 
18
22
  Exit codes are stable: `0` success, `1` web console disabled, `2` CLI usage, `3` not found, `4` invalid state or parameters, `5` unauthorized, `6` daemon unavailable, and `7` internal failure. With `--json`, stdout contains exactly one JSON value and stderr stays empty. This includes foreground `serve`: supervised worker output is suppressed, and its clean or failed terminal status becomes that one JSON result.
@@ -10,8 +10,8 @@ ours-cowork room show <room-id>
10
10
 
11
11
  In the web console, choose Create room, enter Name, Goal, and Briefing, and submit once. Names are trimmed and Unicode NFC-normalized, must contain 1–64 Unicode characters, and cannot contain Unicode control or format characters. Duplicate names are allowed. The created room is selected automatically and its Invite panel opens. Add invitation requirements one at a time; the UI does not combine room creation and invites into a fabricated atomic operation.
12
12
 
13
- Update the display name with `ours-cowork room settings <room-id> --name "New name"`; other mutable mission fields use the same `room settings` command. A new room announces `ours-cowork-room:<initial room_name>`. The display name is persisted as mutable `room_name`, while the authenticated announced identity name is intentionally frozen: renaming a room does not change its CID, signing key, contacts, or history. Duplicate names are allowed because identity CIDs, not names, are the authorization and routing keys. The opaque `room_id` remains the stable URL, storage, and identity-correlation key. Existing `cowork-room-<room_id>` identities are retained without renaming; existing unnamed rooms receive only the deterministic display name `Room <first 8 room_id characters>` when loaded. Inspect admitted seats with `room participants`. A room activates only when its recorded invite requirements are satisfied. Roles are display labels; participant identity CIDs, not roles, are authorization keys.
13
+ Update the display name with `ours-cowork room settings <room-id> --name "New name"`; other mutable mission fields use the same `room settings` command. A new room uses the standard SDK identity `ours-cowork-<room_id>`. The display name is persisted as mutable `room_name`, while the authenticated identity name is intentionally independent of it: renaming a room does not change its CID, contacts, or history. Duplicate display names are allowed because identity CIDs and room IDs, not names, are the authorization and routing keys. Pre-1.0 custom room identities are refused with instructions to back up, recreate, and re-invite rather than being silently renamed or reprovisioned. Inspect admitted seats with `room participants`. A room activates only when its recorded invite requirement is satisfied. Roles are display labels; participant identity CIDs, not roles, are authorization keys.
14
14
 
15
- Close with `ours-cowork room close <room-id>`. Close is forward-only and removes live room packet state while retaining the local archive. Archive deletion is a separate explicit operation described in the limitations topic.
15
+ Close with `ours-cowork room close <room-id>`. Close is forward-only and removes the live standard SDK room identity while retaining the local archive. Archive deletion is a separate explicit operation described in the limitations topic.
16
16
 
17
17
  Every web action has an equivalent CLI fallback in the room commands above and in the invites and messaging topics.
@@ -10,6 +10,8 @@ ours-cowork room revoke <room-id> <invite-id>
10
10
 
11
11
  The invite blob is returned only in an invite or recovery receipt. Store or transmit that receipt as needed; durable room metadata intentionally does not store the blob.
12
12
 
13
+ The standard SDK does not expose the deleted actor's per-contact invite-origin metadata. Cowork therefore permits at most one live invitation per room. Revoke a public invitation, or let a one-time invitation be consumed, before minting another; every newly accepted SDK contact can then be assigned to one unambiguous room role and requirement.
14
+
13
15
  The web console shows each returned invite secret once in a blocking receipt. Copy and save it before choosing Done: closing the receipt discards the browser copy. The secret is not placed in the URL, browser storage, logs, or room metadata. One-time and public receipts use the same handling.
14
16
 
15
17
  After restart, `room recover <room-id>` can mint replacements for durable invites marked as needing replacement. Preserve each returned replacement blob before confirming its exact old/new pair with `room recover <room-id> --confirm <old-invite-id> <new-invite-id>`. The web recovery receipt names both IDs and requires the same confirmation. Each invocation makes exactly one local management request.
@@ -21,9 +21,9 @@ Daemon history responses are capped at 3 MiB of JSON so one 2 MiB file record (a
21
21
 
22
22
  Participant messages and files are accepted only from durable seats in an active room. A file is opaque binary data: cowork neither interprets its MIME metadata nor executes its contents. Its filename must be a path-free name of at most 255 UTF-8 bytes (not `.`, `..`, or a name containing `/`, `\\`, or NUL); MIME metadata may be empty and is limited to 255 UTF-8 bytes. Zero-byte files are valid. The maximum file size is 2 MiB (2,097,152 bytes), and a larger file is rejected explicitly.
23
23
 
24
- Before consuming an incoming file from packet state, cowork durably archives its exact bytes as canonical base64 together with size and SHA-256, then durably creates every per-recipient relay intent. Each other active seat receives two core-protocol items: a signed `room_file` metadata envelope and a binary file containing the original bytes. Core receives bytes, never a local filesystem path, so there is no staging-file or path-permission dependency. A seat removed after fan-out is skipped terminally without receiving metadata or bytes. A crash can redrive an unfinished recipient from the archived bytes; an already terminal recipient is not retried.
24
+ Before consuming an incoming file from standard SDK state, cowork durably archives its exact bytes as canonical base64 together with size, SHA-256, and any SDK reply reference, then durably creates every per-recipient relay intent. Each other active seat receives two SDK items: an authenticated `room_file` metadata envelope and a binary file containing the original bytes. The SDK receives bytes, never a local filesystem path, so there is no staging-file or path-permission dependency. A seat removed after fan-out is skipped terminally without receiving metadata or bytes. A crash can redrive an unfinished recipient from the archived bytes; an already terminal recipient is not retried.
25
25
 
26
- In an anonymous room, signed file metadata identifies the author only by participant ID and alias. The operator archive still retains the real author and exact file bytes. The participant-facing history projection currently contains messages only; file records remain visible in the complete operator Archive/CLI history, while recipients retrieve file bytes through their ordinary ours file inbox.
26
+ In an anonymous room, SDK-authenticated file metadata identifies the author only by participant ID and alias. The operator archive still retains the real author and exact file bytes. The participant-facing history projection currently contains messages only; file records remain visible in the complete operator Archive/CLI history, while recipients retrieve file bytes through their ordinary ours file inbox.
27
27
 
28
28
  History records include messages, files, and the durable relay intent/result trail used for restart recovery.
29
29
 
@@ -1,9 +1,9 @@
1
1
  # Backup and restore
2
2
 
3
- Stop the daemon before taking a backup. A live copy can split metadata, append-only archive records, packet state, and filesystem durability boundaries across different moments.
3
+ Stop the daemon before taking a backup. A live copy can split metadata, append-only archive records, embedded SDK identity state, and filesystem durability boundaries across different moments.
4
4
 
5
5
  Back up the complete state directory as one unit, preserving ownership and file modes. Do not select only `rooms/` or only room JSON files.
6
6
 
7
7
  For restore, stop the daemon, replace the complete state directory with the complete backup, restore its original owner and `0700`/`0600` permissions, and then start the daemon. Do not merge individual room directories from different snapshots. Restore to a compatible package version and verify `ours-cowork status` plus representative `room show` and `room history` calls.
8
8
 
9
- Room restore preserves the persisted signing secret, CID, packet state, and exact announced identity name. Current rooms therefore retain the `ours-cowork-room:<initial room_name>` they were created with, even if mutable display metadata was renamed later. Legacy `cowork-room-<room_id>` identities stay legacy; restore never upgrades or recreates them.
9
+ Room restore requires both cowork room metadata and its private `<state-dir>/ours-sdk` identity state. Current rooms restore the exact `ours-cowork-<room_id>` identity and verify its CID before hosting it. Pre-1.0 custom packet state is not upgraded or recreated in place; startup refuses it with guidance to use the old release for backup, recreate the room, and re-invite participants.
@@ -7,6 +7,8 @@ ours-cowork install-service
7
7
  ours-cowork uninstall-service
8
8
  ```
9
9
 
10
- Linux uses `ours-cowork.service` under the systemd user directory. macOS uses the `network.ours.cowork` launchd agent. Both definitions execute the installed cowork CLI directly in `serve` mode and preserve the effective broker, state directory, and optional REST port settings.
10
+ Linux uses `ours-cowork.service` under the systemd user directory. macOS uses the `network.ours.cowork` launchd agent. Both definitions execute the installed cowork CLI directly in `serve` mode and preserve the effective broker, state directory, and optional REST port settings. When an external ours daemon is selected, they also preserve its mode, endpoint, and state directory — never its API token, which the daemon keeps in its own state directory. A dedicated daemon needs its own service, installed and ordered before this one.
11
11
 
12
- Installation rejects service values containing NUL, newline, carriage return, or other unsafe control characters before creating or replacing a definition. It then stops a manually detached daemon through the authenticated control session before the service takes ownership. Uninstall first requires systemd to disable/stop the unit or launchd to unload the agent. If that operation fails, uninstall reports failure and retains the service definition for inspection or retry. After a successful unload, uninstall removes only the service definition; it retains all cowork configuration, room archives, and packet state. Remove data only through the explicit closed-room deletion command or a separate, deliberate host-data operation.
12
+ Both definitions restart on failure and keep retrying at a fixed interval for as long as the failure lasts. The systemd unit sets `StartLimitIntervalSec=0` in its `[Unit]` section with `RestartSec=5`, because systemd's default start rate limit would otherwise mark the unit failed after a few quick retries and stop trying at all — which is what happens when the broker, or the selected external ours daemon, is not up yet at boot. Ordering the external daemon's own unit before this one still shortens startup; the retry only removes the permanent failure. A clean stop is still final: `Restart=on-failure` does not restart a service that exited successfully. The launchd agent uses `KeepAlive` and retries on launchd's own interval.
13
+
14
+ Installation rejects service values containing NUL, newline, carriage return, or other unsafe control characters before creating or replacing a definition. It then stops a manually detached daemon through the authenticated control session before the service takes ownership. Uninstall first requires systemd to disable/stop the unit or launchd to unload the agent. If that operation fails, uninstall reports failure and retains the service definition for inspection or retry. After a successful unload, uninstall removes only the service definition; it retains all cowork configuration, room archives, and embedded SDK identity state. Remove data only through the explicit closed-room deletion command or a separate, deliberate host-data operation.
@@ -7,10 +7,11 @@
7
7
  - Relay recovery is at-least-once. A crash after transport acceptance but before the durable result can retry a send, so participants may observe duplicates. The system does not provide exactly once relay semantics.
8
8
  - The host records transport acceptance and failures; it does not observe delivery, reading, or remote processing. A successful operator command must not be interpreted as participant receipt.
9
9
  - Backups require a stopped daemon. Back up and restore the complete state directory as one unit, preserving ownership and modes; partial or live copies are unsupported.
10
- - Service uninstall retains data. It removes the systemd or launchd definition, not configuration, archives, room metadata, or packet state.
10
+ - Service uninstall retains data. It removes the systemd or launchd definition, not configuration, archives, room metadata, or embedded SDK identity state.
11
11
  - Closing and deleting are separate. First close the room explicitly with `ours-cowork room close <room-id>`. Only a closed room can then be deleted with `ours-cowork room delete <room-id> --yes`.
12
12
  - Confirmed deletion removes the retained archive and metadata from this host only. It does not claim remote purge, backup erasure, key wipe, or secure erase.
13
13
  - The web console and HTTP room RPC have no authentication. They bind only to `127.0.0.1` and must not be forwarded, proxied, or exposed remotely.
14
14
  - Web updates use periodic polling rather than push. A view can lag daemon state until its next refresh; confirmed mutations trigger an immediate refresh.
15
15
  - Browser state is transient apart from the selected-room URL hash. Invite receipts disappear when closed and are not recoverable from browser storage.
16
- - Room names are not unique. New rooms announce `ours-cowork-room:<initial room_name>`, but that authenticated identity name is immutable while `room_name` remains editable. Interfaces must distinguish duplicate or renamed rooms by CID/room ID and may use local display aliases; an alias does not change authenticated provenance.
16
+ - Room names are not unique. New rooms use the globally unique authenticated identity `ours-cowork-<room_id>` while `room_name` remains editable. Interfaces must distinguish duplicate or renamed rooms by CID/room ID and may use local display aliases; an alias does not change authenticated provenance.
17
+ - Pre-1.0 custom room actor state cannot be opened by the standard SDK runtime. Back it up with the old release, recreate the room, and re-invite its participants.
@@ -10,22 +10,11 @@ The command safely starts the daemon only when it is absent, waits for `GET /` r
10
10
 
11
11
  The console and HTTP room RPC have no authentication. They bind only to `127.0.0.1`; both the `127.0.0.1` and `localhost` browser URLs are accepted. Do not use port forwarding, a reverse proxy, or another mechanism to expose this listener to other hosts.
12
12
 
13
- ## API description
14
-
15
- The same loopback listener serves an OpenAPI 3.1 description of the room management REST API and a browser UI for it:
16
-
17
- - `http://127.0.0.1:3052/openapi.json` — the machine-readable document.
18
- - `http://127.0.0.1:3052/docs` — the UI that renders the document and can send requests.
19
-
20
- Both are read-only GET routes with no authentication, exactly like the console itself, and they are available whenever `rest.enabled` is true. They ship inside the daemon and load no remote assets, so they work on an offline host. Do not expose either route to another host.
21
-
22
- Every room operation is carried by the single route `POST /rpc` with the envelope `{ "version": 1, "id": ..., "method": ..., "params": ... }`; the document describes the seventeen methods the REST listener serves, discriminated on `method`. Operations that carry an invite secret are not part of it — they are reachable only over `management.sock`.
23
-
24
13
  ## Room setup
25
14
 
26
15
  1. Choose Create room and enter the friendly Name, Goal, and Briefing. The name is shown in the room list, workspace header, and room details.
27
16
  2. Submit once. The new room is selected and its Invite panel opens.
28
- 3. Add one invitation requirement at a time. Choose one-time or public mode and set the minimum acceptances for a public invite.
17
+ 3. Add one invitation requirement at a time. Choose one-time or public mode and set the minimum acceptances for a public invite. Revoke or consume it before creating another; the standard-SDK room keeps only one live invite so contact admission remains unambiguous.
29
18
  4. Copy every invite from its blocking receipt before choosing Done. The secret is shown once and is not retained in browser storage, the URL, logs, or durable room metadata.
30
19
 
31
20
  The room activates after its durable invitation requirements are satisfied. Participants shows admitted identities and their invite roles.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ours.network/cowork",
3
- "version": "0.4.0",
4
- "description": "Standalone daemon for durable ours mission rooms.",
3
+ "version": "0.4.1-nightly.20260816.4aaf940",
4
+ "description": "Standalone standard-SDK daemon for durable ours mission rooms.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",
7
7
  "bin": {
@@ -18,7 +18,6 @@
18
18
  },
19
19
  "scripts": {
20
20
  "build": "node build.mjs",
21
- "compile:mufl": "scripts/compile-mufl.sh",
22
21
  "typecheck": "tsc -p tsconfig.json --noEmit",
23
22
  "typecheck:web": "tsc -p web/tsconfig.json --noEmit",
24
23
  "test": "node --import tsx --test --test-concurrency=1 tests/*.test.mjs",
@@ -27,15 +26,13 @@
27
26
  "test:release": "node --test tests/static-gates.test.mjs"
28
27
  },
29
28
  "dependencies": {
30
- "@adapt-toolkit/sdk": "0.10.12",
31
- "@adapt-toolkit/sdk-native": "0.10.12",
29
+ "@ours.network/sdk": "1.3.1",
32
30
  "react": "18.3.1",
33
31
  "react-dom": "18.3.1",
34
32
  "zod": "^3.23.8"
35
33
  },
36
34
  "devDependencies": {
37
35
  "@adapt-toolkit/broker": "0.10.12",
38
- "@adapt-toolkit/mufl": "0.10.12",
39
36
  "@testing-library/jest-dom": "6.6.3",
40
37
  "@testing-library/react": "16.1.0",
41
38
  "@testing-library/user-event": "14.5.2",