@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,326 @@
1
+ # DaemonClient SDK
2
+
3
+ > 上游:`spec/daemon.md`、`spec/events.md`
4
+ > 主题:CLI / GUI / WebUI / HTTP adapter 面向 Device-level Host 的统一客户端。
5
+ > 归属包:`@scorel/client`。该包只依赖 `@scorel/protocol`,不得依赖 `@scorel/core` 或 `@scorel/daemon`。
6
+
7
+ ---
8
+
9
+ ## 0. 定位
10
+
11
+ `DaemonClient` 是 Entry 侧 SDK,不是 Host 的一部分。它负责:
12
+
13
+ - transport 连接、认证、重连和 request/response correlation
14
+ - Device handshake
15
+ - Project Registry 操作
16
+ - Session 操作
17
+ - dual-seq resync、transient buffer 和本地 UI state projection
18
+
19
+ `@scorel/client` 提供 platform-neutral `DaemonClient` 和 browser-safe `WsTransport`。后续 `RelayTransport` 也必须实现同一 `DaemonTransport` contract。需要直接持有 Host 实例的 embedded adapter 由 `@scorel/daemon` 提供。
20
+
21
+ Client 不持有 Runtime,不自行解释工作目录,不从 URL、display name 或路径 slug 反推 project 身份。
22
+
23
+ ---
24
+
25
+ ## 1. 核心接口
26
+
27
+ ```typescript
28
+ interface DaemonClient {
29
+ // Connection
30
+ connect(sessionId?: SessionId): Promise<void>;
31
+ disconnect(): void;
32
+ readonly state: "disconnected" | "connecting" | "connected" | "reconnecting";
33
+ readonly sessionId: SessionId | null;
34
+ readonly clientId: ClientId;
35
+ readonly connectionIdentity: {
36
+ deviceId: DeviceId;
37
+ deviceDisplayName?: string;
38
+ } | null;
39
+
40
+ // Projects
41
+ listDirectories(path?: string): Promise<DirectoryListing>;
42
+ registerProject(workDir: string): Promise<HostProject>;
43
+ listProjects(): Promise<HostProject[]>;
44
+ removeProject(projectId: ProjectId): Promise<void>;
45
+
46
+ // Sessions
47
+ createSession(input: {
48
+ meta: Omit<SessionMeta, "projectId"> & { projectId: ProjectId };
49
+ }): Promise<SessionId>;
50
+ listSessions(filter?: {
51
+ projectId?: ProjectId;
52
+ limit?: number;
53
+ }): Promise<SessionSummary[]>;
54
+ deleteSession(sessionId: SessionId): Promise<void>;
55
+ switchSession(sessionId: SessionId): Promise<void>;
56
+ cloneSession(fromEventId: EventId, meta?: Partial<SessionMeta>): Promise<SessionId>;
57
+ updateSessionInfo(changes: Partial<SessionMeta>): Promise<EventId>;
58
+
59
+ // Conversation
60
+ sendMessage(content: string | ContentBlock[], options?: SendOptions): Promise<{
61
+ userEventId: EventId;
62
+ assistantEventId: EventId;
63
+ }>;
64
+ steer(content: string): void;
65
+ followUp(content: string): void;
66
+ cancel(): Promise<{ sessionId: SessionId; cancelled: boolean }>;
67
+ rewind(targetEventId: EventId): Promise<EventId>;
68
+ branch(leafEventId: EventId): Promise<EventId>;
69
+ compact(): Promise<EventId>;
70
+
71
+ // Query and subscription
72
+ getTree(): Promise<PersistentEvent[]>;
73
+ getStatus(): Promise<DaemonStatus>;
74
+ subscribe(handler: (event: ScorelEvent) => void): Unsubscribe;
75
+ on<T extends ScorelEvent["type"]>(
76
+ type: T,
77
+ handler: (event: Extract<ScorelEvent, { type: T }>) => void
78
+ ): Unsubscribe;
79
+
80
+ // Recovery
81
+ readonly persistentLastSeq: Seq;
82
+ readonly streamLastSeq: Seq;
83
+ resync(anchors?: {
84
+ persistentLastSeq?: Seq;
85
+ streamLastSeq?: Seq;
86
+ }): Promise<ResyncResult>;
87
+
88
+ // Local UI projection
89
+ getLocalState(): ClientSessionState;
90
+ getEvents(): PersistentEvent[];
91
+ getActiveLeaf(): EventId | null;
92
+ }
93
+ ```
94
+
95
+ `connect()` 可以不带 `sessionId`。此时 Client 只绑定 Device,可执行 Project 和 Session 管理操作;`sendMessage()`、`cancel()`、`resync()` 等会话操作必须先绑定 Session。
96
+
97
+ ---
98
+
99
+ ## 2. Project 操作
100
+
101
+ ### 2.1 身份
102
+
103
+ `projectId` 是 Host 生成的稳定身份。Client 只把它当 opaque ID。
104
+
105
+ - `workDir` 是 owning Host 上的 canonical absolute path。
106
+ - `displayName` 是 UI label。
107
+ - Device URL、SSH host、token 和 `displayName` 都不是 project 身份。
108
+ - 不再使用 `projectSlug` 或 `workDirHint`。
109
+
110
+ ```typescript
111
+ interface HostProject {
112
+ projectId: ProjectId;
113
+ displayName: string;
114
+ workDir: string;
115
+ createdAt: number;
116
+ updatedAt: number;
117
+ }
118
+
119
+ interface DirectoryListing {
120
+ path: string;
121
+ parentPath?: string;
122
+ entries: Array<{
123
+ name: string;
124
+ path: string;
125
+ kind: "directory";
126
+ }>;
127
+ }
128
+ ```
129
+
130
+ ### 2.2 添加 Project
131
+
132
+ GUI 和 WebUI 的添加流程统一为:
133
+
134
+ 1. 选择 Device。
135
+ 2. 调用 `listDirectories()` 浏览该 Device 文件系统。
136
+ 3. 用户选中目录。
137
+ 4. 调用 `registerProject(workDir)`。
138
+ 5. Host canonicalize 路径并返回 Project。
139
+
140
+ 本地和远程使用同一套协议。远程 GUI 通过 SSH proxy 接入时,目录浏览仍由远端 Host 完成。
141
+
142
+ ### 2.3 Trusted Full Access
143
+
144
+ 开发阶段默认 Host 拥有本机完整文件访问能力。`listDirectories()` 不做 workspace-root 限制,也不在本轮引入 ACL、scope 或 approval policy。
145
+
146
+ 仍然必须:
147
+
148
+ - canonicalize path
149
+ - 拒绝不存在或不是 directory 的目标
150
+ - 不把 token、API key 或 SSH secret 写入日志
151
+
152
+ ---
153
+
154
+ ## 3. Session 操作
155
+
156
+ 每个 Session 必须归属一个 Project:
157
+
158
+ ```typescript
159
+ interface SessionMeta {
160
+ projectId: ProjectId;
161
+ title?: string;
162
+ model: string;
163
+ thinkingLevel: "none" | "low" | "medium" | "high";
164
+ }
165
+ ```
166
+
167
+ `createSession()` 只接受已注册 `projectId`。Host 根据 Registry 解析 canonical `workDir`,再创建 project-aware Runtime。Client 不允许直接覆盖 `cwd`。
168
+
169
+ `listSessions({ projectId })` 返回目标 Project 下的 Session;省略过滤器时返回当前 Device 的全部 Session。
170
+
171
+ ---
172
+
173
+ ## 4. 本地状态和恢复
174
+
175
+ DaemonClient 内部维护:
176
+
177
+ - `events`:最近一次 resync 以来的 PersistentEvent 列表
178
+ - `tree projection`:只读 UI 投影,不是 core 的权威 SessionTree
179
+ - `activeLeaf`:当前 active 叶子
180
+ - `transient buffer`:从 `message_start` 到对应 PersistentEvent 之间的 delta
181
+
182
+ Client 不实现 `buildContext`。LLM context 构建属于 `@scorel/core/session`。
183
+
184
+ ### 4.1 Dual-Seq
185
+
186
+ Client 保存两个恢复锚点:
187
+
188
+ - `persistentLastSeq`:已经持久化、进程重启后仍可渲染的最高 seq
189
+ - `streamLastSeq`:实时流中已经观察到的最高 seq
190
+
191
+ `resync()` 显式返回:
192
+
193
+ - `stream_resume`:ring buffer 连续覆盖 `streamLastSeq + 1`
194
+ - `persistent_fallback`:无法证明 transient 连续性,只补 persistent events
195
+ - `full_reload`:本地缓存不兼容,重新加载 Host 权威状态
196
+
197
+ ### 4.2 Attach Cache
198
+
199
+ 远程 attach cache 身份来自 Host metadata,不来自 URL:
200
+
201
+ ```text
202
+ remote + deviceId + projectId + sessionId
203
+ ```
204
+
205
+ URL 只是 transport locator。相同 Device 和 Project 改用另一个 URL 后,应复用同一份缓存。
206
+
207
+ ```text
208
+ ~/.scorel/attach-cache/{scopeKey}/{sessionId}.json
209
+ ~/.scorel/attach-cache/{scopeKey}/{sessionId}.log
210
+ ```
211
+
212
+ attach diagnostics 记录连接生命周期、身份解析、cache read/write、resync anchors、恢复模式、事件摘要和 outbound sends。不得记录 token、API key、SSH secret、完整 prompt 或完整 tool result。
213
+
214
+ ---
215
+
216
+ ## 5. Transport
217
+
218
+ 当前产品路径:
219
+
220
+ ```typescript
221
+ // CLI local
222
+ const transport = createEmbeddedTransport(host);
223
+
224
+ // WebUI and direct remote control
225
+ const transport = new WsTransport({ url, token });
226
+ ```
227
+
228
+ 未来新增 adapter:
229
+
230
+ ```typescript
231
+ // Hosted WebUI / Relay device routing
232
+ const transport = new RelayTransport({ relayUrl, deviceId, clientId });
233
+
234
+ // GUI-managed remote device
235
+ const transport = await createSshProxyTransport(sshConfig);
236
+
237
+ // Pure HTTP integration
238
+ const client = new HttpScorelClient({ baseUrl, token });
239
+ ```
240
+
241
+ 规则:
242
+
243
+ - Embedded 和 WebSocket 是当前已实现 transport。
244
+ - RelayTransport 是后续 hosted WebUI / generic WebUI 的默认远程路径:Entry 和 Host 都连接 Relay,Relay 根据 `deviceId -> clientId` 授权关系转发现有 daemon wire payload。
245
+ - GUI 默认通过 SSH 启动或连接远端 Scorel,再使用 stdio proxy 转发协议。
246
+ - 已经部署好的 Host 可作为高级入口直接使用 WS URL + token。
247
+ - HTTP API 是独立 adapter:命令走 HTTP request,事件走 SSE。它映射同一 Host use cases,不复制业务逻辑。
248
+ - 不恢复 Unix socket transport;S0043 已删除该产品路径。
249
+
250
+ RelayTransport 不改变 `DaemonClient` 上层语义:
251
+
252
+ ```text
253
+ Direct:
254
+ DaemonClient -> WsTransport -> Host
255
+
256
+ Relay:
257
+ DaemonClient -> RelayTransport -> Relay -> Host relay adapter -> Host
258
+ ```
259
+
260
+ Relay 只包装路由和授权信息:
261
+
262
+ ```typescript
263
+ type RelayClientPayload =
264
+ | ({ type: "connect" } & ConnectParams)
265
+ | ClientMessage;
266
+
267
+ type EntryToRelayFrame = {
268
+ deviceId: DeviceId;
269
+ payload: RelayClientPayload;
270
+ };
271
+ ```
272
+
273
+ `clientId` 是现有 daemon protocol / JSONL 中的 Entry identity。Relay 文档可用 Entry 解释产品语义,但 V1 不新增单独 `entryId` 字段。
274
+
275
+ ---
276
+
277
+ ## 6. UI 组织方式
278
+
279
+ WebUI 只能联机,采用 Device-first:
280
+
281
+ ```text
282
+ Device
283
+ └── Project
284
+ └── Session
285
+ ```
286
+
287
+ WebUI 的 Device 是业务身份,connector 是可达路径。后续同一 Device 可以有多个 connector:
288
+
289
+ ```typescript
290
+ type DeviceConnector =
291
+ | { kind: "direct_ws"; url: string; token: string }
292
+ | { kind: "relay"; relayUrl: string; deviceId: DeviceId; clientId: ClientId };
293
+ ```
294
+
295
+ 相同 `deviceId` 通过 direct WS 和 Relay 都可达时,应合并为一个 Device。缓存 scope 继续使用:
296
+
297
+ ```text
298
+ deviceId + projectId + sessionId
299
+ ```
300
+
301
+ GUI 同时管理本地和远程环境,采用 Project-first:
302
+
303
+ ```text
304
+ Project
305
+ ├── Local Device
306
+ └── Remote Device
307
+ ```
308
+
309
+ 两种视图都只使用同一组 Host API。区别只是入口和信息架构,不是后端模型分叉。
310
+
311
+ ---
312
+
313
+ ## 7. Pre-1.0 切换规则
314
+
315
+ S0048 实现时直接删除旧兼容面:
316
+
317
+ - 删除 `projectSlug`、`workDirHint` wire/schema。
318
+ - 删除 attach cache 和 WebUI local storage 的旧 key。
319
+ - 删除从 Session JSONL 聚合 Project 的旧逻辑。
320
+ - bump `protocolVersion`,旧 Client 明确失败。
321
+
322
+ 不做 alias、dual-write、迁移脚本或 silent fallback。
323
+
324
+ ---
325
+
326
+ *DaemonClient 是所有 Entry 面向 Host 的统一接口。不同产品入口只替换 transport 和视图组织方式。*