@zhin.js/adapter-milky 7.0.0 → 7.0.2

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.
@@ -3,20 +3,24 @@
3
3
  */
4
4
  import WebSocket from 'ws';
5
5
  import {
6
+ ClientEndpoint,
6
7
  createRecallEndpointControl,
7
8
  createEndpointLifecycle,
8
9
  type EndpointConnectHandle,
9
10
  type EndpointControl,
10
- type EndpointInstance,
11
11
  type EndpointLifecycle,
12
12
  type EndpointManagement,
13
13
  type EndpointSendRequest,
14
14
  } from 'zhin.js/adapter';
15
- import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
16
15
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
17
16
  import type { CapabilityId } from 'zhin.js';
18
17
  import { createMilkyEndpointManagement } from './endpoint-management.js';
19
- import { registerMilkyAgentEndpoint } from './milky-agent-deps.js';
18
+ import {
19
+ callMilkyClient,
20
+ createMilkyEndpointClient,
21
+ forwardMilkyClientEvents,
22
+ type MilkyClient,
23
+ } from './client.js';
20
24
  import {
21
25
  buildSendAction,
22
26
  buildWsConnectOptions,
@@ -42,8 +46,6 @@ const WS_OPEN = 1;
42
46
 
43
47
  export interface MilkyWsEndpointOptions {
44
48
  readonly id: CapabilityId;
45
- readonly gateway: MessageGateway;
46
- readonly sideEvents?: SideEventGateway;
47
49
  readonly config: MilkyWsConfig;
48
50
  readonly createWebSocket?: (
49
51
  url: string,
@@ -52,22 +54,34 @@ export interface MilkyWsEndpointOptions {
52
54
  readonly callApi?: typeof callApi;
53
55
  }
54
56
 
55
- export class MilkyWsEndpoint implements EndpointInstance {
57
+ export class MilkyWsEndpoint extends ClientEndpoint<MilkyClient> {
58
+ readonly client: MilkyClient;
56
59
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
57
60
 
58
61
  readonly #options: MilkyWsEndpointOptions;
59
62
  readonly #callApi: typeof callApi;
60
- readonly management: EndpointManagement = createMilkyEndpointManagement(this);
63
+ readonly management: EndpointManagement;
61
64
  readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
62
65
  readonly #lifecycle: EndpointLifecycle;
63
66
  #ws?: MilkyWsSocket;
64
- #open = false;
65
- #unregisterAgent?: () => void;
67
+ #clientSocketRelease?: () => void;
66
68
 
67
69
  constructor(options: MilkyWsEndpointOptions) {
70
+ super();
68
71
  this.#logger = getAdapterLogger('milky', options.config.id);
69
72
  this.#options = options;
70
73
  this.#callApi = options.callApi ?? callApi;
74
+ this.client = createMilkyEndpointClient(options.config, this.#callApi);
75
+ this.management = createMilkyEndpointManagement({
76
+ callApi: (action, params) => callMilkyClient(this.client, action, params),
77
+ });
78
+ this.bindClientEvents(
79
+ (receive) => forwardMilkyClientEvents(this.client, receive),
80
+ (name, payload) => {
81
+ if (name === 'event') this.#admitRaw(payload as MilkyEvent);
82
+ },
83
+ (_name, error) => this.#warnPlatformEvent(error),
84
+ );
71
85
  this.#lifecycle = createEndpointLifecycle({
72
86
  name: options.config.id,
73
87
  // reconnect_interval 旧语义为固定间隔:multiplier 1 + 无 jitter + 不封顶
@@ -82,30 +96,18 @@ export class MilkyWsEndpoint implements EndpointInstance {
82
96
 
83
97
  async start(): Promise<void> {
84
98
  if (this.#lifecycle.started) return;
85
- this.#unregisterAgent = registerMilkyAgentEndpoint(this.#options.config.id, this);
86
- try {
87
- await this.#lifecycle.start((handle) => this.#connect(handle));
88
- } catch (err) {
89
- // start 失败复位由基座保证;agent 注册/反注册是适配器专有依赖,留在适配器侧
90
- this.#unregisterAgent?.();
91
- this.#unregisterAgent = undefined;
92
- throw err;
93
- }
94
- }
95
-
96
- open(): void {
97
- this.#open = true;
99
+ await this.#lifecycle.start((handle) => this.#connect(handle));
98
100
  }
99
101
 
100
102
  close(): void {
101
- this.#open = false;
103
+ super.close();
104
+ this.#clientSocketRelease?.();
105
+ this.#clientSocketRelease = undefined;
102
106
  }
103
107
 
104
108
  async stop(): Promise<void> {
105
- this.#open = false;
109
+ this.close();
106
110
  await this.#lifecycle.stop();
107
- this.#unregisterAgent?.();
108
- this.#unregisterAgent = undefined;
109
111
  if (this.#ws) {
110
112
  try {
111
113
  this.#ws.close();
@@ -119,7 +121,7 @@ export class MilkyWsEndpoint implements EndpointInstance {
119
121
  async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
120
122
  const message = formatOutboundSegments(payload);
121
123
  const { action, params } = buildSendAction(conversation, message);
122
- const data = await this.callApi(action, params) as { message_seq?: number } | undefined;
124
+ const data = await callMilkyClient<{ message_seq?: number }>(this.client, action, params);
123
125
  const messageId = formatOutboundMessageId(conversation, data?.message_seq);
124
126
  this.#logger.debug(formatCompact({
125
127
  op: 'milky_send',
@@ -130,102 +132,33 @@ export class MilkyWsEndpoint implements EndpointInstance {
130
132
  return messageId;
131
133
  }
132
134
 
133
- /** Public API for agent tools / callers. */
134
- callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
135
- return this.#callApi(this.apiOptions(), action, params);
136
- }
137
-
138
135
  async recallMessage(id: string): Promise<void> {
139
136
  const parsed = parseMilkyMessageId(id);
140
137
  if (!parsed) throw new Error(`Invalid message id: ${id}`);
141
138
  if (parsed.message_scene === 'group') {
142
- await this.callApi('recall_group_message', {
139
+ await callMilkyClient(this.client, 'recall_group_message', {
143
140
  group_id: parsed.peer_id,
144
141
  message_seq: parsed.message_seq,
145
142
  });
146
143
  } else {
147
- await this.callApi('recall_private_message', {
144
+ await callMilkyClient(this.client, 'recall_private_message', {
148
145
  user_id: parsed.peer_id,
149
146
  message_seq: parsed.message_seq,
150
147
  });
151
148
  }
152
149
  }
153
150
 
154
- async kickMember(groupId: number, userId: number, rejectAddRequest = false): Promise<boolean> {
155
- await this.callApi('kick_group_member', {
156
- group_id: groupId,
157
- user_id: userId,
158
- reject_add_request: rejectAddRequest,
159
- });
160
- return true;
161
- }
162
-
163
- async muteMember(groupId: number, userId: number, duration = 600): Promise<boolean> {
164
- await this.callApi('set_group_member_mute', {
165
- group_id: groupId,
166
- user_id: userId,
167
- duration,
168
- });
169
- return true;
170
- }
171
-
172
- async muteAll(groupId: number, enable = true): Promise<boolean> {
173
- await this.callApi('set_group_whole_mute', { group_id: groupId, is_mute: enable });
174
- return true;
175
- }
176
-
177
- async setAdmin(groupId: number, userId: number, enable = true): Promise<boolean> {
178
- await this.callApi('set_group_member_admin', {
179
- group_id: groupId,
180
- user_id: userId,
181
- is_set: enable,
182
- });
183
- return true;
184
- }
185
-
186
- async setCard(groupId: number, userId: number, card: string): Promise<boolean> {
187
- await this.callApi('set_group_member_card', {
188
- group_id: groupId,
189
- user_id: userId,
190
- card,
191
- });
192
- return true;
193
- }
194
-
195
- async setTitle(groupId: number, userId: number, title: string): Promise<boolean> {
196
- await this.callApi('set_group_member_special_title', {
197
- group_id: groupId,
198
- user_id: userId,
199
- special_title: title,
200
- });
201
- return true;
202
- }
203
-
204
- async setGroupName(groupId: number, name: string): Promise<boolean> {
205
- await this.callApi('set_group_name', { group_id: groupId, new_group_name: name });
206
- return true;
207
- }
208
-
209
- async getMemberList(groupId: number): Promise<unknown[]> {
210
- return this.callApi('get_group_member_list', { group_id: groupId }) as Promise<unknown[]>;
211
- }
212
-
213
- async getGroupInfo(groupId: number): Promise<unknown> {
214
- return this.callApi('get_group_info', { group_id: groupId });
215
- }
216
-
217
- /** Test / internal: admit a parsed event when the endpoint is open. */
218
- admit(event: MilkyEvent): void {
151
+ #admitRaw(event: MilkyEvent): void {
219
152
  const data = parseMessageReceiveData(event);
220
- if (!this.#open || !data) return;
153
+ if (!data) return;
221
154
  this.#admitMessage(data, event);
222
155
  }
223
156
 
224
- apiOptions(): { baseUrl: string; access_token?: string } {
225
- return {
226
- baseUrl: this.#options.config.baseUrl,
227
- access_token: this.#options.config.access_token,
228
- };
157
+ #warnPlatformEvent(error: unknown): void {
158
+ this.#logger.warn(formatCompact({
159
+ op: 'milky_platform_event_failed',
160
+ error: error instanceof Error ? error.message : String(error),
161
+ }));
229
162
  }
230
163
 
231
164
  #admitMessage(data: MilkyIncomingMessage, event: MilkyEvent): void {
@@ -236,7 +169,7 @@ export class MilkyWsEndpoint implements EndpointInstance {
236
169
  const audioUrl = extractInboundAudioUrl(data);
237
170
  const nickname = senderNickname(data);
238
171
  const mentioned = isMentioned(data, event.self_id);
239
- void this.#options.gateway.receive({
172
+ void this.emit('message.receive', {
240
173
  conversation,
241
174
  message: { conversation, id: formatInboundMessageId(data) },
242
175
  content,
@@ -308,11 +241,12 @@ export class MilkyWsEndpoint implements EndpointInstance {
308
241
  resolve();
309
242
  });
310
243
 
311
- ws.on('message', (data) => {
312
- this.#onMessage(data);
313
- });
244
+ this.#clientSocketRelease?.();
245
+ this.#clientSocketRelease = this.client.acceptWebSocket(ws);
314
246
 
315
247
  ws.on('close', (code, reason) => {
248
+ this.#clientSocketRelease?.();
249
+ this.#clientSocketRelease = undefined;
316
250
  const reasonStr = typeof reason === 'string'
317
251
  ? reason
318
252
  : Buffer.isBuffer(reason)
@@ -354,23 +288,4 @@ export class MilkyWsEndpoint implements EndpointInstance {
354
288
  });
355
289
  }
356
290
 
357
- #onMessage(data: unknown): void {
358
- try {
359
- const raw = typeof data === 'string'
360
- ? data
361
- : Buffer.isBuffer(data)
362
- ? data.toString()
363
- : data instanceof ArrayBuffer
364
- ? new TextDecoder().decode(data)
365
- : String(data ?? '');
366
- const event = JSON.parse(raw) as MilkyEvent;
367
- this.admit(event);
368
- } catch (error) {
369
- this.#logger.warn(formatCompact({
370
- op: 'milky_parse_failed',
371
- endpoint: this.#options.config.id,
372
- error: error instanceof Error ? error.message : String(error),
373
- }));
374
- }
375
- }
376
291
  }
@@ -1,21 +1,21 @@
1
1
  /**
2
2
  * Milky reverse WSS endpoint — httpHostToken WS upgrade inbound + baseUrl HTTP API outbound.
3
3
  */
4
- import { clearInterval } from 'node:timers';
5
4
  import {
5
+ ClientEndpoint,
6
+ createEndpointLifecycle,
6
7
  createRecallEndpointControl,
8
+ type EndpointLifecycle,
7
9
  type EndpointControl,
8
- type EndpointInstance,
9
10
  type EndpointManagement,
10
11
  type EndpointSendRequest,
11
12
  } from 'zhin.js/adapter';
12
- import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
13
13
  import type { HttpHost, WsConnection } from '@zhin.js/host-http';
14
14
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
15
15
  import type { CapabilityId } from 'zhin.js';
16
16
  import { verifyMilkyAccessToken } from './milky-auth.js';
17
17
  import { createMilkyEndpointManagement } from './endpoint-management.js';
18
- import { registerMilkyAgentEndpoint } from './milky-agent-deps.js';
18
+ import { callMilkyClient, createMilkyEndpointClient, forwardMilkyClientEvents, type MilkyClient } from './client.js';
19
19
  import {
20
20
  buildSendAction,
21
21
  callApi,
@@ -40,40 +40,65 @@ const WS_OPEN = 1;
40
40
 
41
41
  export interface MilkyWssEndpointOptions {
42
42
  readonly id: CapabilityId;
43
- readonly gateway: MessageGateway;
44
- readonly sideEvents?: SideEventGateway;
45
43
  readonly http: HttpHost;
46
44
  readonly config: MilkyWssConfig;
47
45
  readonly callApi?: typeof callApi;
48
46
  }
49
47
 
50
- export class MilkyWssEndpoint implements EndpointInstance {
48
+ export class MilkyWssEndpoint extends ClientEndpoint<MilkyClient> {
49
+ readonly client: MilkyClient;
51
50
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
52
51
 
53
52
  readonly #options: MilkyWssEndpointOptions;
54
53
  readonly #callApi: typeof callApi;
55
- readonly management: EndpointManagement = createMilkyEndpointManagement(this);
54
+ readonly management: EndpointManagement;
56
55
  readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
57
56
  #ws?: MilkyWsSocket;
58
57
  #wsRelease?: () => void;
59
- #heartbeatTimer?: NodeJS.Timeout;
60
- #open = false;
61
- #started = false;
62
- #unregisterAgent?: () => void;
58
+ #clientSocketRelease?: () => void;
59
+ readonly #lifecycle: EndpointLifecycle;
63
60
 
64
61
  constructor(options: MilkyWssEndpointOptions) {
62
+ super();
65
63
  this.#logger = getAdapterLogger('milky', options.config.id);
66
64
  this.#options = options;
67
65
  this.#callApi = options.callApi ?? callApi;
66
+ this.#lifecycle = createEndpointLifecycle({
67
+ name: options.config.id,
68
+ reconnect: false,
69
+ heartbeat: { intervalMs: options.config.heartbeat_interval },
70
+ });
71
+ this.client = createMilkyEndpointClient(options.config, this.#callApi);
72
+ this.management = createMilkyEndpointManagement({
73
+ callApi: (action, params) => callMilkyClient(this.client, action, params),
74
+ });
75
+ this.bindClientEvents(
76
+ (receive) => forwardMilkyClientEvents(this.client, receive),
77
+ (name, payload) => {
78
+ if (name === 'event') this.#admitRaw(payload as MilkyEvent);
79
+ },
80
+ (_name, error) => this.#warnPlatformEvent(error),
81
+ );
68
82
  }
69
83
 
70
84
  async start(): Promise<void> {
71
- if (this.#started) return;
72
- this.#started = true;
73
- this.#unregisterAgent = registerMilkyAgentEndpoint(this.#options.config.id, this);
74
- const handle = this.#options.http.ws(this.#options.config.path);
75
- this.#wsRelease = handle.onConnection((connection) => {
76
- this.#acceptConnection(connection);
85
+ await this.#lifecycle.start(async (lifecycleHandle) => {
86
+ const handle = this.#options.http.ws(this.#options.config.path);
87
+ this.#wsRelease = handle.onConnection((connection) => {
88
+ this.#acceptConnection(connection);
89
+ });
90
+ lifecycleHandle.onForceClose(() => {
91
+ this.#wsRelease?.();
92
+ this.#wsRelease = undefined;
93
+ this.#clientSocketRelease?.();
94
+ this.#clientSocketRelease = undefined;
95
+ try {
96
+ this.#ws?.close();
97
+ } catch {
98
+ /* ignore */
99
+ }
100
+ this.#ws = undefined;
101
+ });
77
102
  });
78
103
  this.#logger.info(formatCompact({
79
104
  op: 'listen',
@@ -83,39 +108,15 @@ export class MilkyWssEndpoint implements EndpointInstance {
83
108
  }));
84
109
  }
85
110
 
86
- open(): void {
87
- this.#open = true;
88
- }
89
-
90
- close(): void {
91
- this.#open = false;
92
- }
93
-
94
111
  async stop(): Promise<void> {
95
- this.#open = false;
96
- this.#unregisterAgent?.();
97
- this.#unregisterAgent = undefined;
98
- this.#wsRelease?.();
99
- this.#wsRelease = undefined;
100
- if (this.#heartbeatTimer) {
101
- clearInterval(this.#heartbeatTimer);
102
- this.#heartbeatTimer = undefined;
103
- }
104
- if (this.#ws) {
105
- try {
106
- this.#ws.close();
107
- } catch {
108
- /* ignore */
109
- }
110
- this.#ws = undefined;
111
- }
112
- this.#started = false;
112
+ this.close();
113
+ await this.#lifecycle.stop();
113
114
  }
114
115
 
115
116
  async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
116
117
  const message = formatOutboundSegments(payload);
117
118
  const { action, params } = buildSendAction(conversation, message);
118
- const data = await this.callApi(action, params) as { message_seq?: number } | undefined;
119
+ const data = await callMilkyClient<{ message_seq?: number }>(this.client, action, params);
119
120
  const messageId = formatOutboundMessageId(conversation, data?.message_seq);
120
121
  this.#logger.debug(formatCompact({
121
122
  op: 'milky_send',
@@ -126,100 +127,30 @@ export class MilkyWssEndpoint implements EndpointInstance {
126
127
  return messageId;
127
128
  }
128
129
 
129
- callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
130
- return this.#callApi(this.apiOptions(), action, params);
131
- }
132
-
133
130
  async recallMessage(id: string): Promise<void> {
134
131
  const parsed = parseMilkyMessageId(id);
135
132
  if (!parsed) throw new Error(`Invalid message id: ${id}`);
136
133
  if (parsed.message_scene === 'group') {
137
- await this.callApi('recall_group_message', {
134
+ await callMilkyClient(this.client, 'recall_group_message', {
138
135
  group_id: parsed.peer_id,
139
136
  message_seq: parsed.message_seq,
140
137
  });
141
138
  } else {
142
- await this.callApi('recall_private_message', {
139
+ await callMilkyClient(this.client, 'recall_private_message', {
143
140
  user_id: parsed.peer_id,
144
141
  message_seq: parsed.message_seq,
145
142
  });
146
143
  }
147
144
  }
148
145
 
149
- async kickMember(groupId: number, userId: number, rejectAddRequest = false): Promise<boolean> {
150
- await this.callApi('kick_group_member', {
151
- group_id: groupId,
152
- user_id: userId,
153
- reject_add_request: rejectAddRequest,
154
- });
155
- return true;
156
- }
157
-
158
- async muteMember(groupId: number, userId: number, duration = 600): Promise<boolean> {
159
- await this.callApi('set_group_member_mute', {
160
- group_id: groupId,
161
- user_id: userId,
162
- duration,
163
- });
164
- return true;
165
- }
166
-
167
- async muteAll(groupId: number, enable = true): Promise<boolean> {
168
- await this.callApi('set_group_whole_mute', { group_id: groupId, is_mute: enable });
169
- return true;
170
- }
171
-
172
- async setAdmin(groupId: number, userId: number, enable = true): Promise<boolean> {
173
- await this.callApi('set_group_member_admin', {
174
- group_id: groupId,
175
- user_id: userId,
176
- is_set: enable,
177
- });
178
- return true;
179
- }
180
-
181
- async setCard(groupId: number, userId: number, card: string): Promise<boolean> {
182
- await this.callApi('set_group_member_card', {
183
- group_id: groupId,
184
- user_id: userId,
185
- card,
186
- });
187
- return true;
188
- }
189
-
190
- async setTitle(groupId: number, userId: number, title: string): Promise<boolean> {
191
- await this.callApi('set_group_member_special_title', {
192
- group_id: groupId,
193
- user_id: userId,
194
- special_title: title,
195
- });
196
- return true;
197
- }
198
-
199
- async setGroupName(groupId: number, name: string): Promise<boolean> {
200
- await this.callApi('set_group_name', { group_id: groupId, new_group_name: name });
201
- return true;
202
- }
203
-
204
- async getMemberList(groupId: number): Promise<unknown[]> {
205
- return this.callApi('get_group_member_list', { group_id: groupId }) as Promise<unknown[]>;
206
- }
207
-
208
- async getGroupInfo(groupId: number): Promise<unknown> {
209
- return this.callApi('get_group_info', { group_id: groupId });
210
- }
211
-
212
- admit(event: MilkyEvent): void {
146
+ #admitRaw(event: MilkyEvent): void {
213
147
  const data = parseMessageReceiveData(event);
214
- if (!this.#open || !data) return;
148
+ if (!data) return;
215
149
  this.#admitMessage(data, event);
216
150
  }
217
151
 
218
- apiOptions(): { baseUrl: string; access_token?: string } {
219
- return {
220
- baseUrl: this.#options.config.baseUrl,
221
- access_token: this.#options.config.access_token,
222
- };
152
+ #warnPlatformEvent(error: unknown): void {
153
+ this.#logger.warn(formatCompact({ op: 'milky_platform_event_failed', error: error instanceof Error ? error.message : String(error) }));
223
154
  }
224
155
 
225
156
  #admitMessage(data: MilkyIncomingMessage, event: MilkyEvent): void {
@@ -230,7 +161,7 @@ export class MilkyWssEndpoint implements EndpointInstance {
230
161
  const audioUrl = extractInboundAudioUrl(data);
231
162
  const nickname = senderNickname(data);
232
163
  const mentioned = isMentioned(data, event.self_id);
233
- void this.#options.gateway.receive({
164
+ void this.emit('message.receive', {
234
165
  conversation,
235
166
  message: { conversation, id: formatInboundMessageId(data) },
236
167
  content,
@@ -270,12 +201,22 @@ export class MilkyWssEndpoint implements EndpointInstance {
270
201
  }
271
202
  }
272
203
  this.#ws = socket;
273
- this.#startHeartbeat();
274
- socket.on('message', (data) => {
275
- this.#onMessage(data);
204
+ this.#lifecycle.startHeartbeat(() => {
205
+ try {
206
+ if (this.#ws?.readyState === WS_OPEN) this.#ws.ping?.();
207
+ } catch {
208
+ /* ignore */
209
+ }
276
210
  });
211
+ this.#clientSocketRelease?.();
212
+ this.#clientSocketRelease = this.client.acceptWebSocket(socket);
277
213
  socket.on('close', () => {
278
- if (this.#ws === socket) this.#ws = undefined;
214
+ this.#clientSocketRelease?.();
215
+ this.#clientSocketRelease = undefined;
216
+ if (this.#ws === socket) {
217
+ this.#ws = undefined;
218
+ this.#lifecycle.stopHeartbeat();
219
+ }
279
220
  });
280
221
  this.#logger.debug(formatCompact({
281
222
  endpoint: this.#options.config.id,
@@ -284,36 +225,4 @@ export class MilkyWssEndpoint implements EndpointInstance {
284
225
  }));
285
226
  }
286
227
 
287
- #onMessage(data: unknown): void {
288
- try {
289
- const raw = typeof data === 'string'
290
- ? data
291
- : Buffer.isBuffer(data)
292
- ? data.toString()
293
- : data instanceof ArrayBuffer
294
- ? new TextDecoder().decode(data)
295
- : String(data ?? '');
296
- const event = JSON.parse(raw) as MilkyEvent;
297
- this.admit(event);
298
- } catch (error) {
299
- this.#logger.warn(formatCompact({
300
- op: 'milky_parse_failed',
301
- endpoint: this.#options.config.id,
302
- error: error instanceof Error ? error.message : String(error),
303
- }));
304
- }
305
- }
306
-
307
- #startHeartbeat(): void {
308
- if (this.#heartbeatTimer) clearInterval(this.#heartbeatTimer);
309
- const interval = this.#options.config.heartbeat_interval;
310
- if (interval <= 0) return;
311
- this.#heartbeatTimer = setInterval(() => {
312
- try {
313
- if (this.#ws?.readyState === WS_OPEN) this.#ws.ping?.();
314
- } catch {
315
- /* ignore */
316
- }
317
- }, interval);
318
- }
319
228
  }
@@ -1,24 +0,0 @@
1
- /**
2
- * Agent tool deps for milky (scene management / callApi).
3
- * Endpoints register themselves on start; tools look up by config name / endpoint id.
4
- */
5
- export interface MilkyAgentEndpoint {
6
- callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
7
- kickMember(groupId: number, userId: number, rejectAddRequest?: boolean): Promise<boolean>;
8
- muteMember(groupId: number, userId: number, duration?: number): Promise<boolean>;
9
- muteAll(groupId: number, enable?: boolean): Promise<boolean>;
10
- setAdmin(groupId: number, userId: number, enable?: boolean): Promise<boolean>;
11
- setCard(groupId: number, userId: number, card: string): Promise<boolean>;
12
- setTitle(groupId: number, userId: number, title: string): Promise<boolean>;
13
- setGroupName(groupId: number, name: string): Promise<boolean>;
14
- getMemberList(groupId: number): Promise<unknown[]>;
15
- getGroupInfo(groupId: number): Promise<unknown>;
16
- recallMessage?(id: string): Promise<void>;
17
- }
18
- export interface MilkyAgentDeps {
19
- getEndpoint: (endpointKey: string) => MilkyAgentEndpoint;
20
- }
21
- export declare function registerMilkyAgentEndpoint(endpointKey: string, endpoint: MilkyAgentEndpoint): () => void;
22
- /** Optional override used by tests / transitional callers. Pass `null` to clear. */
23
- export declare function setMilkyAgentDeps(deps: MilkyAgentDeps | null): void;
24
- export declare function getMilkyAgentDeps(): MilkyAgentDeps;
@@ -1,30 +0,0 @@
1
- /**
2
- * Agent tool deps for milky (scene management / callApi).
3
- * Endpoints register themselves on start; tools look up by config name / endpoint id.
4
- */
5
- const endpoints = new Map();
6
- let override = null;
7
- export function registerMilkyAgentEndpoint(endpointKey, endpoint) {
8
- endpoints.set(endpointKey, endpoint);
9
- return () => {
10
- if (endpoints.get(endpointKey) === endpoint) {
11
- endpoints.delete(endpointKey);
12
- }
13
- };
14
- }
15
- /** Optional override used by tests / transitional callers. Pass `null` to clear. */
16
- export function setMilkyAgentDeps(deps) {
17
- override = deps;
18
- }
19
- export function getMilkyAgentDeps() {
20
- if (override)
21
- return override;
22
- return {
23
- getEndpoint(endpointKey) {
24
- const registered = endpoints.get(endpointKey);
25
- if (!registered)
26
- throw new Error(`Endpoint ${endpointKey} 不存在`);
27
- return registered;
28
- },
29
- };
30
- }