paseo-plugin-zcode-provider 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +29 -0
  3. package/README.md +98 -2
  4. package/client/diagnostics.tsx +397 -0
  5. package/client/setup.tsx +347 -0
  6. package/icon.svg +1 -0
  7. package/index.client.tsx +22 -0
  8. package/index.server.ts +21 -0
  9. package/package.json +75 -4
  10. package/paseo-plugin.json +6 -0
  11. package/server/attachments.ts +150 -0
  12. package/server/build-info.ts +2 -0
  13. package/server/conversation.ts +319 -0
  14. package/server/diagnostics.ts +235 -0
  15. package/server/discovery/discover.ts +225 -0
  16. package/server/discovery/manifest.ts +36 -0
  17. package/server/discovery/types.ts +30 -0
  18. package/server/errors.ts +36 -0
  19. package/server/host/bridge.ts +463 -0
  20. package/server/host/schemas.ts +119 -0
  21. package/server/logger.ts +19 -0
  22. package/server/mapping.ts +270 -0
  23. package/server/models.ts +68 -0
  24. package/server/persistence.ts +146 -0
  25. package/server/presentation.ts +428 -0
  26. package/server/provider.ts +831 -0
  27. package/server/runtime/managed.ts +501 -0
  28. package/server/runtime/pins.ts +72 -0
  29. package/server/runtime-setup.ts +102 -0
  30. package/server/session-types.ts +49 -0
  31. package/server/session.ts +866 -0
  32. package/server/status.ts +89 -0
  33. package/server/timeline.ts +18 -0
  34. package/server/vendor/zcode/LICENSE +202 -0
  35. package/server/vendor/zcode/NOTICE.md +70 -0
  36. package/server/vendor/zcode/THIRD-PARTY-NOTICES.md +37 -0
  37. package/server/vendor/zcode/packages/model-option-map/src/compiler.ts +92 -0
  38. package/server/vendor/zcode/packages/model-option-map/src/evaluator.ts +214 -0
  39. package/server/vendor/zcode/packages/model-option-map/src/index.ts +5 -0
  40. package/server/vendor/zcode/packages/model-option-map/src/merge-patch.ts +97 -0
  41. package/server/vendor/zcode/packages/model-option-map/src/option-maps.ts +42 -0
  42. package/server/vendor/zcode/packages/model-option-map/src/parser.ts +240 -0
  43. package/server/vendor/zcode/packages/model-option-map/src/tokenizer.ts +141 -0
  44. package/server/vendor/zcode/packages/model-option-map/src/types.ts +36 -0
  45. package/server/vendor/zcode/packages/rpc/src/buffer.ts +88 -0
  46. package/server/vendor/zcode/packages/rpc/src/channelClient.ts +275 -0
  47. package/server/vendor/zcode/packages/rpc/src/channelServer.ts +283 -0
  48. package/server/vendor/zcode/packages/rpc/src/channels.shared.ts +66 -0
  49. package/server/vendor/zcode/packages/rpc/src/channels.ts +4 -0
  50. package/server/vendor/zcode/packages/rpc/src/delayedChannel.ts +17 -0
  51. package/server/vendor/zcode/packages/rpc/src/foundation.ts +260 -0
  52. package/server/vendor/zcode/packages/rpc/src/protocol.ts +397 -0
  53. package/server/vendor/zcode/packages/rpc/src/proxy-channel.ts +213 -0
  54. package/server/vendor/zcode/packages/rpc/src/serialization.ts +291 -0
  55. package/server/vendor/zcode/packages/shared/src/account-provider-state.ts +13 -0
  56. package/server/vendor/zcode/packages/shared/src/background-bash-output.ts +24 -0
  57. package/server/vendor/zcode/packages/shared/src/background-task-controls.ts +404 -0
  58. package/server/vendor/zcode/packages/shared/src/bash-output-display.ts +11 -0
  59. package/server/vendor/zcode/packages/shared/src/bots.ts +558 -0
  60. package/server/vendor/zcode/packages/shared/src/browser-use/backend.ts +80 -0
  61. package/server/vendor/zcode/packages/shared/src/browser-use/command-metadata.ts +170 -0
  62. package/server/vendor/zcode/packages/shared/src/browser-use/commands.ts +588 -0
  63. package/server/vendor/zcode/packages/shared/src/browser-use/result.ts +130 -0
  64. package/server/vendor/zcode/packages/shared/src/browser-use/snapshot.ts +77 -0
  65. package/server/vendor/zcode/packages/shared/src/command-types.ts +96 -0
  66. package/server/vendor/zcode/packages/shared/src/config-schema.ts +8 -0
  67. package/server/vendor/zcode/packages/shared/src/cuaAccessibilitySettings.ts +41 -0
  68. package/server/vendor/zcode/packages/shared/src/database-startup.ts +178 -0
  69. package/server/vendor/zcode/packages/shared/src/env.ts +63 -0
  70. package/server/vendor/zcode/packages/shared/src/execution-output-preview.ts +14 -0
  71. package/server/vendor/zcode/packages/shared/src/localTtft.ts +192 -0
  72. package/server/vendor/zcode/packages/shared/src/model-config.ts +123 -0
  73. package/server/vendor/zcode/packages/shared/src/model-execution.ts +23 -0
  74. package/server/vendor/zcode/packages/shared/src/model-provider-family.ts +171 -0
  75. package/server/vendor/zcode/packages/shared/src/model-provider-types.ts +81 -0
  76. package/server/vendor/zcode/packages/shared/src/model-selection.ts +60 -0
  77. package/server/vendor/zcode/packages/shared/src/oauth.ts +203 -0
  78. package/server/vendor/zcode/packages/shared/src/official-mcp-auth.ts +272 -0
  79. package/server/vendor/zcode/packages/shared/src/official-mcp-tool-error.ts +68 -0
  80. package/server/vendor/zcode/packages/shared/src/process-diagnostic.ts +42 -0
  81. package/server/vendor/zcode/packages/shared/src/protocol.ts +369 -0
  82. package/server/vendor/zcode/packages/shared/src/provider-family-connection-selection.ts +32 -0
  83. package/server/vendor/zcode/packages/shared/src/remoteAssetInstallMode.ts +11 -0
  84. package/server/vendor/zcode/packages/shared/src/remoteResourcePackages.ts +91 -0
  85. package/server/vendor/zcode/packages/shared/src/runtimeEnv.ts +315 -0
  86. package/server/vendor/zcode/packages/shared/src/settings-source.ts +10 -0
  87. package/server/vendor/zcode/packages/shared/src/usage-quota.ts +74 -0
  88. package/server/vendor/zcode/packages/shared/src/usage-stats.ts +462 -0
  89. package/server/vendor/zcode/packages/shared/src/validationAppSettings.ts +562 -0
  90. package/server/vendor/zcode/packages/shared/src/workspacePurpose.ts +2 -0
  91. package/server/vendor/zcode/packages/shared/src/wslUserValidation.ts +33 -0
  92. package/server/vendor/zcode/packages/shared/src/zcode-agent-policy.ts +25 -0
  93. package/server/vendor/zcode/packages/shared/src/zcode-media-policy.ts +10 -0
  94. package/server/vendor/zcode/packages/shared/src/zcode-protocol/index.ts +3720 -0
  95. package/server/vendor/zcode/packages/shared/src/zcode-protocol-legacy-types.ts +529 -0
  96. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/apply.ts +218 -0
  97. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/attachment-ref.ts +14 -0
  98. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/command.ts +482 -0
  99. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/core.ts +100 -0
  100. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/create-workflow-display.ts +156 -0
  101. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/cuaPermission.ts +41 -0
  102. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/delta.ts +147 -0
  103. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/input-intent.ts +69 -0
  104. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/rows.ts +432 -0
  105. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/session-config.ts +43 -0
  106. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/sessions-index-workflow-activity.ts +195 -0
  107. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/sessions-index.ts +85 -0
  108. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/shared-context-import.ts +38 -0
  109. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/shared-context-ref.ts +10 -0
  110. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/snapshot.ts +509 -0
  111. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/submission.ts +5 -0
  112. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/toolDisplay.ts +264 -0
  113. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/transport.ts +1152 -0
  114. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-assembler.ts +574 -0
  115. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-binary.ts +65 -0
  116. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-codec.ts +240 -0
  117. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-fault.ts +39 -0
  118. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire.ts +159 -0
  119. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-artifacts.ts +291 -0
  120. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-observation-display.ts +281 -0
  121. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-row-meta.ts +163 -0
  122. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-run-settings-command.ts +62 -0
  123. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-runs-delta.ts +517 -0
  124. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-runs.ts +512 -0
  125. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workspace-config.ts +81 -0
  126. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workspace-hook-review.ts +210 -0
  127. package/server/vendor/zcode/packages/shared/src/zcode-task-mode-schema.ts +6 -0
  128. package/server/vendor/zcode/packages/shared/src/zcode-task-types-core.ts +1187 -0
  129. package/server/vendor/zcode/packages/shared/src/zcodeEndpoint.ts +296 -0
  130. package/server/vendor/zcode/provenance.json +405 -0
  131. package/shared/diagnostics.ts +52 -0
  132. package/shared/runtime-setup.ts +75 -0
  133. package/vendor/acp-registry/LICENSE +176 -0
  134. package/vendor/paseo/LICENSE +211 -0
@@ -0,0 +1,397 @@
1
+ /**
2
+ * Layer 2: 传输协议抽象
3
+ *
4
+ * IMessagePassingProtocol 是整个 IPC 框架的"腰部"——
5
+ * 它上面是 Channel RPC 层(完全传输无关),
6
+ * 它下面是各种具体传输实现(Electron/MessagePort/Socket/ChildProcess)。
7
+ *
8
+ * 只要实现 send() 和 onMessage,就能接入整个 RPC 框架。
9
+ */
10
+
11
+ import { VSBuffer } from "./buffer.js";
12
+ import { Event, Emitter, IDisposable, DisposableStore } from "./foundation.js";
13
+
14
+ // ============================================================================
15
+ // 核心传输接口
16
+ // ============================================================================
17
+
18
+ /**
19
+ * IMessagePassingProtocol —— 整个框架的核心抽象
20
+ *
21
+ * 这就是 VS Code 通信能力的秘密:上层代码只看到 send/onMessage,
22
+ * 不管底层是 Electron IPC、MessagePort、WebSocket 还是 TCP Socket。
23
+ */
24
+ export interface IMessagePassingProtocol {
25
+ send(buffer: VSBuffer): void;
26
+ readonly onMessage: Event<VSBuffer>;
27
+ drain?(): Promise<void>;
28
+ }
29
+
30
+ /**
31
+ * 连接级只读流控观察面。
32
+ *
33
+ * transport 负责维护未确认字节与状态边沿;业务层只能订阅,不能伪造 ACK 或直接改水位。
34
+ */
35
+ export interface ConnectionFlowControl {
36
+ readonly unacknowledgedBytes: number;
37
+ readonly onSaturated: Event<void>;
38
+ readonly onDrained: Event<void>;
39
+ }
40
+
41
+ export type MessagePortFlowState = "saturated" | "drained";
42
+
43
+ export interface MessagePortFlowControl {
44
+ __zcodeRpcControl: "connection-flow-v1";
45
+ state: MessagePortFlowState;
46
+ }
47
+
48
+ export type MessagePortPayload = Uint8Array | MessagePortFlowControl;
49
+
50
+ function isMessagePortFlowControl(value: unknown): value is MessagePortFlowControl {
51
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
52
+ const record = value as Record<string, unknown>;
53
+ return (
54
+ Object.keys(record).length === 2 &&
55
+ record.__zcodeRpcControl === "connection-flow-v1" &&
56
+ (record.state === "saturated" || record.state === "drained")
57
+ );
58
+ }
59
+
60
+ // ============================================================================
61
+ // Socket 接口(用于 TCP/WebSocket 等流式传输)
62
+ // ============================================================================
63
+
64
+ /**
65
+ * ISocket 抽象了底层的网络 socket。
66
+ * 在 Node.js 环境是 net.Socket,在浏览器环境是 WebSocket。
67
+ */
68
+ export interface ISocket extends IDisposable {
69
+ onData: Event<VSBuffer>;
70
+ onClose: Event<void>;
71
+ onEnd: Event<void>;
72
+ write(buffer: VSBuffer): void;
73
+ end(): void;
74
+ drain(): Promise<void>;
75
+ }
76
+
77
+ // ============================================================================
78
+ // ChunkStream —— 处理 TCP 的分片和粘包
79
+ // ============================================================================
80
+
81
+ /**
82
+ * TCP 是流式协议,一次 write 不代表对面一次 read 就能完整收到。
83
+ * ChunkStream 把收到的碎片攒起来,按需读取指定字节数。
84
+ */
85
+ export class ChunkStream {
86
+ private chunks: VSBuffer[] = [];
87
+ private totalLength = 0;
88
+
89
+ get byteLength(): number {
90
+ return this.totalLength;
91
+ }
92
+
93
+ acceptChunk(chunk: VSBuffer): void {
94
+ this.chunks.push(chunk);
95
+ this.totalLength += chunk.byteLength;
96
+ }
97
+
98
+ /**
99
+ * 预览前 byteCount 字节,但不消费底层缓冲。
100
+ *
101
+ * Socket/stdio 传输天然可能分片。
102
+ * 之前协议层在 body 还没收全时就先把 header read 掉,后续再来的 body
103
+ * 会失去对应的帧头,导致消息永远卡住。这里提供 peek,让调用方先判断
104
+ * “整帧是否已经到齐”,确认足够后再真正消费。
105
+ */
106
+ peek(byteCount: number): VSBuffer | null {
107
+ if (this.totalLength < byteCount) {
108
+ return null;
109
+ }
110
+
111
+ if (this.chunks[0].byteLength >= byteCount) {
112
+ return this.chunks[0].slice(0, byteCount);
113
+ }
114
+
115
+ const result = VSBuffer.alloc(byteCount);
116
+ let offset = 0;
117
+ for (const chunk of this.chunks) {
118
+ if (offset >= byteCount) {
119
+ break;
120
+ }
121
+
122
+ const remaining = byteCount - offset;
123
+ const copyLength = Math.min(chunk.byteLength, remaining);
124
+ result.set(copyLength === chunk.byteLength ? chunk : chunk.slice(0, copyLength), offset);
125
+ offset += copyLength;
126
+ }
127
+
128
+ return result;
129
+ }
130
+
131
+ /** 丢弃前 byteCount 字节 */
132
+ skip(byteCount: number): void {
133
+ const discarded = this.read(byteCount);
134
+ if (!discarded) {
135
+ throw new Error(`ChunkStream.skip(${byteCount}) 超出可读范围`);
136
+ }
137
+ }
138
+
139
+ /** 读取 byteCount 字节,不够就返回 null */
140
+ read(byteCount: number): VSBuffer | null {
141
+ if (this.totalLength < byteCount) {
142
+ return null;
143
+ }
144
+
145
+ if (this.chunks[0].byteLength === byteCount) {
146
+ const result = this.chunks.shift()!;
147
+ this.totalLength -= byteCount;
148
+ return result;
149
+ }
150
+
151
+ if (this.chunks[0].byteLength > byteCount) {
152
+ const result = this.chunks[0].slice(0, byteCount);
153
+ this.chunks[0] = this.chunks[0].slice(byteCount);
154
+ this.totalLength -= byteCount;
155
+ return result;
156
+ }
157
+
158
+ // 需要跨多个 chunk 拼接
159
+ const result = VSBuffer.alloc(byteCount);
160
+ let offset = 0;
161
+ while (offset < byteCount) {
162
+ const chunk = this.chunks[0];
163
+ const needed = byteCount - offset;
164
+ if (chunk.byteLength <= needed) {
165
+ result.set(chunk, offset);
166
+ offset += chunk.byteLength;
167
+ this.chunks.shift();
168
+ } else {
169
+ result.set(chunk.slice(0, needed), offset);
170
+ this.chunks[0] = chunk.slice(needed);
171
+ offset += needed;
172
+ }
173
+ }
174
+ this.totalLength -= byteCount;
175
+ return result;
176
+ }
177
+ }
178
+
179
+ // ============================================================================
180
+ // Protocol —— 在 ISocket 上实现 IMessagePassingProtocol
181
+ // ============================================================================
182
+
183
+ /**
184
+ * 消息帧格式 (13 bytes header):
185
+ *
186
+ * ┌─────────┬──────────┬──────────┬──────────────┐
187
+ * │ type(1) │ id(4) │ ack(4) │ length(4) │
188
+ * └─────────┴──────────┴──────────┴──────────────┘
189
+ *
190
+ * type: 消息类型(Regular, Ack, KeepAlive 等)
191
+ * id: 消息序号
192
+ * ack: 确认号(告诉对方"我已收到你的消息到第 ack 号")
193
+ * length: payload 长度
194
+ */
195
+ export enum ProtocolMessageType {
196
+ None = 0,
197
+ Regular = 1,
198
+ Control = 2,
199
+ Ack = 3,
200
+ Disconnect = 5,
201
+ ReplayRequest = 6,
202
+ Pause = 7,
203
+ Resume = 8,
204
+ KeepAlive = 9,
205
+ }
206
+
207
+ export const HEADER_SIZE = 13; // 1 + 4 + 4 + 4
208
+
209
+ export class ProtocolMessage {
210
+ constructor(
211
+ public readonly type: ProtocolMessageType,
212
+ public readonly id: number,
213
+ public readonly ack: number,
214
+ public readonly data: VSBuffer,
215
+ ) {}
216
+
217
+ get byteLength(): number {
218
+ return HEADER_SIZE + this.data.byteLength;
219
+ }
220
+ }
221
+
222
+ export function writeProtocolMessage(msg: ProtocolMessage): VSBuffer {
223
+ const result = VSBuffer.alloc(HEADER_SIZE + msg.data.byteLength);
224
+ result.writeUInt8(msg.type, 0);
225
+ result.writeUInt32BE(msg.id, 1);
226
+ result.writeUInt32BE(msg.ack, 5);
227
+ result.writeUInt32BE(msg.data.byteLength, 9);
228
+ result.set(msg.data, HEADER_SIZE);
229
+ return result;
230
+ }
231
+
232
+ /**
233
+ * 基础 Protocol: 在 ISocket 上加消息帧,实现 IMessagePassingProtocol。
234
+ * 只做消息分帧,不做 ACK/重连(那是 PersistentProtocol 的事)。
235
+ */
236
+ export class SocketProtocol implements IMessagePassingProtocol {
237
+ private readonly _onMessage = new Emitter<VSBuffer>();
238
+ readonly onMessage = this._onMessage.event;
239
+
240
+ private readonly chunkStream = new ChunkStream();
241
+ private readonly disposables = new DisposableStore();
242
+
243
+ constructor(private socket: ISocket) {
244
+ this.disposables.add(
245
+ socket.onData((data) => {
246
+ this.chunkStream.acceptChunk(data);
247
+ this.readMessages();
248
+ }),
249
+ );
250
+ }
251
+
252
+ send(buffer: VSBuffer): void {
253
+ this.writeMessage(new ProtocolMessage(ProtocolMessageType.Regular, 0, 0, buffer));
254
+ }
255
+
256
+ private writeMessage(msg: ProtocolMessage): void {
257
+ this.socket.write(writeProtocolMessage(msg));
258
+ }
259
+
260
+ private readMessages(): void {
261
+ while (true) {
262
+ const header = this.chunkStream.peek(HEADER_SIZE);
263
+ if (!header) {
264
+ break;
265
+ }
266
+
267
+ const type = header.readUInt8(0) as ProtocolMessageType;
268
+ const _id = header.readUInt32BE(1);
269
+ const _ack = header.readUInt32BE(5);
270
+ const length = header.readUInt32BE(9);
271
+
272
+ const totalFrameLength = HEADER_SIZE + length;
273
+ if (this.chunkStream.byteLength < totalFrameLength) {
274
+ // 不能在 body 未到齐时提前消费 header,否则下一段数据拼上来后
275
+ // 已经找不到这帧的长度信息,调用方就会一直等待一个永远不会完成的 Promise。
276
+ break;
277
+ }
278
+
279
+ this.chunkStream.skip(HEADER_SIZE);
280
+
281
+ if (length === 0) {
282
+ if (type === ProtocolMessageType.Regular) {
283
+ this._onMessage.fire(VSBuffer.alloc(0));
284
+ }
285
+ continue;
286
+ }
287
+
288
+ const body = this.chunkStream.read(length);
289
+ if (!body) {
290
+ throw new Error("SocketProtocol 读取到完整帧长度后 body 不应为空");
291
+ }
292
+
293
+ if (type === ProtocolMessageType.Regular) {
294
+ this._onMessage.fire(body);
295
+ }
296
+ }
297
+ }
298
+
299
+ async drain(): Promise<void> {
300
+ return this.socket.drain();
301
+ }
302
+
303
+ dispose(): void {
304
+ this.disposables.dispose();
305
+ this._onMessage.dispose();
306
+ }
307
+ }
308
+
309
+ // ============================================================================
310
+ // QueueProtocol —— 内存中的协议对,用于测试
311
+ // ============================================================================
312
+
313
+ /**
314
+ * 创建一对通过内存队列连接的 protocol,
315
+ * 一端 send 的消息会出现在另一端的 onMessage。
316
+ * 非常适合单元测试,不需要真正的网络连接。
317
+ */
318
+ export function createQueuePair(): [IMessagePassingProtocol, IMessagePassingProtocol] {
319
+ const emitterA = new Emitter<VSBuffer>();
320
+ const emitterB = new Emitter<VSBuffer>();
321
+
322
+ const protocolA: IMessagePassingProtocol = {
323
+ send: (buffer: VSBuffer) => {
324
+ // A 发送的消息 → B 收到
325
+ setTimeout(() => emitterB.fire(buffer), 0);
326
+ },
327
+ onMessage: emitterA.event,
328
+ };
329
+
330
+ const protocolB: IMessagePassingProtocol = {
331
+ send: (buffer: VSBuffer) => {
332
+ // B 发送的消息 → A 收到
333
+ setTimeout(() => emitterA.fire(buffer), 0);
334
+ },
335
+ onMessage: emitterB.event,
336
+ };
337
+
338
+ return [protocolA, protocolB];
339
+ }
340
+
341
+ // ============================================================================
342
+ // MessagePort Protocol —— 用于 Web Worker / Electron sandbox
343
+ // ============================================================================
344
+
345
+ /**
346
+ * MessagePort 接口的最小声明,
347
+ * 使得这个 Protocol 可以同时在浏览器和 Electron 中使用。
348
+ */
349
+ export interface MessagePortLike {
350
+ addEventListener(type: "message", listener: (e: { data: MessagePortPayload }) => void): void;
351
+ removeEventListener(type: "message", listener: (e: { data: MessagePortPayload }) => void): void;
352
+ postMessage(message: MessagePortPayload): void;
353
+ start(): void;
354
+ close(): void;
355
+ }
356
+
357
+ /**
358
+ * 在 MessagePort 上实现 IMessagePassingProtocol。
359
+ * 这是最简单的传输实现——不需要分帧,因为 MessagePort 本身就是消息边界的。
360
+ */
361
+ export class MessagePortProtocol implements IMessagePassingProtocol {
362
+ private readonly _onMessage = new Emitter<VSBuffer>();
363
+ readonly onMessage = this._onMessage.event;
364
+ private readonly _onFlowState = new Emitter<MessagePortFlowState>();
365
+ readonly onFlowState = this._onFlowState.event;
366
+
367
+ private readonly handler: (e: { data: MessagePortPayload }) => void;
368
+
369
+ constructor(private port: MessagePortLike) {
370
+ this.handler = (e: { data: MessagePortPayload }) => {
371
+ if (isMessagePortFlowControl(e.data)) {
372
+ this._onFlowState.fire(e.data.state);
373
+ return;
374
+ }
375
+ // MessagePort control object 不能进入 Channel deserialize;未知对象和伪造
376
+ // connection-flow-v1 一律丢弃,只有真实 Uint8Array 才是 RPC binary。
377
+ if (e.data instanceof Uint8Array) this._onMessage.fire(VSBuffer.wrap(e.data));
378
+ };
379
+ this.port.addEventListener("message", this.handler);
380
+ this.port.start();
381
+ }
382
+
383
+ send(buffer: VSBuffer): void {
384
+ this.port.postMessage(buffer.buffer);
385
+ }
386
+
387
+ sendFlowState(state: MessagePortFlowState): void {
388
+ this.port.postMessage({ __zcodeRpcControl: "connection-flow-v1", state });
389
+ }
390
+
391
+ disconnect(): void {
392
+ this.port.removeEventListener("message", this.handler);
393
+ this.port.close();
394
+ this._onMessage.dispose();
395
+ this._onFlowState.dispose();
396
+ }
397
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Layer 5: ProxyChannel —— 服务自动代理(杀手锏)
3
+ *
4
+ * 这是让 VS Code 开发者效率极高的关键抽象。
5
+ *
6
+ * 没有 ProxyChannel 时,你需要为每个服务手写 IServerChannel:
7
+ * class FileServiceChannel implements IServerChannel {
8
+ * call(ctx, command, arg) {
9
+ * switch (command) {
10
+ * case 'readFile': return this.service.readFile(arg[0]);
11
+ * case 'writeFile': return this.service.writeFile(arg[0], arg[1]);
12
+ * // ... 每个方法都要手动映射
13
+ * }
14
+ * }
15
+ * }
16
+ *
17
+ * 有了 ProxyChannel,一行代码搞定:
18
+ * const channel = ProxyChannel.fromService(fileService);
19
+ * // 自动把所有方法映射为 call,所有 on* 事件映射为 listen
20
+ *
21
+ * 客户端同样一行:
22
+ * const fileService = ProxyChannel.toService<IFileService>(channel);
23
+ * await fileService.readFile(uri); // 就像调用本地方法!
24
+ *
25
+ * 原理:
26
+ * - fromService: 遍历 service 的属性,方法 → call,on* 事件 → listen
27
+ * - toService: 利用 ES6 Proxy 拦截属性访问,自动分派到 call/listen
28
+ */
29
+
30
+ import { Event, Emitter, IDisposable, DisposableStore } from "./foundation.js";
31
+ import { IChannel, IServerChannel } from "./channels.js";
32
+
33
+ // ============================================================================
34
+ // ProxyChannel
35
+ // ============================================================================
36
+
37
+ export namespace ProxyChannel {
38
+ /**
39
+ * 服务端:把一个 service 对象自动包装为 IServerChannel
40
+ *
41
+ * 约定:
42
+ * - 以 on + 大写字母开头的属性视为事件 (如 onDidChange)
43
+ * - 以 onDynamic + 大写字母开头的视为动态事件(方法,调用后返回事件)
44
+ * - 其他方法视为 RPC 方法
45
+ */
46
+ export function fromService<TContext>(
47
+ service: unknown,
48
+ disposables?: DisposableStore,
49
+ ): IServerChannel<TContext> {
50
+ const handler = service as { [key: string]: unknown };
51
+
52
+ // 预缓存所有事件并 buffer
53
+ const eventMap = new Map<string, Event<unknown>>();
54
+ for (const key in handler) {
55
+ if (isEvent(key) && !isDynamicEvent(key) && typeof handler[key] === "function") {
56
+ // 把事件 buffer 化:即使没人订阅,事件也不会丢失
57
+ eventMap.set(key, bufferEvent(handler[key] as Event<unknown>));
58
+ }
59
+ }
60
+
61
+ return {
62
+ listen<T>(_ctx: TContext, event: string, arg?: any): Event<T> {
63
+ // 先查缓存
64
+ const cached = eventMap.get(event);
65
+ if (cached) {
66
+ return cached as Event<T>;
67
+ }
68
+
69
+ const target = handler[event];
70
+ if (typeof target === "function") {
71
+ // 动态事件:onDynamicXxx(arg) 返回一个 Event
72
+ if (isDynamicEvent(event)) {
73
+ return target.call(handler, arg);
74
+ }
75
+ // 延迟发现的事件(Proxy 服务可能不会在 for-in 中出现)
76
+ if (isEvent(event)) {
77
+ eventMap.set(event, bufferEvent(handler[event] as Event<unknown>));
78
+ return eventMap.get(event) as Event<T>;
79
+ }
80
+ }
81
+
82
+ throw new Error(`Event not found: ${event}`);
83
+ },
84
+
85
+ call<T>(_ctx: TContext, command: string, args?: any[]): Promise<T> {
86
+ const target = handler[command];
87
+ if (typeof target === "function") {
88
+ let result = target.apply(handler, args || []);
89
+ if (!(result instanceof Promise)) {
90
+ result = Promise.resolve(result);
91
+ }
92
+ return result;
93
+ }
94
+ throw new Error(`Method not found: ${command}`);
95
+ },
96
+ };
97
+ }
98
+
99
+ /**
100
+ * 客户端:把一个 IChannel 包装成类型安全的 service 对象
101
+ *
102
+ * 利用 ES6 Proxy 拦截所有属性访问:
103
+ * - 访问 on* → channel.listen(propKey)
104
+ * - 访问其他 → 返回一个函数,调用时变成 channel.call(propKey, args)
105
+ */
106
+ export function toService<T extends object>(
107
+ channel: IChannel,
108
+ options?: { context?: unknown },
109
+ ): T {
110
+ return new Proxy({} as T, {
111
+ get(target: T, propKey: PropertyKey, receiver: object) {
112
+ // React 开发态、日志工具和浏览器运行时会探测对象的 Symbol / then 等内置属性。
113
+ // 之前这里把所有未知属性都强行当成 RPC 成员处理,读取 Symbol.toStringTag 会直接抛错,
114
+ // 读取 then 还会把普通 service 误判成 thenable,导致远程 workspace 在选目录后重渲染时炸掉。
115
+ // 这类运行时探测属性应该回退到普通对象语义,而不是走 RPC。
116
+ if (typeof propKey === "symbol") {
117
+ return Reflect.get(target as object, propKey, receiver);
118
+ }
119
+
120
+ if (typeof propKey === "string") {
121
+ if (propKey === "then") {
122
+ return undefined;
123
+ }
124
+
125
+ // 动态事件
126
+ if (isDynamicEvent(propKey)) {
127
+ return (arg: unknown) => channel.listen(propKey, arg);
128
+ }
129
+
130
+ // 普通事件
131
+ if (isEvent(propKey)) {
132
+ return channel.listen(propKey);
133
+ }
134
+
135
+ // 方法调用
136
+ return async (...args: unknown[]) => {
137
+ // 可选:注入 context 作为第一个参数
138
+ const methodArgs = options?.context !== undefined ? [options.context, ...args] : args;
139
+ return channel.call(propKey, methodArgs);
140
+ };
141
+ }
142
+
143
+ return Reflect.get(target as object, propKey, receiver);
144
+ },
145
+ });
146
+ }
147
+ }
148
+
149
+ // ============================================================================
150
+ // 辅助函数
151
+ // ============================================================================
152
+
153
+ /** 匹配 onXxx 事件命名约定 */
154
+ function isEvent(name: string): boolean {
155
+ return (
156
+ name.length >= 3 &&
157
+ name[0] === "o" &&
158
+ name[1] === "n" &&
159
+ name.charCodeAt(2) >= 65 && // A
160
+ name.charCodeAt(2) <= 90
161
+ ); // Z
162
+ }
163
+
164
+ /** 匹配 onDynamicXxx 动态事件命名约定 */
165
+ function isDynamicEvent(name: string): boolean {
166
+ return (
167
+ name.length >= 10 &&
168
+ name.startsWith("onDynamic") &&
169
+ name.charCodeAt(9) >= 65 &&
170
+ name.charCodeAt(9) <= 90
171
+ );
172
+ }
173
+
174
+ /**
175
+ * 缓冲事件:确保在订阅之前触发的事件不会丢失。
176
+ * 未订阅时事件存入队列,一有订阅者就 flush。
177
+ */
178
+ function bufferEvent<T>(event: Event<T>): Event<T> {
179
+ let buffer: T[] = [];
180
+ let flushing = false;
181
+ let listener: IDisposable | undefined;
182
+
183
+ const emitter = new Emitter<T>({
184
+ onWillAddFirstListener: () => {
185
+ listener = event((e) => {
186
+ if (flushing) {
187
+ emitter.fire(e);
188
+ } else {
189
+ buffer.push(e);
190
+ }
191
+ });
192
+ },
193
+ onDidRemoveLastListener: () => {
194
+ listener?.dispose();
195
+ listener = undefined;
196
+ buffer = [];
197
+ },
198
+ });
199
+
200
+ // 一旦有订阅者,先 flush 缓冲区
201
+ const originalEvent = emitter.event;
202
+ return (listener_fn) => {
203
+ const disposable = originalEvent(listener_fn);
204
+ if (!flushing) {
205
+ flushing = true;
206
+ for (const item of buffer) {
207
+ emitter.fire(item);
208
+ }
209
+ buffer = [];
210
+ }
211
+ return disposable;
212
+ };
213
+ }