@agent-phonon/server-sdk 0.2.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 agent-phonon contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @agent-phonon/server-sdk
3
+ *
4
+ * 让任何项目一键成为 agent-phonon 服务端:管理多设备、编排其上的 agent。
5
+ */
6
+ export { PhononServer, PhononDevice, PhononSession } from "./server.js";
7
+ export type { PhononServerOptions, HookDecider, SendResult } from "./server.js";
8
+ export { RpcPeer } from "./rpc.js";
9
+ export type { Transport } from "./rpc.js";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACxE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @agent-phonon/server-sdk
3
+ *
4
+ * 让任何项目一键成为 agent-phonon 服务端:管理多设备、编排其上的 agent。
5
+ */
6
+ export { PhononServer, PhononDevice, PhononSession } from "./server.js";
7
+ export { RpcPeer } from "./rpc.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAExE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC"}
package/dist/rpc.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * 双向 JSON-RPC 2.0 peer(server 侧,零 core 依赖)。
3
+ * 每个 device 连接一个。两端皆可作 requester。
4
+ */
5
+ export interface Transport {
6
+ send(data: string): void;
7
+ close(): void;
8
+ }
9
+ export type RpcHandler = (method: string, params: unknown) => Promise<unknown> | unknown;
10
+ export declare class RpcPeer {
11
+ private transport;
12
+ private handler;
13
+ private pending;
14
+ private nextId;
15
+ constructor(transport: Transport, handler: RpcHandler);
16
+ request(method: string, params: unknown, timeoutMs?: number): Promise<unknown>;
17
+ notify(method: string, params: unknown): void;
18
+ handle(data: string): Promise<void>;
19
+ rejectAll(reason: string): void;
20
+ }
21
+ export declare function newId(): string;
22
+ //# sourceMappingURL=rpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAQzF,qBAAa,OAAO;IAClB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,MAAM,CAAK;gBAEP,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU;IAKrD,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IAS9E,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;IAIvC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgCzC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CAIhC;AAED,wBAAgB,KAAK,IAAI,MAAM,CAE9B"}
package/dist/rpc.js ADDED
@@ -0,0 +1,74 @@
1
+ import { randomUUID } from "node:crypto";
2
+ export class RpcPeer {
3
+ transport;
4
+ handler;
5
+ pending = new Map();
6
+ nextId = 1;
7
+ constructor(transport, handler) {
8
+ this.transport = transport;
9
+ this.handler = handler;
10
+ }
11
+ request(method, params, timeoutMs = 600000) {
12
+ const id = this.nextId++;
13
+ return new Promise((resolve, reject) => {
14
+ const timer = timeoutMs > 0 ? setTimeout(() => { this.pending.delete(id); reject(new Error(`rpc timeout: ${method}`)); }, timeoutMs) : undefined;
15
+ this.pending.set(id, { resolve, reject, timer });
16
+ this.transport.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
17
+ });
18
+ }
19
+ notify(method, params) {
20
+ this.transport.send(JSON.stringify({ jsonrpc: "2.0", method, params }));
21
+ }
22
+ async handle(data) {
23
+ let msg;
24
+ try {
25
+ msg = JSON.parse(data);
26
+ }
27
+ catch {
28
+ return;
29
+ }
30
+ // 响应
31
+ if (("result" in msg || "error" in msg) && "id" in msg) {
32
+ const p = this.pending.get(msg.id);
33
+ if (!p)
34
+ return;
35
+ this.pending.delete(msg.id);
36
+ if (p.timer)
37
+ clearTimeout(p.timer);
38
+ if ("error" in msg && msg.error)
39
+ p.reject(msg.error);
40
+ else
41
+ p.resolve(msg.result);
42
+ return;
43
+ }
44
+ // 请求 / 通知
45
+ if (typeof msg.method === "string") {
46
+ const isNotification = !("id" in msg) || msg.id === undefined || msg.id === null;
47
+ try {
48
+ const result = await this.handler(msg.method, msg.params);
49
+ if (!isNotification)
50
+ this.transport.send(JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: result ?? null }));
51
+ }
52
+ catch (err) {
53
+ if (!isNotification) {
54
+ this.transport.send(JSON.stringify({
55
+ jsonrpc: "2.0", id: msg.id,
56
+ error: { code: -32000, message: err?.message ?? "error" },
57
+ }));
58
+ }
59
+ }
60
+ }
61
+ }
62
+ rejectAll(reason) {
63
+ for (const [, p] of this.pending) {
64
+ if (p.timer)
65
+ clearTimeout(p.timer);
66
+ p.reject(new Error(reason));
67
+ }
68
+ this.pending.clear();
69
+ }
70
+ }
71
+ export function newId() {
72
+ return randomUUID();
73
+ }
74
+ //# sourceMappingURL=rpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.js","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,247 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { RpcPeer } from "./rpc.js";
3
+ import type { AgentDescriptor, SessionMeta, StreamEvent, HookFiredParams, HookAction } from "@agent-phonon/protocol";
4
+ /**
5
+ * agent-phonon Server SDK。
6
+ *
7
+ * 让任何项目「一键成为 phonon 服务端」:导入 SDK → 配鉴权 → 监听 device →
8
+ * 用干净接口(discover / createSession / send / onStream / onHook)编排
9
+ * 多台设备上的 agent。协议帧/握手/ack/HITL 路由全由 SDK 处理。
10
+ *
11
+ * 支持多设备:一个 PhononServer 同时连多个 phonon。
12
+ */
13
+ export interface SendResult {
14
+ turnId: string;
15
+ disposition: string;
16
+ }
17
+ export declare class PhononSession extends EventEmitter {
18
+ readonly sessionId: string;
19
+ readonly device: PhononDevice;
20
+ private lastSeq;
21
+ constructor(device: PhononDevice, sessionId: string);
22
+ /** 发任务(结果走 'stream' 事件)。 */
23
+ send(input: string, opts?: {
24
+ verbosity?: "final" | "messages" | "tools" | "trace";
25
+ skills?: string[];
26
+ whenBusy?: "queue" | "interrupt" | "inject";
27
+ clientRequestId?: string;
28
+ }): Promise<SendResult>;
29
+ inject(context: Array<{
30
+ role: "system" | "user" | "assistant";
31
+ content: string;
32
+ }>): Promise<unknown>;
33
+ interrupt(reason?: string): Promise<unknown>;
34
+ switchModel(model: string): Promise<unknown>;
35
+ compress(mode?: "native" | "custom"): Promise<unknown>;
36
+ status(): Promise<SessionMeta>;
37
+ terminate(): Promise<unknown>;
38
+ /** 内部:收到本 session 的 stream.event。 */
39
+ _onStream(ev: StreamEvent): void;
40
+ /** 本 session 已收到的最大 seq(device 用于 ack)。 */
41
+ get _lastSeq(): number;
42
+ on(event: "stream", listener: (ev: StreamEvent) => void): this;
43
+ on(event: "end", listener: (ev: StreamEvent) => void): this;
44
+ }
45
+ export type HookDecider = (hook: HookFiredParams, session: PhononSession | undefined) => HookAction | {
46
+ action: HookAction;
47
+ reason?: string;
48
+ } | Promise<HookAction | {
49
+ action: HookAction;
50
+ reason?: string;
51
+ }>;
52
+ export declare class PhononDevice extends EventEmitter {
53
+ readonly deviceId: string;
54
+ readonly tenantId: string;
55
+ private peer;
56
+ private sessions;
57
+ private hookDecider?;
58
+ /** 自发输出(无对应 session 时)回调。 */
59
+ private onUnsolicited?;
60
+ constructor(deviceId: string, tenantId: string, peer: RpcPeer);
61
+ call(method: string, params: unknown): Promise<unknown>;
62
+ /** 列设备上可用 agent。 */
63
+ discover(): Promise<AgentDescriptor[]>;
64
+ /** 建会话。 */
65
+ createSession(params: {
66
+ project: string;
67
+ agent: string;
68
+ model: string;
69
+ worktreeId?: string;
70
+ verbosity?: "final" | "messages" | "tools" | "trace";
71
+ clientRequestId?: string;
72
+ }): Promise<PhononSession>;
73
+ /** 列会话。 */
74
+ listSessions(filter?: {
75
+ project?: string;
76
+ agent?: string;
77
+ status?: string;
78
+ limit?: number;
79
+ cursor?: string;
80
+ }): Promise<{
81
+ sessions: SessionMeta[];
82
+ nextCursor?: string;
83
+ }>;
84
+ /** 设备 OS/机器信息,用于 server 做任务调度决策。 */
85
+ info(): Promise<unknown>;
86
+ /** 设备资源快照(CPU/内存/磁盘/进程/GPU best-effort)。 */
87
+ resources(): Promise<unknown>;
88
+ project: {
89
+ create: (p: {
90
+ name: string;
91
+ path?: string;
92
+ git?: boolean;
93
+ remote?: string;
94
+ clientRequestId?: string;
95
+ }) => Promise<{
96
+ project: {
97
+ projectId: string;
98
+ path: string;
99
+ };
100
+ }>;
101
+ list: () => Promise<{
102
+ projects: unknown[];
103
+ }>;
104
+ get: (projectId: string) => Promise<unknown>;
105
+ remove: (projectId: string, opts?: {
106
+ deleteFiles?: boolean;
107
+ whenActiveSessions?: "reject" | "cascade";
108
+ }) => Promise<unknown>;
109
+ worktree: {
110
+ create: (p: {
111
+ projectId: string;
112
+ baseBranch: string;
113
+ newBranch?: string;
114
+ path?: string;
115
+ }) => Promise<unknown>;
116
+ list: (projectId: string) => Promise<unknown>;
117
+ remove: (p: {
118
+ projectId: string;
119
+ worktreeId: string;
120
+ force?: boolean;
121
+ }) => Promise<unknown>;
122
+ };
123
+ deleteBranch: (p: {
124
+ projectId: string;
125
+ branch: string;
126
+ force?: boolean;
127
+ }) => Promise<unknown>;
128
+ };
129
+ env: {
130
+ set: (p: {
131
+ scope: "global" | "project" | "skill";
132
+ projectId?: string;
133
+ agent?: string;
134
+ skillName?: string;
135
+ name: string;
136
+ value: string;
137
+ secret?: boolean;
138
+ clientRequestId?: string;
139
+ }) => Promise<unknown>;
140
+ list: (p?: {
141
+ scope?: "global" | "project" | "skill";
142
+ projectId?: string;
143
+ agent?: string;
144
+ skillName?: string;
145
+ reveal?: boolean;
146
+ }) => Promise<unknown>;
147
+ delete: (p: {
148
+ scope: "global" | "project" | "skill";
149
+ projectId?: string;
150
+ agent?: string;
151
+ skillName?: string;
152
+ name: string;
153
+ clientRequestId?: string;
154
+ }) => Promise<unknown>;
155
+ };
156
+ file: {
157
+ read: (p: {
158
+ projectId: string;
159
+ worktreeId?: string;
160
+ path: string;
161
+ encoding?: "utf8" | "base64";
162
+ maxBytes?: number;
163
+ }) => Promise<unknown>;
164
+ write: (p: {
165
+ projectId: string;
166
+ worktreeId?: string;
167
+ path: string;
168
+ encoding?: "utf8" | "base64";
169
+ data: string;
170
+ overwrite?: boolean;
171
+ createDirs?: boolean;
172
+ clientRequestId?: string;
173
+ }) => Promise<unknown>;
174
+ list: (p: {
175
+ projectId: string;
176
+ worktreeId?: string;
177
+ path?: string;
178
+ recursive?: boolean;
179
+ limit?: number;
180
+ }) => Promise<unknown>;
181
+ stat: (p: {
182
+ projectId: string;
183
+ worktreeId?: string;
184
+ path: string;
185
+ }) => Promise<unknown>;
186
+ mkdir: (p: {
187
+ projectId: string;
188
+ worktreeId?: string;
189
+ path: string;
190
+ recursive?: boolean;
191
+ clientRequestId?: string;
192
+ }) => Promise<unknown>;
193
+ };
194
+ skill: {
195
+ install: (p: {
196
+ agent: string;
197
+ name: string;
198
+ scope: "global" | "project";
199
+ projectId?: string;
200
+ source: unknown;
201
+ clientRequestId?: string;
202
+ }) => Promise<unknown>;
203
+ uninstall: (p: {
204
+ agent: string;
205
+ name: string;
206
+ scope: "global" | "project";
207
+ projectId?: string;
208
+ }) => Promise<unknown>;
209
+ list: (filter?: {
210
+ agent?: string;
211
+ scope?: "global" | "project";
212
+ projectId?: string;
213
+ }) => Promise<unknown>;
214
+ };
215
+ /** 设置 HITL 裁决器(device 级,所有 session 共用)。 */
216
+ setHookDecider(fn: HookDecider): void;
217
+ /** 设置自发输出回调。 */
218
+ setUnsolicitedHandler(fn: (ev: StreamEvent) => void): void;
219
+ /** 内部:处理 phonon → server 的请求/通知。 */
220
+ _handleInbound(method: string, params: unknown): Promise<unknown>;
221
+ _onClose(): void;
222
+ }
223
+ export interface PhononServerOptions {
224
+ port?: number;
225
+ host?: string;
226
+ /** 鉴权:返回 tenantId 表示通过,返回 null 拒绝。缺省全部放行(本地测试)。 */
227
+ authenticate?: (deviceId: string, deviceKey: string | undefined) => {
228
+ tenantId: string;
229
+ } | null | Promise<{
230
+ tenantId: string;
231
+ } | null>;
232
+ }
233
+ export declare class PhononServer extends EventEmitter {
234
+ private wss?;
235
+ private opts;
236
+ private devices;
237
+ private actualPort;
238
+ constructor(opts?: PhononServerOptions);
239
+ listen(): Promise<number>;
240
+ get port(): number;
241
+ /** 当前连接的设备列表。 */
242
+ listDevices(): PhononDevice[];
243
+ getDevice(deviceId: string): PhononDevice | undefined;
244
+ private onConnection;
245
+ close(): Promise<void>;
246
+ }
247
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/dist/server.js ADDED
@@ -0,0 +1,240 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { WebSocketServer, WebSocket } from "ws";
3
+ import { RpcPeer, newId } from "./rpc.js";
4
+ export class PhononSession extends EventEmitter {
5
+ sessionId;
6
+ device;
7
+ lastSeq = -1;
8
+ constructor(device, sessionId) {
9
+ super();
10
+ this.device = device;
11
+ this.sessionId = sessionId;
12
+ }
13
+ /** 发任务(结果走 'stream' 事件)。 */
14
+ async send(input, opts) {
15
+ return this.device.call("session.send", { sessionId: this.sessionId, input, ...opts });
16
+ }
17
+ inject(context) {
18
+ return this.device.call("session.inject", { sessionId: this.sessionId, context });
19
+ }
20
+ interrupt(reason) {
21
+ return this.device.call("session.interrupt", { sessionId: this.sessionId, reason });
22
+ }
23
+ switchModel(model) {
24
+ return this.device.call("session.switchModel", { sessionId: this.sessionId, model });
25
+ }
26
+ compress(mode = "native") {
27
+ return this.device.call("session.compress", { sessionId: this.sessionId, mode });
28
+ }
29
+ status() {
30
+ return this.device.call("session.status", { sessionId: this.sessionId });
31
+ }
32
+ terminate() {
33
+ return this.device.call("session.terminate", { sessionId: this.sessionId });
34
+ }
35
+ /** 内部:收到本 session 的 stream.event。 */
36
+ _onStream(ev) {
37
+ const seq = ev.seq;
38
+ this.emit("stream", ev);
39
+ if (ev.final)
40
+ this.emit("end", ev);
41
+ if (seq > this.lastSeq)
42
+ this.lastSeq = seq;
43
+ }
44
+ /** 本 session 已收到的最大 seq(device 用于 ack)。 */
45
+ get _lastSeq() {
46
+ return this.lastSeq;
47
+ }
48
+ on(event, listener) {
49
+ return super.on(event, listener);
50
+ }
51
+ }
52
+ export class PhononDevice extends EventEmitter {
53
+ deviceId;
54
+ tenantId;
55
+ peer;
56
+ sessions = new Map();
57
+ hookDecider;
58
+ /** 自发输出(无对应 session 时)回调。 */
59
+ onUnsolicited;
60
+ constructor(deviceId, tenantId, peer) {
61
+ super();
62
+ this.deviceId = deviceId;
63
+ this.tenantId = tenantId;
64
+ this.peer = peer;
65
+ }
66
+ call(method, params) {
67
+ return this.peer.request(method, params);
68
+ }
69
+ /** 列设备上可用 agent。 */
70
+ async discover() {
71
+ const r = (await this.peer.request("discovery.list", {}));
72
+ return r.agents;
73
+ }
74
+ /** 建会话。 */
75
+ async createSession(params) {
76
+ const r = (await this.peer.request("session.create", { verbosity: "messages", ...params }));
77
+ const s = new PhononSession(this, r.sessionId);
78
+ this.sessions.set(r.sessionId, s);
79
+ return s;
80
+ }
81
+ /** 列会话。 */
82
+ async listSessions(filter) {
83
+ return this.peer.request("session.list", filter ?? {});
84
+ }
85
+ /** 设备 OS/机器信息,用于 server 做任务调度决策。 */
86
+ info() {
87
+ return this.peer.request("device.info", {});
88
+ }
89
+ /** 设备资源快照(CPU/内存/磁盘/进程/GPU best-effort)。 */
90
+ resources() {
91
+ return this.peer.request("device.resources", {});
92
+ }
93
+ // ---- project / file / skill 便捷封装 ----
94
+ project = {
95
+ create: (p) => this.peer.request("project.create", p),
96
+ list: () => this.peer.request("project.list", {}),
97
+ get: (projectId) => this.peer.request("project.get", { projectId }),
98
+ remove: (projectId, opts) => this.peer.request("project.remove", { projectId, ...opts }),
99
+ worktree: {
100
+ create: (p) => this.peer.request("project.worktree.create", p),
101
+ list: (projectId) => this.peer.request("project.worktree.list", { projectId }),
102
+ remove: (p) => this.peer.request("project.worktree.remove", p),
103
+ },
104
+ deleteBranch: (p) => this.peer.request("project.git.deleteBranch", p),
105
+ };
106
+ env = {
107
+ set: (p) => this.peer.request("env.set", p),
108
+ list: (p) => this.peer.request("env.list", p ?? {}),
109
+ delete: (p) => this.peer.request("env.delete", p),
110
+ };
111
+ file = {
112
+ read: (p) => this.peer.request("file.read", p),
113
+ write: (p) => this.peer.request("file.write", p),
114
+ list: (p) => this.peer.request("file.list", p),
115
+ stat: (p) => this.peer.request("file.stat", p),
116
+ mkdir: (p) => this.peer.request("file.mkdir", p),
117
+ };
118
+ skill = {
119
+ install: (p) => this.peer.request("skill.install", p),
120
+ uninstall: (p) => this.peer.request("skill.uninstall", p),
121
+ list: (filter) => this.peer.request("skill.list", filter ?? {}),
122
+ };
123
+ /** 设置 HITL 裁决器(device 级,所有 session 共用)。 */
124
+ setHookDecider(fn) {
125
+ this.hookDecider = fn;
126
+ }
127
+ /** 设置自发输出回调。 */
128
+ setUnsolicitedHandler(fn) {
129
+ this.onUnsolicited = fn;
130
+ }
131
+ /** 内部:处理 phonon → server 的请求/通知。 */
132
+ async _handleInbound(method, params) {
133
+ if (method === "stream.event") {
134
+ const ev = params;
135
+ const s = this.sessions.get(ev.sessionId);
136
+ if (s) {
137
+ s._onStream(ev);
138
+ // 自动 ack
139
+ this.peer.notify("stream.ack", { sessionId: ev.sessionId, lastSeq: s._lastSeq });
140
+ }
141
+ else if (ev.origin === "unsolicited" && this.onUnsolicited) {
142
+ this.onUnsolicited(ev);
143
+ }
144
+ return null;
145
+ }
146
+ if (method === "hook.fired") {
147
+ const fired = params;
148
+ const s = this.sessions.get(fired.sessionId ?? "");
149
+ if (!this.hookDecider)
150
+ return { applied: true }; // 默认放行
151
+ const d = await this.hookDecider(fired, s);
152
+ const action = typeof d === "string" ? d : d.action;
153
+ const reason = typeof d === "string" ? undefined : d.reason;
154
+ return { action, reason };
155
+ }
156
+ if (method === "discovery.changed") {
157
+ this.emit("discoveryChanged", params);
158
+ return null;
159
+ }
160
+ if (method === "document.send") {
161
+ this.emit("document", params);
162
+ return { delivered: [] };
163
+ }
164
+ if (method === "document.prepare_upload") {
165
+ this.emit("prepareUpload", params);
166
+ return { uploadRef: newId(), uploadUrl: "", method: "PUT" };
167
+ }
168
+ if (method === "interaction.request") {
169
+ this.emit("interaction", params);
170
+ return { requestId: params.requestId, action: "cancel" };
171
+ }
172
+ return null;
173
+ }
174
+ _onClose() {
175
+ this.peer.rejectAll("device disconnected");
176
+ this.emit("disconnect");
177
+ }
178
+ }
179
+ export class PhononServer extends EventEmitter {
180
+ wss;
181
+ opts;
182
+ devices = new Map();
183
+ actualPort = 0;
184
+ constructor(opts = {}) {
185
+ super();
186
+ this.opts = opts;
187
+ }
188
+ listen() {
189
+ return new Promise((resolve) => {
190
+ const wss = new WebSocketServer({ port: this.opts.port ?? 0, host: this.opts.host });
191
+ this.wss = wss;
192
+ wss.on("connection", (ws) => this.onConnection(ws));
193
+ wss.on("listening", () => {
194
+ const addr = wss.address();
195
+ this.actualPort = typeof addr === "object" && addr ? addr.port : (this.opts.port ?? 0);
196
+ resolve(this.actualPort);
197
+ });
198
+ });
199
+ }
200
+ get port() { return this.actualPort; }
201
+ /** 当前连接的设备列表。 */
202
+ listDevices() { return [...this.devices.values()]; }
203
+ getDevice(deviceId) { return this.devices.get(deviceId); }
204
+ onConnection(ws) {
205
+ const transport = { send: (d) => ws.send(d), close: () => ws.close() };
206
+ let device;
207
+ const peer = new RpcPeer(transport, async (method, params) => {
208
+ if (method === "connect.hello") {
209
+ const p = params;
210
+ const auth = this.opts.authenticate
211
+ ? await this.opts.authenticate(p.deviceId, p.auth?.deviceKey)
212
+ : { tenantId: `tenant-${p.deviceId}` };
213
+ if (!auth)
214
+ throw new Error("unauthorized");
215
+ device = new PhononDevice(p.deviceId, auth.tenantId, peer);
216
+ this.devices.set(p.deviceId, device);
217
+ this.emit("device", device); // 用户监听这个
218
+ return { protocolVersion: "0.1.0", tenantId: auth.tenantId, features: [], at: new Date().toISOString() };
219
+ }
220
+ if (!device)
221
+ throw new Error("not connected");
222
+ return device._handleInbound(method, params);
223
+ });
224
+ ws.on("message", (raw) => peer.handle(raw.toString()));
225
+ ws.on("close", () => {
226
+ if (device) {
227
+ this.devices.delete(device.deviceId);
228
+ device._onClose();
229
+ }
230
+ });
231
+ }
232
+ close() {
233
+ return new Promise((resolve) => {
234
+ if (!this.wss)
235
+ return resolve();
236
+ this.wss.close(() => resolve());
237
+ });
238
+ }
239
+ }
240
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@agent-phonon/server-sdk",
3
+ "version": "0.2.0",
4
+ "description": "Server SDK for agent-phonon — build a server that orchestrates agents across multiple devices.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "dependencies": {
20
+ "ws": "^8.18.0",
21
+ "@agent-phonon/protocol": "0.2.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^22.0.0",
25
+ "@types/ws": "^8.5.12",
26
+ "typescript": "^5.6.3"
27
+ },
28
+ "author": "agent-phonon contributors",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/hackerphysics/agent-phonon.git",
32
+ "directory": "packages/sdk-server-ts"
33
+ },
34
+ "homepage": "https://github.com/hackerphysics/agent-phonon#readme",
35
+ "bugs": {
36
+ "url": "https://github.com/hackerphysics/agent-phonon/issues"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc -p tsconfig.json",
43
+ "typecheck": "tsc -p tsconfig.json --noEmit",
44
+ "test": "pnpm build && node --test dist/**/*.test.js"
45
+ }
46
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @agent-phonon/server-sdk
3
+ *
4
+ * 让任何项目一键成为 agent-phonon 服务端:管理多设备、编排其上的 agent。
5
+ */
6
+ export { PhononServer, PhononDevice, PhononSession } from "./server.js";
7
+ export type { PhononServerOptions, HookDecider, SendResult } from "./server.js";
8
+ export { RpcPeer } from "./rpc.js";
9
+ export type { Transport } from "./rpc.js";
package/src/rpc.ts ADDED
@@ -0,0 +1,85 @@
1
+ import { randomUUID } from "node:crypto";
2
+
3
+ /**
4
+ * 双向 JSON-RPC 2.0 peer(server 侧,零 core 依赖)。
5
+ * 每个 device 连接一个。两端皆可作 requester。
6
+ */
7
+
8
+ export interface Transport {
9
+ send(data: string): void;
10
+ close(): void;
11
+ }
12
+
13
+ export type RpcHandler = (method: string, params: unknown) => Promise<unknown> | unknown;
14
+
15
+ interface Pending {
16
+ resolve: (v: unknown) => void;
17
+ reject: (e: unknown) => void;
18
+ timer?: ReturnType<typeof setTimeout>;
19
+ }
20
+
21
+ export class RpcPeer {
22
+ private transport: Transport;
23
+ private handler: RpcHandler;
24
+ private pending = new Map<string | number, Pending>();
25
+ private nextId = 1;
26
+
27
+ constructor(transport: Transport, handler: RpcHandler) {
28
+ this.transport = transport;
29
+ this.handler = handler;
30
+ }
31
+
32
+ request(method: string, params: unknown, timeoutMs = 600000): Promise<unknown> {
33
+ const id = this.nextId++;
34
+ return new Promise((resolve, reject) => {
35
+ const timer = timeoutMs > 0 ? setTimeout(() => { this.pending.delete(id); reject(new Error(`rpc timeout: ${method}`)); }, timeoutMs) : undefined;
36
+ this.pending.set(id, { resolve, reject, timer });
37
+ this.transport.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
38
+ });
39
+ }
40
+
41
+ notify(method: string, params: unknown): void {
42
+ this.transport.send(JSON.stringify({ jsonrpc: "2.0", method, params }));
43
+ }
44
+
45
+ async handle(data: string): Promise<void> {
46
+ let msg: Record<string, unknown>;
47
+ try { msg = JSON.parse(data); } catch { return; }
48
+
49
+ // 响应
50
+ if (("result" in msg || "error" in msg) && "id" in msg) {
51
+ const p = this.pending.get(msg.id as string | number);
52
+ if (!p) return;
53
+ this.pending.delete(msg.id as string | number);
54
+ if (p.timer) clearTimeout(p.timer);
55
+ if ("error" in msg && msg.error) p.reject(msg.error);
56
+ else p.resolve((msg as { result: unknown }).result);
57
+ return;
58
+ }
59
+
60
+ // 请求 / 通知
61
+ if (typeof msg.method === "string") {
62
+ const isNotification = !("id" in msg) || msg.id === undefined || msg.id === null;
63
+ try {
64
+ const result = await this.handler(msg.method, msg.params);
65
+ if (!isNotification) this.transport.send(JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: result ?? null }));
66
+ } catch (err) {
67
+ if (!isNotification) {
68
+ this.transport.send(JSON.stringify({
69
+ jsonrpc: "2.0", id: msg.id,
70
+ error: { code: -32000, message: (err as Error)?.message ?? "error" },
71
+ }));
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ rejectAll(reason: string): void {
78
+ for (const [, p] of this.pending) { if (p.timer) clearTimeout(p.timer); p.reject(new Error(reason)); }
79
+ this.pending.clear();
80
+ }
81
+ }
82
+
83
+ export function newId(): string {
84
+ return randomUUID();
85
+ }
package/src/server.ts ADDED
@@ -0,0 +1,290 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { WebSocketServer, WebSocket } from "ws";
3
+ import { RpcPeer, type Transport, newId } from "./rpc.js";
4
+ import type {
5
+ AgentDescriptor,
6
+ SessionMeta,
7
+ StreamEvent,
8
+ HookFiredParams,
9
+ HookAction,
10
+ } from "@agent-phonon/protocol";
11
+
12
+ /**
13
+ * agent-phonon Server SDK。
14
+ *
15
+ * 让任何项目「一键成为 phonon 服务端」:导入 SDK → 配鉴权 → 监听 device →
16
+ * 用干净接口(discover / createSession / send / onStream / onHook)编排
17
+ * 多台设备上的 agent。协议帧/握手/ack/HITL 路由全由 SDK 处理。
18
+ *
19
+ * 支持多设备:一个 PhononServer 同时连多个 phonon。
20
+ */
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // PhononSession:一个会话,流式事件用 EventEmitter
24
+ // ---------------------------------------------------------------------------
25
+ export interface SendResult {
26
+ turnId: string;
27
+ disposition: string;
28
+ }
29
+
30
+ export class PhononSession extends EventEmitter {
31
+ readonly sessionId: string;
32
+ readonly device: PhononDevice;
33
+ private lastSeq = -1;
34
+
35
+ constructor(device: PhononDevice, sessionId: string) {
36
+ super();
37
+ this.device = device;
38
+ this.sessionId = sessionId;
39
+ }
40
+
41
+ /** 发任务(结果走 'stream' 事件)。 */
42
+ async send(input: string, opts?: { verbosity?: "final" | "messages" | "tools" | "trace"; skills?: string[]; whenBusy?: "queue" | "interrupt" | "inject"; clientRequestId?: string }): Promise<SendResult> {
43
+ return this.device.call("session.send", { sessionId: this.sessionId, input, ...opts }) as Promise<SendResult>;
44
+ }
45
+
46
+ inject(context: Array<{ role: "system" | "user" | "assistant"; content: string }>): Promise<unknown> {
47
+ return this.device.call("session.inject", { sessionId: this.sessionId, context });
48
+ }
49
+ interrupt(reason?: string): Promise<unknown> {
50
+ return this.device.call("session.interrupt", { sessionId: this.sessionId, reason });
51
+ }
52
+ switchModel(model: string): Promise<unknown> {
53
+ return this.device.call("session.switchModel", { sessionId: this.sessionId, model });
54
+ }
55
+ compress(mode: "native" | "custom" = "native"): Promise<unknown> {
56
+ return this.device.call("session.compress", { sessionId: this.sessionId, mode });
57
+ }
58
+ status(): Promise<SessionMeta> {
59
+ return this.device.call("session.status", { sessionId: this.sessionId }) as Promise<SessionMeta>;
60
+ }
61
+ terminate(): Promise<unknown> {
62
+ return this.device.call("session.terminate", { sessionId: this.sessionId });
63
+ }
64
+
65
+ /** 内部:收到本 session 的 stream.event。 */
66
+ _onStream(ev: StreamEvent): void {
67
+ const seq = (ev as { seq: number }).seq;
68
+ this.emit("stream", ev);
69
+ if ((ev as { final?: boolean }).final) this.emit("end", ev);
70
+ if (seq > this.lastSeq) this.lastSeq = seq;
71
+ }
72
+ /** 本 session 已收到的最大 seq(device 用于 ack)。 */
73
+ get _lastSeq(): number {
74
+ return this.lastSeq;
75
+ }
76
+
77
+ // 类型化 on
78
+ override on(event: "stream", listener: (ev: StreamEvent) => void): this;
79
+ override on(event: "end", listener: (ev: StreamEvent) => void): this;
80
+ override on(event: string, listener: (...args: never[]) => void): this {
81
+ return super.on(event, listener as (...args: unknown[]) => void);
82
+ }
83
+ }
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // PhononDevice:一台连入的设备
87
+ // ---------------------------------------------------------------------------
88
+ export type HookDecider = (hook: HookFiredParams, session: PhononSession | undefined) => HookAction | { action: HookAction; reason?: string } | Promise<HookAction | { action: HookAction; reason?: string }>;
89
+
90
+ export class PhononDevice extends EventEmitter {
91
+ readonly deviceId: string;
92
+ readonly tenantId: string;
93
+ private peer: RpcPeer;
94
+ private sessions = new Map<string, PhononSession>();
95
+ private hookDecider?: HookDecider;
96
+ /** 自发输出(无对应 session 时)回调。 */
97
+ private onUnsolicited?: (ev: StreamEvent) => void;
98
+
99
+ constructor(deviceId: string, tenantId: string, peer: RpcPeer) {
100
+ super();
101
+ this.deviceId = deviceId;
102
+ this.tenantId = tenantId;
103
+ this.peer = peer;
104
+ }
105
+
106
+ call(method: string, params: unknown): Promise<unknown> {
107
+ return this.peer.request(method, params);
108
+ }
109
+
110
+ /** 列设备上可用 agent。 */
111
+ async discover(): Promise<AgentDescriptor[]> {
112
+ const r = (await this.peer.request("discovery.list", {})) as { agents: AgentDescriptor[] };
113
+ return r.agents;
114
+ }
115
+
116
+ /** 建会话。 */
117
+ async createSession(params: { project: string; agent: string; model: string; worktreeId?: string; verbosity?: "final" | "messages" | "tools" | "trace"; clientRequestId?: string }): Promise<PhononSession> {
118
+ const r = (await this.peer.request("session.create", { verbosity: "messages", ...params })) as { sessionId: string };
119
+ const s = new PhononSession(this, r.sessionId);
120
+ this.sessions.set(r.sessionId, s);
121
+ return s;
122
+ }
123
+
124
+ /** 列会话。 */
125
+ async listSessions(filter?: { project?: string; agent?: string; status?: string; limit?: number; cursor?: string }): Promise<{ sessions: SessionMeta[]; nextCursor?: string }> {
126
+ return this.peer.request("session.list", filter ?? {}) as Promise<{ sessions: SessionMeta[]; nextCursor?: string }>;
127
+ }
128
+
129
+ /** 设备 OS/机器信息,用于 server 做任务调度决策。 */
130
+ info(): Promise<unknown> {
131
+ return this.peer.request("device.info", {});
132
+ }
133
+
134
+ /** 设备资源快照(CPU/内存/磁盘/进程/GPU best-effort)。 */
135
+ resources(): Promise<unknown> {
136
+ return this.peer.request("device.resources", {});
137
+ }
138
+
139
+ // ---- project / file / skill 便捷封装 ----
140
+ project = {
141
+ create: (p: { name: string; path?: string; git?: boolean; remote?: string; clientRequestId?: string }) => this.peer.request("project.create", p) as Promise<{ project: { projectId: string; path: string } }>,
142
+ list: () => this.peer.request("project.list", {}) as Promise<{ projects: unknown[] }>,
143
+ get: (projectId: string) => this.peer.request("project.get", { projectId }),
144
+ remove: (projectId: string, opts?: { deleteFiles?: boolean; whenActiveSessions?: "reject" | "cascade" }) => this.peer.request("project.remove", { projectId, ...opts }),
145
+ worktree: {
146
+ create: (p: { projectId: string; baseBranch: string; newBranch?: string; path?: string }) => this.peer.request("project.worktree.create", p),
147
+ list: (projectId: string) => this.peer.request("project.worktree.list", { projectId }),
148
+ remove: (p: { projectId: string; worktreeId: string; force?: boolean }) => this.peer.request("project.worktree.remove", p),
149
+ },
150
+ deleteBranch: (p: { projectId: string; branch: string; force?: boolean }) => this.peer.request("project.git.deleteBranch", p),
151
+ };
152
+
153
+ env = {
154
+ set: (p: { scope: "global" | "project" | "skill"; projectId?: string; agent?: string; skillName?: string; name: string; value: string; secret?: boolean; clientRequestId?: string }) => this.peer.request("env.set", p),
155
+ list: (p?: { scope?: "global" | "project" | "skill"; projectId?: string; agent?: string; skillName?: string; reveal?: boolean }) => this.peer.request("env.list", p ?? {}),
156
+ delete: (p: { scope: "global" | "project" | "skill"; projectId?: string; agent?: string; skillName?: string; name: string; clientRequestId?: string }) => this.peer.request("env.delete", p),
157
+ };
158
+
159
+ file = {
160
+ read: (p: { projectId: string; worktreeId?: string; path: string; encoding?: "utf8" | "base64"; maxBytes?: number }) => this.peer.request("file.read", p),
161
+ write: (p: { projectId: string; worktreeId?: string; path: string; encoding?: "utf8" | "base64"; data: string; overwrite?: boolean; createDirs?: boolean; clientRequestId?: string }) => this.peer.request("file.write", p),
162
+ list: (p: { projectId: string; worktreeId?: string; path?: string; recursive?: boolean; limit?: number }) => this.peer.request("file.list", p),
163
+ stat: (p: { projectId: string; worktreeId?: string; path: string }) => this.peer.request("file.stat", p),
164
+ mkdir: (p: { projectId: string; worktreeId?: string; path: string; recursive?: boolean; clientRequestId?: string }) => this.peer.request("file.mkdir", p),
165
+ };
166
+
167
+ skill = {
168
+ install: (p: { agent: string; name: string; scope: "global" | "project"; projectId?: string; source: unknown; clientRequestId?: string }) => this.peer.request("skill.install", p),
169
+ uninstall: (p: { agent: string; name: string; scope: "global" | "project"; projectId?: string }) => this.peer.request("skill.uninstall", p),
170
+ list: (filter?: { agent?: string; scope?: "global" | "project"; projectId?: string }) => this.peer.request("skill.list", filter ?? {}),
171
+ };
172
+
173
+ /** 设置 HITL 裁决器(device 级,所有 session 共用)。 */
174
+ setHookDecider(fn: HookDecider): void {
175
+ this.hookDecider = fn;
176
+ }
177
+ /** 设置自发输出回调。 */
178
+ setUnsolicitedHandler(fn: (ev: StreamEvent) => void): void {
179
+ this.onUnsolicited = fn;
180
+ }
181
+
182
+ /** 内部:处理 phonon → server 的请求/通知。 */
183
+ async _handleInbound(method: string, params: unknown): Promise<unknown> {
184
+ if (method === "stream.event") {
185
+ const ev = params as StreamEvent;
186
+ const s = this.sessions.get((ev as { sessionId: string }).sessionId);
187
+ if (s) {
188
+ s._onStream(ev);
189
+ // 自动 ack
190
+ this.peer.notify("stream.ack", { sessionId: (ev as { sessionId: string }).sessionId, lastSeq: s._lastSeq });
191
+ } else if ((ev as { origin?: string }).origin === "unsolicited" && this.onUnsolicited) {
192
+ this.onUnsolicited(ev);
193
+ }
194
+ return null;
195
+ }
196
+ if (method === "hook.fired") {
197
+ const fired = params as HookFiredParams;
198
+ const s = this.sessions.get((fired as { sessionId?: string }).sessionId ?? "");
199
+ if (!this.hookDecider) return { applied: true }; // 默认放行
200
+ const d = await this.hookDecider(fired, s);
201
+ const action = typeof d === "string" ? d : d.action;
202
+ const reason = typeof d === "string" ? undefined : d.reason;
203
+ return { action, reason };
204
+ }
205
+ if (method === "discovery.changed") { this.emit("discoveryChanged", params); return null; }
206
+ if (method === "document.send") { this.emit("document", params); return { delivered: [] }; }
207
+ if (method === "document.prepare_upload") { this.emit("prepareUpload", params); return { uploadRef: newId(), uploadUrl: "", method: "PUT" }; }
208
+ if (method === "interaction.request") { this.emit("interaction", params); return { requestId: (params as { requestId: string }).requestId, action: "cancel" }; }
209
+ return null;
210
+ }
211
+
212
+ _onClose(): void {
213
+ this.peer.rejectAll("device disconnected");
214
+ this.emit("disconnect");
215
+ }
216
+ }
217
+
218
+ // ---------------------------------------------------------------------------
219
+ // PhononServer:监听 ws,管理多设备
220
+ // ---------------------------------------------------------------------------
221
+ export interface PhononServerOptions {
222
+ port?: number;
223
+ host?: string;
224
+ /** 鉴权:返回 tenantId 表示通过,返回 null 拒绝。缺省全部放行(本地测试)。 */
225
+ authenticate?: (deviceId: string, deviceKey: string | undefined) => { tenantId: string } | null | Promise<{ tenantId: string } | null>;
226
+ }
227
+
228
+ export class PhononServer extends EventEmitter {
229
+ private wss?: WebSocketServer;
230
+ private opts: PhononServerOptions;
231
+ private devices = new Map<string, PhononDevice>();
232
+ private actualPort = 0;
233
+
234
+ constructor(opts: PhononServerOptions = {}) {
235
+ super();
236
+ this.opts = opts;
237
+ }
238
+
239
+ listen(): Promise<number> {
240
+ return new Promise((resolve) => {
241
+ const wss = new WebSocketServer({ port: this.opts.port ?? 0, host: this.opts.host });
242
+ this.wss = wss;
243
+ wss.on("connection", (ws: WebSocket) => this.onConnection(ws));
244
+ wss.on("listening", () => {
245
+ const addr = wss.address();
246
+ this.actualPort = typeof addr === "object" && addr ? addr.port : (this.opts.port ?? 0);
247
+ resolve(this.actualPort);
248
+ });
249
+ });
250
+ }
251
+
252
+ get port(): number { return this.actualPort; }
253
+
254
+ /** 当前连接的设备列表。 */
255
+ listDevices(): PhononDevice[] { return [...this.devices.values()]; }
256
+ getDevice(deviceId: string): PhononDevice | undefined { return this.devices.get(deviceId); }
257
+
258
+ private onConnection(ws: WebSocket): void {
259
+ const transport: Transport = { send: (d) => ws.send(d), close: () => ws.close() };
260
+ let device: PhononDevice | undefined;
261
+
262
+ const peer = new RpcPeer(transport, async (method, params) => {
263
+ if (method === "connect.hello") {
264
+ const p = params as { deviceId: string; auth?: { deviceKey?: string } };
265
+ const auth = this.opts.authenticate
266
+ ? await this.opts.authenticate(p.deviceId, p.auth?.deviceKey)
267
+ : { tenantId: `tenant-${p.deviceId}` };
268
+ if (!auth) throw new Error("unauthorized");
269
+ device = new PhononDevice(p.deviceId, auth.tenantId, peer);
270
+ this.devices.set(p.deviceId, device);
271
+ this.emit("device", device); // 用户监听这个
272
+ return { protocolVersion: "0.1.0", tenantId: auth.tenantId, features: [], at: new Date().toISOString() };
273
+ }
274
+ if (!device) throw new Error("not connected");
275
+ return device._handleInbound(method, params);
276
+ });
277
+
278
+ ws.on("message", (raw: Buffer) => peer.handle(raw.toString()));
279
+ ws.on("close", () => {
280
+ if (device) { this.devices.delete(device.deviceId); device._onClose(); }
281
+ });
282
+ }
283
+
284
+ close(): Promise<void> {
285
+ return new Promise((resolve) => {
286
+ if (!this.wss) return resolve();
287
+ this.wss.close(() => resolve());
288
+ });
289
+ }
290
+ }