@glodon-aiot/bot-client-sdk 3.25.1-alpha.7 → 3.26.0-alpha.5
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 +572 -98
- package/dist/es/AgentSession.mjs +230 -165
- package/dist/es/BotClient.mjs +186 -136
- package/dist/es/Config.mjs +89 -26
- package/dist/es/Session.mjs +12 -0
- package/dist/es/api/index.mjs +42 -33
- package/dist/es/builtinTools/browserRuntime/BrowserCommandRunner.mjs +201 -0
- package/dist/es/builtinTools/browserRuntime/PythonRunner.mjs +356 -0
- package/dist/es/builtinTools/browserRuntime/WorkspaceBashFs.mjs +350 -0
- package/dist/es/builtinTools/browserRuntime/assets.mjs +9 -0
- package/dist/es/builtinTools/browserRuntime/loadBash.mjs +17 -0
- package/dist/es/builtinTools/browserRuntime/pyodideRunner.py.mjs +96 -0
- package/dist/es/builtinTools/browserRuntime/pyodideWorker.mjs +242 -0
- package/dist/es/builtinTools/command.mjs +56 -0
- package/dist/es/builtinTools/constants.mjs +10 -0
- package/dist/es/builtinTools/createBuiltinToolsPlugin.mjs +236 -0
- package/dist/es/builtinTools/overview.mjs +77 -0
- package/dist/es/builtinTools/paths.mjs +63 -0
- package/dist/es/builtinTools/schemas.mjs +216 -0
- package/dist/es/builtinTools/tools.mjs +471 -0
- package/dist/es/index.mjs +69 -9
- package/dist/es/localFiles/adapters/FileSystemAccessAdapter.mjs +264 -0
- package/dist/es/localFiles/adapters/HostBridgeAdapter.mjs +233 -0
- package/dist/es/localFiles/adapters/MemoryWorkspaceAdapter.mjs +197 -0
- package/dist/es/localFiles/adapters/WorkspaceFileAdapter.mjs +54 -0
- package/dist/es/localFiles/adapters/fsaTypes.mjs +9 -0
- package/dist/es/localFiles/constants.mjs +47 -0
- package/dist/es/localFiles/createLocalFilesPlugin.mjs +255 -0
- package/dist/es/localFiles/errors.mjs +57 -0
- package/dist/es/localFiles/host/connectHostBridge.mjs +54 -0
- package/dist/es/localFiles/host/protocol.mjs +27 -0
- package/dist/es/localFiles/limits.mjs +22 -0
- package/dist/es/localFiles/schemas.mjs +312 -0
- package/dist/es/localFiles/security/downloadUrlPolicy.mjs +39 -0
- package/dist/es/localFiles/security/glob.mjs +28 -0
- package/dist/es/localFiles/security/pathPolicy.mjs +59 -0
- package/dist/es/localFiles/security/sensitiveFiles.mjs +115 -0
- package/dist/es/localFiles/tools/readTools.mjs +333 -0
- package/dist/es/localFiles/tools/runtime.mjs +108 -0
- package/dist/es/localFiles/tools/transferTools.mjs +228 -0
- package/dist/es/localFiles/tools/writeTools.mjs +173 -0
- package/dist/es/localFiles/transfer/SessionFileTransfer.mjs +111 -0
- package/dist/es/localFiles/validate.mjs +66 -0
- package/dist/es/localFiles/workspace/LocalWorkspace.mjs +358 -0
- package/dist/es/localFiles/workspace/handleStore.mjs +79 -0
- package/dist/es/localFiles/workspace/watchDirectory.mjs +38 -0
- package/dist/es/package.json.mjs +38 -29
- package/dist/lib/index.js +373 -5
- package/dist/src/AgentSession.d.ts +9 -0
- package/dist/src/BotClient.d.ts +12 -2
- package/dist/src/Config.d.ts +22 -0
- package/dist/src/Session.d.ts +5 -1
- package/dist/src/api/index.d.ts +3 -1
- package/dist/src/builtinTools/browserRuntime/BrowserCommandRunner.d.ts +26 -0
- package/dist/src/builtinTools/browserRuntime/PythonRunner.d.ts +67 -0
- package/dist/src/builtinTools/browserRuntime/WorkspaceBashFs.d.ts +51 -0
- package/dist/src/builtinTools/browserRuntime/assets.d.ts +13 -0
- package/dist/src/builtinTools/browserRuntime/loadBash.d.ts +3 -0
- package/dist/src/builtinTools/browserRuntime/pyodideWorker.d.ts +44 -0
- package/dist/src/builtinTools/command.d.ts +26 -0
- package/dist/src/builtinTools/constants.d.ts +17 -0
- package/dist/src/builtinTools/createBuiltinToolsPlugin.d.ts +11 -0
- package/dist/src/builtinTools/overview.d.ts +5 -0
- package/dist/src/builtinTools/paths.d.ts +15 -0
- package/dist/src/builtinTools/schemas.d.ts +3 -0
- package/dist/src/builtinTools/tools.d.ts +60 -0
- package/dist/src/index.d.ts +4 -2
- package/dist/src/localFiles/adapters/FileSystemAccessAdapter.d.ts +20 -0
- package/dist/src/localFiles/adapters/HostBridgeAdapter.d.ts +43 -0
- package/dist/src/localFiles/adapters/MemoryWorkspaceAdapter.d.ts +21 -0
- package/dist/src/localFiles/adapters/WorkspaceFileAdapter.d.ts +40 -0
- package/dist/src/localFiles/adapters/fsaTypes.d.ts +41 -0
- package/dist/src/localFiles/constants.d.ts +51 -0
- package/dist/src/localFiles/createLocalFilesPlugin.d.ts +11 -0
- package/dist/src/localFiles/createLocalPythonPlugin.d.ts +12 -0
- package/dist/src/localFiles/errors.d.ts +34 -0
- package/dist/src/localFiles/host/connectHostBridge.d.ts +6 -0
- package/dist/src/localFiles/host/protocol.d.ts +46 -0
- package/dist/src/localFiles/index.d.ts +22 -0
- package/dist/src/localFiles/limits.d.ts +3 -0
- package/dist/src/localFiles/schemas.d.ts +29 -0
- package/dist/src/localFiles/security/downloadUrlPolicy.d.ts +4 -0
- package/dist/src/localFiles/security/glob.d.ts +3 -0
- package/dist/src/localFiles/security/pathPolicy.d.ts +14 -0
- package/dist/src/localFiles/security/sensitiveFiles.d.ts +23 -0
- package/dist/src/localFiles/tools/readTools.d.ts +7 -0
- package/dist/src/localFiles/tools/runtime.d.ts +43 -0
- package/dist/src/localFiles/tools/transferTools.d.ts +4 -0
- package/dist/src/localFiles/tools/writeTools.d.ts +4 -0
- package/dist/src/localFiles/transfer/SessionFileTransfer.d.ts +40 -0
- package/dist/src/localFiles/types.d.ts +319 -0
- package/dist/src/localFiles/validate.d.ts +2 -0
- package/dist/src/localFiles/workspace/LocalWorkspace.d.ts +111 -0
- package/dist/src/localFiles/workspace/handleStore.d.ts +11 -0
- package/dist/src/localFiles/workspace/watchDirectory.d.ts +7 -0
- package/package.json +13 -4
package/README.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# @glodon-aiot/bot-client-sdk
|
|
2
2
|
|
|
3
|
-
AIoT
|
|
3
|
+
AIoT 应用客户端 SDK,支持 Agent 应用和对话型应用。会话、消息、产出物和客户端插件的执行由这个包负责,界面由接入方实现。下面按界面需要实现的部分来写。
|
|
4
|
+
|
|
5
|
+
| 能力 | 适用应用 | 运行环境 | 见 |
|
|
6
|
+
| --- | --- | --- | --- |
|
|
7
|
+
| 会话与流式对话 | Agent、对话型 | 浏览器、端侧宿主 | [对话界面](#对话界面) |
|
|
8
|
+
| 会话产出物 | Agent、对话型 | 浏览器、端侧宿主 | [产出物](#产出物) |
|
|
9
|
+
| 业务插件、本地文件 | Agent | 浏览器、端侧宿主 | [Agent 插件](#agent-插件) |
|
|
10
|
+
| 命令与脚本(shell / run_file) | Agent | 浏览器运行时或端侧宿主沙箱 | [命令和脚本](#命令和脚本) |
|
|
11
|
+
| 本地 Python(`localPython` / `run_python`) | Agent | **优先端侧宿主**;浏览器可用 Pyodide | [命令和脚本](#命令和脚本) |
|
|
12
|
+
|
|
13
|
+
浏览器指现代浏览器(命令运行时另需能加载公司 OBS 上的 just-bash / Pyodide)。端侧宿主是内嵌此页面、负责读写本地工程并启动 `@glodon-aiot/workspace-sandbox` 的桌面程序,见[宿主通道](#宿主通道)。
|
|
14
|
+
|
|
15
|
+
集成方总览见仓库内 [`bot-client-ui/docs/agent-integration-handbook.md`](../bot-client-ui/docs/agent-integration-handbook.md)。
|
|
4
16
|
|
|
5
17
|
## 安装
|
|
6
18
|
|
|
@@ -10,148 +22,610 @@ yarn add @glodon-aiot/bot-client-sdk
|
|
|
10
22
|
npm install @glodon-aiot/bot-client-sdk
|
|
11
23
|
```
|
|
12
24
|
|
|
13
|
-
|
|
25
|
+
```ts
|
|
26
|
+
import { BotClient } from '@glodon-aiot/bot-client-sdk';
|
|
27
|
+
```
|
|
14
28
|
|
|
15
|
-
|
|
16
|
-
主客户端类,负责与 AIoT 平台建立连接和管理会话。
|
|
29
|
+
类型、工作区、工具 Schema 和错误码都从包入口导出。
|
|
17
30
|
|
|
18
|
-
|
|
19
|
-
会话类,分为两种类型:
|
|
20
|
-
- `AgentSession`: 代理会话
|
|
21
|
-
- `DialogSession`: 对话会话
|
|
31
|
+
## 创建客户端
|
|
22
32
|
|
|
23
|
-
|
|
33
|
+
```ts
|
|
34
|
+
const client = new BotClient(token, {
|
|
35
|
+
env: 'dev', // 'dev' | 'test' | 'prod' | 'local',决定默认 API 地址
|
|
36
|
+
// apiRoot: 'https://example.com/api/cvforce', // 传入后覆盖 env 对应的地址
|
|
37
|
+
stream: true, // 省略时 Agent 和对话型应用默认流式
|
|
38
|
+
// plugins、builtinPlugins 见「Agent 插件」
|
|
39
|
+
});
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
client.addEventListener('error', (error) => {
|
|
42
|
+
// 配置问题从这里抛出,例如插件编码冲突、backend 写错
|
|
43
|
+
console.error(error);
|
|
44
|
+
});
|
|
26
45
|
|
|
27
|
-
|
|
28
|
-
|
|
46
|
+
client.addEventListener('ready', () => {
|
|
47
|
+
// 应用信息已加载。loadSession、sessions、application 需要等这个事件
|
|
48
|
+
});
|
|
49
|
+
```
|
|
29
50
|
|
|
30
|
-
|
|
31
|
-
const config = {
|
|
32
|
-
apiRoot: 'https://api.example.com' // AIoT API 根地址
|
|
33
|
-
};
|
|
51
|
+
`token` 是 AIoT 资源令牌,其中需要带有应用 ID。应用类型由平台上的应用决定,`loadSession` 会按类型创建对应的会话。
|
|
34
52
|
|
|
35
|
-
|
|
53
|
+
## 对话界面
|
|
36
54
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
界面自己保存渲染状态,数据以 `session.messages` 为准。下面的事件都带当前消息或消息列表,收到后重绘即可。
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const session = await client.loadSession(sessionId); // 空字符串表示新建会话
|
|
59
|
+
|
|
60
|
+
session.addEventListener('history:loaded', () => render(session.messages));
|
|
61
|
+
session.addEventListener('message:new', () => render(session.messages));
|
|
62
|
+
session.addEventListener('message:updated', () => render(session.messages));
|
|
63
|
+
session.addEventListener('message:content', () => render(session.messages)); // 流式增量
|
|
64
|
+
session.addEventListener('answer:finish', () => render(session.messages));
|
|
65
|
+
session.addEventListener('message:error', (error) => showError(error));
|
|
66
|
+
session.addEventListener('message:action', () => render(session.messages)); // 工具调用步骤
|
|
67
|
+
|
|
68
|
+
await session.fetchMessages(); // 拉取历史,结果写入 session.messages
|
|
69
|
+
await session.send({ text: '你好' }, { stream: true });
|
|
70
|
+
|
|
71
|
+
// 停止回答。进行中的客户端插件调用会被取消,确认弹窗应随之关闭
|
|
72
|
+
session.suspend();
|
|
41
73
|
```
|
|
42
74
|
|
|
43
|
-
|
|
75
|
+
会话列表:
|
|
44
76
|
|
|
45
|
-
```
|
|
46
|
-
// 获取会话列表
|
|
77
|
+
```ts
|
|
47
78
|
const sessions = await client.getSessions();
|
|
79
|
+
const current = await client.loadSession(id); // 同时把它设为活跃会话
|
|
80
|
+
await client.removeSession(id, true);
|
|
81
|
+
```
|
|
48
82
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
83
|
+
| 事件 | 界面上做什么 |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `history:loaded` | 用 `session.messages` 渲染历史 |
|
|
86
|
+
| `message:new` / `message:updated` | 重绘消息列表 |
|
|
87
|
+
| `message:content` | 流式更新当前回答 |
|
|
88
|
+
| `message:action` | 刷新当前回答里的工具步骤,见[展示工具步骤](#展示工具步骤) |
|
|
89
|
+
| `answer:finish` | 结束「正在回答」,允许再次发送 |
|
|
90
|
+
| `message:error` / `error` | 展示错误 |
|
|
91
|
+
|
|
92
|
+
发送开始后 `session.frozen === true`,此时 `send` 会失败,输入框保持禁用,直到 `answer:finish`。
|
|
53
93
|
|
|
54
|
-
|
|
55
|
-
|
|
94
|
+
## 产出物
|
|
95
|
+
|
|
96
|
+
任务结束后可以拉取这次会话留下的文件。列表是会话级的,不是某一条消息的附件。没有产出物时返回空数组,不抛错。
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import type { ISessionArtifact } from '@glodon-aiot/bot-client-sdk';
|
|
100
|
+
|
|
101
|
+
const artifacts: ISessionArtifact[] = await session.getArtifacts();
|
|
102
|
+
// 或按会话 id。省略 id 时用当前活跃会话
|
|
103
|
+
const same = await client.getArtifacts(session.id);
|
|
56
104
|
```
|
|
57
105
|
|
|
58
|
-
|
|
106
|
+
| 字段 | 含义 |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| `name` | 文件名 |
|
|
109
|
+
| `size` | 字节数 |
|
|
110
|
+
| `url` | 可访问地址 |
|
|
111
|
+
|
|
112
|
+
`Session.getArtifacts()` 使用该会话自己的应用 id 和会话 id,两者缺失时抛错。`BotClient.getArtifacts(sessionId?)` 要等 `ready`。不传 `sessionId` 且没有活跃会话时抛错。已加载的会话走 `Session.getArtifacts()`,否则直接请求平台。
|
|
113
|
+
|
|
114
|
+
一般在任务成功结束(`index === -1` 且 `code === 200`)和用户中止回答(`index === -2`)后调用。
|
|
115
|
+
|
|
116
|
+
## Agent 插件
|
|
117
|
+
|
|
118
|
+
Agent 应用里,平台上标记为**客户端执行**的插件函数由 SDK 在页面里调用,结果再回传给平台。对话型应用不调用客户端插件。
|
|
119
|
+
|
|
120
|
+
插件分两类,都在创建客户端时配置:
|
|
121
|
+
|
|
122
|
+
| 配置 | 内容 | `pluginCode` |
|
|
123
|
+
| --- | --- | --- |
|
|
124
|
+
| `plugins` | 接入方自己实现的业务插件 | 平台分配,作为 key |
|
|
125
|
+
| `builtinPlugins` | SDK 内置的工作区、命令和技能工具 | 固定 `builtin_tools`,不用传 |
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const client = new BotClient(token, {
|
|
129
|
+
env: 'dev',
|
|
130
|
+
plugins: {
|
|
131
|
+
biz_plugin_code: {
|
|
132
|
+
query_project: async (params, callback, context) => {
|
|
133
|
+
const data = await queryProject(params, context.signal);
|
|
134
|
+
callback({ code: 200, message: '查询到的项目信息', data });
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
builtinPlugins: {
|
|
139
|
+
localFiles: { enabled: true },
|
|
140
|
+
localPython: { enabled: true },
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 业务插件
|
|
146
|
+
|
|
147
|
+
`plugins` 的 key 是平台上的 `pluginCode`,值是 `functionCode` 到函数的映射。SDK 收到客户端执行的调用时,以 `(params, callback, context)` 调用对应函数:
|
|
148
|
+
|
|
149
|
+
| 参数 | 含义 |
|
|
150
|
+
| --- | --- |
|
|
151
|
+
| `params` | 模型传入的参数,已解析为对象 |
|
|
152
|
+
| `callback` | 回传结果,`{ code, message, data? }`。`message` 会交给模型理解结果。只认第一次调用 |
|
|
153
|
+
| `context` | `requestId`、`applicationId`、`sessionId`、`pluginCode`、`functionCode`、`signal`(类型 `ToolContext`) |
|
|
154
|
+
|
|
155
|
+
停止回答或会话失活时 `signal` 变为 aborted,之后的 `callback` 不再回传。函数抛错或返回的 Promise 被拒绝时回传 500。平台调用了没有注册的插件或函数时回传 404。
|
|
156
|
+
|
|
157
|
+
### 内置插件
|
|
158
|
+
|
|
159
|
+
`localFiles` 或 `localPython` 任一启用后,SDK 把同一套内置工具注册到 `pluginCode` `builtin_tools`。`client.localWorkspace` 只在 `localFiles.enabled === true` 时存在。
|
|
59
160
|
|
|
60
|
-
|
|
61
|
-
const session = await client.loadSession('session-id');
|
|
161
|
+
`shell` / `run_file` / `run_skill_script` 按[命令和脚本](#命令和脚本)选择执行器:注入的 `commandRunner` → 宿主声明 `sandbox-exec`(协议一致)→ 浏览器 just-bash / Pyodide(默认开启)→ 否则 501。
|
|
62
162
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
163
|
+
业务 `plugins` 里如果已经有 `builtin_tools` 这个 key,内置工具不会注册,`error` 事件会带上原因。同一页面只支持一个启用了内置插件的 `BotClient`(插件表是全局配置)。
|
|
164
|
+
|
|
165
|
+
平台上注册该插件时,`functionCode` 不可改名。参数定义从 `BUILTIN_TOOL_SCHEMAS` 读取:
|
|
166
|
+
|
|
167
|
+
| functionCode | 名称 | 运行环境 |
|
|
168
|
+
| --- | --- | --- |
|
|
169
|
+
| `shell` | 执行命令 | 浏览器运行时或端侧宿主沙箱 |
|
|
170
|
+
| `read_file` | 读取文件 | 浏览器、端侧宿主 |
|
|
171
|
+
| `write_file` | 写入文件 | 浏览器、端侧宿主 |
|
|
172
|
+
| `append_file` | 追加文件 | 浏览器、端侧宿主 |
|
|
173
|
+
| `run_file` | 执行文件 | 浏览器运行时(`.py` / `.sh`)或端侧宿主(另含 `.js`) |
|
|
174
|
+
| `edit_file` | 修改文件 | 浏览器、端侧宿主 |
|
|
175
|
+
| `grep` | 搜索文件 | 浏览器、端侧宿主 |
|
|
176
|
+
| `find` | 查找文件 | 浏览器、端侧宿主 |
|
|
177
|
+
| `load_skill` | 加载技能 | 浏览器、端侧宿主 |
|
|
178
|
+
| `read_skill_resource` | 读取技能资源 | 浏览器、端侧宿主 |
|
|
179
|
+
| `run_skill_script` | 运行技能脚本 | 同 `run_file` |
|
|
180
|
+
|
|
181
|
+
### 展示工具步骤
|
|
182
|
+
|
|
183
|
+
`message:action` 里出现内置工具步骤时,用已注册的插件编码和 `BUILTIN_TOOL_SCHEMAS` 换成中文名,只展示相对路径和参数,不展示工程的绝对路径:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
import {
|
|
187
|
+
BUILTIN_TOOL_SCHEMAS,
|
|
188
|
+
getRegisteredLocalFilesPluginCode,
|
|
189
|
+
getRegisteredLocalPythonPluginCode,
|
|
190
|
+
} from '@glodon-aiot/bot-client-sdk';
|
|
191
|
+
|
|
192
|
+
const pluginCodes = [getRegisteredLocalFilesPluginCode(), getRegisteredLocalPythonPluginCode()];
|
|
193
|
+
const schema = pluginCodes.includes(pluginCode)
|
|
194
|
+
? BUILTIN_TOOL_SCHEMAS[funcCode as keyof typeof BUILTIN_TOOL_SCHEMAS]
|
|
195
|
+
: undefined;
|
|
196
|
+
// schema 存在时,展示 schema.name 和参数里的 path / destPath
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
业务插件的步骤按接入方自己的规则展示。
|
|
200
|
+
|
|
201
|
+
## 本地工作文件夹
|
|
202
|
+
|
|
203
|
+
内置插件 `localFiles`。应用中的助手只能访问一个文件夹:网页里由用户选择,或由宿主指定。
|
|
204
|
+
|
|
205
|
+
**路径约定(以当前 Schema / 代码为准):**
|
|
206
|
+
|
|
207
|
+
- 工具参数使用工作区内以 `/` 开头的绝对路径,或相对工作区根的相对路径,例如 `/说明.md`、`notes/a.md`。
|
|
208
|
+
- **不要**使用或新建 `input`、`output`、`uploads`、`tmp` 作为约定根目录。
|
|
209
|
+
- 技能在 `skills/<名称>/` 下,只通过技能工具访问。
|
|
210
|
+
- `/dev`、`/usr`、`/bin` 仍是系统路径。写进脚本文件里的路径请用相对路径。
|
|
211
|
+
|
|
212
|
+
网页里由用户选择文件夹时依赖 File System Access API,只在 Chromium 内核(Chrome、Edge、WebView2)的安全上下文(https 或 localhost)中可用。端侧宿主自己掌握工程目录时,页面不再弹选择框,见[宿主通道](#宿主通道)。
|
|
213
|
+
|
|
214
|
+
### 开启
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
const client = new BotClient(token, {
|
|
218
|
+
env: 'dev',
|
|
219
|
+
builtinPlugins: {
|
|
220
|
+
localFiles: {
|
|
221
|
+
enabled: true,
|
|
222
|
+
// 'file-system-access'(默认):网页里弹出目录选择框
|
|
223
|
+
// 'host-bridge':目录由宿主推送,页面不弹选择框
|
|
224
|
+
backend: 'file-system-access',
|
|
225
|
+
onApprovalRequired: async (request) => openApprovalDialog(request),
|
|
226
|
+
// 只能追加,不能放宽基线。见下文「安全与限额」
|
|
227
|
+
sensitiveRulesAppend: [],
|
|
228
|
+
downloadUrlAllowlistAppend: [],
|
|
229
|
+
// 只能调小,大于默认值的项按默认值处理
|
|
230
|
+
limits: { maxReadBytes: 512 * 1024 },
|
|
231
|
+
// 为 true 时 search_text 可以不指定子目录、搜整个工作区。默认 false
|
|
232
|
+
allowWorkspaceWideTextSearch: false,
|
|
233
|
+
// 无宿主沙箱时,是否在页面里跑 shell / python(默认 true)
|
|
234
|
+
commandRuntime: {
|
|
235
|
+
browser: {
|
|
236
|
+
enabled: true,
|
|
237
|
+
// bashModuleURL / pyodideIndexURL 可覆盖公司 OBS 默认地址
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
68
242
|
});
|
|
69
243
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
244
|
+
const workspace = client.localWorkspace;
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`transfer` 仍可供旧的上传下载实现使用。当前注册的内置工具不走这套传输。宿主沙箱是否启用由宿主 `capabilities` 声明决定,不在 `commandRuntime` 里配置。
|
|
248
|
+
|
|
249
|
+
### 十一个工具
|
|
250
|
+
|
|
251
|
+
路径见上文约定。`..`、反斜杠、越出工作区的路径返回 403。
|
|
252
|
+
|
|
253
|
+
**`read_file`** 读取 UTF-8 文本。`start_line` / `end_line` 从 1 计、含端点。不超过 81920 字节时按原文返回。超出后,JSON、XML、SVG、CSV、TSV 返回结构概览和短预览,`truncated` 为 true;指定了行范围则返回该片段。PDF 和图片只返回摘要,不返回正文。
|
|
254
|
+
|
|
255
|
+
**`write_file`** 覆盖写入。`content` 按 UTF-8 计不超过 81920 字节,超出返回 413。父目录会自动创建。需确认。
|
|
256
|
+
|
|
257
|
+
**`append_file`** 把文本接到文件末尾,文件不存在时创建。追加本身不设字节上限。
|
|
258
|
+
|
|
259
|
+
**`edit_file`** 把文件中第一处与 `old_text` 完全相同的内容换成 `new_text`。找不到时,`message` 里带最接近片段的起始行号和原文。
|
|
260
|
+
|
|
261
|
+
**`grep`** 在目录中递归搜索,`pattern` 按 grep 基本正则。`data.matches` 是带路径和行号的文本。
|
|
262
|
+
|
|
263
|
+
**`find`** 按文件名通配查找,`name` 等同 `find -name`。`data.files` 是匹配路径,一行一个。
|
|
264
|
+
|
|
265
|
+
**`shell`** 执行一条 shell 命令(每次新 shell,只保留上次 cwd)。起始目录是工作区根。命令不超过 20000 字。执行器见[命令和脚本](#命令和脚本)。工具 `code` 为 0 时进程退出码在 `data.exitCode`。
|
|
266
|
+
|
|
267
|
+
**`run_file`** 执行已存在的脚本。浏览器:`.py`(Pyodide)、`.sh`(just-bash);`.js` / `node` 明确不支持。宿主沙箱:另支持 `.js`(沙箱自带 Node)。
|
|
268
|
+
|
|
269
|
+
**`load_skill`** 读取 `skills/<名称>/SKILL.md`。
|
|
270
|
+
|
|
271
|
+
**`read_skill_resource`** 读取该技能目录内的相对路径。
|
|
272
|
+
|
|
273
|
+
**`run_skill_script`** 执行技能脚本,`args` 是位置参数数组。工作目录为工作区根(与当前命令执行器一致)。
|
|
274
|
+
|
|
275
|
+
### 浏览器里授权
|
|
276
|
+
|
|
277
|
+
`backend` 为 `file-system-access`(默认)时,`BotClient` 构造时会自己 `restore()`,刷新页面后能恢复上次的文件夹。界面订阅 `workspace:changed`,按 `status` 画按钮。`pick()` 和 `requestPermission()` 必须在点击回调里同步调用,中间不能有 `await`,否则浏览器会拒绝弹出选择框。
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
workspace.on('workspace:changed', ({ status, displayName }) => {
|
|
281
|
+
renderFolder(status, displayName);
|
|
73
282
|
});
|
|
283
|
+
|
|
284
|
+
selectButton.onclick = () => {
|
|
285
|
+
workspace.pick().catch(console.error);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
restoreButton.onclick = () => {
|
|
289
|
+
workspace.requestPermission().catch(console.error);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
closeButton.onclick = () => {
|
|
293
|
+
workspace.clear().catch(console.error);
|
|
294
|
+
};
|
|
74
295
|
```
|
|
75
296
|
|
|
76
|
-
|
|
297
|
+
| status | 含义 | 按钮 |
|
|
298
|
+
| --- | --- | --- |
|
|
299
|
+
| `unsupported` | 当前环境没有目录选择能力 | 提示不可用 |
|
|
300
|
+
| `none` | 还没选过 | 「选择工作文件夹」→ `pick()` |
|
|
301
|
+
| `prompt` | 有上次的文件夹,需要重新授权(例如浏览器重启后) | 「恢复访问」→ `requestPermission()` |
|
|
302
|
+
| `granted` | 可用,`displayName` 是文件夹名 | 「更换」→ `pick()`,「关闭」→ `clear()` |
|
|
303
|
+
| `denied` | 用户拒绝了授权 | 「选择工作文件夹」→ `pick()` |
|
|
304
|
+
|
|
305
|
+
`pick()` 在用户取消选择框时返回 `false`,状态保持不变。`displayName` 只有文件夹名,没有绝对路径。
|
|
306
|
+
|
|
307
|
+
第一次选择前先给用户看一段外发说明:助手会读取该文件夹,内容可能发送给模型;修改、上传、下载前会再确认。SDK 只记住「已经看过」,文案由界面决定。看过之后直接 `pick()`。
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
selectButton.onclick = () => {
|
|
311
|
+
if (disclosureAcked) {
|
|
312
|
+
workspace.pick().catch(console.error);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
openDisclosure({
|
|
316
|
+
onContinue: () => {
|
|
317
|
+
// 仍然要在这次点击里同步调用 pick
|
|
318
|
+
workspace.pick().catch(console.error);
|
|
319
|
+
disclosureAcked = true;
|
|
320
|
+
void workspace.acknowledgeDisclosure();
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const disclosureAcked = await workspace.isDisclosureAcknowledged();
|
|
326
|
+
```
|
|
77
327
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
328
|
+
说明弹窗的「继续」按钮同样不能先 `await acknowledgeDisclosure()` 再 `pick()`。`acknowledgeDisclosure` 按 `applicationId` 记住,同一应用下不再重复提示。
|
|
329
|
+
|
|
330
|
+
### 目录树
|
|
331
|
+
|
|
332
|
+
授权之后用 `workspace.adapter` 列目录。`status !== 'granted'` 时 `adapter` 为 `null`。根目录传空字符串。返回的条目只有 `name`、`kind`(`file` | `directory`)、可选的 `size` 和 `lastModified`,路径要自己拼。目录树按展开加载:展开哪一层,就列哪一层。
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
async function loadDirectory(dir: string) {
|
|
336
|
+
const adapter = workspace.adapter;
|
|
337
|
+
if (!adapter) return [];
|
|
338
|
+
try {
|
|
339
|
+
const entries = await adapter.listEntries(dir);
|
|
340
|
+
return entries.map((entry) => {
|
|
341
|
+
const path = dir ? `${dir}/${entry.name}` : entry.name;
|
|
342
|
+
return {
|
|
343
|
+
path,
|
|
344
|
+
...entry,
|
|
345
|
+
sensitive: entry.kind === 'file' && workspace.sensitive.isSensitive(path),
|
|
346
|
+
};
|
|
347
|
+
});
|
|
348
|
+
} catch (error) {
|
|
349
|
+
if (isNotAllowed(error)) void workspace.handlePermissionLost();
|
|
350
|
+
throw error;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function isNotAllowed(error: unknown) {
|
|
355
|
+
return !!error && typeof error === 'object' && (error as { name?: string }).name === 'NotAllowedError';
|
|
356
|
+
}
|
|
357
|
+
```
|
|
81
358
|
|
|
82
|
-
|
|
83
|
-
- `message:new`: 收到新消息
|
|
84
|
-
- `message:updated`: 消息更新
|
|
85
|
-
- `history:loaded`: 历史消息加载完成
|
|
86
|
-
- `data:updated`: 会话数据更新
|
|
359
|
+
目录会变。SDK 把需要重读的目录合成 `files:changed` 再发出(大约 150 毫秒内的多次变化并成一次):
|
|
87
360
|
|
|
88
|
-
|
|
361
|
+
| 来源 | `paths` |
|
|
362
|
+
| --- | --- |
|
|
363
|
+
| 浏览器 `FileSystemObserver`(Chrome / Edge) | 发生变化的那些目录。根目录是 `""` |
|
|
364
|
+
| 宿主推送 `fs-changed` | 宿主给的相对目录 |
|
|
365
|
+
| `run_python` 结束 | `[]`,表示重读根目录和当前已经展开的每一层 |
|
|
89
366
|
|
|
90
|
-
|
|
367
|
+
```ts
|
|
368
|
+
const expanded = new Set<string>();
|
|
91
369
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
console.log('Stream content:', content);
|
|
370
|
+
workspace.on('files:changed', (event) => {
|
|
371
|
+
const dirs = event.paths.length ? event.paths : ['', ...expanded];
|
|
372
|
+
dirs.forEach((dir) => void loadDirectory(dir));
|
|
96
373
|
});
|
|
374
|
+
```
|
|
97
375
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
376
|
+
`apply_patch`、`write_file`、`download_file` 成功时还会发 `tool:finish`。没有 `FileSystemObserver` 的环境靠它刷新被改文件的父目录。窗口重新获得焦点时,再重读根目录和已展开目录,用来补上观察器没有覆盖到的改动(例如在资源管理器里改了 `.git` 或 `node_modules` 里面的文件;宿主默认不监视这三个目录)。
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
window.addEventListener('focus', () => {
|
|
380
|
+
if (document.visibilityState === 'hidden') return;
|
|
381
|
+
['', ...expanded].forEach((dir) => void loadDirectory(dir));
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
workspace.on('tool:finish', (event) => {
|
|
385
|
+
if (!['apply_patch', 'write_file', 'download_file'].includes(event.functionCode)) return;
|
|
386
|
+
const slash = event.path?.lastIndexOf('/') ?? -1;
|
|
387
|
+
const dir = !event.path || slash < 0 ? '' : event.path.slice(0, slash);
|
|
388
|
+
void loadDirectory(dir);
|
|
102
389
|
});
|
|
103
390
|
```
|
|
104
391
|
|
|
105
|
-
|
|
392
|
+
目录树只做浏览。文件内容由应用中的助手通过工具读取,界面不要另开一条读取通道把内容发给模型。
|
|
393
|
+
|
|
394
|
+
### 确认弹窗
|
|
395
|
+
|
|
396
|
+
`onApprovalRequired` 收到的 `ApprovalRequest`:
|
|
397
|
+
|
|
398
|
+
| 字段 | 用途 |
|
|
399
|
+
| --- | --- |
|
|
400
|
+
| `functionCode` | `apply_patch` / `write_file` / `upload_file` / `download_file` |
|
|
401
|
+
| `path` | 工作区相对路径 |
|
|
402
|
+
| `workspaceName` | 文件夹名 |
|
|
403
|
+
| `preview` | 给用户看的内容,见下表 |
|
|
404
|
+
| `signal` | 停止回答、切换会话、更换或关闭文件夹、确认超时(默认 5 分钟)时变为 aborted |
|
|
405
|
+
|
|
406
|
+
`signal` abort 时关掉弹窗并 resolve `false`。返回值只有严格的 `true` 会继续执行;`false`、抛错、超时都按拒绝处理,磁盘上的原文件保持不变。
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
import type { ApprovalRequest } from '@glodon-aiot/bot-client-sdk';
|
|
410
|
+
|
|
411
|
+
function openApprovalDialog(request: ApprovalRequest): Promise<boolean> {
|
|
412
|
+
return new Promise((resolve) => {
|
|
413
|
+
let settled = false;
|
|
414
|
+
const finish = (approved: boolean) => {
|
|
415
|
+
if (settled) return;
|
|
416
|
+
settled = true;
|
|
417
|
+
closeDialog();
|
|
418
|
+
resolve(approved);
|
|
419
|
+
};
|
|
420
|
+
request.signal.addEventListener('abort', () => finish(false), { once: true });
|
|
421
|
+
showDialog(request, finish);
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
`preview.type` 决定弹窗内容:
|
|
427
|
+
|
|
428
|
+
| preview | 展示 |
|
|
429
|
+
| --- | --- |
|
|
430
|
+
| `{ type: 'text', created: true, after }` | 新建文件,展示 `after` |
|
|
431
|
+
| `{ type: 'text', created: false, before, after }` | 修改文件,按行对比 `before` 和 `after` |
|
|
432
|
+
| `{ type: 'upload', name, size, target: 'session' }` | 文件名、大小、工作区路径,目标是当前会话 |
|
|
433
|
+
| `{ type: 'download', source, name?, size?, overwrite }` | `source.label`(「会话文件」或 URL 主机名)、文件名、大小、保存路径、是否覆盖 |
|
|
434
|
+
|
|
435
|
+
`tool:approval-required` 只有 `requestId`、`functionCode`、`path`、`approvalId`,不含文件内容。预览文本只出现在 `request.preview` 里。
|
|
436
|
+
|
|
437
|
+
界面如果晚于 `BotClient` 挂载,在挂载前不要让回调一直 pending:还没有弹窗时直接返回 `false`,挂载后再 `setApprovalHandler`。
|
|
438
|
+
|
|
439
|
+
### 进度与未授权
|
|
440
|
+
|
|
441
|
+
上传、下载过程订阅 `tool:progress`。`stage` 为 `reading` | `uploading` | `downloading` | `writing` | `done`,用 `loaded` / `total` 画进度。`stage === 'done'`,以及 `tool:finish`、`tool:error`、`tool:cancelled` 时把这条进度拿掉。
|
|
442
|
+
|
|
443
|
+
应用中的助手在未授权时调用工具,会收到 `tool:error`,`reason === 'workspace_not_granted'`。这时把「选择工作文件夹」或「恢复访问」亮出来。`status` 变为 `granted` 后关掉这个提示。授权后不要自动重放刚才失败的调用。
|
|
444
|
+
|
|
445
|
+
工作区事件一览(载荷里没有文件内容):
|
|
446
|
+
|
|
447
|
+
| 事件 | 载荷要点 |
|
|
448
|
+
| --- | --- |
|
|
449
|
+
| `workspace:changed` | `status`、`displayName` |
|
|
450
|
+
| `files:changed` | `paths`:要重读的相对目录;空数组表示根目录和已展开的目录 |
|
|
451
|
+
| `tool:start` | `requestId`、`functionCode`、`path` |
|
|
452
|
+
| `tool:approval-required` | 同上,另有 `approvalId` |
|
|
453
|
+
| `tool:progress` | 同上,另有 `stage`、`loaded`、`total` |
|
|
454
|
+
| `tool:finish` | 同上,另有 `code` |
|
|
455
|
+
| `tool:error` | 同上,另有 `code`、`message`、`reason` |
|
|
456
|
+
| `tool:cancelled` | `requestId`、`functionCode`、`path` |
|
|
457
|
+
|
|
458
|
+
`workspace.on` 返回取消订阅的函数。
|
|
459
|
+
|
|
460
|
+
## 命令和脚本
|
|
461
|
+
|
|
462
|
+
`shell`、`run_file`、`run_skill_script` 共用同一套执行器选择(`runnerFor`),成功时 `data` 为 `{ exitCode, stdout, stderr, cwd? }`。`exitCode !== 0` 仍是工具成功(`code === 0`)。跨命令只保留 cwd,不保留环境变量。
|
|
463
|
+
|
|
464
|
+
### 执行器选择顺序
|
|
465
|
+
|
|
466
|
+
1. **注入的** `LocalWorkspace.commandRunner`(测试 / 自定义)
|
|
467
|
+
2. **宿主沙箱**:`backend: 'host-bridge'`,且 `workspace` 消息声明 `capabilities` 含 `sandbox-exec`,且 `sandboxProtocol === 1` → 经宿主转发 `@glodon-aiot/workspace-sandbox`(MXC 隔离)
|
|
468
|
+
3. **浏览器运行时**:`commandRuntime.browser.enabled` 默认为 `true` → just-bash + Pyodide Worker
|
|
469
|
+
4. 以上皆不可用 → **501**
|
|
470
|
+
|
|
471
|
+
协议版本不一致时:记一次 `error` 事件日志,**回退浏览器运行时**,不硬失败。未声明 `sandbox-exec` 的旧宿主不再使用其裸 `exec`。
|
|
472
|
+
|
|
473
|
+
参照宿主:`examples/qt-workspace-host`。
|
|
474
|
+
|
|
475
|
+
### 浏览器运行时
|
|
476
|
+
|
|
477
|
+
| 能力 | 行为 |
|
|
478
|
+
| --- | --- |
|
|
479
|
+
| shell | 按需 `import(bashModuleURL)` 加载 just-bash,文件系统接到当前 `WorkspaceFileAdapter` |
|
|
480
|
+
| Python | Blob Module Worker 加载 Pyodide;白名单包:numpy、pandas、matplotlib、pillow、lxml |
|
|
481
|
+
| Node / `.js` | 明确报错,提示改用 python3 或 shell |
|
|
482
|
+
| pip / 任意第三方 | 不支持 |
|
|
106
483
|
|
|
107
|
-
|
|
108
|
-
// 设置会话知识库
|
|
109
|
-
await session.setKnowledges([{id: 'knowledge-1'}]);
|
|
484
|
+
默认 CDN(路径带版本,长缓存;可用配置覆盖):
|
|
110
485
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
486
|
+
| 配置项 | 默认 |
|
|
487
|
+
| --- | --- |
|
|
488
|
+
| `commandRuntime.browser.bashModuleURL` | `https://cv-cdn.obs.cn-north-4.myhuaweicloud.com/glodon/libs/just-bash/3.6.0/just-bash.mjs` |
|
|
489
|
+
| `commandRuntime.browser.pyodideIndexURL` | `https://cv-cdn.obs.cn-north-4.myhuaweicloud.com/glodon/libs/pyodide/314.0.7/` |
|
|
115
490
|
|
|
116
|
-
|
|
117
|
-
|
|
491
|
+
**CSP:** 放行 OBS 的 `script-src` / `connect-src`;Pyodide Worker 需 `worker-src blob:`。
|
|
492
|
+
|
|
493
|
+
浏览器运行时用于能力对齐与防误用,**不是**与宿主 MXC 同级的强隔离。
|
|
494
|
+
|
|
495
|
+
**打包注意:** just-bash / Pyodide **不打进** SDK npm 包。请使用包的 **ES 入口**(`import` / `exports.import`)。CJS 产物里动态 `import(url)` 的 `webpackIgnore` 注释可能被压缩去掉,webpack 从 CJS 入口打包时可能错误解析该导入。
|
|
496
|
+
|
|
497
|
+
### 宿主沙箱(摘要)
|
|
498
|
+
|
|
499
|
+
- 宿主启动一次 `workspace-sandbox --serve`,每请求一行 JSON;文件 IO 在该进程直读写,命令进 MXC
|
|
500
|
+
- 随安装包分发:`Resources/node/`(Node 24)+ `Resources/workspace-sandbox/`(`yarn bundle --platform …` 产物),可选 `python/`
|
|
501
|
+
- 输出做路径反向替换,页面看不到工程绝对路径
|
|
502
|
+
- 细节与 Windows 步骤见 [`local-workspace-windows-host.md`](../bot-client-ui/docs/local-workspace-windows-host.md)
|
|
503
|
+
|
|
504
|
+
## 宿主通道
|
|
505
|
+
|
|
506
|
+
端侧宿主(下文简称宿主)是内嵌此页面的桌面程序,例如用 WebView2 加载页面的客户端。
|
|
507
|
+
|
|
508
|
+
`localFiles.backend` 为 `host-bridge` 时,页面通过 `window.chrome.webview` 和宿主说话。通道名固定 `glodon-workspace`。消息里只有相对路径和文件夹名,绝对路径留在宿主内。页面上不再出现「选择工作文件夹」。宿主还没推送目录时,`status` 为 `unsupported`。
|
|
509
|
+
|
|
510
|
+
页面加载后先发:
|
|
511
|
+
|
|
512
|
+
```json
|
|
513
|
+
{ "channel": "glodon-workspace", "type": "hello" }
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
宿主立刻回当前工程。之后每次打开或关闭工程再推一次:
|
|
517
|
+
|
|
518
|
+
```json
|
|
519
|
+
{
|
|
520
|
+
"channel": "glodon-workspace",
|
|
521
|
+
"type": "workspace",
|
|
522
|
+
"granted": true,
|
|
523
|
+
"displayName": "sample-project",
|
|
524
|
+
"capabilities": ["fs-mutate", "sandbox-exec"],
|
|
525
|
+
"sandboxVersion": "0.1.0",
|
|
526
|
+
"sandboxProtocol": 1
|
|
527
|
+
}
|
|
118
528
|
```
|
|
119
529
|
|
|
120
|
-
|
|
530
|
+
`granted` 为 `false` 时页面断开工作区。`displayName` 只放文件夹名。找不到 Node / 沙箱包时 **不要** 声明 `fs-mutate` / `sandbox-exec`。
|
|
531
|
+
|
|
532
|
+
| capability | 含义 |
|
|
533
|
+
| --- | --- |
|
|
534
|
+
| `sandbox-exec` | 支持 `exec`(经 workspace-sandbox);须同时带 `sandboxProtocol: 1` |
|
|
535
|
+
| `fs-mutate` | 支持 `mkdir` / `remove` / `move` |
|
|
536
|
+
|
|
537
|
+
目录有增删时再推一条。`paths` 是发生变化的目录,相对工程根,根目录用 `""`。空数组表示让页面重读已展开的全部目录。短时间内多次变化可以合成一条。
|
|
538
|
+
|
|
539
|
+
```json
|
|
540
|
+
{ "channel": "glodon-workspace", "type": "fs-changed", "paths": ["notes", ""] }
|
|
541
|
+
```
|
|
121
542
|
|
|
122
|
-
|
|
543
|
+
脚本跑完、用户在资源管理器里改文件,都应推送。Qt 示例用目录监视做这件事,默认不监视 `.git`、`node_modules`、`__pycache__` 内部。
|
|
123
544
|
|
|
124
|
-
`
|
|
545
|
+
其余请求都带 `id` 和 `op`。路径用 `/`,不能含 `..`、盘符、反斜杠或绝对路径。不合法时回 `{ "id", "ok": false, "name": "SecurityError" }`,不要访问磁盘。宿主补上本地 `root` 后转发给 `workspace-sandbox --serve`,按 `id` 回传。
|
|
125
546
|
|
|
126
|
-
|
|
547
|
+
| op | 页面发来的字段 | 成功时宿主回 |
|
|
548
|
+
| --- | --- | --- |
|
|
549
|
+
| `list` | `path`,根目录是 `""` 或 `"."` | `entries`: `{ name, kind: "file" \| "directory", size?, lastModified? }[]` |
|
|
550
|
+
| `stat` | `path` | `entry`:同样的字段 |
|
|
551
|
+
| `read` | `path` | `contentBase64`。单文件不超过 50 MB |
|
|
552
|
+
| `write` | `path`、`contentBase64`、`createDirectories` | `{ "ok": true }`。父目录不存在且 `createDirectories` 不是 `true` 时回 `NotFoundError` |
|
|
553
|
+
| `mkdir` / `remove` / `move` | 路径规则同 write;`move` 用 `from`/`to`;需 `fs-mutate` | `{ "ok": true }` |
|
|
554
|
+
| `exec` | `protocol`(当前为 1)、`cwd`(工作区相对目录)、`command` 或 `argv`、`timeoutMs` | `exitCode`、`stdout`、`stderr`、`cwd?`、`timedOut?` |
|
|
555
|
+
| `run_python` | `path`、`args`、`timeoutMs` | `exitCode`、`timedOut`、`stdout`、`stderr` |
|
|
127
556
|
|
|
128
|
-
`
|
|
557
|
+
失败时统一 `{ "id", "ok": false, "name" }`:
|
|
129
558
|
|
|
130
|
-
|
|
559
|
+
| name | 何时 |
|
|
560
|
+
| --- | --- |
|
|
561
|
+
| `NotAllowedError` | 还没有工程目录,或文件打不开 |
|
|
562
|
+
| `SecurityError` | 路径越出工程目录 |
|
|
563
|
+
| `NotFoundError` | 目标不存在 |
|
|
564
|
+
| `TypeMismatchError` | 把文件当目录,或脚本不是 `.py` |
|
|
565
|
+
| `QuotaExceededError` | 读写超过 50 MB |
|
|
566
|
+
| `NoModificationAllowedError` | 写入没有完成 |
|
|
567
|
+
| `AbortError` | 已经有一个脚本在跑,或调用超时 |
|
|
568
|
+
| `InvalidModificationError` | 非空目录删除未带 `recursive` 等 |
|
|
131
569
|
|
|
132
|
-
|
|
133
|
-
2. 拉取最新代码: `git pull`
|
|
134
|
-
3. 创建新分支: `git switch -c feature/xxx`
|
|
135
|
-
4. 提交代码:
|
|
136
|
-
```bash
|
|
137
|
-
git add .
|
|
138
|
-
git commit -m "feat: your feature"
|
|
139
|
-
git push
|
|
140
|
-
```
|
|
141
|
-
5. 创建 PR 合并到 develop 分支
|
|
570
|
+
`lastModified` 用 Unix 毫秒。`size` 用字节数。搜索、按行修改、上传、下载仍由页面完成,底层落到上面的 `list` / `read` / `write`。宿主不要自己实现那些工具名。文件 op 可并发;命令类同一时间只跑一个。
|
|
142
571
|
|
|
143
|
-
|
|
572
|
+
## 安全与限额
|
|
144
573
|
|
|
145
|
-
|
|
146
|
-
- `feat`: 新功能
|
|
147
|
-
- `fix`: bug 修复
|
|
148
|
-
- `docs`: 文档更新
|
|
149
|
-
- `style`: 代码样式
|
|
150
|
-
- `refactor`: 代码重构
|
|
151
|
-
- `test`: 测试相关
|
|
152
|
-
- `chore`: 构建/工具变更
|
|
574
|
+
敏感文件不可读、不可写、不可上传、不可运行,也不出现在搜索结果里。`list_directory` / `stat_file` 仍返回它们,并标记 `sensitive: true`。目录树上用 `workspace.sensitive.isSensitive(path)` 做同样的标记。命中时工具返回 403,`reason` 为 `sensitive_file`,`data.ruleId` 是规则 ID。
|
|
153
575
|
|
|
154
|
-
|
|
576
|
+
基线(`SDK_SENSITIVE_BASELINE`)始终生效,`sensitiveRulesAppend` 只能再加规则:
|
|
577
|
+
|
|
578
|
+
| 匹配 | 规则 |
|
|
579
|
+
| --- | --- |
|
|
580
|
+
| 文件名 | `.env*`、`*.pem`、`*.key`、`*.p12`、`*.pfx`、`id_rsa*`、`id_ed25519*`、`id_ecdsa*`、`.credentials`、`.npmrc`、`.pypirc` |
|
|
581
|
+
| 整段相对路径 | `**/.aws/credentials`、`**/.ssh/**` |
|
|
582
|
+
|
|
583
|
+
追加规则的 `pattern` 是 `/` 分隔的 glob。`match` 省略时只比较文件名,`path` 时比较整段相对路径。`operations` 可限制为 `read` / `write` / `search` 的子集,省略则三种都拦。`source` 取 `product` 或 `enterprise`。
|
|
584
|
+
|
|
585
|
+
下载 URL 只允许 https,且域名是允许列表中的某一项或其子域。默认只有 `glodon.com`。`downloadUrlAllowlistAppend` 追加域名后缀。IP、localhost、带用户名密码的地址会被拒绝;重定向后的地址同样校验,不通过则不写盘。请求不带 Cookie。
|
|
586
|
+
|
|
587
|
+
`limits` 各项只能调小(`DEFAULT_LIMITS`):
|
|
588
|
+
|
|
589
|
+
| 项 | 默认 |
|
|
590
|
+
| --- | --- |
|
|
591
|
+
| `maxReadBytes` | 1 MiB |
|
|
592
|
+
| `maxWriteBytes` | 5 MiB |
|
|
593
|
+
| `maxTransferBytes` | 50 MB |
|
|
594
|
+
| `maxSearchResults` | 200 |
|
|
595
|
+
| `maxConcurrentRequests` | 4 |
|
|
596
|
+
| `toolTimeoutMs` | 30 秒 |
|
|
597
|
+
| `searchTimeoutMs` | 60 秒 |
|
|
598
|
+
| `transferTimeoutMs` | 10 分钟 |
|
|
599
|
+
| `approvalTimeoutMs` | 5 分钟,不计入工具超时 |
|
|
600
|
+
|
|
601
|
+
停止回答会取消进行中的调用,确认框应关闭,结果不再回传。更换或关闭文件夹同样取消进行中的调用。
|
|
602
|
+
|
|
603
|
+
已知限制:File System Access API 不暴露符号链接目标,链接的处理以浏览器行为为准。`upload_file` 返回 `fileId` 即视为成功。
|
|
604
|
+
|
|
605
|
+
## 错误码
|
|
606
|
+
|
|
607
|
+
工具结果是 `{ code, message, data? }`。界面主要看 `tool:error` 的 `code` 和 `reason`。
|
|
608
|
+
|
|
609
|
+
| code | 含义 |
|
|
610
|
+
| --- | --- |
|
|
611
|
+
| 0 | 成功 |
|
|
612
|
+
| 400 | 参数错误、二进制文件、非 UTF-8、`search_text` 的范围是整个工作区 |
|
|
613
|
+
| 403 | 未授权(`workspace_not_granted`)、越界(`path_outside_workspace`)、敏感文件(`sensitive_file`)、用户拒绝(`user_rejected`)、没有确认回调(`approval_unavailable`)、下载地址不允许(`url_not_allowed`) |
|
|
614
|
+
| 404 | 文件或目录不存在 |
|
|
615
|
+
| 408 | 超时 |
|
|
616
|
+
| 409 | `expectedHash` 不一致(`data.currentHash` 为当前值),或下载目标已存在 |
|
|
617
|
+
| 413 | 超过大小上限 |
|
|
618
|
+
| 429 | 并发过多,或已有一个脚本在运行 |
|
|
619
|
+
| 499 | 已取消(停止回答、更换文件夹) |
|
|
620
|
+
| 500 | 内部异常。消息里不含路径和调用栈 |
|
|
621
|
+
| 501 | 无可用命令执行器(关闭浏览器运行时且无宿主沙箱 / 注入 runner) |
|
|
622
|
+
|
|
623
|
+
## 开发
|
|
624
|
+
|
|
625
|
+
```bash
|
|
626
|
+
yarn install
|
|
627
|
+
yarn test
|
|
628
|
+
yarn start
|
|
155
629
|
```
|
|
156
|
-
|
|
157
|
-
|
|
630
|
+
|
|
631
|
+
提交信息使用 Conventional Commits:`feat`、`fix`、`docs`、`refactor`、`test`、`chore`。
|