@my-life-buddies/buddy-runtime 0.16.0 → 0.18.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 +204 -170
- package/dist/conversation/agent.d.ts.map +1 -1
- package/dist/conversation/agent.js +10 -5
- package/dist/conversation/agent.js.map +1 -1
- package/dist/conversation/index.d.ts +3 -1
- package/dist/conversation/index.d.ts.map +1 -1
- package/dist/conversation/index.js +13 -5
- package/dist/conversation/index.js.map +1 -1
- package/dist/conversation/mlbMessageAgentMessageConverter.d.ts +1 -0
- package/dist/conversation/mlbMessageAgentMessageConverter.d.ts.map +1 -1
- package/dist/conversation/mlbMessageAgentMessageConverter.js +5 -5
- package/dist/conversation/mlbMessageAgentMessageConverter.js.map +1 -1
- package/dist/conversation/writeMLBMessage.d.ts +1 -0
- package/dist/conversation/writeMLBMessage.d.ts.map +1 -1
- package/dist/conversation/writeMLBMessage.js.map +1 -1
- package/dist/dataAccessRequirements.d.ts +5 -0
- package/dist/dataAccessRequirements.d.ts.map +1 -0
- package/dist/dataAccessRequirements.js +53 -0
- package/dist/dataAccessRequirements.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/mlbClient.d.ts +6 -4
- package/dist/mlbClient.d.ts.map +1 -1
- package/dist/mlbClient.js +12 -6
- package/dist/mlbClient.js.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +38 -36
- package/dist/server.js.map +1 -1
- package/dist/tools/askQuestion.d.ts +2 -1
- package/dist/tools/askQuestion.d.ts.map +1 -1
- package/dist/tools/askQuestion.js +4 -1
- package/dist/tools/askQuestion.js.map +1 -1
- package/dist/tools/dataAccess.d.ts +14 -4
- package/dist/tools/dataAccess.d.ts.map +1 -1
- package/dist/tools/dataAccess.js +60 -10
- package/dist/tools/dataAccess.js.map +1 -1
- package/dist/tools/dataAccessRequest.js +1 -1
- package/dist/tools/dataAccessRequest.js.map +1 -1
- package/dist/tools/proactive.js +1 -1
- package/dist/tools/proactive.js.map +1 -1
- package/dist/types.d.ts +1 -9
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,15 +20,25 @@ npm install @my-life-buddies/buddy-runtime typebox
|
|
|
20
20
|
|
|
21
21
|
### 1.2 创建搭子
|
|
22
22
|
|
|
23
|
-
通过 `@my-life-buddies/cli` 完成搭搭账号注册,并创建一个新的搭子,获取 buddyId 与开发令牌(平台另有一把正式令牌,只在托管运行时注入,不经 CLI
|
|
23
|
+
通过 `@my-life-buddies/cli` 完成搭搭账号注册,并创建一个新的搭子,获取 buddyId 与开发令牌(平台另有一把正式令牌,只在托管运行时注入,不经 CLI 下发)。buddyId 写进 `package.json` 的 `buddyId` 字段,开发令牌写进 `.env` 的 `BUDDY_TOKEN`。
|
|
24
24
|
|
|
25
25
|
### 1.3 搭子工程框架
|
|
26
26
|
|
|
27
27
|
```
|
|
28
28
|
my-buddy/
|
|
29
|
-
├─ .env
|
|
29
|
+
├─ .env BUDDY_TOKEN
|
|
30
30
|
├─ src/index.ts
|
|
31
|
-
└─ package.json
|
|
31
|
+
└─ package.json buddyId 字段
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"name": "my-buddy",
|
|
37
|
+
"buddyId": "b_1234567890abcdef",
|
|
38
|
+
"private": true,
|
|
39
|
+
"type": "module",
|
|
40
|
+
"scripts": { "start": "node --env-file-if-exists=.env src/index.ts" }
|
|
41
|
+
}
|
|
32
42
|
```
|
|
33
43
|
|
|
34
44
|
```ts
|
|
@@ -49,44 +59,27 @@ const planMenu: AgentTool<typeof PLAN_MENU_PARAMS, undefined> = {
|
|
|
49
59
|
}),
|
|
50
60
|
};
|
|
51
61
|
|
|
52
|
-
await BuddyServer.start({
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
},
|
|
60
|
-
}),
|
|
61
|
-
});
|
|
62
|
+
await BuddyServer.start(() => ({
|
|
63
|
+
initialState: {
|
|
64
|
+
model: "kimi-k3",
|
|
65
|
+
systemPrompt: "你是「小涂阿姨」——一位管做饭的阿姨……",
|
|
66
|
+
tools: [planMenu],
|
|
67
|
+
},
|
|
68
|
+
}));
|
|
62
69
|
```
|
|
63
70
|
|
|
64
|
-
完整示例:[examples/aunt-tu/](examples/aunt-tu/)(提问、记忆、小挂件)、[examples/cardio/](examples/cardio/)(授权数据、主动服务、提问、记忆、小挂件)。
|
|
65
|
-
|
|
66
71
|
## 2. 配置
|
|
67
72
|
|
|
68
|
-
### 2.1 `BuddyServer.start(
|
|
73
|
+
### 2.1 `BuddyServer.start(buddy)`
|
|
69
74
|
|
|
70
75
|
连上平台开始处理消息,返回 `{ stop(): Promise<void> }`。
|
|
71
76
|
|
|
72
|
-
|
|
73
|
-
interface StartOptions {
|
|
74
|
-
// 必填。这只搭子需要的用户数据及用途,见 3.4;不需要时传 []
|
|
75
|
-
dataRequirements: { id: DataAccessDatasetId; purpose: string }[];
|
|
76
|
-
|
|
77
|
-
// 必填。每场会话建起来时调一次,返回这场会话的配置,见 2.2、2.3
|
|
78
|
-
buddy: (conversation: Conversation) => BuddyOptions | Promise<BuddyOptions>;
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Runtime 启动时只在本地校验 `dataRequirements`,不会额外调用接口注册声明。Runtime 根据自身导出的能力目录补齐标题、来源和 Schema 版本;实际调用 `data_access_query`、发送 HealthKit 权限卡或申请使用数据的主动服务时,会把完整声明随业务请求携带给平台。平台在该次请求中更新用途版本并校验 Grant。
|
|
77
|
+
`buddy` 是每场会话建起来时调用一次的工厂函数,返回这场会话的配置,见 2.2、2.3。DataAccess 声明不属于会话工厂,单独写在工程根目录的 `data-access.json`,见 3.4。
|
|
83
78
|
|
|
84
|
-
|
|
79
|
+
**buddyId 与 token**:buddyId 从工程 `package.json` 顶层的 `buddyId` 字段读,token 从环境变量 `BUDDY_TOKEN` 读,缺一个都启动失败。创建搭子时会下发 buddyId 与开发 token:buddyId 写进 `package.json`,随工程进 git 与发布包,平台收包时会比对它与推送目标是否一致;开发 token 写在 `.env`,由启动命令带进环境变量。正式上线时平台托管注入正式 token 到同名变量,对开发者无感。
|
|
85
80
|
|
|
86
81
|
**buddy 的生命周期**:`buddy` 在每场会话收到第一条消息时调用一次。会话空闲 30 分钟被回收、或进程重启后,下一条消息会重新调用,所以闭包里的变量不能当长期存储,需要存储会话状态可以根据场景使用 3.3 记忆、3.5 小挂件实现。
|
|
87
82
|
|
|
88
|
-
**同一时间只有一个进程**:同一个搭子身份同一时间只有一个进程连着平台,本地调试用的 `<buddyId>-dev` 也一样。新进程连上平台后,原来那个进程会打一条 ERROR 日志说明被顶掉,然后退出、不再重连。本地不小心起了多个进程时,留下的是最后连上平台的那个。
|
|
89
|
-
|
|
90
83
|
**buddy 的会话压缩**:会话历史长到一定程度时,运行时在会话被回收后把早先的对话压成一份摘要写回平台,下次重建时模型看到的是「摘要 + 最近的原文」。开发者不用做任何事,也没有开关。
|
|
91
84
|
|
|
92
85
|
### 2.2 `BuddyOptions`
|
|
@@ -216,8 +209,6 @@ interface Conversation {
|
|
|
216
209
|
}
|
|
217
210
|
```
|
|
218
211
|
|
|
219
|
-
`tools`、`memory`、`dataAccess`、`widget`、`proactive` 已经绑好这场会话,直接用,不用传令牌或拼接口路径。
|
|
220
|
-
|
|
221
212
|
## 3. 平台能力
|
|
222
213
|
|
|
223
214
|
| 能力 | `conversation` 对象方法 | 模型工具(`conversation.tools` 上) |
|
|
@@ -241,11 +232,11 @@ buddy: (conversation) => ({
|
|
|
241
232
|
|
|
242
233
|
### 3.1 资料
|
|
243
234
|
|
|
244
|
-
|
|
235
|
+
搭子随身带的只读资料:把 `.md` 放进工程目录下的 `resources/`,模型先列出有哪些资料,再按需读一篇。资料在进程启动时一次读好,整场会话、每个回合读到的都是同一份。
|
|
245
236
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
237
|
+
**准备工作**
|
|
238
|
+
|
|
239
|
+
资料放在工程目录(`package.json` 所在目录)下的 `resources/`:
|
|
249
240
|
|
|
250
241
|
```
|
|
251
242
|
my-buddy/ 工程目录
|
|
@@ -256,41 +247,63 @@ my-buddy/ 工程目录
|
|
|
256
247
|
└─ src/index.ts 入口脚本
|
|
257
248
|
```
|
|
258
249
|
|
|
259
|
-
|
|
250
|
+
- 资料更改要重启进程,建议 watch `resources/` 实时重启。
|
|
260
251
|
|
|
261
|
-
|
|
252
|
+
**`conversation` 对象方法**
|
|
262
253
|
|
|
263
254
|
| 方法 | 做什么 |
|
|
264
255
|
|---|---|
|
|
265
256
|
| `list()` | 全部资料的路径,相对 `resources/`,按路径排序 |
|
|
266
|
-
| `read(file)` | 读一篇的正文,`file` 照 `list()`
|
|
257
|
+
| `read(file)` | 读一篇的正文,`file` 照 `list()` 给的路径原样填 |
|
|
267
258
|
|
|
268
259
|
```ts
|
|
269
260
|
const notes = conversation.resource.read("research/01-writings.md");
|
|
270
261
|
```
|
|
271
262
|
|
|
263
|
+
**模型工具**
|
|
264
|
+
|
|
265
|
+
把 `conversation.tools.resource_list` 和 `conversation.tools.resource_read` 放进 `initialState.tools`,模型就能先列出有哪些资料,再按路径读一篇。`systemPrompt` 里交代清楚这只搭子带了哪类资料、什么时候该去翻。
|
|
266
|
+
|
|
267
|
+
**限制与异常**
|
|
268
|
+
|
|
272
269
|
- 两个方法都是同步的,读的是进程启动时读好的那份。
|
|
270
|
+
- 没有资料时 `resource_list` 回「没有资料」。
|
|
271
|
+
- `read(file)` 读不存在的那篇时抛异常。
|
|
273
272
|
- 主动服务的回合内也能调。
|
|
274
273
|
|
|
275
274
|
### 3.2 提问
|
|
276
275
|
|
|
277
|
-
让模型发一张题卡请用户选:一个问题加 2 到 4
|
|
276
|
+
让模型发一张题卡请用户选:一个问题加 2 到 4 个可以直接点的选项。私聊、群聊都能用。题卡发出,这一轮就到此结束,等用户操作。
|
|
277
|
+
|
|
278
|
+
**`conversation` 对象方法**
|
|
279
|
+
|
|
280
|
+
没有。`conversation` 不提供发题卡的方法,题卡只由模型发。
|
|
281
|
+
|
|
282
|
+
**模型工具**
|
|
278
283
|
|
|
279
284
|
把 `conversation.tools.ask_question` 放进 `initialState.tools`,模型就能发题卡。
|
|
280
285
|
|
|
281
|
-
- 参数是 `question`(题目,不超过 500 字)和 `options`(2 到 4 个,每个不超过 30
|
|
282
|
-
-
|
|
283
|
-
-
|
|
286
|
+
- 参数是 `question`(题目,不超过 500 字)和 `options`(2 到 4 个,每个不超过 30 字,不能重复)。
|
|
287
|
+
- 题目和选项只写在参数里,回复正文里不再说一遍。
|
|
288
|
+
- 用户点选项,就是用选项原文发一条消息,跟自己打字一样;也可以不点,直接打字回答,或者说别的。
|
|
289
|
+
|
|
290
|
+
**限制与异常**
|
|
291
|
+
|
|
292
|
+
- 参数不合规时模型会看到错误,改了再调。
|
|
293
|
+
- 这一轮读过用户授权数据之后不能再发题卡,调用抛异常,异常信息模型能看到;原因见 3.4 的「数据只在当轮可见」。
|
|
294
|
+
- 主动服务的回合里照常能发,运行时不拦。
|
|
284
295
|
|
|
285
296
|
### 3.3 记忆
|
|
286
297
|
|
|
287
|
-
|
|
298
|
+
这场会话的长期记忆,搭子写给自己的印象,存在平台上,平台只存不读。只在本会话内可见,会话删除时一并清除。
|
|
288
299
|
|
|
289
|
-
|
|
290
|
-
- `write(key, text)`:覆盖写一条。
|
|
291
|
-
- `delete(key)`:删一条,不存在也算成功。
|
|
300
|
+
**`conversation` 对象方法**
|
|
292
301
|
|
|
293
|
-
|
|
302
|
+
| 方法 | 做什么 |
|
|
303
|
+
|---|---|
|
|
304
|
+
| `list()` | 列出全部,按 `key` 字典序 |
|
|
305
|
+
| `write(key, text)` | 覆盖写一条 |
|
|
306
|
+
| `delete(key)` | 删一条,不存在也算成功 |
|
|
294
307
|
|
|
295
308
|
```ts
|
|
296
309
|
interface MemoryEntry {
|
|
@@ -300,46 +313,50 @@ interface MemoryEntry {
|
|
|
300
313
|
}
|
|
301
314
|
```
|
|
302
315
|
|
|
303
|
-
|
|
316
|
+
**模型工具**
|
|
317
|
+
|
|
318
|
+
运行时不提供记忆工具。要让模型自己记东西,按这只搭子要记什么,自己包一个:
|
|
304
319
|
|
|
305
320
|
```ts
|
|
306
321
|
const REMEMBER_PARAMS = Type.Object({ topic: Type.String(), text: Type.String() });
|
|
307
322
|
|
|
308
|
-
await BuddyServer.start({
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
};
|
|
321
|
-
return { initialState: { model: "kimi-k3", systemPrompt: PERSONA, tools: [remember] } };
|
|
322
|
-
},
|
|
323
|
+
await BuddyServer.start((conversation) => {
|
|
324
|
+
const remember: AgentTool<typeof REMEMBER_PARAMS, undefined> = {
|
|
325
|
+
name: "remember",
|
|
326
|
+
label: "正在记下",
|
|
327
|
+
description: "把一条关于对方的印象记下来,同一主题覆盖旧的。",
|
|
328
|
+
parameters: REMEMBER_PARAMS,
|
|
329
|
+
execute: async (_id, { topic, text }) => {
|
|
330
|
+
await conversation.memory.write(topic, text);
|
|
331
|
+
return { content: [{ type: "text", text: "记下了。" }], details: undefined };
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
return { initialState: { model: "kimi-k3", systemPrompt: PERSONA, tools: [remember] } };
|
|
323
335
|
});
|
|
324
336
|
```
|
|
325
337
|
|
|
338
|
+
**限制与异常**
|
|
339
|
+
|
|
340
|
+
- `key` 非空、不含空白与 `/`、最长 64 字;`text` 单条不超过 16 KB;每场会话最多 64 条、合计不超过 256 KB。
|
|
341
|
+
- 超限、`key` 不合法或网络出错时抛异常,异常信息是平台给出的原因,在工具里调用时模型能看到。
|
|
342
|
+
- 这一轮读过用户授权数据之后,运行时拒绝本回合的记忆写入,见 3.4 的「数据只在当轮可见」。
|
|
343
|
+
- 主动服务的回合里跟普通回合一样能读能写,运行时不拦。
|
|
344
|
+
|
|
326
345
|
### 3.4 用户授权数据
|
|
327
346
|
|
|
328
|
-
用户同意后,搭子可以读用户的睡眠、运动等数据。数据按 Dataset 分项,用户逐项同意。搭子拿不到用户的真实 id
|
|
347
|
+
用户同意后,搭子可以读用户的睡眠、运动等数据。数据按 Dataset 分项,用户逐项同意。搭子拿不到用户的真实 id。查到的数据只在当轮可见,见本节末尾。
|
|
329
348
|
|
|
330
349
|
这里有三层不同概念:
|
|
331
350
|
|
|
332
351
|
1. **Runtime 能力目录**:当前 SDK 支持哪些 Dataset,由包导出的 `DATA_ACCESS_CAPABILITIES` 提供。
|
|
333
|
-
2. **搭子数据声明**:这只搭子实际需要其中哪些 Dataset
|
|
352
|
+
2. **搭子数据声明**:这只搭子实际需要其中哪些 Dataset,以及各自用途,写在工程根目录的 `data-access.json`。
|
|
334
353
|
3. **用户 Grant**:用户是否同意这只搭子读取某个已声明 Dataset,由客户端授权流程管理,搭子不能代替用户授予。
|
|
335
354
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
位置和日历是当前会话的一次性授权结果,通过 `data_access_request` 请求,不属于 Dataset,也不写进 `dataRequirements`。HealthKit 数据会形成可重复查询的 Dataset,因此必须先在 `dataRequirements` 中声明。
|
|
355
|
+
位置和日历是当前会话的一次性授权结果,通过 `data_access_request` 请求,不属于 Dataset,也不写进 `data-access.json`。HealthKit 数据会形成可重复查询的 Dataset,因此必须先在配置中声明。
|
|
339
356
|
|
|
340
|
-
|
|
357
|
+
**准备工作**
|
|
341
358
|
|
|
342
|
-
|
|
359
|
+
Runtime 能力目录直接从 Runtime 包导入,开发者工具和项目代码都不需要网络请求:
|
|
343
360
|
|
|
344
361
|
```ts
|
|
345
362
|
import {
|
|
@@ -357,25 +374,26 @@ const sleep = dataAccessCapability("health.sleep");
|
|
|
357
374
|
|
|
358
375
|
这份导出是开发者可选 `id` 的权威来源。新增 Dataset 随 Runtime 版本发布;developer-platform、脚手架和预检都应读取该导出,不再维护一份手写枚举。
|
|
359
376
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
声明写在 `BuddyServer.start` 的初始化参数里,Runtime 在本地展开成带标题、来源和 Schema 版本的完整声明:
|
|
377
|
+
声明写在工程根目录(与 `package.json` 同级)的 `data-access.json`:
|
|
363
378
|
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
{ id: "health.sleep", purpose: "根据你最近的睡眠调整作息建议" }
|
|
368
|
-
]
|
|
369
|
-
|
|
370
|
-
});
|
|
379
|
+
```json
|
|
380
|
+
{
|
|
381
|
+
"requirements": [
|
|
382
|
+
{ "id": "health.sleep", "purpose": "根据你最近的睡眠调整作息建议" }
|
|
383
|
+
]
|
|
384
|
+
}
|
|
371
385
|
```
|
|
372
386
|
|
|
373
|
-
- `id`:从 `DATA_ACCESS_CAPABILITIES`
|
|
387
|
+
- `id`:从 `DATA_ACCESS_CAPABILITIES` 中选。
|
|
374
388
|
- `purpose`:用途,1~200 字。用户看到这句话再决定是否同意。
|
|
375
|
-
-
|
|
376
|
-
-
|
|
389
|
+
- `requirements` 最多 32 项,不允许重复;配置对象和每一项都不接受额外字段。
|
|
390
|
+
- 文件缺失等价于 `{ "requirements": [] }`。Runtime 启动时先严格校验,再把整份声明上传平台;即使为空也会上传,以清除旧声明。上传失败则启动失败。
|
|
391
|
+
- Server 在注册时按声明用途和 Schema 计算版本;`id`、`purpose` 与 Schema 都没变化时保留已有 Grant,变化后要求重新授权。
|
|
392
|
+
- 后续查询、HealthKit 权限卡和主动服务请求只发送 Dataset ID,不再重复携带用途声明。
|
|
377
393
|
- 本地 `.env` 里放的是 CLI 发的开发令牌,进程连上平台后身份是 `<buddyId>-dev`,实际使用形成的授权上下文与正式搭子隔离。
|
|
378
394
|
|
|
395
|
+
可以声明的 Dataset:
|
|
396
|
+
|
|
379
397
|
| Dataset | 内容 | `data.items` 每条的字段 |
|
|
380
398
|
|---|---|---|
|
|
381
399
|
| `health.sleep` | 睡眠,每天一条 | `date`、`totalSleepMinutes`、`sleepStartLocal`、`sleepEndLocal`、`awakeMinutes`、`coreSleepMinutes`、`deepSleepMinutes`、`remSleepMinutes`、`unspecifiedSleepMinutes` |
|
|
@@ -389,34 +407,45 @@ await BuddyServer.start({
|
|
|
389
407
|
|
|
390
408
|
部分字段可能缺失,取值前先判断。
|
|
391
409
|
|
|
392
|
-
|
|
410
|
+
**`conversation` 对象方法**
|
|
411
|
+
|
|
412
|
+
开发者自己的工具直接调用 `conversation.dataAccess`,不用把 `data_access_*` 放进 `initialState.tools`:
|
|
393
413
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
414
|
+
| 方法 | 做什么 |
|
|
415
|
+
|---|---|
|
|
416
|
+
| `query({ datasets, memberId? })` | 查询已授权 Dataset,每个 Dataset 各自一个状态 |
|
|
417
|
+
| `request({ kind, reason, datasets?, calendarDays?, idempotencyKey })` | 在私聊里发数据授权卡,`kind` 取 `healthkit`、`location` 或 `calendar` |
|
|
418
|
+
| `readResult(requestId)` | 按授权卡的 `requestId` 读一次性的位置或日历结果;HealthKit 不走这个接口 |
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
const [sleep] = await conversation.dataAccess.query({ datasets: ["health.sleep"] });
|
|
422
|
+
if (sleep.status === "available") {
|
|
423
|
+
// sleep.data.items 是每天一条的睡眠记录
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
- `datasets`:一次最多 8 个。
|
|
428
|
+
- `memberId`:私聊不传;群聊必传,取群消息开头 `[名字 #m_…]` 里的 `m_…`,查这位成员的数据,传错时抛异常。
|
|
397
429
|
|
|
398
|
-
|
|
430
|
+
**模型工具**
|
|
399
431
|
|
|
400
|
-
|
|
432
|
+
把 `conversation.tools.data_access_query`、`data_access_request`、`data_access_read_result` 放进 `initialState.tools`,模型就能自己查数据、发授权卡。放进去只决定模型能不能调用它们,不等于声明 Dataset,也不等于获得用户授权;没声明 Dataset 时照常注册,查询的每一项都回 `notDeclared`。
|
|
433
|
+
|
|
434
|
+
HealthKit 先查询,缺授权再发卡:
|
|
401
435
|
|
|
402
436
|
```ts
|
|
403
|
-
await BuddyServer.start({
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
].join("\n"),
|
|
416
|
-
tools: [conversation.tools.data_access_query, conversation.tools.data_access_request],
|
|
417
|
-
},
|
|
418
|
-
}),
|
|
419
|
-
});
|
|
437
|
+
await BuddyServer.start((conversation) => ({
|
|
438
|
+
initialState: {
|
|
439
|
+
model: "kimi-k3",
|
|
440
|
+
systemPrompt: [
|
|
441
|
+
"聊到作息时,先用 data_access_query 查询 health.sleep。",
|
|
442
|
+
"返回 notGranted 时,说明当前问题为什么需要数据,再用 data_access_request 发 HealthKit 授权卡。",
|
|
443
|
+
"用户处理授权卡后会开启新回合;再次调用 data_access_query,不要调用 data_access_read_result。",
|
|
444
|
+
"用户拒绝后不要在同一任务里反复申请。",
|
|
445
|
+
].join("\n"),
|
|
446
|
+
tools: [conversation.tools.data_access_query, conversation.tools.data_access_request],
|
|
447
|
+
},
|
|
448
|
+
}));
|
|
420
449
|
```
|
|
421
450
|
|
|
422
451
|
完整链路:
|
|
@@ -431,11 +460,7 @@ data_access_query
|
|
|
431
460
|
→ available + data
|
|
432
461
|
```
|
|
433
462
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
**位置和日历:读取一次性结果**
|
|
437
|
-
|
|
438
|
-
位置和日历不属于 Dataset,不写进 `dataRequirements`。模型先用 `data_access_request` 发卡,用户处理后,隐藏结果会带回这张卡的 `requestId`;再用 `data_access_read_result` 读取。结果只在平台短期保存,读不到时返回 `unavailable`。
|
|
463
|
+
位置和日历不属于 Dataset,不写进 `data-access.json`。模型先用 `data_access_request` 发卡,用户处理后,隐藏结果会带回这张卡的 `requestId`;再用 `data_access_read_result` 读取。结果只在平台短期保存,读不到时返回 `unavailable`。
|
|
439
464
|
|
|
440
465
|
```text
|
|
441
466
|
data_access_request { kind: "location", reason: "查找你附近的地点" }
|
|
@@ -445,19 +470,9 @@ data_access_request { kind: "location", reason: "查找你附近的地点" }
|
|
|
445
470
|
→ available + data,或 unavailable
|
|
446
471
|
```
|
|
447
472
|
|
|
448
|
-
`calendar` 还可传 `calendarDays`,范围为 1~31,默认 7
|
|
449
|
-
|
|
450
|
-
**开发者代码查询 Dataset**
|
|
451
|
-
|
|
452
|
-
```ts
|
|
453
|
-
const [sleep] = await conversation.dataAccess.query({ datasets: ["health.sleep"] });
|
|
454
|
-
if (sleep.status === "available") {
|
|
455
|
-
// sleep.data.items 是每天一条的睡眠记录
|
|
456
|
-
}
|
|
457
|
-
```
|
|
473
|
+
`calendar` 还可传 `calendarDays`,范围为 1~31,默认 7。
|
|
458
474
|
|
|
459
|
-
|
|
460
|
-
- `memberId`:私聊不传;群聊必传,取群消息开头 `[名字 #m_…]` 里的 `m_…`,查这位成员的数据,传错时抛异常。
|
|
475
|
+
**限制与异常**
|
|
461
476
|
|
|
462
477
|
每个 Dataset 各自一个状态,只有 `available` 带 `data`:
|
|
463
478
|
|
|
@@ -469,22 +484,27 @@ if (sleep.status === "available") {
|
|
|
469
484
|
| `disabledInGroup` | 这位成员在这个群里关掉了这项数据 |
|
|
470
485
|
| `unavailable` | 平台上还没有这位用户的这项数据,或数据已过期 |
|
|
471
486
|
|
|
487
|
+
- 查询接口不会把「从未授权」和「用途变化后需重新确认」分成两种模型状态,都会返回 `notGranted`;重新确认原因由用户侧授权页面展示。
|
|
488
|
+
- 一次查询的响应超过 512 KiB 时抛异常,减少 Dataset 再查。
|
|
489
|
+
- 发出授权卡后当前回合结束,等待用户操作。
|
|
490
|
+
- 授权请求第一期只支持私聊;群聊需要引导目标成员去私聊操作。
|
|
491
|
+
|
|
472
492
|
**数据只在当轮可见**
|
|
473
493
|
|
|
474
|
-
|
|
494
|
+
查到的数据只用于这一轮回复:平台不存原文,搭子进程里的那份也在这一轮结束时被抹掉,下一轮读不到。Runtime 按步骤追踪数据流:读取数据之前已经生成的工具步骤正常保留;DataAccess 结果只保留 Dataset 状态;模型看到数据后生成的工具参数、工具结果、工具步骤正文和心声不写进平台,最终给用户看的普通回复只保留正文、不保留心声。
|
|
475
495
|
|
|
476
|
-
|
|
496
|
+
这么设计是为了**用户撤销授权时能真的收回**:确保模型不会记住授权的具体数据。
|
|
477
497
|
|
|
478
498
|
由此有两条实操结论:
|
|
479
499
|
|
|
480
500
|
- **想让某个结论以后还在场,就在回复里说出来**(用户也会看到)。模型对这些数据的记忆只能活在它说过的话里——下次它读到的是「查了 health.sleep,可用」,不是具体数字。
|
|
481
|
-
-
|
|
501
|
+
- **不要把查到的数据写进记忆、题卡、小挂件或别的工具参数**。Runtime 会直接拒绝当前回合的记忆写入和题卡发送,并脱敏后续工具调用;其他开发者自定义持久化能力仍需自行遵守这条边界。
|
|
482
502
|
|
|
483
503
|
### 3.5 小挂件
|
|
484
504
|
|
|
485
|
-
|
|
505
|
+
小挂件是会话里一块给用户看的结构化面板,比如一份菜单。建小挂件、读整个或一个模块、改标题、写记录、删记录、删整个小挂件,并把它发进聊天,模型和开发者代码都能做。
|
|
486
506
|
|
|
487
|
-
|
|
507
|
+
**准备工作**
|
|
488
508
|
|
|
489
509
|
一个小挂件是工程目录下 `widgets/` 里的一个子目录,目录名就是小挂件类型 id。
|
|
490
510
|
|
|
@@ -498,18 +518,22 @@ my-buddy/ 工程目录
|
|
|
498
518
|
└─ src/index.ts
|
|
499
519
|
```
|
|
500
520
|
|
|
501
|
-
|
|
521
|
+
资料小挂件要重启进程,建议 watch `widgets/` 实时重启。
|
|
502
522
|
|
|
503
|
-
|
|
523
|
+
**`conversation` 对象方法**
|
|
504
524
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
525
|
+
| 方法 | 做什么 |
|
|
526
|
+
|---|---|
|
|
527
|
+
| `list()` | 列出这场会话里的小挂件,不带记录 |
|
|
528
|
+
| `create({ type, title, idempotencyKey? })` | 新建一个,回 `{ id }`;同一个 `idempotencyKey` 重复建回同一个 id |
|
|
529
|
+
| `read(id)` | 读整个小挂件,含各模块的记录 |
|
|
530
|
+
| `readModule(id, module)` | 读一个模块,回 `{ records }` |
|
|
531
|
+
| `rename(id, title)` | 改标题,id 不变,聊天里发过的卡片跟着显示新标题 |
|
|
532
|
+
| `writeRecord(id, { module, recordId, data, expectedRevision? })` | 写一条记录,已存在就整条覆盖,回 `{ revision }`;带 `expectedRevision` 时版本号对不上,平台回 409 |
|
|
533
|
+
| `deleteRecord(id, module, recordId)` | 删一条记录 |
|
|
534
|
+
| `delete(id)` | 删整个小挂件连记录;聊天里发过的卡片留着,点开显示已删除 |
|
|
535
|
+
| `setPresentation(id, { summary, imageMediaId })` | 设聊天里卡片的摘要(不超过 500 字)和封面图,`imageMediaId` 传空串去掉封面 |
|
|
536
|
+
| `send(id, { idempotencyKey })` | 发进聊天:放进发送队列就返回,不等平台确认送达;同一个 `idempotencyKey` 重复发只落一条 |
|
|
513
537
|
|
|
514
538
|
```ts
|
|
515
539
|
execute: async (toolCallId, params) => {
|
|
@@ -520,18 +544,16 @@ execute: async (toolCallId, params) => {
|
|
|
520
544
|
},
|
|
521
545
|
```
|
|
522
546
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
| `setPresentation(id, { summary, imageMediaId })` | 设聊天里卡片的摘要(不超过 500 字)和封面图,`imageMediaId` 传空串去掉封面 | `PUT /internal/v1/widgets/:widgetId/presentation` |
|
|
534
|
-
| `send(id, { idempotencyKey })` | 发进聊天:放进发送队列就返回,不等平台确认送达;同一个 `idempotencyKey` 重复发只落一条 | `POST /internal/v1/messages` |
|
|
547
|
+
**模型工具**
|
|
548
|
+
|
|
549
|
+
把要用的 `conversation.tools.widget_*` 放进 `initialState.tools`,模型就能自己建、读、改、发小挂件。
|
|
550
|
+
|
|
551
|
+
- 工具说明里附上各类型的模块与字段,模型不用另外交代形状。
|
|
552
|
+
- 这场会话里已有哪些小挂件,平台会在消息里告诉模型。
|
|
553
|
+
- 平台请求失败时,模型只看到「平台请求失败(HTTP 409),请查询最新状态后再操作」这类提示,不带具体原因。
|
|
554
|
+
- `widget_send` 把小挂件放进发送队列就返回,不等平台确认送达。
|
|
555
|
+
|
|
556
|
+
**限制与异常**
|
|
535
557
|
|
|
536
558
|
- `list` 和 `read` 回的 `canEdit` 为 `false` 的,是别的会话转发来的引用,只能读;改名、写记录、删除、设卡片时平台回 403。
|
|
537
559
|
- `data` 要符合 `schema.json` 里这个模块的 `record`,不符合时平台回 400。
|
|
@@ -546,9 +568,17 @@ execute: async (toolCallId, params) => {
|
|
|
546
568
|
2. 触发条件满足,且没被免打扰时段、冷却时间、每日上限拦下时,平台向搭子发起一次主动服务的回合。
|
|
547
569
|
3. 模型判断要不要开口:要就直接写出发给用户的消息,平台在落库那一刻再过一遍闸门,过了才发给用户,拦下就不发;不要就输出跳过标记,这一轮到此结束,用户什么也看不到。
|
|
548
570
|
|
|
549
|
-
|
|
571
|
+
**`conversation` 对象方法**
|
|
550
572
|
|
|
551
|
-
|
|
573
|
+
| 方法 | 做什么 |
|
|
574
|
+
|---|---|
|
|
575
|
+
| `subscribe(key, request)` | 按稳定 `key` 创建或更新一条订阅,相同 `key` 更新原订阅并重新启用 |
|
|
576
|
+
| `list()` | 当前会话的全部订阅,包括已关闭项 |
|
|
577
|
+
| `unsubscribe(key)` | 关闭指定订阅,回是否找到 |
|
|
578
|
+
| `unsubscribeAll()` | 关闭全部 active 订阅,回关掉几条 |
|
|
579
|
+
| `isRunning()` | 现在是不是主动服务的回合 |
|
|
580
|
+
|
|
581
|
+
它挂在这场会话的 `conversation` 上,凡是拿得到 `conversation` 的地方都能调:自己工具的 `execute` 里、`beforeToolCall` / `afterToolCall` 里、配置工厂函数体里。
|
|
552
582
|
|
|
553
583
|
```ts
|
|
554
584
|
await conversation.proactive.subscribe("weekly-review", {
|
|
@@ -558,20 +588,7 @@ await conversation.proactive.subscribe("weekly-review", {
|
|
|
558
588
|
});
|
|
559
589
|
```
|
|
560
590
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
```ts
|
|
564
|
-
await BuddyServer.start({
|
|
565
|
-
dataRequirements: [{ id: "health.workouts", purpose: "跑完后帮你复盘这次训练" }],
|
|
566
|
-
buddy: (conversation) => ({
|
|
567
|
-
initialState: {
|
|
568
|
-
model: "kimi-k3",
|
|
569
|
-
systemPrompt: "你是跑步搭子。用户表达想坚持训练时,可以用 proactive_subscribe 订阅跑后复盘;用户要求关闭时先查询再关闭。",
|
|
570
|
-
tools: [conversation.tools.proactive_subscribe, conversation.tools.proactive_list, conversation.tools.proactive_unsubscribe, conversation.tools.data_access_query],
|
|
571
|
-
},
|
|
572
|
-
}),
|
|
573
|
-
});
|
|
574
|
-
```
|
|
591
|
+
`subscribe` 的字段:
|
|
575
592
|
|
|
576
593
|
| 字段 | 说明 |
|
|
577
594
|
|---|---|
|
|
@@ -583,11 +600,28 @@ await BuddyServer.start({
|
|
|
583
600
|
| `cooldownMinutes` | 可选,冷却时间,缺省 120,取 0~10080 |
|
|
584
601
|
| `dailyLimit` | 可选,每天最多几次,缺省 3,取 1~20 |
|
|
585
602
|
|
|
586
|
-
|
|
603
|
+
**模型工具**
|
|
604
|
+
|
|
605
|
+
让模型自己决定是否订阅或关闭时,把需要的 `conversation.tools.proactive_*` 放进 `initialState.tools`,字段与上面那张表一样。什么场景可以订阅、何时只能查询或关闭,要在 `systemPrompt` 里说清楚:
|
|
606
|
+
|
|
607
|
+
```ts
|
|
608
|
+
await BuddyServer.start((conversation) => ({
|
|
609
|
+
initialState: {
|
|
610
|
+
model: "kimi-k3",
|
|
611
|
+
systemPrompt: "你是跑步搭子。用户表达想坚持训练时,可以用 proactive_subscribe 订阅跑后复盘;用户要求关闭时先查询再关闭。",
|
|
612
|
+
tools: [conversation.tools.proactive_subscribe, conversation.tools.proactive_list, conversation.tools.proactive_unsubscribe, conversation.tools.data_access_query],
|
|
613
|
+
},
|
|
614
|
+
}));
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
代码订阅和模型订阅二选一或并用,产物都是一条订阅,只是谁决定何时订阅不同。
|
|
618
|
+
|
|
619
|
+
**限制与异常**
|
|
587
620
|
|
|
588
|
-
字段不合法、在群聊里调用,或同一用户对这只搭子已有 20 个 active 订阅时抛异常。
|
|
621
|
+
- 字段不合法、在群聊里调用,或同一用户对这只搭子已有 20 个 active 订阅时抛异常。
|
|
622
|
+
- 关闭订阅时,尚未完成的主动服务会同步取消。
|
|
589
623
|
|
|
590
|
-
|
|
624
|
+
**主动服务的回合,不允许使用部分模型工具**
|
|
591
625
|
|
|
592
626
|
主动服务的回合里工具清单一个字都不动,平台工具和你自己的工具都照常执行,运行时不按回合性质拦任何一个。有时我们想禁用部分模型工具,比如一些写操作,可以使用 `beforeToolCall` 的钩子函数,用 `conversation.proactive.isRunning()` 能判断当前是不是主动服务的回合:
|
|
593
627
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/conversation/agent.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAExF,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7D,OAAO,EAAkB,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAKrD,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAA8B,eAAe,EAAE,iBAAiB,EAAiB,MAAM,aAAa,CAAC;AAE5I,MAAM,WAAW,SAAS;IACzB,mCAAmC;IACnC,cAAc,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACrD,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,uDAAuD;IACvD,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,gBAAgB,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,OAAO,EAAE,YAAY,CAAC;IACtB,GAAG,EAAE,WAAW,CAAC;IACjB,oCAAoC;IACpC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACnD;AAED,wDAAwD;AACxD,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,KAAK,CAAC;IACb,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,eAAe,EAAE,sBAAsB,CAAC;IACxC,iFAAiF;IACjF,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,kFAAkF;AAClF,wBAAsB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/conversation/agent.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAExF,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG7D,OAAO,EAAkB,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAKrD,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAA8B,eAAe,EAAE,iBAAiB,EAAiB,MAAM,aAAa,CAAC;AAE5I,MAAM,WAAW,SAAS;IACzB,mCAAmC;IACnC,cAAc,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACrD,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,CAAC;IAClB,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,uDAAuD;IACvD,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,gBAAgB,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,OAAO,EAAE,YAAY,CAAC;IACtB,GAAG,EAAE,WAAW,CAAC;IACjB,oCAAoC;IACpC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACnD;AAED,wDAAwD;AACxD,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,KAAK,CAAC;IACb,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,eAAe,EAAE,sBAAsB,CAAC;IACxC,iFAAiF;IACjF,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,kFAAkF;AAClF,wBAAsB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CAwMxE;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,iHAAuB,CAAC;AAEtD,iEAAiE;AACjE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,EAAE,CAErF;AAED,iDAAiD;AACjD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAQxD"}
|
|
@@ -43,7 +43,7 @@ export async function createAgent(deps) {
|
|
|
43
43
|
proactive_unsubscribe_all: proactiveUnsubscribeAll,
|
|
44
44
|
resource_list: resourceListTool(deps.resources),
|
|
45
45
|
resource_read: resourceReadTool(deps.resources),
|
|
46
|
-
ask_question: askQuestionTool(deps.enqueueMessage, waitForUser),
|
|
46
|
+
ask_question: askQuestionTool(deps.enqueueMessage, waitForUser, dataAccessUsage),
|
|
47
47
|
data_access_query: dataAccessTool(client, deps.key, dataAccessUsage, deps.dataRequirements),
|
|
48
48
|
data_access_request: dataAccessRequest,
|
|
49
49
|
data_access_read_result: dataAccessReadResult,
|
|
@@ -73,15 +73,20 @@ export async function createAgent(deps) {
|
|
|
73
73
|
},
|
|
74
74
|
memory: {
|
|
75
75
|
list: () => client.memoryList(deps.key),
|
|
76
|
-
write: (name, text) =>
|
|
76
|
+
write: async (name, text) => {
|
|
77
|
+
if (dataAccessUsage.isPersistenceTainted()) {
|
|
78
|
+
throw new Error("本回合已经读取用户授权数据,不能写入长期记忆;请只在当前回复中使用,并避免复述敏感数值。");
|
|
79
|
+
}
|
|
80
|
+
await client.memoryWrite(deps.key, name, text);
|
|
81
|
+
},
|
|
77
82
|
delete: (name) => client.memoryDelete(deps.key, name),
|
|
78
83
|
},
|
|
79
84
|
// 经 dataAccessUsage 查:开发者工具里查过的那一轮,写回平台和留在历史里的内容也会脱敏;主动服务回合里改走执行令牌
|
|
80
85
|
dataAccess: {
|
|
81
|
-
query: ({ datasets, memberId }) => dataAccessUsage.query(client, deps.key, datasets,
|
|
86
|
+
query: ({ datasets, memberId }) => dataAccessUsage.query(client, deps.key, datasets, memberId),
|
|
82
87
|
request: async (input) => {
|
|
83
88
|
const validated = validateDataAccessRequest(input, deps.key, deps.dataRequirements);
|
|
84
|
-
deps.enqueueMessage({ type: "permissionRequest", permissionRequest: validated,
|
|
89
|
+
deps.enqueueMessage({ type: "permissionRequest", permissionRequest: validated, text: validated.reason, idempotencyKey: input.idempotencyKey });
|
|
85
90
|
waitForUser();
|
|
86
91
|
},
|
|
87
92
|
readResult: async (requestId) => {
|
|
@@ -93,7 +98,7 @@ export async function createAgent(deps) {
|
|
|
93
98
|
proactive: {
|
|
94
99
|
subscribe: (key, request) => {
|
|
95
100
|
const validated = validateProactiveSubscribe(key, request, deps.dataRequirements);
|
|
96
|
-
return client.subscribeProactive(deps.key, validated.key, validated.request
|
|
101
|
+
return client.subscribeProactive(deps.key, validated.key, validated.request);
|
|
97
102
|
},
|
|
98
103
|
list: () => client.listProactive(deps.key),
|
|
99
104
|
unsubscribe: (key) => client.unsubscribeProactive(deps.key, key),
|