@chanlerdev/scorel 0.0.1
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/README.md +110 -0
- package/dist/index.js +6675 -0
- package/dist/index.js.map +7 -0
- package/docs/CHANGELOG.md +12 -0
- package/docs/README.md +116 -0
- package/docs/ROADMAP.md +669 -0
- package/docs/SHIP.md +242 -0
- package/docs/spec/channels.md +156 -0
- package/docs/spec/client.md +326 -0
- package/docs/spec/daemon.md +408 -0
- package/docs/spec/events.md +423 -0
- package/docs/spec/extensions.md +255 -0
- package/docs/spec/relay.md +391 -0
- package/docs/spec/runtime.md +251 -0
- package/docs/spec/session.md +380 -0
- package/docs/spec/ship/S0001-docs-baseline.md +41 -0
- package/docs/spec/ship/S0002-package-skeleton.md +56 -0
- package/docs/spec/ship/S0003-protocol-contracts.md +49 -0
- package/docs/spec/ship/S0004-session-core.md +50 -0
- package/docs/spec/ship/S0005-runtime-loop.md +48 -0
- package/docs/spec/ship/S0006-embedded-daemon-client.md +51 -0
- package/docs/spec/ship/S0007-cli-alpha.md +49 -0
- package/docs/spec/ship/S0008-coding-tools.md +107 -0
- package/docs/spec/ship/S0009-code-discovery-tools.md +82 -0
- package/docs/spec/ship/S0010-todo-tool-and-cli.md +81 -0
- package/docs/spec/ship/S0011-coding-agent-alpha-smoke.md +110 -0
- package/docs/spec/ship/S0012-coding-tools-maturity.md +143 -0
- package/docs/spec/ship/S0013-local-daemon-protocol.md +57 -0
- package/docs/spec/ship/S0014-local-daemon-lifecycle.md +64 -0
- package/docs/spec/ship/S0015-local-attach-and-broadcast.md +58 -0
- package/docs/spec/ship/S0016-local-daemon-resync-smoke.md +60 -0
- package/docs/spec/ship/S0017-grep-files-output-mode.md +49 -0
- package/docs/spec/ship/S0018-daemon-entrypoint-smoke.md +48 -0
- package/docs/spec/ship/S0019-remote-transport-contract.md +59 -0
- package/docs/spec/ship/S0020-remote-websocket-server.md +56 -0
- package/docs/spec/ship/S0021-remote-websocket-client-transport.md +55 -0
- package/docs/spec/ship/S0022-remote-daemon-cli-lifecycle.md +60 -0
- package/docs/spec/ship/S0023-remote-control-e2e-validation.md +66 -0
- package/docs/spec/ship/S0024-remote-attach-interactive-stream.md +49 -0
- package/docs/spec/ship/S0025-remote-attach-session-event-view.md +57 -0
- package/docs/spec/ship/S0026-attach-project-cache-and-dual-seq-reconnect.md +87 -0
- package/docs/spec/ship/S0027-session-diagnostics-log.md +77 -0
- package/docs/spec/ship/S0028-client-attach-diagnostics-log.md +70 -0
- package/docs/spec/ship/S0029-project-index-for-session-lookup.md +119 -0
- package/docs/spec/ship/S0030-webui-product-intent.md +73 -0
- package/docs/spec/ship/S0031-daemon-projectslug-rule.md +72 -0
- package/docs/spec/ship/S0032-daemon-protocol-completion.md +123 -0
- package/docs/spec/ship/S0033-webui-skeleton-routing.md +92 -0
- package/docs/spec/ship/S0034-webui-device-settings.md +121 -0
- package/docs/spec/ship/S0035-webui-device-handshake.md +83 -0
- package/docs/spec/ship/S0036-webui-project-session-sync.md +70 -0
- package/docs/spec/ship/S0037-webui-chatbox-v1.md +97 -0
- package/docs/spec/ship/S0038-webui-cancel-multiclient.md +65 -0
- package/docs/spec/ship/S0039-webui-e2e-newchat.md +74 -0
- package/docs/spec/ship/S0040-webui-codex-visual-tokens.md +227 -0
- package/docs/spec/ship/S0041-webui-markdown-and-tool-block.md +248 -0
- package/docs/spec/ship/S0042-webui-streaming-ux-autoscroll.md +130 -0
- package/docs/spec/ship/S0043-startup-ergonomics.md +278 -0
- package/docs/spec/ship/S0044-webui-chatbox-rebuild.md +556 -0
- package/docs/spec/ship/S0045-webui-card-sidebar-and-session-fixes.md +469 -0
- package/docs/spec/ship/S0046-webui-empty-composer-and-lazy-session.md +428 -0
- package/docs/spec/ship/S0047-webui-project-hover-newchat-and-dynamic-greeting.md +176 -0
- package/docs/spec/ship/S0048-device-level-host-project-registry.md +253 -0
- package/docs/spec/ship/S0049-webui-add-project-directory-browser.md +217 -0
- package/docs/spec/ship/S0050-instruction-snapshot-and-agents-assembly.md +338 -0
- package/docs/spec/ship/S0051-harness-item-and-system-reminder.md +190 -0
- package/docs/spec/ship/S0052-follow-up-queue-and-dual-loop.md +195 -0
- package/docs/spec/ship/S0053-skill-index-and-skill-tool.md +252 -0
- package/docs/spec/ship/S0054-webui-running-message-behavior.md +72 -0
- package/docs/spec/ship/S0055-webui-composer-acceptance-and-queue-strip.md +68 -0
- package/docs/spec/ship/S0056-relay-and-hosted-webui-contract.md +106 -0
- package/docs/spec/ship/S0057-relay-service-protocol-skeleton.md +161 -0
- package/docs/spec/ship/S0058-host-outbound-relay-and-pair-command.md +138 -0
- package/docs/spec/ship/S0059-relay-transport-and-hosted-webui-connector.md +140 -0
- package/docs/spec/ship/S0060-relay-hosted-webui-e2e-validation.md +132 -0
- package/docs/spec/ship/S0060-relay-hosted-webui-e2e-validation.verification.md +90 -0
- package/docs/spec/ship/S0061-hosted-defaults-and-cli-command-surface.md +208 -0
- package/docs/spec/ship/S0062-npm-package-and-release-workflow.md +166 -0
- package/docs/spec/tools.md +173 -0
- package/package.json +51 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# S0057: Relay Service Protocol Skeleton
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Create the first runnable Relay service slice:
|
|
6
|
+
|
|
7
|
+
- `apps/relay` exists as the deployable Relay process.
|
|
8
|
+
- `packages/protocol` exposes Relay frame and store record types.
|
|
9
|
+
- Relay can accept Entry and Host presence sockets.
|
|
10
|
+
- Relay can create/redeem pair sessions and persist `deviceId -> clientId` bindings.
|
|
11
|
+
- Relay can proxy existing daemon wire payloads between an authorized Entry and an online Host.
|
|
12
|
+
|
|
13
|
+
This spec proves the Relay proxy model without touching Host runtime behavior or WebUI product flows.
|
|
14
|
+
|
|
15
|
+
## Scope
|
|
16
|
+
|
|
17
|
+
- Add `apps/relay` to the workspace.
|
|
18
|
+
- Add `packages/protocol/src/relay.ts` and export it from `@scorel/protocol`.
|
|
19
|
+
- Define Relay frame types around existing `ClientMessage` / `DaemonMessage` payloads:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
type RelayEntryFrame =
|
|
23
|
+
| { type: "entry_hello"; clientId: ClientId }
|
|
24
|
+
| { type: "create_pair_session"; requestId: string; clientId: ClientId }
|
|
25
|
+
| { type: "entry_to_device"; deviceId: DeviceId; payload: ClientMessage }
|
|
26
|
+
| { type: "list_authorized_devices"; requestId: string };
|
|
27
|
+
|
|
28
|
+
type RelayHostFrame =
|
|
29
|
+
| { type: "host_hello"; deviceId: DeviceId }
|
|
30
|
+
| { type: "redeem_pair"; requestId: string; pairCode: string; deviceId: DeviceId }
|
|
31
|
+
| { type: "host_to_entry"; clientId: ClientId; payload: DaemonMessage };
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- Define Relay responses/errors with structured `requestId`, `code`, and `message`.
|
|
35
|
+
- Implement Relay process entrypoint and WebSocket server in `apps/relay`.
|
|
36
|
+
- Implement a Relay store interface:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
interface RelayStore {
|
|
40
|
+
upsertDevice(record: RelayDeviceRecord): Promise<void>;
|
|
41
|
+
upsertClient(record: RelayClientRecord): Promise<void>;
|
|
42
|
+
bind(input: { deviceId: DeviceId; clientId: ClientId }): Promise<void>;
|
|
43
|
+
isBound(input: { deviceId: DeviceId; clientId: ClientId }): Promise<boolean>;
|
|
44
|
+
listDevicesForClient(clientId: ClientId): Promise<RelayDeviceRecord[]>;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- Provide a file-backed store for local/dev use and tests using a temp data directory.
|
|
49
|
+
- Keep pair sessions and presence in memory.
|
|
50
|
+
- Route Entry payloads only when:
|
|
51
|
+
- Entry socket announced a `clientId`.
|
|
52
|
+
- Relay binding exists for `(deviceId, clientId)`.
|
|
53
|
+
- Host socket for `deviceId` is online.
|
|
54
|
+
- Route Host responses back to online sockets for `clientId`.
|
|
55
|
+
- Add relay diagnostics that never log payload content.
|
|
56
|
+
|
|
57
|
+
## Non-Goals
|
|
58
|
+
|
|
59
|
+
- Host outbound Relay adapter.
|
|
60
|
+
- `scorel pair` CLI command.
|
|
61
|
+
- `RelayTransport` in `@scorel/client`.
|
|
62
|
+
- Hosted WebUI pairing UI.
|
|
63
|
+
- User accounts.
|
|
64
|
+
- Cryptographic signatures or key rotation.
|
|
65
|
+
- End-to-end encryption beyond the current WebSocket transport.
|
|
66
|
+
- Project, Session, Runtime, replay, or resync logic inside Relay.
|
|
67
|
+
|
|
68
|
+
## Contract
|
|
69
|
+
|
|
70
|
+
Relay durable state:
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
devices
|
|
74
|
+
clients
|
|
75
|
+
bindings(deviceId, clientId)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Relay transient state:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
pairSessions(pairCode -> clientId)
|
|
82
|
+
presence.devices(deviceId -> hostSocket)
|
|
83
|
+
presence.clients(clientId -> entrySocket[])
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Relay routing:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
entry_to_device(deviceId, payload)
|
|
90
|
+
-> check binding(deviceId, clientId)
|
|
91
|
+
-> check device online
|
|
92
|
+
-> host socket receives { clientId, payload }
|
|
93
|
+
|
|
94
|
+
host_to_entry(clientId, payload)
|
|
95
|
+
-> entry sockets for clientId receive payload
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Pair code rules:
|
|
99
|
+
|
|
100
|
+
- short-lived
|
|
101
|
+
- single-use
|
|
102
|
+
- random enough for local/dev use
|
|
103
|
+
- invalidated after redeem
|
|
104
|
+
- safe to lose on Relay process restart
|
|
105
|
+
|
|
106
|
+
## Acceptance Criteria
|
|
107
|
+
|
|
108
|
+
- `pnpm --filter @scorel/relay test` runs Relay unit/integration tests.
|
|
109
|
+
- Relay can start with a temp data directory.
|
|
110
|
+
- Entry socket can create a pair session.
|
|
111
|
+
- Host socket can redeem the pair code.
|
|
112
|
+
- Store persists the binding.
|
|
113
|
+
- Entry can list its authorized Device.
|
|
114
|
+
- Relay refuses unbound Entry -> Device routing.
|
|
115
|
+
- Relay refuses routing to an offline Device.
|
|
116
|
+
- Relay forwards an authorized `ClientMessage` payload to the Host socket.
|
|
117
|
+
- Relay forwards a Host `DaemonMessage` payload back to the Entry socket.
|
|
118
|
+
- Relay diagnostics do not include prompt/tool payload bodies.
|
|
119
|
+
|
|
120
|
+
## Test Requirements
|
|
121
|
+
|
|
122
|
+
- Protocol tests cover Relay frame type exports and browser-safety imports.
|
|
123
|
+
- Relay server tests use a real local WebSocket server, real client sockets, and a temp file store.
|
|
124
|
+
- Tests cover:
|
|
125
|
+
- create/redeem pair
|
|
126
|
+
- pair code expiry
|
|
127
|
+
- pair code single-use
|
|
128
|
+
- authorized routing
|
|
129
|
+
- unbound routing rejection
|
|
130
|
+
- offline device rejection
|
|
131
|
+
- host response fan-out to one or more Entry sockets for the same `clientId`
|
|
132
|
+
- Run:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pnpm --filter @scorel/protocol test
|
|
136
|
+
pnpm --filter @scorel/relay test
|
|
137
|
+
pnpm typecheck
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Affected Paths
|
|
141
|
+
|
|
142
|
+
- `apps/relay/package.json`
|
|
143
|
+
- `apps/relay/tsconfig.json`
|
|
144
|
+
- `apps/relay/vitest.config.ts`
|
|
145
|
+
- `apps/relay/src/index.ts`
|
|
146
|
+
- `apps/relay/src/server.ts`
|
|
147
|
+
- `apps/relay/src/store.ts`
|
|
148
|
+
- `apps/relay/src/pairing.ts`
|
|
149
|
+
- `apps/relay/src/presence.ts`
|
|
150
|
+
- `apps/relay/src/routing.ts`
|
|
151
|
+
- `apps/relay/src/diagnostics.ts`
|
|
152
|
+
- `packages/protocol/src/relay.ts`
|
|
153
|
+
- `packages/protocol/src/index.ts`
|
|
154
|
+
- `packages/protocol/src/browser-safety.test.ts`
|
|
155
|
+
- `pnpm-lock.yaml`
|
|
156
|
+
|
|
157
|
+
## Risks
|
|
158
|
+
|
|
159
|
+
- Overbuilding Relay service before Host/WebUI integrate it. Keep this spec focused on proxy mechanics.
|
|
160
|
+
- Accidentally logging daemon payload content. Diagnostics must summarize routing metadata only.
|
|
161
|
+
- Treating file store as production storage. It is only the local/dev durable adapter for V1.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# S0058: Host Outbound Relay And Pair Command
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Let a local Scorel Host connect outbound to Relay and let the user authorize an Entry with `scorel pair <code>`.
|
|
6
|
+
|
|
7
|
+
After this spec, a Host can be online behind Relay and Relay can route authorized daemon wire payloads into the existing Host handler.
|
|
8
|
+
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
- Add Host-side Relay adapter under `packages/daemon/src/relay/`.
|
|
12
|
+
- Add `scorel pair <pairCode>` command wiring in `apps/cli`.
|
|
13
|
+
- Load or create stable Host `deviceId` using the existing Device identity path.
|
|
14
|
+
- Connect Host outbound to Relay:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
Host -> Relay: host_hello(deviceId)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- Redeem pair code:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
scorel pair <code> --relay <relayUrl>
|
|
24
|
+
-> Host identity loaded/created
|
|
25
|
+
-> Relay redeem_pair(pairCode, deviceId)
|
|
26
|
+
-> Relay binding deviceId -> clientId created
|
|
27
|
+
-> Host records authorized clientId locally when Relay returns it
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- Add local Host allowlist storage for authorized `clientId` values.
|
|
31
|
+
- Implement Host relay adapter:
|
|
32
|
+
- receive `{ clientId, payload: RelayClientPayload }` frames from Relay
|
|
33
|
+
- handle the existing daemon `connect` handshake as a relay payload so `DaemonClient.connect()` enters the normal Host connection set
|
|
34
|
+
- construct a logical daemon connection context using `clientId`
|
|
35
|
+
- pass payload into the existing Host request handler
|
|
36
|
+
- send `{ clientId, payload: DaemonMessage }` frames back to Relay
|
|
37
|
+
- Do not create a separate Relay-only Host API.
|
|
38
|
+
- Add diagnostics for Relay connection lifecycle without prompt/tool payloads.
|
|
39
|
+
|
|
40
|
+
## Non-Goals
|
|
41
|
+
|
|
42
|
+
- `RelayTransport` in `@scorel/client`.
|
|
43
|
+
- WebUI pairing UI.
|
|
44
|
+
- Hosted WebUI device list.
|
|
45
|
+
- Relay user accounts.
|
|
46
|
+
- Keypair/signature proof.
|
|
47
|
+
- Background supervisor for reconnect forever.
|
|
48
|
+
- Changing Session JSONL schema.
|
|
49
|
+
- Changing `user_message.clientId` semantics beyond using stable Entry `clientId`.
|
|
50
|
+
|
|
51
|
+
## Contract
|
|
52
|
+
|
|
53
|
+
Host outbound connection:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
scorel daemon serve --relay <relayUrl>
|
|
57
|
+
-> starts normal Host
|
|
58
|
+
-> opens Relay host socket
|
|
59
|
+
-> announces deviceId
|
|
60
|
+
-> accepts authorized Relay frames
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Pair command:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
scorel pair <pairCode> --relay <relayUrl>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Required behavior:
|
|
70
|
+
|
|
71
|
+
- If local Host identity does not exist, create it.
|
|
72
|
+
- If Relay rejects the pair code, print a clear error and do not mutate allowlist.
|
|
73
|
+
- If pair succeeds, persist the authorized `clientId` locally.
|
|
74
|
+
- Re-running pair for an already authorized `clientId` is idempotent.
|
|
75
|
+
|
|
76
|
+
Host allowlist:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
type HostRelayAuthFile = {
|
|
80
|
+
version: 1;
|
|
81
|
+
clients: Array<{
|
|
82
|
+
clientId: ClientId;
|
|
83
|
+
createdAt: number;
|
|
84
|
+
label?: string;
|
|
85
|
+
}>;
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Default location should be under `~/.scorel` and must be documented in the implementation spec if it differs.
|
|
90
|
+
|
|
91
|
+
## Acceptance Criteria
|
|
92
|
+
|
|
93
|
+
- `scorel pair <code> --relay <relayUrl>` can redeem a pair session created by Relay.
|
|
94
|
+
- Host stores authorized `clientId` locally after successful pair.
|
|
95
|
+
- Host can connect outbound to Relay and appear online.
|
|
96
|
+
- Relay can send an authorized `RelayClientPayload` frame to Host.
|
|
97
|
+
- Host routes that payload through the existing daemon handler.
|
|
98
|
+
- Host sends the resulting `DaemonMessage` frame back through Relay.
|
|
99
|
+
- Unrecognized `clientId` is rejected by Host even if Relay sends a frame.
|
|
100
|
+
- No Project, Session, Runtime, replay, or context-build logic exists in the relay adapter.
|
|
101
|
+
|
|
102
|
+
## Test Requirements
|
|
103
|
+
|
|
104
|
+
- Host relay adapter tests use the real Host object and real Relay frame types.
|
|
105
|
+
- CLI tests cover:
|
|
106
|
+
- missing pair code
|
|
107
|
+
- invalid Relay response
|
|
108
|
+
- successful pair
|
|
109
|
+
- idempotent already-authorized pair
|
|
110
|
+
- Integration test uses a real local Relay server from `apps/relay` and a real Host with temp `~/.scorel`.
|
|
111
|
+
- Tests cover a simple daemon request such as `get_status` through Relay.
|
|
112
|
+
- Run:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pnpm --filter @scorel/daemon test
|
|
116
|
+
pnpm --filter @scorel/cli test
|
|
117
|
+
pnpm --filter @scorel/relay test
|
|
118
|
+
pnpm typecheck
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Affected Paths
|
|
122
|
+
|
|
123
|
+
- `packages/daemon/src/relay/host-client.ts`
|
|
124
|
+
- `packages/daemon/src/relay/auth.ts`
|
|
125
|
+
- `packages/daemon/src/relay/pair.ts`
|
|
126
|
+
- `packages/daemon/src/index.ts`
|
|
127
|
+
- `packages/daemon/src/index.test.ts`
|
|
128
|
+
- `apps/cli/src/relay-cli.ts`
|
|
129
|
+
- `apps/cli/src/index.ts`
|
|
130
|
+
- `apps/cli/src/index.test.ts`
|
|
131
|
+
- `apps/relay/src/*`
|
|
132
|
+
- `packages/protocol/src/relay.ts`
|
|
133
|
+
|
|
134
|
+
## Risks
|
|
135
|
+
|
|
136
|
+
- Host relay adapter could accidentally become a second daemon protocol path. Keep it as a frame adapter around existing Host handlers.
|
|
137
|
+
- Host allowlist and Relay binding can drift. V1 accepts explicit re-pairing as recovery.
|
|
138
|
+
- Long-running outbound reconnect can grow too broad. Keep lifecycle minimal and testable.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# S0059: RelayTransport And Hosted WebUI Connector
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Let an Entry use Relay to connect to a paired Device through the existing `DaemonClient` contract, and let WebUI manage Relay-backed Devices alongside direct WS Devices.
|
|
6
|
+
|
|
7
|
+
After this spec, WebUI can pair with a Device through Relay, list authorized Relay Devices, open a Relay-backed `DaemonClient`, and use existing Project / Session flows through that connection.
|
|
8
|
+
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
- Add `RelayTransport` in `packages/client`.
|
|
12
|
+
- `RelayTransport` implements the existing `DaemonTransport` interface.
|
|
13
|
+
- `RelayTransport`:
|
|
14
|
+
- opens an Entry socket to Relay
|
|
15
|
+
- announces stable `clientId`
|
|
16
|
+
- sends `{ deviceId, payload: ClientMessage }` frames
|
|
17
|
+
- receives `DaemonMessage` payloads
|
|
18
|
+
- maps Relay errors to existing client transport errors where possible
|
|
19
|
+
- Add stable WebUI `clientId` storage.
|
|
20
|
+
- Extend WebUI Device store from one `link + token` to a connector model that can represent:
|
|
21
|
+
- direct WS connector
|
|
22
|
+
- Relay connector
|
|
23
|
+
- Merge Devices by `remoteIdentity.deviceId`.
|
|
24
|
+
- Add hosted WebUI pairing flow:
|
|
25
|
+
- create pair session through Relay
|
|
26
|
+
- show pair code
|
|
27
|
+
- wait for pair completion / authorized Device presence
|
|
28
|
+
- add or merge Relay connector for that Device
|
|
29
|
+
- Add Relay device discovery:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
list_authorized_devices(clientId)
|
|
33
|
+
-> [{ deviceId, label?, online }]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- Use Relay connector through the existing WebUI connection pool.
|
|
37
|
+
- Keep direct local WS as preferred connector when healthy.
|
|
38
|
+
|
|
39
|
+
## Non-Goals
|
|
40
|
+
|
|
41
|
+
- Implement Relay service internals beyond what S0057 provides.
|
|
42
|
+
- Implement Host outbound adapter beyond S0058.
|
|
43
|
+
- User accounts.
|
|
44
|
+
- Fine-grained permissions.
|
|
45
|
+
- Desktop GUI.
|
|
46
|
+
- SSH remote Device.
|
|
47
|
+
- Changing transcript rendering, composer behavior, or Session event projection.
|
|
48
|
+
- Changing JSONL schema.
|
|
49
|
+
|
|
50
|
+
## Contract
|
|
51
|
+
|
|
52
|
+
Entry-side transport:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
const transport = new RelayTransport({
|
|
56
|
+
relayUrl,
|
|
57
|
+
deviceId,
|
|
58
|
+
clientId,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const client = new DaemonClient(transport, options);
|
|
62
|
+
await client.connect(sessionId);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
WebUI Device model:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
type DeviceConnector =
|
|
69
|
+
| { kind: "direct_ws"; url: string; token: string }
|
|
70
|
+
| { kind: "relay"; relayUrl: string; deviceId: DeviceId; clientId: ClientId };
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Connection selection:
|
|
74
|
+
|
|
75
|
+
1. Prefer healthy direct WS.
|
|
76
|
+
2. Use Relay when direct is unavailable and Relay says Device is online.
|
|
77
|
+
3. Render offline cached state when no connector is reachable.
|
|
78
|
+
|
|
79
|
+
Cache scope:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
deviceId + projectId + sessionId
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Connector URL or Relay URL must not create a separate cache namespace for the same Host.
|
|
86
|
+
|
|
87
|
+
## Acceptance Criteria
|
|
88
|
+
|
|
89
|
+
- `RelayTransport` passes the same core behavior tests expected of `DaemonTransport`.
|
|
90
|
+
- WebUI can store a stable `clientId`.
|
|
91
|
+
- WebUI can create a Relay pair session and display the pair code.
|
|
92
|
+
- WebUI can add a Relay connector after pair succeeds.
|
|
93
|
+
- WebUI merges a direct and Relay connector when they resolve to the same `deviceId`.
|
|
94
|
+
- WebUI uses existing `syncProjects`, `syncSessions`, `sendMessage`, and `resync` through `DaemonClient` without Relay-specific forks above transport.
|
|
95
|
+
- WebUI shows Relay-backed Device online/offline state from Relay presence.
|
|
96
|
+
- Direct WS continues to work unchanged.
|
|
97
|
+
|
|
98
|
+
## Test Requirements
|
|
99
|
+
|
|
100
|
+
- `packages/client` tests cover:
|
|
101
|
+
- RelayTransport connect
|
|
102
|
+
- request/response correlation through Relay
|
|
103
|
+
- event delivery through Relay
|
|
104
|
+
- Relay offline / unauthorized errors
|
|
105
|
+
- disconnect cleanup
|
|
106
|
+
- WebUI store tests cover:
|
|
107
|
+
- stable `clientId`
|
|
108
|
+
- connector add/update/remove
|
|
109
|
+
- merge by `remoteIdentity.deviceId`
|
|
110
|
+
- cache scope independent of connector kind
|
|
111
|
+
- WebUI connection pool tests cover connector selection and fallback.
|
|
112
|
+
- WebUI pairing UI tests cover create pair session, show code, pair success, pair failure, and timeout.
|
|
113
|
+
- Integration tests use real local Relay and Host where possible.
|
|
114
|
+
- Run:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pnpm --filter @scorel/client test
|
|
118
|
+
pnpm --filter @scorel/webui test
|
|
119
|
+
pnpm --filter @scorel/relay test
|
|
120
|
+
pnpm typecheck
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Affected Paths
|
|
124
|
+
|
|
125
|
+
- `packages/client/src/relay-transport.ts`
|
|
126
|
+
- `packages/client/src/index.ts`
|
|
127
|
+
- `packages/client/src/relay-transport.test.ts`
|
|
128
|
+
- `apps/webui/lib/store/devices.ts`
|
|
129
|
+
- `apps/webui/lib/store/client-identity.ts`
|
|
130
|
+
- `apps/webui/lib/connection/pool.ts`
|
|
131
|
+
- `apps/webui/components/settings/*`
|
|
132
|
+
- `apps/webui/components/shell/*`
|
|
133
|
+
- `apps/webui/lib/sync/*`
|
|
134
|
+
- `apps/webui/src/package-boundaries.test.ts`
|
|
135
|
+
|
|
136
|
+
## Risks
|
|
137
|
+
|
|
138
|
+
- WebUI Device store migration can split one real Device into duplicates. Merge by `deviceId` once known.
|
|
139
|
+
- Relay-specific UI branches can leak above `DaemonClient`. Keep connector handling below connection acquisition.
|
|
140
|
+
- Pairing UI can imply Relay owns Projects. Keep Project/Session sync explicitly Host-backed.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# S0060: Relay Hosted WebUI E2E Validation
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Close M8 by proving the full Relay + hosted WebUI product path with real components:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Hosted WebUI Entry -> Relay -> local Host -> Project -> Session -> Runtime
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This spec validates that Relay is only a proxy/authorization registry and that Host remains the Project, Session, Runtime, JSONL, replay, and resync authority.
|
|
12
|
+
|
|
13
|
+
## Scope
|
|
14
|
+
|
|
15
|
+
- Run a real local Relay service.
|
|
16
|
+
- Run a real local Host connected outbound to Relay.
|
|
17
|
+
- Run WebUI in Relay connector mode.
|
|
18
|
+
- Pair WebUI Entry with Host using the real pair flow.
|
|
19
|
+
- Add or select a real local Project through Host.
|
|
20
|
+
- Create a Session through Relay-backed `DaemonClient`.
|
|
21
|
+
- Send a prompt using a real configured LLM provider.
|
|
22
|
+
- Observe persistent and transient events in WebUI.
|
|
23
|
+
- Refresh/reconnect WebUI and verify Host-owned resync.
|
|
24
|
+
- Stop/restart Relay and verify:
|
|
25
|
+
- live sockets are lost
|
|
26
|
+
- durable bindings survive if using the configured durable store
|
|
27
|
+
- Host JSONL and Project Registry remain unaffected
|
|
28
|
+
- Audit Relay store/logs to verify no Project Registry, prompt, tool result, Session JSONL, provider response, or replay cache is stored.
|
|
29
|
+
- Update `docs/ROADMAP.md` M8 status to Done only if the real path passes.
|
|
30
|
+
|
|
31
|
+
## Non-Goals
|
|
32
|
+
|
|
33
|
+
- Add new Relay features beyond S0057-S0059.
|
|
34
|
+
- Add accounts or OAuth.
|
|
35
|
+
- Add hosted execution.
|
|
36
|
+
- Add desktop GUI.
|
|
37
|
+
- Add SSH bootstrap.
|
|
38
|
+
- Add HTTP API.
|
|
39
|
+
- Introduce fake provider, fake Host, or test-only Relay route.
|
|
40
|
+
|
|
41
|
+
## Contract
|
|
42
|
+
|
|
43
|
+
The successful product path is:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
WebUI loads stable clientId
|
|
47
|
+
WebUI creates pair code
|
|
48
|
+
User runs scorel pair <code>
|
|
49
|
+
Host connects outbound to Relay
|
|
50
|
+
WebUI opens RelayTransport to deviceId
|
|
51
|
+
DaemonClient.connect succeeds
|
|
52
|
+
WebUI syncs Projects from Host
|
|
53
|
+
WebUI creates Session under Project
|
|
54
|
+
WebUI sends prompt
|
|
55
|
+
Host writes user_message.clientId to JSONL
|
|
56
|
+
Host runs Runtime in Project cwd
|
|
57
|
+
WebUI receives event stream through Relay
|
|
58
|
+
WebUI refreshes and resyncs from Host
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Relay must only contain:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
devices
|
|
65
|
+
clients
|
|
66
|
+
bindings
|
|
67
|
+
presence
|
|
68
|
+
pair sessions
|
|
69
|
+
routing metadata
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Relay must not contain user workspace content.
|
|
73
|
+
|
|
74
|
+
## Acceptance Criteria
|
|
75
|
+
|
|
76
|
+
- Real hosted/WebUI Relay mode can pair with local Host.
|
|
77
|
+
- WebUI displays the paired Relay Device as online.
|
|
78
|
+
- WebUI can list Host Projects through Relay.
|
|
79
|
+
- WebUI can create a Session under a Project through Relay.
|
|
80
|
+
- WebUI can send a prompt through Relay and receive the assistant event stream.
|
|
81
|
+
- The resulting Session JSONL is written under Host-owned `~/.scorel`, not Relay storage.
|
|
82
|
+
- `user_message.clientId` in JSONL is the WebUI Entry `clientId`.
|
|
83
|
+
- WebUI refresh can recover via existing `DaemonClient` resync through Relay.
|
|
84
|
+
- Relay logs and store contain no prompt/tool/session payload.
|
|
85
|
+
- Direct WS mode still works after Relay changes.
|
|
86
|
+
- `pnpm typecheck && pnpm test` passes.
|
|
87
|
+
- ROADMAP marks M8 Done only after this validation passes.
|
|
88
|
+
|
|
89
|
+
## Test Requirements
|
|
90
|
+
|
|
91
|
+
- Run the executable relay e2e verifier:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pnpm verify:m8-relay
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The verifier starts:
|
|
98
|
+
|
|
99
|
+
- Relay
|
|
100
|
+
- Host outbound Relay connection
|
|
101
|
+
- WebUI
|
|
102
|
+
- real LLM provider configuration
|
|
103
|
+
|
|
104
|
+
It automates pair/list/create/send/resync/storage-audit coverage where practical.
|
|
105
|
+
Manual verification remains acceptable for browser-only UI inspection, but must record exact commands and expected evidence in the spec or an M8 verification note.
|
|
106
|
+
- No mock/fake provider is accepted as M8 completion proof.
|
|
107
|
+
- Run:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pnpm typecheck
|
|
111
|
+
pnpm test
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Plus `pnpm verify:m8-relay`.
|
|
115
|
+
|
|
116
|
+
## Affected Paths
|
|
117
|
+
|
|
118
|
+
- `apps/relay/*`
|
|
119
|
+
- `apps/cli/*`
|
|
120
|
+
- `apps/webui/*`
|
|
121
|
+
- `packages/client/*`
|
|
122
|
+
- `packages/daemon/*`
|
|
123
|
+
- `packages/protocol/*`
|
|
124
|
+
- `docs/ROADMAP.md`
|
|
125
|
+
- optional `docs/spec/ship/S0060-*.verification.md` or `self/discussions/*` for local evidence before docs sync
|
|
126
|
+
|
|
127
|
+
## Risks
|
|
128
|
+
|
|
129
|
+
- Passing unit tests can still miss the real hosted workflow. M8 closure requires real components.
|
|
130
|
+
- Relay logs may accidentally include payload bodies. Audit logs and store files directly.
|
|
131
|
+
- WebUI may pass through Relay but still use stale direct connector cache. Explicitly verify connector kind and `deviceId` cache scope.
|
|
132
|
+
- Real provider configuration may be unavailable in CI. Keep the manual real-provider checklist explicit.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# S0060 Verification Note: Relay Hosted WebUI E2E
|
|
2
|
+
|
|
3
|
+
Date: 2026-06-06
|
|
4
|
+
|
|
5
|
+
## Result
|
|
6
|
+
|
|
7
|
+
S0060 passed.
|
|
8
|
+
|
|
9
|
+
M8 Relay is complete as of this verification. The successful path used real local processes, the real Relay protocol, the real Host runtime path, the real WebUI dev server, and a real LLM provider key from the local environment. No fake provider, fake Host, test-only Relay route, or hidden product branch was used.
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm verify:m8-relay
|
|
15
|
+
pnpm typecheck && pnpm test
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`pnpm verify:m8-relay` requires either `SCOREL_API_KEY` or `OPENAI_API_KEY` in the environment. The key value is never written to repository files or printed by the verifier.
|
|
19
|
+
|
|
20
|
+
## Real E2E Evidence
|
|
21
|
+
|
|
22
|
+
Last successful run:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"ok": true,
|
|
27
|
+
"relayUrl": "ws://127.0.0.1:62939",
|
|
28
|
+
"webuiUrl": "http://127.0.0.1:62940",
|
|
29
|
+
"deviceId": "device_7dfab650-02bc-4b47-b482-8d29530d355e",
|
|
30
|
+
"entryClientId": "client_webui_mq14pc91",
|
|
31
|
+
"projectId": "prj_3e877eaa-501c-40de-9182-c5af57016b75",
|
|
32
|
+
"sessionId": "ses_421b97e8-ec77-4b6b-b34c-d4d0b7db7316"
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The verifier starts:
|
|
37
|
+
|
|
38
|
+
- a real local Relay process with durable file store
|
|
39
|
+
- a real local Host daemon process connected outbound to Relay
|
|
40
|
+
- a real WebUI Next dev server
|
|
41
|
+
- a temporary real Project with project-level `.scorel/config.toml`
|
|
42
|
+
- a Relay Entry using `RelayTransport` and `DaemonClient`
|
|
43
|
+
|
|
44
|
+
The verifier then:
|
|
45
|
+
|
|
46
|
+
- creates a Relay pair session
|
|
47
|
+
- runs `pnpm scorel pair <pair-code> --relay <relay-url>` against the same temporary Host home
|
|
48
|
+
- confirms Relay lists the paired Device as online
|
|
49
|
+
- lists Host Projects through Relay
|
|
50
|
+
- creates a Session under the Host Project through Relay
|
|
51
|
+
- sends a real prompt through Relay to the Host runtime
|
|
52
|
+
- receives a real assistant event stream through Relay
|
|
53
|
+
- reconnects a fresh Entry client and resyncs the real Session through Relay
|
|
54
|
+
- checks the Host-owned JSONL contains the real prompt and preserves the WebUI Entry `clientId`
|
|
55
|
+
- checks Relay durable storage contains device/client/binding metadata
|
|
56
|
+
- checks Relay storage/logs do not contain prompt content
|
|
57
|
+
- restarts Relay and confirms durable bindings survive
|
|
58
|
+
|
|
59
|
+
## Bug Found During Validation
|
|
60
|
+
|
|
61
|
+
The first real S0060 run exposed a Relay presence bug:
|
|
62
|
+
|
|
63
|
+
- `scorel pair` opens a temporary Host socket using the same `deviceId` as the daemon Host.
|
|
64
|
+
- The old Relay presence model allowed only one socket per `deviceId`.
|
|
65
|
+
- When the pair socket closed, it removed the daemon Host presence and WebUI saw the Device as offline.
|
|
66
|
+
|
|
67
|
+
Fix:
|
|
68
|
+
|
|
69
|
+
- Relay presence now tracks a set of Host sockets per `deviceId`.
|
|
70
|
+
- Closing the pair socket no longer marks the daemon Host offline while the daemon socket remains open.
|
|
71
|
+
- Regression test: `keeps a daemon Host online when a second pair socket for the same device closes`.
|
|
72
|
+
|
|
73
|
+
## Acceptance Coverage
|
|
74
|
+
|
|
75
|
+
- Real hosted/WebUI Relay mode can pair with local Host: passed.
|
|
76
|
+
- WebUI dev server starts and serves `/settings`: passed.
|
|
77
|
+
- WebUI Entry identity is represented by the verifier's stable `client_webui_*` client id: passed.
|
|
78
|
+
- Relay displays the paired Device as online through `list_authorized_devices`: passed.
|
|
79
|
+
- Host Projects can be listed through Relay: passed.
|
|
80
|
+
- Session can be created under a Host Project through Relay: passed.
|
|
81
|
+
- Prompt can be sent through Relay and receives assistant events from a real provider: passed.
|
|
82
|
+
- Resulting Session JSONL is written under Host-owned state, not Relay storage: passed.
|
|
83
|
+
- `user_message.clientId` in JSONL is the WebUI Entry `clientId`: passed.
|
|
84
|
+
- WebUI refresh/reconnect equivalent resync through a fresh Relay Entry client: passed.
|
|
85
|
+
- Relay logs/store contain no prompt/tool/session payload: passed for prompt-content audit.
|
|
86
|
+
- Direct WS mode regression remains covered by the full workspace test suite.
|
|
87
|
+
|
|
88
|
+
## Notes
|
|
89
|
+
|
|
90
|
+
The verifier intentionally uses temporary `HOME`, Relay data, and Project directories so it does not mutate the engineer's real `~/.scorel` state. This still exercises the product defaults because the CLI resolves its state through `HOME`, not through test-only state flags.
|