@1agents/dreammate-network 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,7 +28,7 @@ dreammate-network/
28
28
  import type { NodeManifest, Execution, SessionURI } from "dreammate-network";
29
29
  ```
30
30
 
31
- Go / Swift 侧读同一份 `schemas/*.json`。详见 [`docs/protocol.md` §9](docs/protocol.md)。
31
+ Go / Swift 侧读同一份 `schemas/*.json`。详见 [`docs/protocol.md` §10](docs/protocol.md)。
32
32
 
33
33
  ## 三条不能破的规则
34
34
 
@@ -8,7 +8,11 @@
8
8
  *
9
9
  * @packageDocumentation
10
10
  */
11
- export declare const PROTOCOL_VERSION = "0.1.0";
11
+ /**
12
+ * 协议版本,与本包的 package.json 版本保持一致——两个版本号会让人永远猜不准
13
+ * 该看哪个。服务在 `manifest.metadata.protocol_version` 里报告它遵循的版本。
14
+ */
15
+ export declare const PROTOCOL_VERSION = "0.3.0";
12
16
  /** `session://<node>/<runtime>/<session_id>` */
13
17
  export type SessionURI = `session://${string}/${string}/${string}`;
14
18
  /** 资源寻址,如 `recording://tingqi-01/abc`、`speaker://tingqi-001/speaker-23` */
@@ -47,6 +51,35 @@ export interface AccessDescriptor {
47
51
  /** protocol=cli:命令,如 `tingqi transcript get` */
48
52
  command?: string;
49
53
  }
54
+ /**
55
+ * 约定端口。发现是 pull 的(Control Plane 探测节点的 `/manifest` 与
56
+ * `/health`),所以「哪个服务在哪个端口」必须是公共词汇,否则探测方无从下手。
57
+ *
58
+ * 这是**默认值,不是强制**:服务可以跑在别的端口,代价是探测发现不了它,
59
+ * 得由它自己 `POST /nodes/register` 告知。
60
+ *
61
+ * 与 docs/protocol.md §6 的表格一一对应,改动必须同步两边。
62
+ */
63
+ export declare const DEFAULT_PORTS: {
64
+ /**
65
+ * 本机 node agent(`@1agents/dreammate-node`)。**固定端口**,不可改:
66
+ * 它是整台机器对网络的唯一入口,外部节点靠"探 36908"找到这台机器上的一切。
67
+ */
68
+ readonly "node-agent": 36908;
69
+ readonly "session-registry": 7777;
70
+ readonly "data-service": 7778;
71
+ readonly "control-plane": 7779;
72
+ readonly "tingqi-adapter": 7780;
73
+ };
74
+ export type WellKnownService = keyof typeof DEFAULT_PORTS;
75
+ /**
76
+ * 节点身份是从哪来的。
77
+ *
78
+ * `tailscale` 表示取自 tailnet(`Self.ID` / `DNSName` / `OS`),此时 `name`
79
+ * 跨设备唯一。`local` 是回退,`name` **不保证唯一**——iOS 的 hostname 全是
80
+ * `localhost`——只适合单机自用。放进 `metadata.identity_source` 如实告诉对端。
81
+ */
82
+ export type IdentitySource = "tailscale" | "local";
50
83
  /**
51
84
  * 非 generic 的几种在传输层保留原生协议,不强行塞进
52
85
  * `POST /capabilities/:name/invoke`。
@@ -58,6 +91,8 @@ export type ServiceKind = "generic" | "agent_runtime" | "session_registry" | "re
58
91
  * 三层结构是 Node → Service → Capability / Resource,
59
92
  * 不要把 Capability 扁平铺到 Node 上。
60
93
  */
94
+ /** 这个 Service 能被谁连上。省略时按 `network` 理解。 */
95
+ export type Reachability = "localhost" | "network";
61
96
  export interface Service {
62
97
  id: string;
63
98
  name?: string | null;
@@ -67,6 +102,15 @@ export interface Service {
67
102
  capabilities: Capability[];
68
103
  resources?: ResourceDescriptor[];
69
104
  access?: AccessDescriptor[];
105
+ /**
106
+ * 只监听回环的服务,外部节点发现得了但连不上。node agent 如实转述这个
107
+ * 声明而**不做代理**——调用方看到 `localhost` 就知道不用白跑一趟。
108
+ */
109
+ reachability?: Reachability;
110
+ /** 实际监听端口。不必等于 {@link DEFAULT_PORTS} 里的默认值。 */
111
+ port?: number | null;
112
+ /** 存活探测路径,默认 `/health`。 */
113
+ health?: string | null;
70
114
  metadata?: Record<string, unknown>;
71
115
  }
72
116
  /**
@@ -8,4 +8,31 @@
8
8
  *
9
9
  * @packageDocumentation
10
10
  */
11
- export const PROTOCOL_VERSION = "0.1.0";
11
+ /**
12
+ * 协议版本,与本包的 package.json 版本保持一致——两个版本号会让人永远猜不准
13
+ * 该看哪个。服务在 `manifest.metadata.protocol_version` 里报告它遵循的版本。
14
+ */
15
+ export const PROTOCOL_VERSION = "0.3.0";
16
+ /* ------------------------------------------------------------------ *
17
+ * 发现
18
+ * ------------------------------------------------------------------ */
19
+ /**
20
+ * 约定端口。发现是 pull 的(Control Plane 探测节点的 `/manifest` 与
21
+ * `/health`),所以「哪个服务在哪个端口」必须是公共词汇,否则探测方无从下手。
22
+ *
23
+ * 这是**默认值,不是强制**:服务可以跑在别的端口,代价是探测发现不了它,
24
+ * 得由它自己 `POST /nodes/register` 告知。
25
+ *
26
+ * 与 docs/protocol.md §6 的表格一一对应,改动必须同步两边。
27
+ */
28
+ export const DEFAULT_PORTS = {
29
+ /**
30
+ * 本机 node agent(`@1agents/dreammate-node`)。**固定端口**,不可改:
31
+ * 它是整台机器对网络的唯一入口,外部节点靠"探 36908"找到这台机器上的一切。
32
+ */
33
+ "node-agent": 36908,
34
+ "session-registry": 7777,
35
+ "data-service": 7778,
36
+ "control-plane": 7779,
37
+ "tingqi-adapter": 7780,
38
+ };
package/docs/protocol.md CHANGED
@@ -118,7 +118,126 @@ POST /capabilities/:name/invoke
118
118
 
119
119
  Capability Manifest 只负责告诉调用方:**我有什么,以及应该怎么访问。**
120
120
 
121
- ## 6. 传输只出现在 `access` 里
121
+ ## 6. 节点与服务的发现
122
+
123
+ 发现分两层,各有各的事实源:
124
+
125
+ ```
126
+ 有哪些节点 ← tailnet(tailscale status --json)
127
+ 节点是不是开着 ← tailnet 的 Online
128
+ 节点上有什么服务 ← 探测约定端口的 GET /manifest
129
+ 服务还活着吗 ← 定期探 GET /health
130
+ ```
131
+
132
+ **方向永远是 L3 → L2(pull),不是 L2 → L3(push)。** 服务不需要知道
133
+ Control Plane 存在,也不需要心跳定时器;Control Plane 挂了,服务毫无感觉。
134
+ 这与「上层通过 HTTP 调用下层」的单向依赖一致。
135
+
136
+ ⚠️ **Node 在线 ≠ Service 在线。** tailnet 的 `Online` 只说明机器开着;
137
+ 进程被 kill 了它照样报在线。Service 级的存活只能靠探 `/health`。
138
+
139
+ ### 约定端口
140
+
141
+ pull 要知道探哪儿,所以端口是公共词汇的一部分:
142
+
143
+ | 端口 | Service | 层 |
144
+ |------|---------|-----|
145
+ | **36908** | **`node-agent`(dreammate-node)** | **本机基础设施** |
146
+ | 7777 | `session-registry`(session-reader) | L2 |
147
+ | 7778 | `data-service` | L2 |
148
+ | 7779 | `control-plane` | L3 |
149
+ | 7780 | `tingqi-adapter` 等 Resource Provider | L2 |
150
+ | 7781–7789 | 预留给后续 L2 服务 | — |
151
+
152
+ 这是**默认值,不是强制**。服务可以跑在别的端口,代价是探测发现不了它,
153
+ 得由它自己 `POST /nodes/register` 告知——register 因此是可选的加速/兜底,
154
+ 不是必需品。
155
+
156
+ ### 本机 node agent
157
+
158
+ 每台机器跑一个 `@1agents/dreammate-node`,固定监听 **36908**。它是这台机器
159
+ 对网络的唯一入口:
160
+
161
+ ```
162
+ Control Plane / 任意节点
163
+ │ 探 36908(每台机器只探一个端口)
164
+
165
+ node-agent :36908
166
+ ├─ GET /manifest 本机聚合视图:节点身份 + 所有已报备的服务
167
+ ├─ GET /health
168
+ ├─ GET /services 各服务的存活与可达性
169
+ └─ POST /services 服务报备(**仅接受 localhost**)
170
+
171
+ ┌───────────┴───────────┐ localhost 报备
172
+ session-reader :7777 task-service :xxxx
173
+ ```
174
+
175
+ 这把 pull 探测的成本从「N 个节点 × M 个端口」降到「N × 1」。
176
+
177
+ **报备是可选的。** 服务不报备也能工作,只是外部得靠约定端口才找得到它。
178
+ 报备时必须声明**可达性**:
179
+
180
+ | `reachability` | 含义 |
181
+ |---|---|
182
+ | `localhost` | 只监听回环,外部节点发现得了但连不上 |
183
+ | `network` | 监听 0.0.0.0 或 tailnet 地址,外部可直连 |
184
+
185
+ agent 如实转述这个声明,**不做代理**。调用方看到 `localhost` 就知道这个能力
186
+ 只对本机开放,不用白跑一趟。
187
+
188
+ > ⚠️ `POST /services` 只接受来自回环的请求。否则网络上任何人都能往你的节点
189
+ > 里塞一个假服务,把调用方引到别处去。
190
+
191
+ ### Control Plane 不是一个进程
192
+
193
+ node 注册与探活下沉成了每台机器的基础设施(就是上面的 agent),原本设想中
194
+ Control Plane 的其余职责——Task、Agent 编排、Execution 账本——**降级为平级的
195
+ 普通服务**,各自独立起进程、各自向本机 agent 报备。
196
+
197
+ ```
198
+ 以前:Control Plane = Node Registry + Execution Ledger + Task + Orchestrator
199
+ (一个大进程)
200
+
201
+ 现在:node-agent = Node Registry + 探活 (每机一个,基础设施)
202
+ task-service ┐
203
+ agent-service ├ 平级服务,各自解耦运行,都向本机 agent 报备
204
+ execution-* ┘
205
+ ```
206
+
207
+ 好处是任何一个服务挂掉都不会让整个控制面消失,也不存在"必须先起 Control Plane
208
+ 才能用"的启动顺序。代价是全网视图需要有人聚合——那也只是另一个服务,它靠
209
+ tailnet + 探 36908 自己拼出来。
210
+
211
+ ### 节点身份取自 tailnet
212
+
213
+ 节点的 `node_id` / `name` / `type` 应该直接用 tailnet 的事实,而不是自己生成:
214
+
215
+ | Manifest 字段 | tailscale status 的来源 |
216
+ |---|---|
217
+ | `node_id` | `Self.ID`(稳定,重启不变) |
218
+ | `name` | `Self.DNSName` 的第一段 |
219
+ | `type` | `Self.OS`(macOS→macos,iOS→ios,…) |
220
+ | `tailscale_name` | `Self.DNSName` |
221
+
222
+ > ⚠️ **不要用 `HostName`。** iOS 设备的 HostName 全是 `localhost`——实测一个
223
+ > 11 节点的 tailnet 里只有 9 个 HostName 唯一,而 DNSName 是 11/11 唯一且可读
224
+ > (`iphone-15-pro`)。用 HostName 做 `session://<node>/...` 的第一段,几台
225
+ > 手机接进来就会全部撞在 `session://localhost/...`。
226
+
227
+ 本机所有服务读同一份 tailnet 状态,所以不会各自生成 id 把一台机器裂成几个 Node。
228
+ 拿不到 tailscale 时可以回退到本地身份,但要在 `metadata.identity_source` 里
229
+ 如实标明,因为回退身份的 `name` 不保证跨设备唯一。
230
+
231
+ ### `/manifest` 永远是部分视图
232
+
233
+ 一个节点上跑着多个服务时,每个服务的 `/manifest` 只报**自己**那一个 service,
234
+ 但 `node_id` 是相同的。完整的节点视图(把同一 `node_id` 下的 services 合并)
235
+ 只存在于 Control Plane 的 `GET /nodes/:id/manifest`。
236
+
237
+ 消费方看到两份 `node_id` 相同、`services` 不同的 manifest 是**正常的**,
238
+ 不是冲突。
239
+
240
+ ## 7. 传输只出现在 `access` 里
122
241
 
123
242
  数据模型里**不存在** MCP Capability / CLI Capability / HTTP Capability。
124
243
  一个 Capability 可以有多种 access,调用方不关心底层是谁:
@@ -141,7 +260,7 @@ Capability Manifest 只负责告诉调用方:**我有什么,以及应该怎
141
260
  ACP 面向 Agent Runtime 的 Session 控制(`agent.session.new` / `prompt` / `cancel` /
142
261
  `status` / `resume`)。
143
262
 
144
- ## 7. 图的边由事件实时写入
263
+ ## 8. 图的边由事件实时写入
145
264
 
146
265
  不跑后台扫库猜 DAG。调用发生时就写边:
147
266
 
@@ -156,7 +275,7 @@ ACP 面向 Agent Runtime 的 Session 控制(`agent.session.new` / `prompt` / `
156
275
 
157
276
  整体 Work Graph **不是** DAG(Session 之间可以成环);DAG 只是它的投影视图。
158
277
 
159
- ## 8. Execution 的粒度边界
278
+ ## 9. Execution 的粒度边界
160
279
 
161
280
  | 层级 | 记录为 |
162
281
  |------|--------|
@@ -166,7 +285,7 @@ ACP 面向 Agent Runtime 的 Session 控制(`agent.session.new` / `prompt` / `
166
285
  否则一个 338 次 tool call 的 Codex Session 会生成 338 个 Execution,
167
286
  Work Graph 立刻失去意义。
168
287
 
169
- ## 9. 怎么消费本包
288
+ ## 10. 怎么消费本包
170
289
 
171
290
  **TypeScript**(工作区内直接引源码,不引入构建链,从而保持真正零依赖):
172
291
 
@@ -205,7 +324,7 @@ npx -p ajv-cli@5 -p ajv-formats@2 ajv validate --spec=draft2020 -c ajv-formats \
205
324
 
206
325
  每个 schema 自带 `examples`,可以直接抽出来当冒烟用例跑。
207
326
 
208
- ## 10. 第一版明确不做
327
+ ## 11. 第一版明确不做
209
328
 
210
329
  - ❌ 不写任何业务逻辑(本包只是公共语言)
211
330
  - ❌ Capability 不带 provider / permission / availability 元数据
@@ -215,7 +334,7 @@ npx -p ajv-cli@5 -p ajv-formats@2 ajv validate --spec=draft2020 -c ajv-formats \
215
334
  - ❌ 不要求所有能力统一成 MCP
216
335
  - ❌ 不存 `planned = true/false`(由时间事实推导)
217
336
 
218
- ## 11. 版本
337
+ ## 12. 版本
219
338
 
220
339
  `PROTOCOL_VERSION = "0.1.0"`。v0.x 期间 schema 可能破坏性变更,
221
340
  以设计册 06-实施路线图的 M1–M5 验收结果为准收敛。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1agents/dreammate-network",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "DreamMate Network L0 协议包:schema + types,零运行时依赖,不含业务逻辑。",
5
5
  "keywords": [
6
6
  "dreammate",
@@ -4,82 +4,165 @@
4
4
  "title": "DreamMate Service",
5
5
  "description": "Node 上的一组能力聚合。三层结构是 Node → Service → Capability / Resource,不要把 Capability 扁平铺到 Node 上。Agent Runtime 是 Service 的一种特殊 kind。",
6
6
  "type": "object",
7
- "required": ["id", "capabilities"],
7
+ "required": [
8
+ "id",
9
+ "capabilities"
10
+ ],
8
11
  "properties": {
9
12
  "id": {
10
13
  "type": "string",
11
14
  "description": "Service 在所属 Node 内唯一。",
12
15
  "minLength": 1,
13
- "examples": ["session-registry", "reminders", "recordings", "agent-runtime"]
16
+ "examples": [
17
+ "session-registry",
18
+ "reminders",
19
+ "recordings",
20
+ "agent-runtime"
21
+ ]
14
22
  },
15
23
  "name": {
16
- "type": ["string", "null"],
24
+ "type": [
25
+ "string",
26
+ "null"
27
+ ],
17
28
  "description": "人类可读名称,可省略。"
18
29
  },
19
30
  "kind": {
20
31
  "type": "string",
21
32
  "description": "Service 类型。generic 之外的几种在传输层保留原生协议(见 protocol.md §5),不强行塞进 /capabilities/:name/invoke。",
22
- "enum": ["generic", "agent_runtime", "session_registry", "resource_provider", "mcp"],
33
+ "enum": [
34
+ "generic",
35
+ "agent_runtime",
36
+ "session_registry",
37
+ "resource_provider",
38
+ "mcp"
39
+ ],
23
40
  "default": "generic"
24
41
  },
25
42
  "capabilities": {
26
43
  "type": "array",
27
44
  "description": "该 Service 能做什么。第一版就是点分命名的字符串,不带权限/可用性等元数据。可以为空数组(纯 Resource Provider)。",
28
- "items": { "type": "string", "minLength": 1 },
29
- "examples": [["sessions.list", "sessions.read"], ["reminders.create"], ["recordings.list", "recording.read"]]
45
+ "items": {
46
+ "type": "string",
47
+ "minLength": 1
48
+ },
49
+ "examples": [
50
+ [
51
+ "sessions.list",
52
+ "sessions.read"
53
+ ],
54
+ [
55
+ "reminders.create"
56
+ ],
57
+ [
58
+ "recordings.list",
59
+ "recording.read"
60
+ ]
61
+ ]
30
62
  },
31
63
  "resources": {
32
64
  "type": "array",
33
65
  "description": "该 Service 拥有什么(可选,声明资源 URI 的 scheme 而非逐条枚举)。Capability=能做什么,Resource=拥有什么,两者不要混:检索侧走 resource.search/read,调度侧走 capability.invoke。",
34
66
  "items": {
35
67
  "type": "object",
36
- "required": ["scheme"],
68
+ "required": [
69
+ "scheme"
70
+ ],
37
71
  "properties": {
38
72
  "scheme": {
39
73
  "type": "string",
40
74
  "description": "资源 URI 的 scheme。",
41
- "examples": ["recording", "transcript", "speaker", "session"]
75
+ "examples": [
76
+ "recording",
77
+ "transcript",
78
+ "speaker",
79
+ "session"
80
+ ]
42
81
  },
43
- "description": { "type": ["string", "null"] }
82
+ "description": {
83
+ "type": [
84
+ "string",
85
+ "null"
86
+ ]
87
+ }
44
88
  }
45
89
  }
46
90
  },
47
91
  "access": {
48
92
  "type": "array",
49
93
  "description": "怎么访问这个 Service。传输方式只出现在这里,绝不进数据模型——不存在所谓 MCP Capability / CLI Capability。同一组能力可以同时有多种 access,调用方不必关心底层是谁。包装优先级 MCP > CLI > HTTP,但第一版不做自动 fallback 编排,手工声明即可。",
50
- "items": { "$ref": "#/$defs/access" }
94
+ "items": {
95
+ "$ref": "#/$defs/access"
96
+ }
51
97
  },
52
98
  "metadata": {
53
99
  "type": "object",
54
100
  "description": "Service 级自由扩展字段。协议不解释其内容。",
55
101
  "additionalProperties": true
102
+ },
103
+ "reachability": {
104
+ "type": "string",
105
+ "enum": [
106
+ "localhost",
107
+ "network"
108
+ ],
109
+ "description": "这个 Service 能被谁连上。localhost=只监听回环,外部节点发现得了但连不上;network=监听 0.0.0.0 或 tailnet 地址,外部可直连。node agent 如实转述服务自己的声明,不做代理——调用方看到 localhost 就知道不用白跑一趟。省略时按 network 理解。"
110
+ },
111
+ "port": {
112
+ "type": [
113
+ "integer",
114
+ "null"
115
+ ],
116
+ "minimum": 1,
117
+ "maximum": 65535,
118
+ "description": "服务实际监听的端口。向本机 node agent 报备时带上,外部才知道该连哪儿;它不必等于 DEFAULT_PORTS 里的默认值。"
119
+ },
120
+ "health": {
121
+ "type": [
122
+ "string",
123
+ "null"
124
+ ],
125
+ "description": "存活探测路径,默认 /health。node agent 定期探它来判断服务死活——注意 Node 在线不代表 Service 在线。"
56
126
  }
57
127
  },
58
128
  "$defs": {
59
129
  "access": {
60
130
  "type": "object",
61
131
  "title": "Access Descriptor",
62
- "required": ["protocol"],
132
+ "required": [
133
+ "protocol"
134
+ ],
63
135
  "properties": {
64
136
  "protocol": {
65
137
  "type": "string",
66
- "enum": ["mcp", "http", "cli", "acp"],
138
+ "enum": [
139
+ "mcp",
140
+ "http",
141
+ "cli",
142
+ "acp"
143
+ ],
67
144
  "description": "acp 只用于 kind=agent_runtime 的 Session 控制,与前三者不是一回事:MCP/CLI/HTTP 面向 Capability / Resource,ACP 面向 Agent Runtime / Session Control。"
68
145
  },
69
146
  "base_url": {
70
147
  "type": "string",
71
148
  "description": "protocol=http 时的服务基址。",
72
- "examples": ["http://scott-mac:7777/v1"]
149
+ "examples": [
150
+ "http://scott-mac:7777/v1"
151
+ ]
73
152
  },
74
153
  "endpoint": {
75
154
  "type": "string",
76
155
  "description": "protocol=http 时单个 capability 的相对路由。",
77
- "examples": ["GET /transcripts/:id"]
156
+ "examples": [
157
+ "GET /transcripts/:id"
158
+ ]
78
159
  },
79
160
  "tool": {
80
161
  "type": "string",
81
162
  "description": "protocol=mcp 时对应的 tool 名。",
82
- "examples": ["transcript_read"]
163
+ "examples": [
164
+ "transcript_read"
165
+ ]
83
166
  },
84
167
  "server": {
85
168
  "type": "string",
@@ -88,7 +171,10 @@
88
171
  "command": {
89
172
  "type": "string",
90
173
  "description": "protocol=cli 时的命令。",
91
- "examples": ["tingqi transcript get", "1session overview"]
174
+ "examples": [
175
+ "tingqi transcript get",
176
+ "1session overview"
177
+ ]
92
178
  }
93
179
  }
94
180
  }
@@ -96,11 +182,22 @@
96
182
  "examples": [
97
183
  {
98
184
  "id": "transcripts",
99
- "capabilities": ["transcript.read"],
185
+ "capabilities": [
186
+ "transcript.read"
187
+ ],
100
188
  "access": [
101
- { "protocol": "mcp", "tool": "transcript_read" },
102
- { "protocol": "http", "endpoint": "GET /transcripts/:id" },
103
- { "protocol": "cli", "command": "tingqi transcript get" }
189
+ {
190
+ "protocol": "mcp",
191
+ "tool": "transcript_read"
192
+ },
193
+ {
194
+ "protocol": "http",
195
+ "endpoint": "GET /transcripts/:id"
196
+ },
197
+ {
198
+ "protocol": "cli",
199
+ "command": "tingqi transcript get"
200
+ }
104
201
  ]
105
202
  },
106
203
  {
@@ -113,7 +210,11 @@
113
210
  "agent.session.status",
114
211
  "agent.session.resume"
115
212
  ],
116
- "access": [{ "protocol": "acp" }]
213
+ "access": [
214
+ {
215
+ "protocol": "acp"
216
+ }
217
+ ]
117
218
  }
118
219
  ]
119
220
  }