Skip to content

Commit 270b64f

Browse files
authored
feat(agent): preview skill artifacts and verify installs without running them (#2823)
* feat(agent): preview skill artifacts and verify installs without running them Generated files open in the chat drawer with overlay/Esc returning to the list, and install verification no longer executes guessed skill entry points. A failed install can be retried from the saved archive. * ui(chat): use a folder icon and corner count for skill artifacts The eye and document glyphs read as preview or copy, and TDesign's loading spinner sat beside the icon. A folder with a top-right count matches the generated-files drawer without covering the toolbar action. * feat(artifact): implement inline artifact references with previews Add support for inline artifact references in Markdown, allowing generated files to be displayed as clickable cards. Implemented a new rendering mechanism for artifacts, enabling previews directly from the chat interface. Updated localization files for new inline preview hints and missing file messages. Introduced tests for artifact reference normalization and rendering logic. * fix(client): document AgentResponseType constants for revive Touching the const block made golangci-lint require comments on every exported value. Match the rest of the SDK and describe artifacts_pending. * fix(agent): wrap artifact-reference prompt lines for lll Pre-push golangci-lint rejected the branch on three lines over 120 characters in the sandbox: reference guidance. * fix(agent): scope HTML scripts and tighten install verification Keep scripted HTML preview on skill artifacts only, recreate sandbox workspace dirs after they are deleted, and treat nested skill files as dependencies rather than first-party imports. * refactor(artifact): enhance artifact reference handling and metadata Updated artifact metadata to include a stable resource handle for better identification. Adjusted artifact reference resolution to support both handle and name forms, ensuring compatibility with existing references. Enhanced tests to validate the new handling logic and ensure proper rendering of artifacts in chat messages. Improved documentation for clarity on artifact reference formats and their usage in the system.
1 parent 87d8e8a commit 270b64f

80 files changed

Lines changed: 5152 additions & 489 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

client/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ err = agentSession.Ask(context.Background(), "什么是深度学习?",
222222
| `AgentResponseTypeToolResult` | 工具执行结果 | 工具执行完成后 |
223223
| `AgentResponseTypeReferences` | 知识引用 | 检索到相关知识时 |
224224
| `AgentResponseTypeAnswer` | 最终答案 | Agent生成回答时(流式) |
225+
| `AgentResponseTypeArtifactsPending` | 生成文件上传中 | 回答结束后、文件写入对象存储完成前 |
225226
| `AgentResponseTypeReflection` | 自我反思 | Agent评估自己的回答时 |
226227
| `AgentResponseTypeError` | 错误 | 发生错误时 |
227228

@@ -400,6 +401,17 @@ if err != nil {
400401
_ = skillID // 用 skillID 订阅 /sandbox-configs/{id}/skills/{skillID}/install-events
401402
```
402403

404+
### 示例:重试失败的安装
405+
406+
安装失败的原因常与安装包无关(沙箱不可达、依赖源超时)。服务端保留着原始安装包,重试无需再传一次。
407+
408+
```go
409+
skillID, err := apiClient.ReinstallSandboxSkill(context.Background(), sandboxConfigID, skillID)
410+
if err != nil {
411+
// 处理错误
412+
}
413+
```
414+
403415
### 示例:查看已安装技能的文件
404416

405417
```go

client/README_EN.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,19 @@ if err != nil {
213213
_ = skillID // follow /sandbox-configs/{id}/skills/{skillID}/install-events
214214
```
215215

216+
### Example: Retry a failed install
217+
218+
Installs usually fail for reasons the bundle cannot fix — an unreachable
219+
sandbox, a package index that timed out. The server still holds the archive,
220+
so the retry needs nothing from you.
221+
222+
```go
223+
skillID, err := apiClient.ReinstallSandboxSkill(context.Background(), sandboxConfigID, skillID)
224+
if err != nil {
225+
// Handle error
226+
}
227+
```
228+
216229
### Example: Browse files of an installed skill
217230

218231
```go

client/agent.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,25 @@ type AgentQARequest struct {
4343
type AgentResponseType string
4444

4545
const (
46-
AgentResponseTypeThinking AgentResponseType = "thinking"
47-
AgentResponseTypeToolCall AgentResponseType = "tool_call"
46+
// AgentResponseTypeThinking is emitted while the agent is reasoning.
47+
AgentResponseTypeThinking AgentResponseType = "thinking"
48+
// AgentResponseTypeToolCall is emitted when the agent invokes a tool.
49+
AgentResponseTypeToolCall AgentResponseType = "tool_call"
50+
// AgentResponseTypeToolResult is emitted when a tool returns.
4851
AgentResponseTypeToolResult AgentResponseType = "tool_result"
52+
// AgentResponseTypeReferences is emitted with knowledge references.
4953
AgentResponseTypeReferences AgentResponseType = "references"
50-
AgentResponseTypeAnswer AgentResponseType = "answer"
54+
// AgentResponseTypeAnswer is emitted for answer tokens.
55+
AgentResponseTypeAnswer AgentResponseType = "answer"
56+
// AgentResponseTypeReflection is emitted for agent reflection.
5157
AgentResponseTypeReflection AgentResponseType = "reflection"
52-
AgentResponseTypeError AgentResponseType = "error"
53-
AgentResponseTypeComplete AgentResponseType = "complete"
58+
// AgentResponseTypeError is emitted when the agent fails.
59+
AgentResponseTypeError AgentResponseType = "error"
60+
// AgentResponseTypeComplete is emitted when the agent run has finished.
61+
AgentResponseTypeComplete AgentResponseType = "complete"
62+
// AgentResponseTypeArtifactsPending is emitted while skill-generated files
63+
// are still being collected after the answer has streamed.
64+
AgentResponseTypeArtifactsPending AgentResponseType = "artifacts_pending"
5465
)
5566

5667
// AgentStreamResponse agent streaming response

client/skill.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,33 @@ func (c *Client) UploadSandboxSkill(
119119
return response.Data.SkillID, nil
120120
}
121121

122+
// ReinstallSandboxSkill runs the install of one skill again from the archive
123+
// the server already stores, so a failure that had nothing to do with the
124+
// bundle can be retried without re-uploading it. Like the install it is
125+
// accepted asynchronously. A skill already serving the current image is left
126+
// alone rather than rebuilt.
127+
func (c *Client) ReinstallSandboxSkill(
128+
ctx context.Context, configID, skillID string,
129+
) (string, error) {
130+
if configID == "" {
131+
return "", fmt.Errorf("sandbox config ID is required")
132+
}
133+
if skillID == "" {
134+
return "", fmt.Errorf("skill ID is required")
135+
}
136+
path := "/api/v1/sandbox-configs/" + url.PathEscape(configID) +
137+
"/skills/" + url.PathEscape(skillID) + "/reinstall"
138+
resp, err := c.doRequest(ctx, http.MethodPost, path, nil, nil)
139+
if err != nil {
140+
return "", err
141+
}
142+
var response SandboxSkillInstallResponse
143+
if err := parseResponse(resp, &response); err != nil {
144+
return "", err
145+
}
146+
return response.Data.SkillID, nil
147+
}
148+
122149
// SandboxSkillFile is one path in an installed skill's stored archive.
123150
type SandboxSkillFile struct {
124151
Path string `json:"path"`

docs/agent-skills.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -541,14 +541,16 @@ Docker 模式提供最强的隔离:
541541

542542
```bash
543543
# 方式一:直接拉取
544-
docker pull wechatopenai/weknora-sandbox:latest
544+
docker pull wechatopenai/weknora-sandbox:main
545545

546546
# 方式二:本地构建
547547
sh scripts/build_images.sh -s
548548
```
549549

550550
> 如果未预拉取,创建第一个沙箱时会先拉取镜像,首次执行需要等待下载完成;也可以在设置页的模板步骤提前触发拉取。
551551
552+
> `main` 而非 `latest``latest` 只在发版时移动,目前仍停在 `/workspace` 及其 `input`/`output` 目录交给沙箱账号之前的版本,用它建出来的沙箱写不了自己的产物目录。发版带上该修复后即可换回 `latest`
553+
552554
**镜像内置环境**
553555
- Python 3.11 + pip(requests、pyyaml、pandas、beautifulsoup4)
554556
- Node.js 20 + npm
@@ -564,7 +566,7 @@ docker run --rm \
564566
--network=none \
565567
-v /path/to/skill:/skill:ro \
566568
-w /skill \
567-
wechatopenai/weknora-sandbox:latest \
569+
wechatopenai/weknora-sandbox:main \
568570
python scripts/analyze.py input.pdf
569571
```
570572

docs/api/chat.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ curl --location 'http://localhost:8080/api/v1/agent-chat/ceb9babb-1e30-41d7-817d
165165
| `tool_result` | 工具调用结果 |
166166
| `references` | 知识库检索引用 |
167167
| `answer` | 最终回答内容 |
168+
| `artifacts_pending` | Skill/沙箱产物正在上传;`data.count` 为待保存文件数。回答可能已经 `done`,文件按钮会在此期间显示加载态,直至 `complete` 带上 `artifacts` |
168169
| `reflection` | Agent 反思内容 |
169170
| `session_title` | 自动生成的会话标题 |
170171
| `error` | 错误信息 |

docs/api/skill.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
| ---- | --------- | ------------------ |
77
| GET | `/skills` | 获取预装 Skills 列表 |
88
| POST | `/sandbox-configs/{id}/skills` | 安装技能(zip 上传或托管平台 source) |
9+
| POST | `/sandbox-configs/{id}/skills/{skillId}/reinstall` | 用已保存的安装包重试安装 |
910
| GET | `/sandbox-configs/{id}/skills/{skillId}/files` | 列出已安装技能的文件 |
1011
| GET | `/sandbox-configs/{id}/skills/{skillId}/files/content` | 读取已安装技能中的单个文件 |
1112

@@ -103,6 +104,32 @@ curl --location 'http://localhost:8080/api/v1/sandbox-configs/{id}/skills' \
103104
}
104105
```
105106

107+
## POST `/sandbox-configs/{id}/skills/{skillId}/reinstall` - 重试安装
108+
109+
用服务端已保存的安装包重新跑一遍安装,无需重新上传 zip 或重新提供 source。适用于安装失败的原因与安装包本身无关的情况:沙箱不可达、依赖源超时、安装过程被中断等。
110+
111+
与安装接口一样只负责受理,进度同样通过
112+
`GET /sandbox-configs/{id}/skills/{skillId}/install-events` 跟随。技能会复用同一个 `skill_id`,不会产生新记录。
113+
114+
已经在当前镜像中正常服务、且安装包未变的技能会被跳过,不会重复构建快照。若该技能的安装包已不在存储中,返回 400,此时只能重新上传。
115+
116+
```curl
117+
curl --location --request POST \
118+
'http://localhost:8080/api/v1/sandbox-configs/{id}/skills/{skillId}/reinstall' \
119+
--header 'X-API-Key: sk-xxxxx'
120+
```
121+
122+
**响应**(202):
123+
124+
```json
125+
{
126+
"success": true,
127+
"data": {
128+
"skill_id": "..."
129+
}
130+
}
131+
```
132+
106133
## GET `/sandbox-configs/{id}/skills/{skillId}/files` - 列出技能文件
107134

108135
返回该技能存档里的文件路径与大小。路径相对技能根目录(`SKILL.md` 所在目录),不启动沙箱。

docs/docs.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12093,6 +12093,70 @@ const docTemplate = `{
1209312093
}
1209412094
}
1209512095
},
12096+
"/sandbox-configs/{id}/skills/{skillId}/reinstall": {
12097+
"post": {
12098+
"security": [
12099+
{
12100+
"Bearer": []
12101+
},
12102+
{
12103+
"ApiKeyAuth": []
12104+
}
12105+
],
12106+
"description": "Retry a failed install from the stored archive; does not re-upload.",
12107+
"produces": [
12108+
"application/json"
12109+
],
12110+
"tags": [
12111+
"SandboxConfig"
12112+
],
12113+
"summary": "Retry a skill install",
12114+
"parameters": [
12115+
{
12116+
"type": "string",
12117+
"description": "Sandbox config ID",
12118+
"name": "id",
12119+
"in": "path",
12120+
"required": true
12121+
},
12122+
{
12123+
"type": "string",
12124+
"description": "Skill ID",
12125+
"name": "skillId",
12126+
"in": "path",
12127+
"required": true
12128+
}
12129+
],
12130+
"responses": {
12131+
"202": {
12132+
"description": "Reinstall accepted",
12133+
"schema": {
12134+
"type": "object",
12135+
"additionalProperties": true
12136+
}
12137+
},
12138+
"400": {
12139+
"description": "The stored archive is gone",
12140+
"schema": {
12141+
"$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError"
12142+
}
12143+
},
12144+
"401": {
12145+
"description": "Unauthorized",
12146+
"schema": {
12147+
"type": "object",
12148+
"additionalProperties": true
12149+
}
12150+
},
12151+
"404": {
12152+
"description": "Skill not found",
12153+
"schema": {
12154+
"$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError"
12155+
}
12156+
}
12157+
}
12158+
}
12159+
},
1209612160
"/sandbox-configs/{id}/skills/{skillId}/transcript": {
1209712161
"get": {
1209812162
"security": [

docs/sandbox-cluster.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Cube 变体只发布 linux/amd64——envd 的来源镜像 `cubesandbox-base`
6363

6464
1.[CubeSandbox Quick Start](https://github.com/TencentCloud/CubeSandbox/blob/master/docs/zh/guide/quickstart.md) 完成控制面、计算节点、CubeProxy 与域名解析。生产环境还需按官方文档完成鉴权、TLS、网络策略与多节点部署。
6565
2. 在 WeKnora 的空间设置中填写 CubeAPI、CubeProxy、sandbox domain 和可选 API Key。需要自定义 guest DNS 时填写「DNS 服务器」(须为 IP);留空则使用集群默认。若这些端点位于 RFC1918/loopback 网络,显式打开“允许访问私网集群地址”。
66-
3. 点击“连接并继续”。WeKnora 先验证控制面地址与凭据,通过后才进入模板步骤并列出集群模板。**不会自动创建**。没有 WeKnora 标准模板时在占位卡片上点「创建」,会从 `wechatopenai/weknora-sandbox:latest-cube` 发起构建。改 DNS 或需要换镜像时在 weknora 卡片上点「重建」:优先对现有标准模板做 in-place rebuild(模板 ID 不变);只有 redo 被拒绝时才先建新模板、成功后再删旧的。已安装 Skill 的配置(以及同一集群上其它已装 Skill 的配置)不能重建。失败模板同样用「重建」(CubeMaster 拒绝 redo、错误码 130400 时尤其需要)。
66+
3. 点击“连接并继续”。WeKnora 先验证控制面地址与凭据,通过后才进入模板步骤并列出集群模板。**不会自动创建**。没有 WeKnora 标准模板时在占位卡片上点「创建」,会从 `wechatopenai/weknora-sandbox:main-cube` 发起构建。改 DNS 或需要换镜像时在 weknora 卡片上点「重建」:优先对现有标准模板做 in-place rebuild(模板 ID 不变);只有 redo 被拒绝时才先建新模板、成功后再删旧的。已安装 Skill 的配置(以及同一集群上其它已装 Skill 的配置)不能重建。失败模板同样用「重建」(CubeMaster 拒绝 redo、错误码 130400 时尤其需要)。
6767
4. 模板构建状态会自动刷新。状态变为 `READY` 后才可选择并进入运行配置;界面显示模板名称、状态和版本,配置内部才保存该集群自己的 `template_id`
6868

6969
模板镜像必须提供 uid 1000 的 `user` 账号:WeKnora 以该账号执行脚本与文件操作。写权限只保证在 `/workspace/output``/workspace/input` 下。

docs/swagger.json

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12086,6 +12086,70 @@
1208612086
}
1208712087
}
1208812088
},
12089+
"/sandbox-configs/{id}/skills/{skillId}/reinstall": {
12090+
"post": {
12091+
"security": [
12092+
{
12093+
"Bearer": []
12094+
},
12095+
{
12096+
"ApiKeyAuth": []
12097+
}
12098+
],
12099+
"description": "Retry a failed install from the stored archive; does not re-upload.",
12100+
"produces": [
12101+
"application/json"
12102+
],
12103+
"tags": [
12104+
"SandboxConfig"
12105+
],
12106+
"summary": "Retry a skill install",
12107+
"parameters": [
12108+
{
12109+
"type": "string",
12110+
"description": "Sandbox config ID",
12111+
"name": "id",
12112+
"in": "path",
12113+
"required": true
12114+
},
12115+
{
12116+
"type": "string",
12117+
"description": "Skill ID",
12118+
"name": "skillId",
12119+
"in": "path",
12120+
"required": true
12121+
}
12122+
],
12123+
"responses": {
12124+
"202": {
12125+
"description": "Reinstall accepted",
12126+
"schema": {
12127+
"type": "object",
12128+
"additionalProperties": true
12129+
}
12130+
},
12131+
"400": {
12132+
"description": "The stored archive is gone",
12133+
"schema": {
12134+
"$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError"
12135+
}
12136+
},
12137+
"401": {
12138+
"description": "Unauthorized",
12139+
"schema": {
12140+
"type": "object",
12141+
"additionalProperties": true
12142+
}
12143+
},
12144+
"404": {
12145+
"description": "Skill not found",
12146+
"schema": {
12147+
"$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError"
12148+
}
12149+
}
12150+
}
12151+
}
12152+
},
1208912153
"/sandbox-configs/{id}/skills/{skillId}/transcript": {
1209012154
"get": {
1209112155
"security": [

0 commit comments

Comments
 (0)