@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.
Files changed (80) hide show
  1. package/README.md +110 -0
  2. package/dist/index.js +6675 -0
  3. package/dist/index.js.map +7 -0
  4. package/docs/CHANGELOG.md +12 -0
  5. package/docs/README.md +116 -0
  6. package/docs/ROADMAP.md +669 -0
  7. package/docs/SHIP.md +242 -0
  8. package/docs/spec/channels.md +156 -0
  9. package/docs/spec/client.md +326 -0
  10. package/docs/spec/daemon.md +408 -0
  11. package/docs/spec/events.md +423 -0
  12. package/docs/spec/extensions.md +255 -0
  13. package/docs/spec/relay.md +391 -0
  14. package/docs/spec/runtime.md +251 -0
  15. package/docs/spec/session.md +380 -0
  16. package/docs/spec/ship/S0001-docs-baseline.md +41 -0
  17. package/docs/spec/ship/S0002-package-skeleton.md +56 -0
  18. package/docs/spec/ship/S0003-protocol-contracts.md +49 -0
  19. package/docs/spec/ship/S0004-session-core.md +50 -0
  20. package/docs/spec/ship/S0005-runtime-loop.md +48 -0
  21. package/docs/spec/ship/S0006-embedded-daemon-client.md +51 -0
  22. package/docs/spec/ship/S0007-cli-alpha.md +49 -0
  23. package/docs/spec/ship/S0008-coding-tools.md +107 -0
  24. package/docs/spec/ship/S0009-code-discovery-tools.md +82 -0
  25. package/docs/spec/ship/S0010-todo-tool-and-cli.md +81 -0
  26. package/docs/spec/ship/S0011-coding-agent-alpha-smoke.md +110 -0
  27. package/docs/spec/ship/S0012-coding-tools-maturity.md +143 -0
  28. package/docs/spec/ship/S0013-local-daemon-protocol.md +57 -0
  29. package/docs/spec/ship/S0014-local-daemon-lifecycle.md +64 -0
  30. package/docs/spec/ship/S0015-local-attach-and-broadcast.md +58 -0
  31. package/docs/spec/ship/S0016-local-daemon-resync-smoke.md +60 -0
  32. package/docs/spec/ship/S0017-grep-files-output-mode.md +49 -0
  33. package/docs/spec/ship/S0018-daemon-entrypoint-smoke.md +48 -0
  34. package/docs/spec/ship/S0019-remote-transport-contract.md +59 -0
  35. package/docs/spec/ship/S0020-remote-websocket-server.md +56 -0
  36. package/docs/spec/ship/S0021-remote-websocket-client-transport.md +55 -0
  37. package/docs/spec/ship/S0022-remote-daemon-cli-lifecycle.md +60 -0
  38. package/docs/spec/ship/S0023-remote-control-e2e-validation.md +66 -0
  39. package/docs/spec/ship/S0024-remote-attach-interactive-stream.md +49 -0
  40. package/docs/spec/ship/S0025-remote-attach-session-event-view.md +57 -0
  41. package/docs/spec/ship/S0026-attach-project-cache-and-dual-seq-reconnect.md +87 -0
  42. package/docs/spec/ship/S0027-session-diagnostics-log.md +77 -0
  43. package/docs/spec/ship/S0028-client-attach-diagnostics-log.md +70 -0
  44. package/docs/spec/ship/S0029-project-index-for-session-lookup.md +119 -0
  45. package/docs/spec/ship/S0030-webui-product-intent.md +73 -0
  46. package/docs/spec/ship/S0031-daemon-projectslug-rule.md +72 -0
  47. package/docs/spec/ship/S0032-daemon-protocol-completion.md +123 -0
  48. package/docs/spec/ship/S0033-webui-skeleton-routing.md +92 -0
  49. package/docs/spec/ship/S0034-webui-device-settings.md +121 -0
  50. package/docs/spec/ship/S0035-webui-device-handshake.md +83 -0
  51. package/docs/spec/ship/S0036-webui-project-session-sync.md +70 -0
  52. package/docs/spec/ship/S0037-webui-chatbox-v1.md +97 -0
  53. package/docs/spec/ship/S0038-webui-cancel-multiclient.md +65 -0
  54. package/docs/spec/ship/S0039-webui-e2e-newchat.md +74 -0
  55. package/docs/spec/ship/S0040-webui-codex-visual-tokens.md +227 -0
  56. package/docs/spec/ship/S0041-webui-markdown-and-tool-block.md +248 -0
  57. package/docs/spec/ship/S0042-webui-streaming-ux-autoscroll.md +130 -0
  58. package/docs/spec/ship/S0043-startup-ergonomics.md +278 -0
  59. package/docs/spec/ship/S0044-webui-chatbox-rebuild.md +556 -0
  60. package/docs/spec/ship/S0045-webui-card-sidebar-and-session-fixes.md +469 -0
  61. package/docs/spec/ship/S0046-webui-empty-composer-and-lazy-session.md +428 -0
  62. package/docs/spec/ship/S0047-webui-project-hover-newchat-and-dynamic-greeting.md +176 -0
  63. package/docs/spec/ship/S0048-device-level-host-project-registry.md +253 -0
  64. package/docs/spec/ship/S0049-webui-add-project-directory-browser.md +217 -0
  65. package/docs/spec/ship/S0050-instruction-snapshot-and-agents-assembly.md +338 -0
  66. package/docs/spec/ship/S0051-harness-item-and-system-reminder.md +190 -0
  67. package/docs/spec/ship/S0052-follow-up-queue-and-dual-loop.md +195 -0
  68. package/docs/spec/ship/S0053-skill-index-and-skill-tool.md +252 -0
  69. package/docs/spec/ship/S0054-webui-running-message-behavior.md +72 -0
  70. package/docs/spec/ship/S0055-webui-composer-acceptance-and-queue-strip.md +68 -0
  71. package/docs/spec/ship/S0056-relay-and-hosted-webui-contract.md +106 -0
  72. package/docs/spec/ship/S0057-relay-service-protocol-skeleton.md +161 -0
  73. package/docs/spec/ship/S0058-host-outbound-relay-and-pair-command.md +138 -0
  74. package/docs/spec/ship/S0059-relay-transport-and-hosted-webui-connector.md +140 -0
  75. package/docs/spec/ship/S0060-relay-hosted-webui-e2e-validation.md +132 -0
  76. package/docs/spec/ship/S0060-relay-hosted-webui-e2e-validation.verification.md +90 -0
  77. package/docs/spec/ship/S0061-hosted-defaults-and-cli-command-surface.md +208 -0
  78. package/docs/spec/ship/S0062-npm-package-and-release-workflow.md +166 -0
  79. package/docs/spec/tools.md +173 -0
  80. 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.