@zhin.js/adapter-milky 1.1.0 → 1.1.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.
- package/CHANGELOG.md +84 -0
- package/README.md +6 -4
- package/adapters/{milky.js → milky/index.js} +6 -6
- package/adapters/{milky.ts → milky/index.ts} +9 -9
- package/commands/{endpoint/add/[id].js → milky/endpoint/add/[id]/index.js} +1 -1
- package/commands/milky/endpoint/add/[id]/index.ts +3 -0
- package/{lib/milky-endpoint-commands.js → commands/milky/endpoint/definition.js} +4 -3
- package/{src/milky-endpoint-commands.ts → commands/milky/endpoint/definition.ts} +3 -3
- package/commands/{endpoint/list.js → milky/endpoint/list/index.js} +1 -1
- package/commands/milky/endpoint/list/index.ts +3 -0
- package/commands/{endpoint/remove/[id].js → milky/endpoint/remove/[id]/index.js} +1 -1
- package/commands/milky/endpoint/remove/[id]/index.ts +3 -0
- package/lib/index.d.ts +1 -1
- package/lib/milky-runtime-state.js +1 -1
- package/lib/protocol.d.ts +6 -20
- package/lib/protocol.js +22 -25
- package/lib/wss-endpoint.js +65 -50
- package/package.json +23 -17
- package/plugin.js +1 -1
- package/schema.json +20 -1
- package/src/index.ts +0 -1
- package/src/milky-runtime-state.ts +1 -1
- package/src/protocol.ts +31 -45
- package/src/wss-endpoint.ts +60 -47
- package/agent/skills/milky.md +0 -65
- package/commands/endpoint/add/[id].ts +0 -3
- package/commands/endpoint/list.ts +0 -3
- package/commands/endpoint/remove/[id].ts +0 -3
- package/lib/milky-endpoint-commands.d.ts +0 -1
- /package/{agent/PERMITS.md → PERMITS.md} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,89 @@
|
|
|
1
1
|
# @zhin.js/adapter-milky
|
|
2
2
|
|
|
3
|
+
## 1.1.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a9e4a40: Derive Command user routes only from explicit `commands/**/*/index.ts(x)` entry paths instead of prepending the plugin owner. Child plugins can now publish top-level static or dynamic commands, while duplicate routes across owners fail during generation construction. Adapter endpoint commands move to explicit paths such as `commands/qq/endpoint/list/index.ts` and are invoked as `qq endpoint list`.
|
|
8
|
+
- 8740059: Standardize TypeScript capabilities on named module directories such as `commands/foo/index.ts`, `middlewares/audit/index.ts`, `handlers/message-receive/index.ts`, `pages/workroom/index.tsx`, and `mcps/filesystem/index.ts`. Only the fixed `index` entry is discovered; sibling files remain private helpers.
|
|
9
|
+
|
|
10
|
+
Command route segments come from directories, while `[name]`, `[[name]]`, `[...name]`, and `[[...name]]` directories declare dynamic parameters. Plugin owners do not enter the route unless their config explicitly sets `commandNamespace`; Endpoint `commandPrefix` remains platform-owned and defaults to an empty string.
|
|
11
|
+
|
|
12
|
+
Migrate the built-in adapters, plugins, examples, generators, migration tooling, hot reload classification, Agent authoring surfaces, documentation, and release artifacts to the explicit entry convention.
|
|
13
|
+
|
|
14
|
+
Make Tool ownership and progressive disclosure explicit across all four supported locations: plugin-public `tools/`, Agent-private `agents/<name>/tools/`, Skill-private `skills/<name>/tools/`, and Agent-Skill-private `agents/<name>/skills/<name>/tools/`. Move adapter and group-suite operations that require domain instructions into their owning Skills so `load_skill` is the only path that unlocks their schemas.
|
|
15
|
+
|
|
16
|
+
Remove the package-root `agent/` convention. Public capabilities now use named package-root directories, schedules use `schedules/<name>/index.ts` or `plugin.ts` injection, MCP connections use `mcps/<name>/index.ts`, Prompt Sections use `prompt-sections/<name>/index.ts`, Agent definitions use `agents/<name>/`, and permission vocabulary is published as `PERMITS.md`.
|
|
17
|
+
|
|
18
|
+
- 25a845a: Unify Skills on `skills/<name>/SKILL.md`, support Agent-private Skills and nested Skill-private Tools, publish and mount existing plugin Skills, add governed Skill metadata and Turn access filtering, unlock only already-admitted same-owner Tools, and make `on-risk` and `once` approval behavior precise.
|
|
19
|
+
- 4380cf9: Colocate adapter command definitions and Agent Skill handlers with their owning capabilities, and provide a shared validated platform permission constructor.
|
|
20
|
+
- 86087cf: Require one canonical expanded Milky endpoint configuration. The protocol no longer reinterprets nested `endpoints` or reads endpoint identity from `process.env`, and reverse WebSocket connection replacement, heartbeat, and cleanup now use the shared endpoint lifecycle.
|
|
21
|
+
- 5c3858e: Make `@zhin.js/im-contract` the explicit zero-dependency owner of transport identities, canonical segments, media guards, conversation facts, stores, and delivery contracts. Platform adapters now read media contracts from that foundation instead of reaching through Core.
|
|
22
|
+
- Updated dependencies [c861789]
|
|
23
|
+
- Updated dependencies [1414ccb]
|
|
24
|
+
- Updated dependencies [743d470]
|
|
25
|
+
- Updated dependencies [62dee52]
|
|
26
|
+
- Updated dependencies [cd54131]
|
|
27
|
+
- Updated dependencies [244e475]
|
|
28
|
+
- Updated dependencies [a9e4a40]
|
|
29
|
+
- Updated dependencies [5855db7]
|
|
30
|
+
- Updated dependencies [ec921d2]
|
|
31
|
+
- Updated dependencies [8740059]
|
|
32
|
+
- Updated dependencies [25a845a]
|
|
33
|
+
- Updated dependencies [103f2c3]
|
|
34
|
+
- Updated dependencies [9110ab8]
|
|
35
|
+
- Updated dependencies [cd2a888]
|
|
36
|
+
- Updated dependencies [eb3227a]
|
|
37
|
+
- Updated dependencies [b853dba]
|
|
38
|
+
- Updated dependencies [e561309]
|
|
39
|
+
- Updated dependencies [d4c6175]
|
|
40
|
+
- Updated dependencies [7a0e1ca]
|
|
41
|
+
- Updated dependencies [103b5d3]
|
|
42
|
+
- Updated dependencies [5a7a7f7]
|
|
43
|
+
- Updated dependencies [b076eae]
|
|
44
|
+
- Updated dependencies [73a24b7]
|
|
45
|
+
- Updated dependencies [2dbbc15]
|
|
46
|
+
- Updated dependencies [1cb1163]
|
|
47
|
+
- Updated dependencies [be3061e]
|
|
48
|
+
- Updated dependencies [75f8332]
|
|
49
|
+
- Updated dependencies [81935e2]
|
|
50
|
+
- Updated dependencies [f9ed01b]
|
|
51
|
+
- Updated dependencies [ac0ab50]
|
|
52
|
+
- Updated dependencies [5140ce1]
|
|
53
|
+
- Updated dependencies [522d75f]
|
|
54
|
+
- Updated dependencies [a7611b3]
|
|
55
|
+
- Updated dependencies [8823044]
|
|
56
|
+
- Updated dependencies [379439b]
|
|
57
|
+
- Updated dependencies [11c9352]
|
|
58
|
+
- Updated dependencies [6b70e46]
|
|
59
|
+
- Updated dependencies [135ac91]
|
|
60
|
+
- Updated dependencies [140cf0f]
|
|
61
|
+
- Updated dependencies [251e4d2]
|
|
62
|
+
- Updated dependencies [203ad34]
|
|
63
|
+
- Updated dependencies [e6c5113]
|
|
64
|
+
- Updated dependencies [e0f6478]
|
|
65
|
+
- Updated dependencies [baef56b]
|
|
66
|
+
- Updated dependencies [da0a8e3]
|
|
67
|
+
- Updated dependencies [2fd8017]
|
|
68
|
+
- Updated dependencies [5c3858e]
|
|
69
|
+
- Updated dependencies [507d602]
|
|
70
|
+
- Updated dependencies [df9f76b]
|
|
71
|
+
- @zhin.js/core@1.1.37
|
|
72
|
+
- @zhin.js/host-http@1.1.1
|
|
73
|
+
- @zhin.js/adapter@1.1.14
|
|
74
|
+
- @zhin.js/feature-kit@1.1.1
|
|
75
|
+
- @zhin.js/skill@1.1.1
|
|
76
|
+
- @zhin.js/logger@1.1.1
|
|
77
|
+
- @zhin.js/im-contract@1.1.1
|
|
78
|
+
|
|
79
|
+
## 1.1.1
|
|
80
|
+
|
|
81
|
+
### Patch Changes
|
|
82
|
+
|
|
83
|
+
- Updated dependencies [769e67f]
|
|
84
|
+
- @zhin.js/adapter@1.1.13
|
|
85
|
+
- @zhin.js/core@1.1.36
|
|
86
|
+
|
|
3
87
|
## 1.1.0
|
|
4
88
|
|
|
5
89
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -18,11 +18,13 @@ pnpm add @zhin.js/adapter-milky
|
|
|
18
18
|
|
|
19
19
|
## Plugin Runtime
|
|
20
20
|
|
|
21
|
-
- `@zhin.js/adapter` — 约定式 `adapters/milky.ts`(`defineAdapter`)
|
|
21
|
+
- `@zhin.js/adapter` — 约定式 `adapters/milky/index.ts`(`defineAdapter`)
|
|
22
22
|
- `@zhin.js/core` — `Endpoint.emit(...)` 入站、`outboundMessageToken` 出站
|
|
23
23
|
- `zhin.js` — `plugin.ts`(`definePlugin`)
|
|
24
24
|
- 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
|
|
25
25
|
|
|
26
|
+
`AdapterIndex` 会把实例默认值与 `endpoints[]` 的逐项覆盖合并;协议层只接收一个已经展开的 endpoint 配置,不再读取嵌套 endpoint 或从进程环境推断 endpoint 身份。
|
|
27
|
+
|
|
26
28
|
入站:`gateway.receive({ conversation: ConversationRef(kind: "private"|"group", id), message, content, sender, metadata })`
|
|
27
29
|
出站:`send({ conversation, payload })` → HTTP `send_private_message` / `send_group_message`(payload 已由 gateway/core 渲染;无 segment-mapper)
|
|
28
30
|
|
|
@@ -47,7 +49,7 @@ plugins:
|
|
|
47
49
|
reconnect_interval: 5000
|
|
48
50
|
heartbeat_interval: 30000
|
|
49
51
|
endpoints:
|
|
50
|
-
-
|
|
52
|
+
- id: milky-bot
|
|
51
53
|
baseUrl: "http://127.0.0.1:8080"
|
|
52
54
|
access_token: "${MILKY_ACCESS_TOKEN}"
|
|
53
55
|
```
|
|
@@ -77,8 +79,8 @@ plugins:
|
|
|
77
79
|
|
|
78
80
|
| 类别 | 路径 |
|
|
79
81
|
|------|------|
|
|
80
|
-
| Permit 词汇 | `
|
|
81
|
-
| 技能说明 | `
|
|
82
|
+
| Permit 词汇 | `PERMITS.md` |
|
|
83
|
+
| 技能说明 | `skills/milky/SKILL.md` |
|
|
82
84
|
|
|
83
85
|
## 文档链接
|
|
84
86
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
/**
|
|
3
|
-
* Convention entry: discover `adapters/milky.ts` → defineAdapter.
|
|
3
|
+
* Convention entry: discover `adapters/milky/index.ts` → defineAdapter.
|
|
4
4
|
*/
|
|
5
5
|
import { defineAdapter } from 'zhin.js/adapter';
|
|
6
6
|
import { httpHostToken } from '@zhin.js/host-http';
|
|
7
|
-
import { MilkySseEndpoint, MilkyWebhookEndpoint, MilkyWssEndpoint, MilkyWsEndpoint, } from "
|
|
8
|
-
import { resolveMilkyConfig, } from "
|
|
9
|
-
import { milkyRuntimeStateToken } from "
|
|
10
|
-
export { MilkySseEndpoint, MilkyWebhookEndpoint, MilkyWssEndpoint, MilkyWsEndpoint, } from "
|
|
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";
|
|
10
|
+
export { MilkySseEndpoint, MilkyWebhookEndpoint, MilkyWssEndpoint, MilkyWsEndpoint, } from "../../lib/endpoint.js";
|
|
11
11
|
export default defineAdapter({
|
|
12
12
|
capabilities: ['inbound', 'outbound'],
|
|
13
13
|
operations: ['recall'],
|
|
@@ -18,7 +18,7 @@ export default defineAdapter({
|
|
|
18
18
|
},
|
|
19
19
|
create(context) {
|
|
20
20
|
const config = resolveMilkyConfig(context.config);
|
|
21
|
-
// 注册到插件运行时状态(milky
|
|
21
|
+
// 注册到插件运行时状态(milky endpoint list 的"运行中"数据源)
|
|
22
22
|
context.use(milkyRuntimeStateToken).endpoints.set(config.id, {
|
|
23
23
|
id: config.id,
|
|
24
24
|
mode: config.connection,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Convention entry: discover `adapters/milky.ts` → defineAdapter.
|
|
2
|
+
* Convention entry: discover `adapters/milky/index.ts` → defineAdapter.
|
|
3
3
|
*/
|
|
4
4
|
import { defineAdapter } from 'zhin.js/adapter';
|
|
5
5
|
import { httpHostToken } from '@zhin.js/host-http';
|
|
@@ -8,19 +8,19 @@ import {
|
|
|
8
8
|
MilkyWebhookEndpoint,
|
|
9
9
|
MilkyWssEndpoint,
|
|
10
10
|
MilkyWsEndpoint,
|
|
11
|
-
} from '
|
|
11
|
+
} from '../../src/endpoint.js';
|
|
12
12
|
import {
|
|
13
13
|
resolveMilkyConfig,
|
|
14
|
-
type
|
|
15
|
-
} from '
|
|
16
|
-
import { milkyRuntimeStateToken } from '
|
|
14
|
+
type MilkyEndpointConfig,
|
|
15
|
+
} from '../../src/protocol.js';
|
|
16
|
+
import { milkyRuntimeStateToken } from '../../src/milky-runtime-state.js';
|
|
17
17
|
|
|
18
18
|
export {
|
|
19
19
|
MilkySseEndpoint,
|
|
20
20
|
MilkyWebhookEndpoint,
|
|
21
21
|
MilkyWssEndpoint,
|
|
22
22
|
MilkyWsEndpoint,
|
|
23
|
-
} from '
|
|
23
|
+
} from '../../src/endpoint.js';
|
|
24
24
|
export type {
|
|
25
25
|
CreateMilkySseStream,
|
|
26
26
|
MilkySseEndpointOptions,
|
|
@@ -29,9 +29,9 @@ export type {
|
|
|
29
29
|
MilkyWsEndpointOptions,
|
|
30
30
|
MilkyWsCreateOptions,
|
|
31
31
|
MilkyWsSocket,
|
|
32
|
-
} from '
|
|
32
|
+
} from '../../src/endpoint.js';
|
|
33
33
|
|
|
34
|
-
export default defineAdapter<
|
|
34
|
+
export default defineAdapter<MilkyEndpointConfig>({
|
|
35
35
|
capabilities: ['inbound', 'outbound'],
|
|
36
36
|
operations: ['recall'],
|
|
37
37
|
// Milky 协议资源 uri 消费 http(s):// 与 base64:// 形式;无卡片交互面,交互段降级纯文本。
|
|
@@ -41,7 +41,7 @@ export default defineAdapter<MilkyAdapterConfig>({
|
|
|
41
41
|
},
|
|
42
42
|
create(context) {
|
|
43
43
|
const config = resolveMilkyConfig(context.config);
|
|
44
|
-
// 注册到插件运行时状态(milky
|
|
44
|
+
// 注册到插件运行时状态(milky endpoint list 的"运行中"数据源)
|
|
45
45
|
context.use(milkyRuntimeStateToken).endpoints.set(config.id, {
|
|
46
46
|
id: config.id,
|
|
47
47
|
mode: config.connection,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
1
2
|
/**
|
|
2
|
-
* `milky
|
|
3
|
-
* commands/endpoint/ 下的 list / add / remove 直接默认导出这三项。
|
|
3
|
+
* `milky endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
|
|
4
|
+
* commands/milky/endpoint/ 下的 list / add / remove 直接默认导出这三项。
|
|
4
5
|
*/
|
|
5
6
|
import { createEndpointCommands } from 'zhin.js/adapter';
|
|
6
7
|
import { defineCommand } from 'zhin.js/command';
|
|
7
|
-
import { milkyRuntimeStateToken } from
|
|
8
|
+
import { milkyRuntimeStateToken } from "../../../lib/milky-runtime-state.js";
|
|
8
9
|
export const milkyEndpointCommands = createEndpointCommands({
|
|
9
10
|
adapterKey: 'milky',
|
|
10
11
|
adapterDisplayName: 'Milky',
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `milky
|
|
3
|
-
* commands/endpoint/ 下的 list / add / remove 直接默认导出这三项。
|
|
2
|
+
* `milky endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
|
|
3
|
+
* commands/milky/endpoint/ 下的 list / add / remove 直接默认导出这三项。
|
|
4
4
|
*/
|
|
5
5
|
import { createEndpointCommands } from 'zhin.js/adapter';
|
|
6
6
|
import { defineCommand } from 'zhin.js/command';
|
|
7
|
-
import { milkyRuntimeStateToken } from '
|
|
7
|
+
import { milkyRuntimeStateToken } from '../../../src/milky-runtime-state.js';
|
|
8
8
|
|
|
9
9
|
export const milkyEndpointCommands = createEndpointCommands({
|
|
10
10
|
adapterKey: 'milky',
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { buildSendAction, buildSseConnectOptions, buildWsConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatOutboundSegments, isMentioned, isMessageReceiveEvent, milkyInboundConversation, parseMessageReceiveData, resolveMilkyConfig, senderNickname, type
|
|
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';
|
|
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
3
|
export { MilkyClient, milkyClient, type MilkyClientEventMap, } from './client.js';
|
|
4
4
|
export type { MilkyAdapterConfig as ImHelperMilkyAdapterConfig, MilkyActionUrlResolver, MilkyCall, MilkyMessageReceiveEvent, MilkyMessageRecallEvent, MilkyV1ClientConfig, MilkyV1Event, MilkyV1Response, } from '@imhelper/milky-v1';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Milky 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
|
-
* 由 plugin.ts setup() provide,adapter create 与 `milky
|
|
3
|
+
* 由 plugin.ts setup() provide,adapter create 与 `milky endpoint` 命令共享(同一 owner generation)。
|
|
4
4
|
*/
|
|
5
5
|
import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
|
|
6
6
|
export const milkyRuntimeStateToken = defineEndpointRuntimeStateToken('milky');
|
package/lib/protocol.d.ts
CHANGED
|
@@ -3,30 +3,17 @@
|
|
|
3
3
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
4
|
* Spec: https://milky.ntqqrev.org/
|
|
5
5
|
*/
|
|
6
|
+
import { type ConversationRef } from '@zhin.js/im-contract';
|
|
6
7
|
import type { Segment } from '@zhin.js/core/runtime';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export interface MilkyLegacyEndpointRow {
|
|
10
|
-
readonly context?: string;
|
|
8
|
+
/** One endpoint config after AdapterIndex expands `plugins.<instanceKey>.endpoints`. */
|
|
9
|
+
export interface MilkyEndpointConfig {
|
|
11
10
|
readonly connection?: 'ws' | 'sse' | 'webhook' | 'wss';
|
|
12
|
-
readonly id
|
|
13
|
-
readonly baseUrl
|
|
14
|
-
readonly access_token?: string;
|
|
15
|
-
readonly path?: string;
|
|
16
|
-
readonly reconnect_interval?: number;
|
|
17
|
-
readonly heartbeat_interval?: number;
|
|
18
|
-
}
|
|
19
|
-
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
20
|
-
export interface MilkyAdapterConfig {
|
|
21
|
-
readonly connection?: 'ws' | 'sse' | 'webhook' | 'wss';
|
|
22
|
-
readonly id?: string;
|
|
23
|
-
readonly baseUrl?: string;
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly baseUrl: string;
|
|
24
13
|
readonly access_token?: string;
|
|
25
14
|
readonly path?: string;
|
|
26
15
|
readonly reconnect_interval?: number;
|
|
27
16
|
readonly heartbeat_interval?: number;
|
|
28
|
-
/** Transitional: legacy root `endpoints[]` with `context: milky`. */
|
|
29
|
-
readonly endpoints?: ReadonlyArray<MilkyLegacyEndpointRow>;
|
|
30
17
|
}
|
|
31
18
|
export interface MilkyConfigBase {
|
|
32
19
|
readonly context: 'milky';
|
|
@@ -57,7 +44,6 @@ export interface MilkyWssConfig extends MilkyConfigBase {
|
|
|
57
44
|
readonly heartbeat_interval: number;
|
|
58
45
|
}
|
|
59
46
|
export type ResolvedMilkyConfig = MilkyWsConfig | MilkySseConfig | MilkyWebhookConfig | MilkyWssConfig;
|
|
60
|
-
export type MilkyEndpointConfig = ResolvedMilkyConfig;
|
|
61
47
|
export interface MilkyApiResponse<T = unknown> {
|
|
62
48
|
status: 'ok' | 'failed';
|
|
63
49
|
retcode: number;
|
|
@@ -108,7 +94,7 @@ export interface MilkyApiClientOptions {
|
|
|
108
94
|
readonly baseUrl: string;
|
|
109
95
|
readonly access_token?: string;
|
|
110
96
|
}
|
|
111
|
-
export declare function resolveMilkyConfig(config
|
|
97
|
+
export declare function resolveMilkyConfig(config: MilkyEndpointConfig): ResolvedMilkyConfig;
|
|
112
98
|
/**
|
|
113
99
|
* 调用协议端 API:POST {baseUrl}/api/{apiName},Body JSON,鉴权。
|
|
114
100
|
* HTTP 层错误抛出;协议响应原样返回,由 Client 公开 call() 保留完整语义。
|
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/
|
|
6
|
+
import { 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) {
|
|
@@ -11,18 +11,11 @@ function normalizeConnection(connection) {
|
|
|
11
11
|
return connection;
|
|
12
12
|
return 'ws';
|
|
13
13
|
}
|
|
14
|
-
export function resolveMilkyConfig(config
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|| process.env.MILKY_BOT_NAME
|
|
20
|
-
|| 'milky-bot';
|
|
21
|
-
const baseUrl = config.baseUrl ?? entry?.baseUrl;
|
|
22
|
-
if (!baseUrl) {
|
|
23
|
-
throw new TypeError('Milky requires baseUrl (plugins.<key>.baseUrl or endpoints with context: milky)');
|
|
24
|
-
}
|
|
25
|
-
const access_token = config.access_token ?? entry?.access_token;
|
|
14
|
+
export function resolveMilkyConfig(config) {
|
|
15
|
+
const connection = normalizeConnection(config.connection);
|
|
16
|
+
const id = requiredEndpointField(config.id, 'id');
|
|
17
|
+
const baseUrl = requiredEndpointField(config.baseUrl, 'baseUrl');
|
|
18
|
+
const access_token = optionalEndpointField(config.access_token);
|
|
26
19
|
const base = {
|
|
27
20
|
context: 'milky',
|
|
28
21
|
id,
|
|
@@ -33,38 +26,42 @@ export function resolveMilkyConfig(config = {}) {
|
|
|
33
26
|
return {
|
|
34
27
|
...base,
|
|
35
28
|
connection: 'ws',
|
|
36
|
-
reconnect_interval: config.reconnect_interval ??
|
|
37
|
-
heartbeat_interval: config.heartbeat_interval ??
|
|
29
|
+
reconnect_interval: config.reconnect_interval ?? 5000,
|
|
30
|
+
heartbeat_interval: config.heartbeat_interval ?? 30_000,
|
|
38
31
|
};
|
|
39
32
|
}
|
|
40
33
|
if (connection === 'sse') {
|
|
41
34
|
return {
|
|
42
35
|
...base,
|
|
43
36
|
connection: 'sse',
|
|
44
|
-
reconnect_interval: config.reconnect_interval
|
|
45
|
-
?? entry?.reconnect_interval
|
|
46
|
-
?? 5_000,
|
|
37
|
+
reconnect_interval: config.reconnect_interval ?? 5_000,
|
|
47
38
|
};
|
|
48
39
|
}
|
|
49
40
|
if (connection === 'webhook') {
|
|
50
|
-
const path = config.path
|
|
51
|
-
if (!path)
|
|
52
|
-
throw new TypeError('Milky connection:webhook requires path');
|
|
41
|
+
const path = requiredEndpointField(config.path, 'path');
|
|
53
42
|
return { ...base, connection: 'webhook', path };
|
|
54
43
|
}
|
|
55
44
|
if (connection === 'wss') {
|
|
56
|
-
const path = config.path
|
|
57
|
-
if (!path)
|
|
58
|
-
throw new TypeError('Milky connection:wss requires path');
|
|
45
|
+
const path = requiredEndpointField(config.path, 'path');
|
|
59
46
|
return {
|
|
60
47
|
...base,
|
|
61
48
|
connection: 'wss',
|
|
62
49
|
path,
|
|
63
|
-
heartbeat_interval: config.heartbeat_interval ??
|
|
50
|
+
heartbeat_interval: config.heartbeat_interval ?? 30_000,
|
|
64
51
|
};
|
|
65
52
|
}
|
|
66
53
|
throw new TypeError(`Unknown Milky connection: ${String(connection)}`);
|
|
67
54
|
}
|
|
55
|
+
function requiredEndpointField(value, field) {
|
|
56
|
+
const resolved = optionalEndpointField(value);
|
|
57
|
+
if (!resolved) {
|
|
58
|
+
throw new TypeError(`Milky endpoint requires a non-empty ${field}`);
|
|
59
|
+
}
|
|
60
|
+
return resolved;
|
|
61
|
+
}
|
|
62
|
+
function optionalEndpointField(value) {
|
|
63
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
64
|
+
}
|
|
68
65
|
/** 鉴权:Header Authorization: Bearer {token} 或 URL query access_token=xxx */
|
|
69
66
|
function authHeaders(access_token) {
|
|
70
67
|
const headers = {};
|
package/lib/wss-endpoint.js
CHANGED
|
@@ -18,16 +18,17 @@ export class MilkyWssEndpoint extends ClientEndpoint {
|
|
|
18
18
|
#ws;
|
|
19
19
|
#wsRelease;
|
|
20
20
|
#clientSocketRelease;
|
|
21
|
-
#
|
|
21
|
+
#connectionLifecycle;
|
|
22
|
+
#connectionTask = Promise.resolve();
|
|
23
|
+
#started = false;
|
|
22
24
|
constructor(options) {
|
|
23
25
|
super();
|
|
24
26
|
this.#logger = getAdapterLogger('milky', options.config.id);
|
|
25
27
|
this.#options = options;
|
|
26
28
|
this.#callApi = options.callApi ?? callApi;
|
|
27
|
-
this.#
|
|
28
|
-
name: options.config.id
|
|
29
|
+
this.#connectionLifecycle = createEndpointLifecycle({
|
|
30
|
+
name: `${options.config.id}:inbound`,
|
|
29
31
|
reconnect: false,
|
|
30
|
-
heartbeat: { intervalMs: options.config.heartbeat_interval },
|
|
31
32
|
});
|
|
32
33
|
this.client = createMilkyEndpointClient(options.config, this.#callApi);
|
|
33
34
|
this.management = createMilkyEndpointManagement({
|
|
@@ -39,23 +40,19 @@ export class MilkyWssEndpoint extends ClientEndpoint {
|
|
|
39
40
|
}, (_name, error) => this.#warnPlatformEvent(error));
|
|
40
41
|
}
|
|
41
42
|
async start() {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
this.#
|
|
49
|
-
|
|
50
|
-
this.#
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
/* ignore */
|
|
57
|
-
}
|
|
58
|
-
this.#ws = undefined;
|
|
43
|
+
if (this.#started)
|
|
44
|
+
return;
|
|
45
|
+
this.#started = true;
|
|
46
|
+
const handle = this.#options.http.ws(this.#options.config.path);
|
|
47
|
+
this.#wsRelease = handle.onConnection((connection) => {
|
|
48
|
+
this.#connectionTask = this.#connectionTask
|
|
49
|
+
.then(() => this.#acceptConnection(connection))
|
|
50
|
+
.catch((error) => {
|
|
51
|
+
this.#logger.warn(formatCompact({
|
|
52
|
+
op: 'wss_connection_failed',
|
|
53
|
+
endpoint: this.#options.config.id,
|
|
54
|
+
error: error instanceof Error ? error.message : String(error),
|
|
55
|
+
}));
|
|
59
56
|
});
|
|
60
57
|
});
|
|
61
58
|
this.#logger.info(formatCompact({
|
|
@@ -67,7 +64,12 @@ export class MilkyWssEndpoint extends ClientEndpoint {
|
|
|
67
64
|
}
|
|
68
65
|
async stop() {
|
|
69
66
|
this.close();
|
|
70
|
-
|
|
67
|
+
this.#wsRelease?.();
|
|
68
|
+
this.#wsRelease = undefined;
|
|
69
|
+
await this.#connectionTask;
|
|
70
|
+
await this.#connectionLifecycle.stop();
|
|
71
|
+
this.#ws = undefined;
|
|
72
|
+
this.#started = false;
|
|
71
73
|
}
|
|
72
74
|
async send({ conversation, payload }) {
|
|
73
75
|
const message = formatOutboundSegments(payload);
|
|
@@ -141,39 +143,52 @@ export class MilkyWssEndpoint extends ClientEndpoint {
|
|
|
141
143
|
}));
|
|
142
144
|
});
|
|
143
145
|
}
|
|
144
|
-
#acceptConnection(connection) {
|
|
146
|
+
async #acceptConnection(connection) {
|
|
145
147
|
if (!verifyMilkyAccessToken(this.#options.config.access_token, connection.request)) {
|
|
146
148
|
connection.socket.close(4003, 'Unauthorized');
|
|
147
149
|
return;
|
|
148
150
|
}
|
|
149
151
|
const socket = connection.socket;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
152
|
+
await this.#connectionLifecycle.stop();
|
|
153
|
+
await this.#connectionLifecycle.start(async (lifecycleHandle) => {
|
|
154
|
+
this.#ws = socket;
|
|
155
|
+
const releaseClientSocket = this.client.acceptWebSocket(socket);
|
|
156
|
+
this.#clientSocketRelease = releaseClientSocket;
|
|
157
|
+
lifecycleHandle.onForceClose(() => {
|
|
158
|
+
if (this.#clientSocketRelease === releaseClientSocket) {
|
|
159
|
+
releaseClientSocket();
|
|
160
|
+
this.#clientSocketRelease = undefined;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
socket.close();
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
/* ignore */
|
|
167
|
+
}
|
|
168
|
+
if (this.#ws === socket)
|
|
169
|
+
this.#ws = undefined;
|
|
170
|
+
});
|
|
171
|
+
this.#connectionLifecycle.startHeartbeat(() => {
|
|
172
|
+
try {
|
|
173
|
+
if (socket.readyState === WS_OPEN)
|
|
174
|
+
socket.ping?.();
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
/* ignore */
|
|
178
|
+
}
|
|
179
|
+
}, this.#options.config.heartbeat_interval);
|
|
180
|
+
socket.on('message', () => {
|
|
181
|
+
this.#connectionLifecycle.notifyHeartbeatAck();
|
|
182
|
+
});
|
|
183
|
+
socket.on('close', () => {
|
|
184
|
+
if (this.#clientSocketRelease === releaseClientSocket) {
|
|
185
|
+
releaseClientSocket();
|
|
186
|
+
this.#clientSocketRelease = undefined;
|
|
187
|
+
}
|
|
188
|
+
if (this.#ws === socket)
|
|
189
|
+
this.#ws = undefined;
|
|
190
|
+
lifecycleHandle.notifyClosed(new Error('Milky reverse WebSocket closed'));
|
|
191
|
+
});
|
|
177
192
|
});
|
|
178
193
|
this.#logger.debug(formatCompact({
|
|
179
194
|
endpoint: this.#options.config.id,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-milky",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Zhin.js Milky adapter for Plugin Runtime (WebSocket client)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
"schema.json",
|
|
20
20
|
"src",
|
|
21
21
|
"lib",
|
|
22
|
-
"
|
|
22
|
+
"skills",
|
|
23
23
|
"README.md",
|
|
24
|
-
"CHANGELOG.md"
|
|
24
|
+
"CHANGELOG.md",
|
|
25
|
+
"PERMITS.md"
|
|
25
26
|
],
|
|
26
27
|
"keywords": [
|
|
27
28
|
"zhin",
|
|
@@ -42,12 +43,13 @@
|
|
|
42
43
|
"@imhelper/milky-v1": "1.0.7",
|
|
43
44
|
"imhelper": "1.0.7",
|
|
44
45
|
"ws": "^8.21.1",
|
|
45
|
-
"@zhin.js/adapter": "1.1.
|
|
46
|
-
"@zhin.js/core": "1.1.
|
|
47
|
-
"@zhin.js/feature-kit": "1.1.
|
|
48
|
-
"@zhin.js/host-http": "1.1.
|
|
49
|
-
"@zhin.js/im-contract": "1.1.
|
|
50
|
-
"@zhin.js/logger": "1.1.
|
|
46
|
+
"@zhin.js/adapter": "1.1.14",
|
|
47
|
+
"@zhin.js/core": "1.1.37",
|
|
48
|
+
"@zhin.js/feature-kit": "1.1.1",
|
|
49
|
+
"@zhin.js/host-http": "1.1.1",
|
|
50
|
+
"@zhin.js/im-contract": "1.1.1",
|
|
51
|
+
"@zhin.js/logger": "1.1.1",
|
|
52
|
+
"@zhin.js/skill": "1.1.1"
|
|
51
53
|
},
|
|
52
54
|
"devDependencies": {
|
|
53
55
|
"@types/node": "^26.1.2",
|
|
@@ -55,17 +57,17 @@
|
|
|
55
57
|
"typescript": "^6.0.3",
|
|
56
58
|
"vitest": "^4.1.10",
|
|
57
59
|
"zod": "^4.4.3",
|
|
58
|
-
"@zhin.js/agent": "1.1.
|
|
59
|
-
"@zhin.js/host-http": "1.1.
|
|
60
|
-
"zhin.js": "1.1.
|
|
60
|
+
"@zhin.js/agent": "1.1.26",
|
|
61
|
+
"@zhin.js/host-http": "1.1.1",
|
|
62
|
+
"zhin.js": "1.1.2"
|
|
61
63
|
},
|
|
62
64
|
"peerDependencies": {
|
|
63
65
|
"zod": "^4.0.0",
|
|
64
|
-
"@zhin.js/adapter": "^1.1.
|
|
65
|
-
"@zhin.js/agent": "^1.1.
|
|
66
|
-
"@zhin.js/command": "^1.1.
|
|
67
|
-
"@zhin.js/core": "^1.1.
|
|
68
|
-
"zhin.js": "^1.1.
|
|
66
|
+
"@zhin.js/adapter": "^1.1.14",
|
|
67
|
+
"@zhin.js/agent": "^1.1.26",
|
|
68
|
+
"@zhin.js/command": "^1.1.1",
|
|
69
|
+
"@zhin.js/core": "^1.1.37",
|
|
70
|
+
"zhin.js": "^1.1.2"
|
|
69
71
|
},
|
|
70
72
|
"peerDependenciesMeta": {
|
|
71
73
|
"@zhin.js/agent": {
|
|
@@ -107,6 +109,10 @@
|
|
|
107
109
|
{
|
|
108
110
|
"package": "@zhin.js/command",
|
|
109
111
|
"api": "^1.0.0"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"package": "@zhin.js/skill",
|
|
115
|
+
"api": "^1.0.0"
|
|
110
116
|
}
|
|
111
117
|
],
|
|
112
118
|
"plugins": []
|
package/plugin.js
CHANGED
|
@@ -8,7 +8,7 @@ export default definePlugin({
|
|
|
8
8
|
displayName: 'Milky Adapter',
|
|
9
9
|
},
|
|
10
10
|
setup(context) {
|
|
11
|
-
// 运行中 endpoint 注册表(milky
|
|
11
|
+
// 运行中 endpoint 注册表(milky endpoint list 的"运行中"数据源)
|
|
12
12
|
context.resources.provide(milkyRuntimeStateToken, createEndpointRuntimeState());
|
|
13
13
|
},
|
|
14
14
|
});
|
package/schema.json
CHANGED
|
@@ -42,10 +42,11 @@
|
|
|
42
42
|
},
|
|
43
43
|
"endpoints": {
|
|
44
44
|
"type": "array",
|
|
45
|
+
"minItems": 1,
|
|
45
46
|
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
|
|
46
47
|
"items": {
|
|
47
48
|
"type": "object",
|
|
48
|
-
"additionalProperties":
|
|
49
|
+
"additionalProperties": false,
|
|
49
50
|
"properties": {
|
|
50
51
|
"master": {
|
|
51
52
|
"type": [
|
|
@@ -65,6 +66,15 @@
|
|
|
65
66
|
},
|
|
66
67
|
"description": "本 endpoint 的 trusted 列表"
|
|
67
68
|
},
|
|
69
|
+
"connection": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"enum": [
|
|
72
|
+
"ws",
|
|
73
|
+
"sse",
|
|
74
|
+
"webhook",
|
|
75
|
+
"wss"
|
|
76
|
+
]
|
|
77
|
+
},
|
|
68
78
|
"baseUrl": {
|
|
69
79
|
"type": "string",
|
|
70
80
|
"description": "Milky HTTP API base URL (required); WS event path is derived as ws(s)://host/event"
|
|
@@ -77,6 +87,15 @@
|
|
|
77
87
|
"type": "string",
|
|
78
88
|
"description": "Milky access token"
|
|
79
89
|
},
|
|
90
|
+
"reconnect_interval": {
|
|
91
|
+
"type": "number"
|
|
92
|
+
},
|
|
93
|
+
"heartbeat_interval": {
|
|
94
|
+
"type": "number"
|
|
95
|
+
},
|
|
96
|
+
"commandPrefix": {
|
|
97
|
+
"type": "string"
|
|
98
|
+
},
|
|
80
99
|
"id": {
|
|
81
100
|
"type": "string",
|
|
82
101
|
"description": "Milky bot name"
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Milky 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
|
-
* 由 plugin.ts setup() provide,adapter create 与 `milky
|
|
3
|
+
* 由 plugin.ts setup() provide,adapter create 与 `milky endpoint` 命令共享(同一 owner generation)。
|
|
4
4
|
*/
|
|
5
5
|
import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
|
|
6
6
|
|
package/src/protocol.ts
CHANGED
|
@@ -4,36 +4,21 @@
|
|
|
4
4
|
* Spec: https://milky.ntqqrev.org/
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { isMediaRef } from '@zhin.js/
|
|
7
|
+
import { isMediaRef, type ConversationRef } from '@zhin.js/im-contract';
|
|
8
8
|
import type { Segment } from '@zhin.js/core/runtime';
|
|
9
|
-
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
10
9
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
11
10
|
|
|
12
11
|
const logger = getLogger('milky');
|
|
13
12
|
|
|
14
|
-
/**
|
|
15
|
-
export interface
|
|
16
|
-
readonly context?: string;
|
|
13
|
+
/** One endpoint config after AdapterIndex expands `plugins.<instanceKey>.endpoints`. */
|
|
14
|
+
export interface MilkyEndpointConfig {
|
|
17
15
|
readonly connection?: 'ws' | 'sse' | 'webhook' | 'wss';
|
|
18
|
-
readonly id
|
|
19
|
-
readonly baseUrl
|
|
20
|
-
readonly access_token?: string;
|
|
21
|
-
readonly path?: string;
|
|
22
|
-
readonly reconnect_interval?: number;
|
|
23
|
-
readonly heartbeat_interval?: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
27
|
-
export interface MilkyAdapterConfig {
|
|
28
|
-
readonly connection?: 'ws' | 'sse' | 'webhook' | 'wss';
|
|
29
|
-
readonly id?: string;
|
|
30
|
-
readonly baseUrl?: string;
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly baseUrl: string;
|
|
31
18
|
readonly access_token?: string;
|
|
32
19
|
readonly path?: string;
|
|
33
20
|
readonly reconnect_interval?: number;
|
|
34
21
|
readonly heartbeat_interval?: number;
|
|
35
|
-
/** Transitional: legacy root `endpoints[]` with `context: milky`. */
|
|
36
|
-
readonly endpoints?: ReadonlyArray<MilkyLegacyEndpointRow>;
|
|
37
22
|
}
|
|
38
23
|
|
|
39
24
|
export interface MilkyConfigBase {
|
|
@@ -74,7 +59,6 @@ export type ResolvedMilkyConfig =
|
|
|
74
59
|
| MilkySseConfig
|
|
75
60
|
| MilkyWebhookConfig
|
|
76
61
|
| MilkyWssConfig;
|
|
77
|
-
export type MilkyEndpointConfig = ResolvedMilkyConfig;
|
|
78
62
|
|
|
79
63
|
export interface MilkyApiResponse<T = unknown> {
|
|
80
64
|
status: 'ok' | 'failed';
|
|
@@ -127,20 +111,11 @@ function normalizeConnection(connection: string | undefined): 'ws' | 'sse' | 'we
|
|
|
127
111
|
return 'ws';
|
|
128
112
|
}
|
|
129
113
|
|
|
130
|
-
export function resolveMilkyConfig(config:
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|| process.env.MILKY_BOT_NAME
|
|
136
|
-
|| 'milky-bot';
|
|
137
|
-
const baseUrl = config.baseUrl ?? entry?.baseUrl;
|
|
138
|
-
if (!baseUrl) {
|
|
139
|
-
throw new TypeError(
|
|
140
|
-
'Milky requires baseUrl (plugins.<key>.baseUrl or endpoints with context: milky)',
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
const access_token = config.access_token ?? entry?.access_token;
|
|
114
|
+
export function resolveMilkyConfig(config: MilkyEndpointConfig): ResolvedMilkyConfig {
|
|
115
|
+
const connection = normalizeConnection(config.connection);
|
|
116
|
+
const id = requiredEndpointField(config.id, 'id');
|
|
117
|
+
const baseUrl = requiredEndpointField(config.baseUrl, 'baseUrl');
|
|
118
|
+
const access_token = optionalEndpointField(config.access_token);
|
|
144
119
|
const base = {
|
|
145
120
|
context: 'milky' as const,
|
|
146
121
|
id,
|
|
@@ -152,8 +127,8 @@ export function resolveMilkyConfig(config: MilkyAdapterConfig = {}): ResolvedMil
|
|
|
152
127
|
return {
|
|
153
128
|
...base,
|
|
154
129
|
connection: 'ws',
|
|
155
|
-
reconnect_interval: config.reconnect_interval ??
|
|
156
|
-
heartbeat_interval: config.heartbeat_interval ??
|
|
130
|
+
reconnect_interval: config.reconnect_interval ?? 5000,
|
|
131
|
+
heartbeat_interval: config.heartbeat_interval ?? 30_000,
|
|
157
132
|
};
|
|
158
133
|
}
|
|
159
134
|
|
|
@@ -161,32 +136,43 @@ export function resolveMilkyConfig(config: MilkyAdapterConfig = {}): ResolvedMil
|
|
|
161
136
|
return {
|
|
162
137
|
...base,
|
|
163
138
|
connection: 'sse',
|
|
164
|
-
reconnect_interval: config.reconnect_interval
|
|
165
|
-
?? entry?.reconnect_interval
|
|
166
|
-
?? 5_000,
|
|
139
|
+
reconnect_interval: config.reconnect_interval ?? 5_000,
|
|
167
140
|
};
|
|
168
141
|
}
|
|
169
142
|
|
|
170
143
|
if (connection === 'webhook') {
|
|
171
|
-
const path = config.path
|
|
172
|
-
if (!path) throw new TypeError('Milky connection:webhook requires path');
|
|
144
|
+
const path = requiredEndpointField(config.path, 'path');
|
|
173
145
|
return { ...base, connection: 'webhook', path };
|
|
174
146
|
}
|
|
175
147
|
|
|
176
148
|
if (connection === 'wss') {
|
|
177
|
-
const path = config.path
|
|
178
|
-
if (!path) throw new TypeError('Milky connection:wss requires path');
|
|
149
|
+
const path = requiredEndpointField(config.path, 'path');
|
|
179
150
|
return {
|
|
180
151
|
...base,
|
|
181
152
|
connection: 'wss',
|
|
182
153
|
path,
|
|
183
|
-
heartbeat_interval: config.heartbeat_interval ??
|
|
154
|
+
heartbeat_interval: config.heartbeat_interval ?? 30_000,
|
|
184
155
|
};
|
|
185
156
|
}
|
|
186
157
|
|
|
187
158
|
throw new TypeError(`Unknown Milky connection: ${String(connection)}`);
|
|
188
159
|
}
|
|
189
160
|
|
|
161
|
+
function requiredEndpointField(
|
|
162
|
+
value: unknown,
|
|
163
|
+
field: 'id' | 'baseUrl' | 'path',
|
|
164
|
+
): string {
|
|
165
|
+
const resolved = optionalEndpointField(value);
|
|
166
|
+
if (!resolved) {
|
|
167
|
+
throw new TypeError(`Milky endpoint requires a non-empty ${field}`);
|
|
168
|
+
}
|
|
169
|
+
return resolved;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function optionalEndpointField(value: unknown): string | undefined {
|
|
173
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
190
176
|
/** 鉴权:Header Authorization: Bearer {token} 或 URL query access_token=xxx */
|
|
191
177
|
function authHeaders(access_token?: string): Record<string, string> {
|
|
192
178
|
const headers: Record<string, string> = {};
|
package/src/wss-endpoint.ts
CHANGED
|
@@ -56,17 +56,18 @@ export class MilkyWssEndpoint extends ClientEndpoint<MilkyClient> {
|
|
|
56
56
|
#ws?: MilkyWsSocket;
|
|
57
57
|
#wsRelease?: () => void;
|
|
58
58
|
#clientSocketRelease?: () => void;
|
|
59
|
-
readonly #
|
|
59
|
+
readonly #connectionLifecycle: EndpointLifecycle;
|
|
60
|
+
#connectionTask = Promise.resolve();
|
|
61
|
+
#started = false;
|
|
60
62
|
|
|
61
63
|
constructor(options: MilkyWssEndpointOptions) {
|
|
62
64
|
super();
|
|
63
65
|
this.#logger = getAdapterLogger('milky', options.config.id);
|
|
64
66
|
this.#options = options;
|
|
65
67
|
this.#callApi = options.callApi ?? callApi;
|
|
66
|
-
this.#
|
|
67
|
-
name: options.config.id
|
|
68
|
+
this.#connectionLifecycle = createEndpointLifecycle({
|
|
69
|
+
name: `${options.config.id}:inbound`,
|
|
68
70
|
reconnect: false,
|
|
69
|
-
heartbeat: { intervalMs: options.config.heartbeat_interval },
|
|
70
71
|
});
|
|
71
72
|
this.client = createMilkyEndpointClient(options.config, this.#callApi);
|
|
72
73
|
this.management = createMilkyEndpointManagement({
|
|
@@ -82,23 +83,19 @@ export class MilkyWssEndpoint extends ClientEndpoint<MilkyClient> {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
async start(): Promise<void> {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
/* ignore */
|
|
99
|
-
}
|
|
100
|
-
this.#ws = undefined;
|
|
101
|
-
});
|
|
86
|
+
if (this.#started) return;
|
|
87
|
+
this.#started = true;
|
|
88
|
+
const handle = this.#options.http.ws(this.#options.config.path);
|
|
89
|
+
this.#wsRelease = handle.onConnection((connection) => {
|
|
90
|
+
this.#connectionTask = this.#connectionTask
|
|
91
|
+
.then(() => this.#acceptConnection(connection))
|
|
92
|
+
.catch((error) => {
|
|
93
|
+
this.#logger.warn(formatCompact({
|
|
94
|
+
op: 'wss_connection_failed',
|
|
95
|
+
endpoint: this.#options.config.id,
|
|
96
|
+
error: error instanceof Error ? error.message : String(error),
|
|
97
|
+
}));
|
|
98
|
+
});
|
|
102
99
|
});
|
|
103
100
|
this.#logger.info(formatCompact({
|
|
104
101
|
op: 'listen',
|
|
@@ -110,7 +107,12 @@ export class MilkyWssEndpoint extends ClientEndpoint<MilkyClient> {
|
|
|
110
107
|
|
|
111
108
|
async stop(): Promise<void> {
|
|
112
109
|
this.close();
|
|
113
|
-
|
|
110
|
+
this.#wsRelease?.();
|
|
111
|
+
this.#wsRelease = undefined;
|
|
112
|
+
await this.#connectionTask;
|
|
113
|
+
await this.#connectionLifecycle.stop();
|
|
114
|
+
this.#ws = undefined;
|
|
115
|
+
this.#started = false;
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
@@ -187,36 +189,47 @@ export class MilkyWssEndpoint extends ClientEndpoint<MilkyClient> {
|
|
|
187
189
|
});
|
|
188
190
|
}
|
|
189
191
|
|
|
190
|
-
#acceptConnection(connection: WsConnection): void {
|
|
192
|
+
async #acceptConnection(connection: WsConnection): Promise<void> {
|
|
191
193
|
if (!verifyMilkyAccessToken(this.#options.config.access_token, connection.request)) {
|
|
192
194
|
connection.socket.close(4003, 'Unauthorized');
|
|
193
195
|
return;
|
|
194
196
|
}
|
|
195
197
|
const socket = connection.socket as unknown as MilkyWsSocket;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
198
|
+
await this.#connectionLifecycle.stop();
|
|
199
|
+
await this.#connectionLifecycle.start(async (lifecycleHandle) => {
|
|
200
|
+
this.#ws = socket;
|
|
201
|
+
const releaseClientSocket = this.client.acceptWebSocket(socket);
|
|
202
|
+
this.#clientSocketRelease = releaseClientSocket;
|
|
203
|
+
lifecycleHandle.onForceClose(() => {
|
|
204
|
+
if (this.#clientSocketRelease === releaseClientSocket) {
|
|
205
|
+
releaseClientSocket();
|
|
206
|
+
this.#clientSocketRelease = undefined;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
socket.close();
|
|
210
|
+
} catch {
|
|
211
|
+
/* ignore */
|
|
212
|
+
}
|
|
213
|
+
if (this.#ws === socket) this.#ws = undefined;
|
|
214
|
+
});
|
|
215
|
+
this.#connectionLifecycle.startHeartbeat(() => {
|
|
216
|
+
try {
|
|
217
|
+
if (socket.readyState === WS_OPEN) socket.ping?.();
|
|
218
|
+
} catch {
|
|
219
|
+
/* ignore */
|
|
220
|
+
}
|
|
221
|
+
}, this.#options.config.heartbeat_interval);
|
|
222
|
+
socket.on('message', () => {
|
|
223
|
+
this.#connectionLifecycle.notifyHeartbeatAck();
|
|
224
|
+
});
|
|
225
|
+
socket.on('close', () => {
|
|
226
|
+
if (this.#clientSocketRelease === releaseClientSocket) {
|
|
227
|
+
releaseClientSocket();
|
|
228
|
+
this.#clientSocketRelease = undefined;
|
|
229
|
+
}
|
|
230
|
+
if (this.#ws === socket) this.#ws = undefined;
|
|
231
|
+
lifecycleHandle.notifyClosed(new Error('Milky reverse WebSocket closed'));
|
|
232
|
+
});
|
|
220
233
|
});
|
|
221
234
|
this.#logger.debug(formatCompact({
|
|
222
235
|
endpoint: this.#options.config.id,
|
package/agent/skills/milky.md
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: milky
|
|
3
|
-
platforms:
|
|
4
|
-
- milky
|
|
5
|
-
description: >-
|
|
6
|
-
Milky 协议群管理能力。当用户在 Milky 协议的 QQ 群中请求踢人、禁言、设管理员、
|
|
7
|
-
改名片/群名或查成员/群信息时使用。即使用户没有提到 Milky,只要 Endpoint 平台是
|
|
8
|
-
Milky 协议且涉及群管理操作,就应触发。仅有昵称时必须先查成员列表获取 user_id。
|
|
9
|
-
keywords:
|
|
10
|
-
- milky
|
|
11
|
-
- adapter:milky
|
|
12
|
-
- 群管理
|
|
13
|
-
- 禁言
|
|
14
|
-
- 踢人
|
|
15
|
-
- 管理员
|
|
16
|
-
- list_members
|
|
17
|
-
tags:
|
|
18
|
-
- group
|
|
19
|
-
- management
|
|
20
|
-
- im
|
|
21
|
-
tools:
|
|
22
|
-
- milky_kick_member
|
|
23
|
-
- milky_mute_member
|
|
24
|
-
- milky_mute_all
|
|
25
|
-
- milky_set_admin
|
|
26
|
-
- milky_set_nickname
|
|
27
|
-
- milky_set_group_name
|
|
28
|
-
- milky_list_members
|
|
29
|
-
- milky_get_group_info
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
# Milky 群管理技能
|
|
33
|
-
|
|
34
|
-
Milky 是 QQ 生态中的一种协议实现,提供标准群管理工具集。
|
|
35
|
-
|
|
36
|
-
## 核心原则
|
|
37
|
-
|
|
38
|
-
### 先查后操作
|
|
39
|
-
|
|
40
|
-
只有昵称时,先 `milky_list_members` 获取 QQ 号再操作。
|
|
41
|
-
|
|
42
|
-
**Example:**
|
|
43
|
-
```
|
|
44
|
-
用户: 禁言那个叫"阿强"的
|
|
45
|
-
步骤1: milky_list_members → 找到 user_id = 654321
|
|
46
|
-
步骤2: milky_mute_member(user_id=654321, duration=600)
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## 工具详解
|
|
50
|
-
|
|
51
|
-
| 工具 | 用途 | 权限 | 关键参数 |
|
|
52
|
-
|------|------|------|----------|
|
|
53
|
-
| `milky_kick_member` | 踢出成员 | admin | `user_id` |
|
|
54
|
-
| `milky_mute_member` | 禁言/解禁 | admin | `user_id`, `duration`(秒,**0=解禁**) |
|
|
55
|
-
| `milky_mute_all` | 全员禁言/解除 | admin | `enable` |
|
|
56
|
-
| `milky_set_admin` | 设/取消管理员 | owner | `user_id`, `enable` |
|
|
57
|
-
| `milky_set_nickname` | 改群名片 | admin | `user_id`, `nickname` |
|
|
58
|
-
| `milky_set_group_name` | 改群名 | admin | `name` |
|
|
59
|
-
| `milky_list_members` | 成员列表 | user | — |
|
|
60
|
-
| `milky_get_group_info` | 群信息 | user | — |
|
|
61
|
-
|
|
62
|
-
## 易错点
|
|
63
|
-
|
|
64
|
-
1. **禁言 duration=0 是解禁**,不是永久禁言。
|
|
65
|
-
2. **管理员设置需要群主权限**(owner),管理员无法设置其他管理员。
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const milkyEndpointCommands: import("@zhin.js/adapter").EndpointCommands<Readonly<import("@zhin.js/command").CommandDefinition<unknown, unknown, import("@zhin.js/command").CommandMessage, string | undefined>>>;
|
|
File without changes
|