@zhin.js/adapter-milky 1.1.18 → 1.1.20

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/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # @zhin.js/adapter-milky
2
2
 
3
+ ## 1.1.20
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [5e878ba]
8
+ - @zhin.js/page@1.1.12
9
+
10
+ ## 1.1.19
11
+
12
+ ### Patch Changes
13
+
14
+ - 2c3610a: Move account lifecycle ownership into the generic Adapter base and let native Endpoints retain their SDK Client inheritance. Infer platform-specific operation Sessions from a single adapter registry while preserving command parameter, option, configuration and factory inference.
15
+
16
+ Bind message replies, management ports, login challenges and Agent cancellation to the captured account incarnation. Persist runtime account intent with encrypted, revision-checked Host storage; keep account management independent of Root process restarts. Require explicit operator confirmation before importing existing accounts, and give replacement accounts a new identity without inheriting previous Sessions.
17
+
18
+ Expose account save, start, stop, update and replacement through the existing adapter Console, with exact account ownership, truthful pending/unknown outcomes and no startup configuration or environment-file rewrites.
19
+
20
+ Complete the target migration across every adapter, the public authoring API, generators, examples and Console. This intentionally removes the old create factory, inheritable Endpoint/ClientEndpoint classes, Client descriptors and file-rewrite account management; plugin authors must use createAdapter, native Client inheritance and the single AdapterRegistry. Satori and OneBot12 require explicit platform/userId selection, and WeCom requires a separate agentId.
21
+
22
+ Verify opaque credential changes before the same configuration snapshot is committed. Drain admitted native callbacks across close/rollback, cancel target work on stop, and keep raw transport parsing, platform ACK timing and SDK native methods under their existing owners. Account metadata conceals credentials carried in URLs.
23
+
24
+ - a9f4e48: PATCH COMPATIBILITY CUTOVER: this batch intentionally changes plugin APIs. The maintainer explicitly chose patch releases with declared version boundaries. Upgrade the complete tested dependency set; patch numbering does not promise compatibility with the removed interfaces.
25
+
26
+ The first planned floors are zhin.js 1.1.19, Core 1.1.54, Adapter 1.1.26, FeatureKit 1.1.10, Plugin Runtime 1.1.18, Runtime/Isolate 1.1.16 and Agent 1.1.45. Keep old-interface plugins with the earlier locked stack until they are migrated. Adapter's independent Feature API changes from 1.0.0 to 2.0.0; official consumers declare ^2.0.0. Existing Feature API admission rejects explicitly incompatible ranges; omitted API metadata keeps its existing behavior and does not certify compatibility. Runtime engine and patch-only release gates remain unchanged.
27
+
28
+ Use the native-Client Adapter target contract, canonical FeatureKit AdapterRegistry augmentation and named Endpoint control/management requests. Route message controls from canonical message/conversation refs; retain exact source authority and propagate caller/account/explicit-Root-stop cancellation. Replace removed Agent activity feedback exports with source-bound feedback. HMR pause/resume belongs to concrete plugin instances; plugins choose accepted-work policy without changing Controller commit/rollback authority. Cooperative worker/process control cancellation does not kill an isolate or reopen retired business admission.
29
+
30
+ Provide authenticated historical-data preview, exact selected binding, transactional application, authoritative inspection and explicit resumption, including active games. Preserve before-images and journals; never replay an unconfirmed effect, infer an account from an alias, or reuse historical permission grants. Owner V2 approvals remain inactive legacy data, while new writes use V3. Rollback requires matching software, lockfile and verified data; installing old code alone cannot read a new V3 approval file. Conflicting after-images are never overwritten.
31
+
32
+ See docs/plans/adapter-operation-version-boundaries.md and the Chinese/English upgrade guide for the complete version inventory, custom-plugin changes, acceptance and recovery sequence. These code and metadata changes do not authorize merging, publication, deployment, login, production migration or removal of installed workspace patches.
33
+
34
+ - Updated dependencies [2c3610a]
35
+ - Updated dependencies [a9f4e48]
36
+ - Updated dependencies [f8505b0]
37
+ - Updated dependencies [2c3610a]
38
+ - @zhin.js/adapter@1.1.26
39
+ - @zhin.js/feature-kit@1.1.10
40
+ - @zhin.js/core@1.1.54
41
+ - @zhin.js/host-http@1.1.12
42
+ - @zhin.js/client@1.1.22
43
+ - @zhin.js/page@1.1.11
44
+ - @zhin.js/skill@1.1.11
45
+
3
46
  ## 1.1.18
4
47
 
5
48
  ### Patch Changes
package/README.md CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  Zhin.js [Milky](https://milky.ntqqrev.org/) 协议适配器(Plugin Runtime)。默认 **正向 WebSocket 客户端**(`connection: ws`);亦支持 **Webhook**、**反向 WS**(经 `httpHostToken`)与 **SSE**(HTTP GET `/event`,fetch 解析 `text/event-stream`)。
4
4
 
5
+ 账号由 `MilkyAdapter` 管理。Endpoint 直接继承实际 `MilkyV1Client`,保留原生方法与事件;框架生命周期、规范入站与发送经 `endpointSpec` 和 AdapterIndex 接入,不占用 Client 的原生 `start` / `stop` / `send` 方法。
6
+
5
7
  ## 功能特性
6
8
 
7
9
  - [Milky 协议](https://milky.ntqqrev.org/guide/communication) 兼容(事件 + HTTP API)
8
10
  - 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
9
11
  - **正向 WebSocket**(`connection: ws`):应用连协议端 `ws(s)://baseUrl/event`
10
12
  - `access_token` 鉴权(Bearer + query)
11
- - 入站经 `Endpoint.emit(...)`;出站 `send({ conversation, payload })` → HTTP `send_*_message`
13
+ - 入站经 `endpointSpec.events`;出站 `send({ conversation, payload })` → HTTP `send_*_message`
12
14
 
13
15
  ## 安装
14
16
 
@@ -19,19 +21,20 @@ pnpm add @zhin.js/adapter-milky
19
21
  ## Plugin Runtime
20
22
 
21
23
  - `@zhin.js/adapter` — 约定式 `adapters/milky/index.ts`(`defineAdapter`)
22
- - `@zhin.js/core` — `Endpoint.emit(...)` 入站、`outboundMessageToken` 出站
24
+ - `@zhin.js/core` — `endpointSpec.events` 入站、`outboundMessageToken` 出站
23
25
  - `zhin.js` — `plugin.ts`(`definePlugin`)
24
26
  - 配置经插件 `schema.json` 落到顶层 `<instanceKey>`
25
27
 
26
28
  `AdapterIndex` 会把实例默认值与 `endpoints[]` 的逐项覆盖合并;协议层只接收一个已经展开的 endpoint 配置,不再读取嵌套 endpoint 或从进程环境推断 endpoint 身份。
27
29
 
28
- 入站:`gateway.receive({ conversation: ConversationRef(kind: "private"|"group", id), message, content, sender, metadata })`
30
+ 入站:`endpoint[endpointSpec].events.emit('message.receive', { conversation: ConversationRef(kind: "private"|"group", id), message, content, sender, metadata })`
31
+
29
32
  出站:`send({ conversation, payload })` → HTTP `send_private_message` / `send_group_message`(payload 已由 gateway/core 渲染;无 segment-mapper)
30
33
 
31
- 每个 Endpoint 的 `$client` 是 `@imhelper/milky-v1` 的 `MilkyV1Client`。插件可直接调用
34
+ Endpoint 直接继承 `@imhelper/milky-v1` 的 `MilkyV1Client`;当前操作的 `$client` 就是该实际实例。插件可直接调用
32
35
  `$client.call()`、`getGroupList()` 等公开能力;`defineHandler({ adapter: 'milky', event: ... })`
33
36
  使用 imhelper 的精确事件名与 payload 类型。HTTP、纯事件 WS/WSS 和 `ingest()` 都汇入同一个
34
- Client 事件流,Endpoint 仍独占账号连接、鉴权、重连和心跳。
37
+ 原生 Client 事件流;所属 `MilkyAdapter` 管理账号与运行实例,协议连接、鉴权、重连和心跳保留在该实际 Client 中。
35
38
 
36
39
  ## 前置条件
37
40
 
@@ -95,7 +98,7 @@ milky:
95
98
  启动后打开 Console 的「Milky」页(地址 `/milky`),可以:
96
99
 
97
100
  - 查看运行中的连接、点「测试连接」做连通性诊断;
98
- - 增删 `plugins.milky.endpoints`(凭据写入 `.env`,配置里只保留 `${ENV}` 引用);
101
+ - 经所属 Adapter 增删持久化账号、启停单个 Endpoint;凭据由 Host 加密仓储保存,不改启动 YAML 或 `.env`,不重启进程;
99
102
  - 如果适配器要求登录,页面会显示 LoginAssist 的扫码 / 滑块 / 设备确认待办;
100
103
 
101
104
  读取需要 `milky:read`,写操作需要 `milky:write`;管理员 full token 不受限。页面里的「添加 endpoint」等价于原来的 `milky endpoint add`。
@@ -3,10 +3,8 @@
3
3
  * Convention entry: discover `adapters/milky/index.ts` → defineAdapter.
4
4
  */
5
5
  import { defineAdapter } from 'zhin.js/adapter';
6
- import { httpHostToken } from '@zhin.js/host-http';
7
- import { MilkySseEndpoint, MilkyWebhookEndpoint, MilkyWssEndpoint, MilkyWsEndpoint, } from "../../lib/endpoint.js";
8
- import { resolveMilkyConfig, } from "../../lib/protocol.js";
9
- import { milkyRuntimeStateToken } from "../../lib/milky-runtime-state.js";
6
+ import { MilkyAdapter } from "../../lib/adapter.js";
7
+ import {} from "../../lib/protocol.js";
10
8
  export { MilkySseEndpoint, MilkyWebhookEndpoint, MilkyWssEndpoint, MilkyWsEndpoint, } from "../../lib/endpoint.js";
11
9
  export default defineAdapter({
12
10
  capabilities: ['inbound', 'outbound'],
@@ -16,36 +14,5 @@ export default defineAdapter({
16
14
  outboundMedia: ['url', 'base64'],
17
15
  interactive: 'text',
18
16
  },
19
- create(context) {
20
- const config = resolveMilkyConfig(context.config);
21
- // 注册到插件运行时状态(milky endpoint list 的"运行中"数据源)
22
- context.use(milkyRuntimeStateToken).endpoints.set(config.id, {
23
- id: config.id,
24
- mode: config.connection,
25
- });
26
- if (config.connection === 'webhook') {
27
- return new MilkyWebhookEndpoint({
28
- id: context.id,
29
- http: context.use(httpHostToken),
30
- config,
31
- });
32
- }
33
- if (config.connection === 'wss') {
34
- return new MilkyWssEndpoint({
35
- id: context.id,
36
- http: context.use(httpHostToken),
37
- config,
38
- });
39
- }
40
- if (config.connection === 'sse') {
41
- return new MilkySseEndpoint({
42
- id: context.id,
43
- config,
44
- });
45
- }
46
- return new MilkyWsEndpoint({
47
- id: context.id,
48
- config,
49
- });
50
- },
17
+ createAdapter: context => new MilkyAdapter(context),
51
18
  });
@@ -2,18 +2,10 @@
2
2
  * Convention entry: discover `adapters/milky/index.ts` → defineAdapter.
3
3
  */
4
4
  import { defineAdapter } from 'zhin.js/adapter';
5
- import { httpHostToken } from '@zhin.js/host-http';
5
+ import { MilkyAdapter } from '../../src/adapter.js';
6
6
  import {
7
- MilkySseEndpoint,
8
- MilkyWebhookEndpoint,
9
- MilkyWssEndpoint,
10
- MilkyWsEndpoint,
11
- } from '../../src/endpoint.js';
12
- import {
13
- resolveMilkyConfig,
14
7
  type MilkyEndpointConfig,
15
8
  } from '../../src/protocol.js';
16
- import { milkyRuntimeStateToken } from '../../src/milky-runtime-state.js';
17
9
 
18
10
  export {
19
11
  MilkySseEndpoint,
@@ -39,36 +31,5 @@ export default defineAdapter<MilkyEndpointConfig>({
39
31
  outboundMedia: ['url', 'base64'],
40
32
  interactive: 'text',
41
33
  },
42
- create(context) {
43
- const config = resolveMilkyConfig(context.config);
44
- // 注册到插件运行时状态(milky endpoint list 的"运行中"数据源)
45
- context.use(milkyRuntimeStateToken).endpoints.set(config.id, {
46
- id: config.id,
47
- mode: config.connection,
48
- });
49
- if (config.connection === 'webhook') {
50
- return new MilkyWebhookEndpoint({
51
- id: context.id,
52
- http: context.use(httpHostToken),
53
- config,
54
- });
55
- }
56
- if (config.connection === 'wss') {
57
- return new MilkyWssEndpoint({
58
- id: context.id,
59
- http: context.use(httpHostToken),
60
- config,
61
- });
62
- }
63
- if (config.connection === 'sse') {
64
- return new MilkySseEndpoint({
65
- id: context.id,
66
- config,
67
- });
68
- }
69
- return new MilkyWsEndpoint({
70
- id: context.id,
71
- config,
72
- });
73
- },
34
+ createAdapter: context => new MilkyAdapter(context),
74
35
  });
@@ -5,7 +5,6 @@
5
5
  */
6
6
  import { createEndpointCommands } from 'zhin.js/adapter';
7
7
  import { defineCommand } from 'zhin.js/command';
8
- import { milkyRuntimeStateToken } from "../../../lib/milky-runtime-state.js";
9
8
  export const milkyEndpointCommands = createEndpointCommands({
10
9
  adapterKey: 'milky',
11
10
  adapterDisplayName: 'Milky',
@@ -14,6 +13,5 @@ export const milkyEndpointCommands = createEndpointCommands({
14
13
  { key: 'path', description: 'webhook / reverse-wss 路径', descriptionI18n: { en: "Webhook / reverse-wss path" } },
15
14
  { key: 'access_token', env: true, description: 'Milky access token' },
16
15
  ],
17
- running: (use) => use(milkyRuntimeStateToken).endpoints.values(),
18
16
  describeEntry: (entry) => `baseUrl: ${String(entry.baseUrl)}`,
19
17
  }, defineCommand);
@@ -4,7 +4,6 @@
4
4
  */
5
5
  import { createEndpointCommands } from 'zhin.js/adapter';
6
6
  import { defineCommand } from 'zhin.js/command';
7
- import { milkyRuntimeStateToken } from '../../../src/milky-runtime-state.js';
8
7
 
9
8
  export const milkyEndpointCommands = createEndpointCommands({
10
9
  adapterKey: 'milky',
@@ -14,6 +13,5 @@ export const milkyEndpointCommands = createEndpointCommands({
14
13
  { key: 'path', description: 'webhook / reverse-wss 路径', descriptionI18n: { en: "Webhook / reverse-wss path" } },
15
14
  { key: 'access_token', env: true, description: 'Milky access token' },
16
15
  ],
17
- running: (use) => use(milkyRuntimeStateToken).endpoints.values(),
18
16
  describeEntry: (entry) => `baseUrl: ${String(entry.baseUrl)}`,
19
17
  }, defineCommand);
@@ -0,0 +1,21 @@
1
+ import { Adapter, type AdapterContext, type EndpointCreationContext } from 'zhin.js/adapter';
2
+ import { type MilkyEndpointConfig } from './protocol.js';
3
+ import { MilkyWsEndpoint, MilkyWssEndpoint, MilkySseEndpoint, MilkyWebhookEndpoint } from './endpoint.js';
4
+ import { type MilkyClientEventMap } from './client.js';
5
+ type MilkyEndpoint = MilkyWsEndpoint | MilkyWssEndpoint | MilkySseEndpoint | MilkyWebhookEndpoint;
6
+ export declare class MilkyAdapter extends Adapter<MilkyEndpoint, MilkyEndpointConfig, MilkyClientEventMap> {
7
+ #private;
8
+ constructor(context: AdapterContext<MilkyEndpointConfig>);
9
+ get configurationIdentityFields(): readonly string[];
10
+ get configurationIdentityProbeFields(): readonly string[];
11
+ get configurationUpdateFields(): readonly string[];
12
+ protected validateConfiguration(config: MilkyEndpointConfig): void;
13
+ protected identifyConfiguration(input: MilkyEndpointConfig, signal: AbortSignal): Promise<string | undefined>;
14
+ protected createEndpoint(input: MilkyEndpointConfig, execution: EndpointCreationContext): MilkyEndpoint;
15
+ }
16
+ declare module '@zhin.js/feature-kit' {
17
+ interface AdapterRegistry {
18
+ readonly milky: MilkyAdapter;
19
+ }
20
+ }
21
+ export {};
package/lib/adapter.js ADDED
@@ -0,0 +1,36 @@
1
+ import { Adapter } from 'zhin.js/adapter';
2
+ import { httpHostToken } from '@zhin.js/host-http';
3
+ import { callApi, resolveMilkyConfig } from './protocol.js';
4
+ import { MilkyWsEndpoint, MilkyWssEndpoint, MilkySseEndpoint, MilkyWebhookEndpoint } from './endpoint.js';
5
+ import { readMilkyResponse } from './client.js';
6
+ export class MilkyAdapter extends Adapter {
7
+ #context;
8
+ constructor(context) { super(); this.#context = context; }
9
+ get configurationIdentityFields() { return ['id']; }
10
+ get configurationIdentityProbeFields() { return ['access_token', 'baseUrl']; }
11
+ get configurationUpdateFields() {
12
+ return ['connection', 'baseUrl', 'path', 'access_token', 'reconnect_interval', 'heartbeat_interval'];
13
+ }
14
+ validateConfiguration(config) { resolveMilkyConfig(config); }
15
+ async identifyConfiguration(input, signal) {
16
+ const config = resolveMilkyConfig(input);
17
+ signal.throwIfAborted();
18
+ const response = await callApi({ ...config, signal, action: 'get_login_info' });
19
+ signal.throwIfAborted();
20
+ const userId = readMilkyResponse(response, 'get_login_info')?.uin;
21
+ if (userId === 0 || userId === '0' || (typeof userId === 'string' && !userId.trim()))
22
+ return undefined;
23
+ if ((typeof userId !== 'number' && typeof userId !== 'string') || !String(userId).trim())
24
+ throw new Error('Milky login identity is unavailable');
25
+ return `qq:${userId}`;
26
+ }
27
+ createEndpoint(input, execution) {
28
+ const config = resolveMilkyConfig(input), id = execution.canonicalId, signal = execution.signal;
29
+ switch (config.connection) {
30
+ case 'wss': return new MilkyWssEndpoint({ id, config, signal, http: this.#context.use(httpHostToken) });
31
+ case 'webhook': return new MilkyWebhookEndpoint({ id, config, signal, http: this.#context.use(httpHostToken) });
32
+ case 'sse': return new MilkySseEndpoint({ id, config, signal });
33
+ case 'ws': return new MilkyWsEndpoint({ id, config, signal });
34
+ }
35
+ }
36
+ }
package/lib/client.d.ts CHANGED
@@ -1,21 +1,26 @@
1
- import { MilkyV1Client, type MilkyV1Event } from '@imhelper/milky-v1';
1
+ import { MilkyV1Client, type MilkyV1Event, type MilkyV1Response } from '@imhelper/milky-v1';
2
2
  import { type EventMap, type ImHelperEventMap } from 'imhelper';
3
- import { type ClientEventPayloads } from 'zhin.js/adapter';
3
+ import { type ClientEventPayloads, type PendingRpcRequest } from 'zhin.js/adapter';
4
4
  import type { ResolvedMilkyConfig, callApi } from './protocol.js';
5
5
  export { MilkyV1Client as MilkyClient } from '@imhelper/milky-v1';
6
6
  type MilkyClientEvents = ImHelperEventMap<string, MilkyV1Event, EventMap<string>>;
7
7
  export type MilkyClientEventMap = ClientEventPayloads<MilkyClientEvents>;
8
- /** Construct the exact public imhelper Client without handing it transport ownership. */
9
- export declare function createMilkyEndpointClient(config: ResolvedMilkyConfig, request: typeof callApi): MilkyV1Client;
10
- /** Use the Client's complete protocol response while keeping Endpoint internals data-oriented. */
11
- export declare function callMilkyClient<T = unknown>(client: MilkyV1Client, action: string, params?: Record<string, unknown>): Promise<T | undefined>;
12
- export declare function forwardMilkyClientEvents(client: MilkyV1Client, receive: (name: string, payload: unknown) => void): () => void;
13
- declare module '@zhin.js/feature-kit' {
14
- interface AdapterClientRegistry {
15
- readonly milky: {
16
- readonly client: MilkyV1Client;
17
- readonly events: MilkyClientEventMap;
18
- };
19
- }
8
+ /** One account-bound transport, shared by native SDK and semantic requests. */
9
+ export interface MilkyClientBinding {
10
+ readonly config: ResolvedMilkyConfig;
11
+ readonly request: typeof callApi;
12
+ readonly signal?: AbortSignal;
20
13
  }
21
- export declare const milkyClient: import("@zhin.js/adapter").EndpointClientToken<MilkyV1Client, ClientEventPayloads<MilkyClientEvents>>;
14
+ export type MilkyActionCaller = (request: PendingRpcRequest) => Promise<MilkyV1Response>;
15
+ export declare function createMilkyActionCaller({ config, request, signal }: MilkyClientBinding): MilkyActionCaller;
16
+ export declare function milkyClientOptions({ config, call }: {
17
+ readonly config: ResolvedMilkyConfig;
18
+ readonly call: MilkyActionCaller;
19
+ }): ConstructorParameters<typeof MilkyV1Client>[0];
20
+ /**
21
+ * Semantic ports must retain the SDK's status + retcode check when using its shared HTTP caller.
22
+ * A contradictory envelope proves neither success nor rejection.
23
+ * @see https://milky.ntqqrev.org/guide/communication
24
+ */
25
+ export declare function readMilkyResponse<T = unknown>(response: unknown, action: string): T | undefined;
26
+ export declare function forwardMilkyClientEvents(client: MilkyV1Client, receive: (name: string, payload: unknown) => void): () => void;
package/lib/client.js CHANGED
@@ -1,27 +1,35 @@
1
- import { MilkyV1Client, } from '@imhelper/milky-v1';
1
+ import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
2
  import { EventFactory, } from 'imhelper';
3
- import { defineEndpointClient, forwardEndpointClientEvents, } from 'zhin.js/adapter';
3
+ import { forwardEndpointClientEvents, } from 'zhin.js/adapter';
4
4
  export { MilkyV1Client as MilkyClient } from '@imhelper/milky-v1';
5
- /** Construct the exact public imhelper Client without handing it transport ownership. */
6
- export function createMilkyEndpointClient(config, request) {
7
- return new MilkyV1Client({
8
- baseUrl: config.baseUrl,
9
- selfId: config.id,
10
- accessToken: config.access_token,
11
- receiveMode: 'manual',
12
- call: (action, params) => request({
13
- baseUrl: config.baseUrl,
14
- access_token: config.access_token,
15
- }, action, params),
16
- });
5
+ export function createMilkyActionCaller({ config, request, signal }) {
6
+ return action => {
7
+ const operationSignal = signal && action.signal ? AbortSignal.any([signal, action.signal]) : signal ?? action.signal;
8
+ if (operationSignal?.aborted)
9
+ throw new EndpointDeliveryError('endpoint_inactive', 'Endpoint request ended before submission', 'not_sent', { cause: operationSignal.reason });
10
+ return request({ baseUrl: config.baseUrl, access_token: config.access_token, ...action, signal: operationSignal });
11
+ };
17
12
  }
18
- /** Use the Client's complete protocol response while keeping Endpoint internals data-oriented. */
19
- export async function callMilkyClient(client, action, params) {
20
- const response = await client.call(action, params);
21
- if (response.status !== 'ok') {
22
- throw new Error(`Milky API ${action}: retcode=${response.retcode}${response.message ? ` ${response.message}` : ''}`);
13
+ export function milkyClientOptions({ config, call }) {
14
+ return { baseUrl: config.baseUrl, selfId: config.id, accessToken: config.access_token, receiveMode: 'manual',
15
+ call: (action, params) => call({ action, params }),
16
+ };
17
+ }
18
+ /**
19
+ * Semantic ports must retain the SDK's status + retcode check when using its shared HTTP caller.
20
+ * A contradictory envelope proves neither success nor rejection.
21
+ * @see https://milky.ntqqrev.org/guide/communication
22
+ */
23
+ export function readMilkyResponse(response, action) {
24
+ if (response && typeof response === 'object') {
25
+ const result = response;
26
+ if (result.status === 'ok' && result.retcode === 0)
27
+ return result.data;
28
+ if (result.status === 'failed' && Number.isSafeInteger(result.retcode) && result.retcode !== 0) {
29
+ throw new EndpointDeliveryError('platform_rejected', `Milky API ${action}: retcode=${result.retcode}${typeof result.message === 'string' && result.message ? ` ${result.message}` : ''}`, 'rejected');
30
+ }
23
31
  }
24
- return response.data;
32
+ throw new EndpointDeliveryError('delivery_unconfirmed', `Milky API ${action} did not confirm the action result`, 'unknown');
25
33
  }
26
34
  const milkyClientEventNames = Object.freeze([
27
35
  ...EventFactory.getSupportedEventTypes(),
@@ -30,4 +38,3 @@ const milkyClientEventNames = Object.freeze([
30
38
  export function forwardMilkyClientEvents(client, receive) {
31
39
  return forwardEndpointClientEvents(client, milkyClientEventNames, receive);
32
40
  }
33
- export const milkyClient = defineEndpointClient('milky');
package/lib/console.d.ts CHANGED
@@ -5,4 +5,4 @@
5
5
  */
6
6
  import { type AdapterConsoleResources } from 'zhin.js/adapter';
7
7
  import type { PluginConsoleRpc } from 'zhin.js';
8
- export declare function createMilkyConsoleRpc(resources: AdapterConsoleResources, getConfig: () => unknown): PluginConsoleRpc;
8
+ export declare function createMilkyConsoleRpc(_resources: AdapterConsoleResources, getConfig: () => unknown): PluginConsoleRpc;
package/lib/console.js CHANGED
@@ -4,13 +4,10 @@
4
4
  * 页面绑定 `pages/index`,读取 / 写入分别要求 `<adapter>:read` / `<adapter>:write`。
5
5
  */
6
6
  import { createAdapterConsoleRpc } from 'zhin.js/adapter';
7
- import { milkyRuntimeStateToken } from './milky-runtime-state.js';
8
7
  import { milkyConsoleSpec } from './console-spec.js';
9
- export function createMilkyConsoleRpc(resources, getConfig) {
10
- const state = resources.use(milkyRuntimeStateToken);
8
+ export function createMilkyConsoleRpc(_resources, getConfig) {
11
9
  return createAdapterConsoleRpc({
12
10
  ...milkyConsoleSpec,
13
- running: () => state.endpoints.values(),
14
11
  getConfig,
15
- }, resources);
12
+ });
16
13
  }
@@ -9,9 +9,9 @@
9
9
  * - group → {group_id:number, name: group_name ?? name}
10
10
  * - 群成员列表保持 Milky 原生形状,仅保证数组
11
11
  */
12
- import type { EndpointManagement } from 'zhin.js/adapter';
12
+ import { type EndpointManagement, type PendingRpcRequest } from 'zhin.js/adapter';
13
13
  /** 管理面只依赖 endpoint 的 callApi(ws/wss/sse/webhook 四种传输各自实现)。 */
14
14
  export interface MilkyManagementCaller {
15
- callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
15
+ callApi(request: PendingRpcRequest): Promise<unknown>;
16
16
  }
17
17
  export declare function createMilkyEndpointManagement(endpoint: MilkyManagementCaller): EndpointManagement;
@@ -1,7 +1,7 @@
1
1
  export function createMilkyEndpointManagement(endpoint) {
2
2
  return Object.freeze({
3
- async listFriends() {
4
- const data = asRecord(await endpoint.callApi('get_friend_list'));
3
+ async listFriends(request = {}) {
4
+ const data = asRecord(await endpoint.callApi({ action: 'get_friend_list', signal: request.signal }));
5
5
  return toArray(data.friends).map((value) => {
6
6
  const friend = asRecord(value);
7
7
  return {
@@ -11,8 +11,8 @@ export function createMilkyEndpointManagement(endpoint) {
11
11
  };
12
12
  });
13
13
  },
14
- async listGroups() {
15
- const data = asRecord(await endpoint.callApi('get_group_list'));
14
+ async listGroups(request = {}) {
15
+ const data = asRecord(await endpoint.callApi({ action: 'get_group_list', signal: request.signal }));
16
16
  return toArray(data.groups).map((value) => {
17
17
  const group = asRecord(value);
18
18
  return {
@@ -21,10 +21,10 @@ export function createMilkyEndpointManagement(endpoint) {
21
21
  };
22
22
  });
23
23
  },
24
- async listGroupMembers(groupId) {
25
- const data = asRecord(await endpoint.callApi('get_group_member_list', {
26
- group_id: toNumberId(groupId, 'group_id'),
27
- }));
24
+ async listGroupMembers({ groupId, signal }) {
25
+ const data = asRecord(await endpoint.callApi({ action: 'get_group_member_list', signal, params: {
26
+ group_id: toNumberId(groupId, 'group_id'),
27
+ } }));
28
28
  return toArray(data.members);
29
29
  },
30
30
  });
package/lib/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- export { buildSendAction, buildSseConnectOptions, buildWsConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatOutboundSegments, isMentioned, isMessageReceiveEvent, milkyInboundConversation, parseMessageReceiveData, resolveMilkyConfig, senderNickname, type MilkyApiClientOptions, type MilkyApiResponse, type MilkyConfigBase, type MilkyEndpointConfig, type MilkyEvent, type MilkyIncomingMessage, type MilkyIncomingSegment, type MilkyOutgoingSegment, type MilkySseConfig, type MilkyWebhookConfig, type MilkyWireSegment, type MilkyWsConfig, type MilkyWssConfig, type ResolvedMilkyConfig, } from './protocol.js';
1
+ export { buildSendAction, buildSseConnectOptions, buildWsConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatOutboundSegments, isMentioned, isMessageReceiveEvent, milkyInboundConversation, parseMessageReceiveData, resolveMilkyConfig, senderNickname, type MilkyApiClientOptions, type MilkyApiRequest, type MilkyApiResponse, type MilkyConfigBase, type MilkyEndpointConfig, type MilkyEvent, type MilkyIncomingMessage, type MilkyIncomingSegment, type MilkyOutgoingSegment, type MilkySseConfig, type MilkyWebhookConfig, type MilkyWireSegment, type MilkyWsConfig, type MilkyWssConfig, type ResolvedMilkyConfig, } from './protocol.js';
2
2
  export { MilkySseEndpoint, MilkyWebhookEndpoint, MilkyWssEndpoint, MilkyWsEndpoint, consumeSseBuffer, openSseStream, verifyMilkyAccessToken, type CreateMilkySseStream, type MilkySseEndpointOptions, type MilkyWebhookEndpointOptions, type MilkyWssEndpointOptions, type MilkyWsCreateOptions, type MilkyWsEndpointOptions, type MilkyWsSocket, } from './endpoint.js';
3
- export { MilkyClient, milkyClient, type MilkyClientEventMap, } from './client.js';
3
+ export { MilkyClient, type MilkyClientEventMap, } from './client.js';
4
4
  export type { MilkyAdapterConfig as ImHelperMilkyAdapterConfig, MilkyActionUrlResolver, MilkyCall, MilkyMessageReceiveEvent, MilkyMessageRecallEvent, MilkyV1ClientConfig, MilkyV1Event, MilkyV1Response, } from '@imhelper/milky-v1';
5
5
  export { ProtocolError } from '@imhelper/milky-v1';
6
6
  export type { ProtocolErrorKind, ProtocolErrorOptions } from 'imhelper';
7
+ export { MilkyAdapter } from './adapter.js';
package/lib/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { buildSendAction, buildSseConnectOptions, buildWsConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatOutboundSegments, isMentioned, isMessageReceiveEvent, milkyInboundConversation, parseMessageReceiveData, resolveMilkyConfig, senderNickname, } from './protocol.js';
2
2
  export { MilkySseEndpoint, MilkyWebhookEndpoint, MilkyWssEndpoint, MilkyWsEndpoint, consumeSseBuffer, openSseStream, verifyMilkyAccessToken, } from './endpoint.js';
3
- export { MilkyClient, milkyClient, } from './client.js';
3
+ export { MilkyClient, } from './client.js';
4
4
  export { ProtocolError } from '@imhelper/milky-v1';
5
+ export { MilkyAdapter } from './adapter.js';
package/lib/protocol.d.ts CHANGED
@@ -93,13 +93,18 @@ export interface MilkyOutgoingSegment {
93
93
  export interface MilkyApiClientOptions {
94
94
  readonly baseUrl: string;
95
95
  readonly access_token?: string;
96
+ readonly signal?: AbortSignal;
96
97
  }
97
98
  export declare function resolveMilkyConfig(config: MilkyEndpointConfig): ResolvedMilkyConfig;
98
99
  /**
99
100
  * 调用协议端 API:POST {baseUrl}/api/{apiName},Body JSON,鉴权。
100
101
  * HTTP 层错误抛出;协议响应原样返回,由 Client 公开 call() 保留完整语义。
101
102
  */
102
- export declare function callApi<T = unknown>(options: MilkyApiClientOptions, apiName: string, params?: Record<string, unknown>): Promise<MilkyApiResponse<T>>;
103
+ export interface MilkyApiRequest extends MilkyApiClientOptions {
104
+ readonly action: string;
105
+ readonly params?: Record<string, unknown>;
106
+ }
107
+ export declare function callApi<T = unknown>({ action: apiName, params, ...options }: MilkyApiRequest): Promise<MilkyApiResponse<T>>;
103
108
  /** 根据 event_type 判断是否为 message_receive,并解析 data */
104
109
  export declare function parseMessageReceiveData(event: MilkyEvent): MilkyIncomingMessage | null;
105
110
  export declare function isMessageReceiveEvent(event: MilkyEvent): event is MilkyEvent & {
package/lib/protocol.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * Canonicalization is owned by gateway/core before endpoint.send.
4
4
  * Spec: https://milky.ntqqrev.org/
5
5
  */
6
- import { isMediaRef } from '@zhin.js/im-contract';
6
+ import { EndpointDeliveryError, isMediaRef } from '@zhin.js/im-contract';
7
7
  import { formatCompact, getLogger } from '@zhin.js/logger';
8
8
  const logger = getLogger('milky');
9
9
  function normalizeConnection(connection) {
@@ -75,24 +75,25 @@ function authQuery(access_token) {
75
75
  return '';
76
76
  return `access_token=${encodeURIComponent(access_token)}`;
77
77
  }
78
- /**
79
- * 调用协议端 API:POST {baseUrl}/api/{apiName},Body JSON,鉴权。
80
- * HTTP 层错误抛出;协议响应原样返回,由 Client 公开 call() 保留完整语义。
81
- */
82
- export async function callApi(options, apiName, params = {}) {
78
+ export async function callApi({ action: apiName, params = {}, ...options }) {
83
79
  const { baseUrl, access_token } = options;
84
80
  const url = new URL(`/api/${apiName}`, baseUrl.replace(/\/$/, ''));
85
81
  const q = authQuery(access_token);
86
82
  if (q)
87
83
  url.search = (url.search ? `${url.search}&` : '') + q;
88
- const res = await fetch(url.toString(), {
89
- method: 'POST',
90
- headers: {
91
- 'Content-Type': 'application/json',
92
- ...authHeaders(access_token),
93
- },
94
- body: JSON.stringify(Object.keys(params).length ? params : {}),
95
- });
84
+ if (options.signal?.aborted)
85
+ throw new EndpointDeliveryError('endpoint_inactive', 'Milky request ended before submission', 'not_sent', { cause: options.signal.reason });
86
+ const payload = JSON.stringify(Object.keys(params).length ? params : {});
87
+ let res;
88
+ try {
89
+ res = await fetch(url.toString(), {
90
+ method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeaders(access_token) },
91
+ body: payload, signal: options.signal,
92
+ });
93
+ }
94
+ catch (cause) {
95
+ throw new EndpointDeliveryError('delivery_unconfirmed', 'Milky HTTP request outcome is unknown', 'unknown', { cause });
96
+ }
96
97
  const text = await res.text();
97
98
  let body;
98
99
  try {
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Milky SSE client endpoint — GET text/event-stream on /event.
3
3
  */
4
- import { ClientEndpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
4
+ import { endpointSpec, type Endpoint, type EndpointSpecification } from 'zhin.js/adapter';
5
5
  import type { CapabilityId } from 'zhin.js';
6
- import { type MilkyClient } from './client.js';
6
+ import { MilkyClient } from './client.js';
7
7
  import { callApi, type MilkySseConfig } from './protocol.js';
8
8
  import { type SseClientHandle } from './sse-client.js';
9
9
  export type CreateMilkySseStream = (options: {
@@ -15,18 +15,13 @@ export type CreateMilkySseStream = (options: {
15
15
  }) => SseClientHandle;
16
16
  export interface MilkySseEndpointOptions {
17
17
  readonly id: CapabilityId;
18
+ readonly signal?: AbortSignal;
18
19
  readonly config: MilkySseConfig;
19
20
  readonly createSseStream?: CreateMilkySseStream;
20
21
  readonly callApi?: typeof callApi;
21
22
  }
22
- export declare class MilkySseEndpoint extends ClientEndpoint<MilkyClient> {
23
+ export declare class MilkySseEndpoint extends MilkyClient implements Endpoint {
23
24
  #private;
24
- readonly client: MilkyClient;
25
- readonly management: EndpointManagement;
26
- readonly control: EndpointControl;
25
+ readonly [endpointSpec]: EndpointSpecification;
27
26
  constructor(options: MilkySseEndpointOptions);
28
- start(): Promise<void>;
29
- stop(): Promise<void>;
30
- send({ conversation, payload }: EndpointSendRequest): Promise<string>;
31
- recallMessage(id: string): Promise<void>;
32
27
  }