Skip to content

Commit dcdc068

Browse files
authored
feat(sandbox): support runtime upgrade and advanced file/command operations (labring#7189)
* feat(sandbox): support edit-debug runtime image upgrade with workspace archive and restore * feat(sandbox): support multi-select, absolute path copy, zip extraction and command execution * feat(sandbox): support batch move with rollback, auto-naming zip extract, and debug sandbox rebuild fallback * refactor(app): generalize ProModal and apply to sandbox upgrade modal * fix(sandbox): prevent data loss by removing unsafe silent restore fallbacks on edit-debug sandbox upgrade or start
1 parent 7c54cd6 commit dcdc068

46 files changed

Lines changed: 2041 additions & 559 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.
Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,108 @@
11
---
2-
title: 'V4.15.0-beta5 (In Progress)'
2+
title: 'V4.15.0-beta5'
33
description: 'FastGPT V4.15.0-beta5 Release Notes'
44
---
55

6+
## 📦 Upgrade Guide
7+
8+
### 1. Update Environment Variables
9+
10+
Add the `CHAT_TITLE_MODEL` environment variable to `fastgpt` and `fastgpt-pro`. It is used to automatically generate chat titles. For example:
11+
12+
```shell
13+
CHAT_TITLE_MODEL=deepseek-v4-flash
14+
```
15+
16+
If Agent Sandbox is enabled, also add the following environment variables to `fastgpt`:
17+
18+
```shell
19+
# Shared with fastgpt-agent-sandbox-proxy. In production, replace it with a random secret longer than 32 characters.
20+
AGENT_SANDBOX_PROXY_SECRET=replace_with_32_chars_random_secret
21+
# Browser-accessible WebSocket URL for agent-sandbox-proxy. Use wss:// if it is proxied through an HTTPS domain.
22+
AGENT_SANDBOX_PROXY_URL=ws://{{host}}:1006
23+
```
24+
25+
### 2. Image Changes
26+
27+
- Update the fastgpt-app (FastGPT main service) image tag to v4.15.0-beta5.
28+
- Update the fastgpt-pro (FastGPT commercial edition) image tag to v4.15.0-beta5.
29+
- Update the fastgpt-plugin image tag to v1.0.0-beta5.
30+
- Update the aiproxy image tag to v0.6.2.
31+
32+
If Agent Sandbox is enabled, also update the following images:
33+
34+
- Add the fastgpt-agent-sandbox-proxy image with tag v0.2.0-beta3.
35+
- Update the fastgpt-agent-sandbox image tag to v0.2.0-beta3.
36+
37+
Also add the `fastgpt-agent-sandbox-proxy` service to `docker-compose.yml`. The example below uses the China Mainland image registry. For global deployments, change the image to `ghcr.io/labring/fastgpt-agent-sandbox-proxy:v0.2.0-beta3`:
38+
39+
```yml
40+
fastgpt-agent-sandbox-proxy:
41+
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-agent-sandbox-proxy:v0.2.0-beta3
42+
container_name: fastgpt-agent-sandbox-proxy
43+
restart: always
44+
ports:
45+
- 1006:1006
46+
networks:
47+
- fastgpt
48+
environment:
49+
PORT: 1006
50+
# Must exactly match AGENT_SANDBOX_PROXY_SECRET in fastgpt.
51+
AGENT_SANDBOX_PROXY_SECRET: replace_with_32_chars_random_secret
52+
# Internal URL of the main app container. If your service name is not fastgpt, update it accordingly.
53+
FASTGPT_APP_URL: http://fastgpt:3000
54+
FASTGPT_APP_REQUEST_TIMEOUT_SECS: 10
55+
RUST_LOG: info,fastgpt_agent_sandbox_proxy=debug
56+
# Configure this only when the upstream sandbox endpoint returns localhost/127.0.0.1 and the proxy container cannot reach it.
57+
# AGENT_SANDBOX_PROXY_REWRITE_HOST: host.docker.internal
58+
```
59+
60+
### 3. Upgrade Script
61+
62+
Archive all old sandbox workspaces to S3 to more thoroughly release inactive sandboxes. Some old sandboxes may fail to install zip packages because of timeouts. Because most old sandboxes are tied to old chats, you may also remove all old sandboxes directly instead of running this script. This script only affects old sandboxes and does not affect newly created sandboxes.
63+
64+
```shell
65+
curl --location --request POST 'https://{{host}}/api/admin/initSandboxArchive' \
66+
--header 'rootkey: {{rootkey}}' \
67+
--header 'Content-Type: application/json' \
68+
-d '{"runArchive":true,"inactiveDays":0}'
69+
```
70+
671
## Breaking Changes
772

873
1. API Key behavior has changed. FastGPT no longer distinguishes between app keys and system keys; only system keys are kept. For OpenAI SDK compatibility, pass the token as `apikey-appId`. Existing API keys remain compatible and continue to work. For details, see the [FastGPT API documentation](../../../openapi/intro).
974

1075
## 🚀 New Features
1176

1277
1. The HTTP node now supports ignoring TLS certificate verification, which is useful when calling HTTPS services that use self-signed or internal certificates.
78+
2. Added an environment variable for maximum folder depth to prevent unlimited nested folders.
79+
3. Chat windows now support a quick scroll-to-bottom button.
80+
4. Optimized streaming output animations based on Lobe UI.
81+
5. Added model-generated chat titles. Configure the `CHAT_TITLE_MODEL` variable to enable this feature.
82+
6. Adjusted the Skill Edit editing experience.
83+
7. The HTTP node now supports returning the complete error object.
84+
8. Knowledge Base search in agent mode now supports permission filtering.
85+
9. Optimized API key logic by unifying APIKey management and requiring requests to explicitly pass the app context.
86+
10. Optimized agent context compression.
87+
11. Added output syntax for quick replies.
1388

1489
## ⚙️ Improvements
1590

1691
1. HTML output now automatically switches to preview mode after generation, reducing the need to open the preview manually.
17-
2. Improved long-name display for apps, datasets, files, and folders: names are truncated when they exceed the available width, and the full name is shown when hovering over the name.
92+
2. Improved long-name display for apps, Knowledge Bases, files, and folders: names are truncated when they exceed the available width, and the full name is shown on hover.
93+
3. Removed `temperature` and `max_tokens` from all built-in LLM requests to avoid incompatibility with some models.
94+
4. Improved error prompts for Knowledge Base training failures, including one-click retry for all failed items.
95+
5. Filtered out invalid Knowledge Base citation markers.
96+
6. When a tool returns an empty response, FastGPT now automatically fills in `"none"` to avoid errors from some models.
97+
7. Added a second permission check before system tools run.
98+
8. Optimized SSRF checks after redirects.
1899

19100
## 🐛 Bug Fixes
20101

21102
1. Fixed a potential cross-resource file access risk when private S3 object keys were not bound to the already-authorized resource.
103+
2. Fixed abnormal tool call parameter schemas for `array` and `object` types in Workflow tools.
104+
3. Fixed a UI offset issue in the portal publish channel.
105+
106+
## Code Improvements
107+
108+
1. Added a length guard for system string processing. When the string is too long, synchronous replacement stops to avoid high CPU load. You can adjust the limit with the `SYSTEM_MAX_STRING_LENGTH_M` environment variable.

document/content/self-host/upgrading/4-15/41505.mdx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,50 @@ description: 'FastGPT V4.15.0-beta5 更新说明'
1313
CHAT_TITLE_MODEL=deepseek-v4-flash
1414
```
1515

16+
如果启用 Agent Sandbox,`fastgpt` 还需要增加下面环境变量:
17+
18+
```shell
19+
# 与 fastgpt-agent-sandbox-proxy 共用,生产环境请改为 32 位以上随机密钥
20+
AGENT_SANDBOX_PROXY_SECRET=replace_with_32_chars_random_secret
21+
# 浏览器可访问的 agent-sandbox-proxy WebSocket 地址;如已通过 HTTPS 域名代理,请使用 wss://
22+
AGENT_SANDBOX_PROXY_URL=ws://{{host}}:1006
23+
```
24+
1625
### 2. 镜像变更
1726

1827
- 更新 fastgpt-app(fastgpt 主服务) 镜像 tag: v4.15.0-beta5
1928
- 更新 fastgpt-pro(fastgpt 商业版) 镜像 tag: v4.15.0-beta5
2029
- 更新 fastgpt-plugin 镜像 tag: v1.0.0-beta5
2130
- 更新 aiproxy 镜像 tag: v0.6.2
2231

32+
如果启用 Agent Sandbox,需同步更新下面镜像:
33+
34+
- 新增 fastgpt-agent-sandbox-proxy 镜像 tag: v0.2.0-beta3
35+
- 更新 fastgpt-agent-sandbox 镜像 tag: v0.2.0-beta3
36+
37+
同时在 `docker-compose.yml` 中新增 `fastgpt-agent-sandbox-proxy` 服务。下面示例使用国内镜像源,海外部署可将镜像改为 `ghcr.io/labring/fastgpt-agent-sandbox-proxy:v0.2.0-beta3`
38+
39+
```yml
40+
fastgpt-agent-sandbox-proxy:
41+
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-agent-sandbox-proxy:v0.2.0-beta3
42+
container_name: fastgpt-agent-sandbox-proxy
43+
restart: always
44+
ports:
45+
- 1006:1006
46+
networks:
47+
- fastgpt
48+
environment:
49+
PORT: 1006
50+
# 必须与 fastgpt 中的 AGENT_SANDBOX_PROXY_SECRET 完全一致
51+
AGENT_SANDBOX_PROXY_SECRET: replace_with_32_chars_random_secret
52+
# 主站容器内网地址;如果服务名不是 fastgpt,请按实际 docker-compose 服务名调整
53+
FASTGPT_APP_URL: http://fastgpt:3000
54+
FASTGPT_APP_REQUEST_TIMEOUT_SECS: 10
55+
RUST_LOG: info,fastgpt_agent_sandbox_proxy=debug
56+
# 当上游 sandbox endpoint 返回 localhost/127.0.0.1 且 proxy 容器无法访问时再配置
57+
# AGENT_SANDBOX_PROXY_REWRITE_HOST: host.docker.internal
58+
```
59+
2360
### 3. 升级脚本
2461

2562
将所有旧的沙盒 workspace 归档到 s3 里,从而更彻底的释放不活跃的沙盒,旧的沙盒可能因为超时安装 zip 失败。因为旧的沙盒大部分关联的是旧的对话,不执行该脚本,直接把旧的沙盒全部移除也可以。该脚本仅影响旧的沙盒,不影响新生成沙盒。

packages/global/common/error/code/sandbox.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ const startCode = 510000;
66

77
export enum SandboxErrEnum {
88
agentSandboxPermissionDenied = 'agentSandboxPermissionDenied',
9-
agentSandboxInitializing = 'agentSandboxInitializing'
9+
agentSandboxInitializing = 'agentSandboxInitializing',
10+
runtimeUpgradeFailed = 'runtimeUpgradeFailed'
1011
}
1112

1213
const sandboxErr = [
@@ -18,6 +19,10 @@ const sandboxErr = [
1819
statusText: SandboxErrEnum.agentSandboxInitializing,
1920
message: i18nT('common:code_error.sandbox_error.agent_sandbox_initializing'),
2021
httpStatus: 409
22+
},
23+
{
24+
statusText: SandboxErrEnum.runtimeUpgradeFailed,
25+
message: i18nT('common:code_error.sandbox_error.runtime_upgrade_failed')
2126
}
2227
];
2328

packages/global/common/string/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ export const getTextValidLength = (chunk: string) => {
55
export const isObjectId = (str: string) => {
66
return /^[0-9a-fA-F]{24}$/.test(str);
77
};
8+
9+
/** Shell 单参数安全转义,用于拼接传给 sandbox 的命令。 */
10+
export const shellQuote = (value: string): string => `'${value.replace(/'/g, `'\\''`)}'`;

packages/global/core/chat/type.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ export type SandboxStatusPhase =
5757
| 'downloadingPackage' // downloading skill package from MinIO
5858
| 'uploadingPackage' // uploading package into sandbox container
5959
| 'extractingPackage' // extracting package in sandbox
60+
// Runtime image upgrade phases
61+
| 'runtimeUpgradeRequired' // existing edit-debug sandbox uses an outdated runtime image
62+
| 'runtimeUpgradeArchiving' // archiving workspace before recreating with current image
63+
| 'runtimeUpgradeArchived' // outdated runtime is archived or removed; caller should refresh/restart
6064
// Lazy-init phases
6165
| 'lazyInit' // LLM first calls sandbox tool, triggers container creation
6266
// Terminal phases

packages/global/openapi/core/ai/skill/api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ export type ImportSkillResponse = z.infer<typeof ImportSkillResponseSchema>;
152152

153153
export const CreateEditDebugSandboxBodySchema = z.object({
154154
skillId: IdSchema,
155-
image: SandboxImageConfigSchema.optional()
155+
image: SandboxImageConfigSchema.optional(),
156+
archiveForUpgrade: z.boolean().optional()
156157
});
157158
export type CreateEditDebugSandboxBody = z.infer<typeof CreateEditDebugSandboxBodySchema>;
158159

packages/service/core/ai/sandbox/instance/repository.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,32 @@ export async function markSandboxArchiving(resource: SandboxResourceDoc, inactiv
325325
},
326326
{
327327
$set: {
328-
'metadata.archive.state': 'archiving'
328+
'metadata.archive.state': 'archiving',
329+
'metadata.archive.startedAt': new Date()
330+
}
331+
},
332+
{ new: true }
333+
).lean<SandboxResourceDoc | null>();
334+
}
335+
336+
/**
337+
* 用户主动升级 edit-debug runtime 时抢占实例进行归档。
338+
*
339+
* 该入口不依赖 inactive/stopped 判断,因为升级由 Skill detail 显式触发;
340+
* 但仍使用 archive state CAS,避免与恢复、定时归档或重复点击并发抢同一条记录。
341+
*/
342+
export async function markSandboxArchivingForRuntimeUpgrade(resource: SandboxResourceDoc) {
343+
return MongoSandboxInstance.findOneAndUpdate(
344+
{
345+
...buildSandboxResourceRecordFilter(resource),
346+
lastActiveAt: resource.lastActiveAt,
347+
'metadata.archive.state': { $exists: false }
348+
},
349+
{
350+
$set: {
351+
status: SandboxStatusEnum.stopped,
352+
'metadata.archive.state': 'archiving',
353+
'metadata.archive.startedAt': new Date()
329354
}
330355
},
331356
{ new: true }
@@ -394,6 +419,42 @@ export async function clearSandboxArchiveState(resource: SandboxResourceRef) {
394419
);
395420
}
396421

422+
/**
423+
* 清理用户升级归档的中间状态,并恢复抢占归档前的本地 status。
424+
*
425+
* 升级归档会临时把 running 实例标为 stopped 来复用 archived CAS;失败时必须恢复原状态,
426+
* 否则用户重试前 Mongo 记录会短暂呈现为 stopped。
427+
*/
428+
export async function clearSandboxRuntimeUpgradeArchiveState(resource: SandboxResourceDoc) {
429+
return MongoSandboxInstance.updateOne(
430+
{
431+
...buildSandboxResourceRecordFilter(resource),
432+
'metadata.archive.state': 'archiving'
433+
},
434+
{
435+
$set: {
436+
status: resource.status
437+
},
438+
$unset: {
439+
'metadata.archive': ''
440+
}
441+
}
442+
);
443+
}
444+
445+
/**
446+
* 清理卡在 runtime 升级归档中的 edit-debug 记录。
447+
*
448+
* 只允许处理仍处于 archiving 的同一条资源;调用方随后会删除远端资源并从当前发布包重建。
449+
* 这里直接删除 Mongo 记录,避免刷新后继续命中 archiving 状态。
450+
*/
451+
export async function deleteStaleRuntimeUpgradeArchivingRecord(resource: SandboxResourceDoc) {
452+
return MongoSandboxInstance.deleteOne({
453+
...buildSandboxResourceRecordFilter(resource),
454+
'metadata.archive.state': 'archiving'
455+
});
456+
}
457+
397458
/**
398459
* 原子抢占一个已归档实例进行恢复。
399460
*/

packages/service/core/ai/sandbox/runtime/entrypoint.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { ISandbox } from '@fastgpt-sdk/sandbox-adapter';
2+
import { shellQuote } from '@fastgpt/global/common/string/utils';
23
import { getLogger, LogCategories } from '../../../../common/logger';
34
import { serviceEnv } from '../../../../env';
45
import { isRedisLeaseError, withRedisLease } from '../../../../common/redis/lock';
56
import { createAgentSandboxInitializingError } from '../error';
67
import type { SandboxPrepareContext, SandboxPrepareStep } from './prepare';
7-
import { buildRuntimeHash, shellQuote } from './utils';
8+
import { buildRuntimeHash } from './utils';
89
import {
910
getRuntimeStateValue,
1011
readSandboxRuntimeState,

packages/service/core/ai/sandbox/runtime/mirrors.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { ISandbox } from '@fastgpt-sdk/sandbox-adapter';
2+
import { shellQuote } from '@fastgpt/global/common/string/utils';
23
import { getLogger, LogCategories } from '../../../../common/logger';
34
import { serviceEnv } from '../../../../env';
4-
import { buildRuntimeHash, joinSandboxPath, shellQuote } from './utils';
5+
import { buildRuntimeHash, joinSandboxPath } from './utils';
56
import {
67
getRuntimeStateValue,
78
readSandboxRuntimeState,

packages/service/core/ai/sandbox/runtime/prepare.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import type { ISandbox } from '@fastgpt-sdk/sandbox-adapter';
2+
import { shellQuote } from '@fastgpt/global/common/string/utils';
23
import {
34
injectInputFilesToSandbox,
45
readSandboxPwd,
56
type SandboxCommandClient,
67
type SandboxInputFile
78
} from './files';
89
import { prepareSandboxRuntimeMirrors } from './mirrors';
9-
import { shellQuote } from './utils';
1010

1111
export type SandboxPrepareContext = {
1212
sandbox: ISandbox;

0 commit comments

Comments
 (0)