Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ tool, KnowledgeBase, or long-term memory without exposing component credentials.
Studio also manages user-owned Codex, OpenClaw, and Hermes AgentKit Sessions.
Users can create, reopen, inspect, and explicitly delete each Agent; leaving a
Codex conversation only disconnects it, while OpenClaw and Hermes expose their
main interface and Terminal through Studio.
main interface and Terminal through Studio. When AgentKit has retained a
snapshot but no Session is running, Studio shows the Agent as wakeable and can
restore it from that snapshot before reopening it.
When configuring skills, Studio can also browse account-scoped AgentKit Skill
Spaces and their paginated skill lists by region and project. These requests are
signed on the server, so browser clients never receive Volcengine credentials.
Expand Down
6 changes: 6 additions & 0 deletions docs/content/docs/framework/frontend.en.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@ export SANDBOX_SKILL_CREATOR=<skill-code-env-tool-id>
veadk frontend --agents-dir examples
```

Enable snapshots on the Codex, OpenClaw, and Hermes Tools. When AgentKit retains
a snapshot but no Session is running, Studio marks the Agent as **Wakeable** and
restores the latest snapshot before opening it. A cloud Studio deployment enables
this capability on both newly created and explicitly configured Agent Tools;
the Skill-creation Tool remains snapshot-free.

For local Studio, configure the Ark model variables on both dedicated CodeEnv
Tools. Adding a candidate to AgentKit uploads its ZIP to TOS, then creates or
updates the Skill through the Skills API. It is published to a SkillSpace only
Expand Down
14 changes: 9 additions & 5 deletions docs/content/docs/framework/frontend.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ VeADK 自带一个 React 前端,用于渲染智能体经由 Google ADK API Ser
Codex 进行多轮对话,实时展示公开思考摘要、命令、文件修改、MCP 调用和最终回复。
退出后云端 Session 会被删除,内容不会写入普通历史会话;
页面刷新后可直接新建,遗留 Session 由 AgentKit 在 TTL 到期后自动回收。
- **沙箱智能体快照**:Codex、OpenClaw 和 Hermes Tool 开启快照后,AgentKit 已保存
快照但没有运行中 Session 的智能体会显示为“可唤醒”。点击后 Studio 使用最新快照
恢复 Session,再进入 Codex 对话或 OpenClaw/Hermes 主界面;同一智能体已有运行中
Session 时不会重复展示快照。
- **智能搜索**:「会话」源在当前 agent 的历史消息中做全文检索;「网页」源调用该 agent 挂载的联网搜索工具实时检索(使用服务端环境变量里的凭据)。
- **消息反馈回流**:连接云端 AgentKit Runtime 时,回答下方的赞/踩按钮会将
当前问题、回答和反馈状态写入 AgentKit 评测集。每个 Agent 自动维护
Expand Down Expand Up @@ -200,11 +204,11 @@ ZIP 上传到 TOS,再调用 Skills API 创建或更新 Skill;只有用户填
候选 Session 的 TTL 为 30 分钟,用户重新创建或离开任务时会立即清理。任务状态保存
在 Sandbox 而非 Frontend 进程内存,因此 FaaS 请求命中不同实例时仍可继续轮询和下载。

云上首次部署时,若没有指定 Tool ID,部署命令会使用部署 AK/SK 并行创建两个独立
CodeEnv Tool。也可以分别使用 `--sandbox-chat-codex-tool-id` 和
`--sandbox-skill-creator-tool-id` 指定已有 Tool。部署命令会通过 AgentKit 凭据托管
Ark 凭据保存到 KMS,并把代理地址和可撤销票据绑定到两个 Tool;VeFaaS Function
只接收 Tool ID,不接收模型凭据:
云上首次部署时,若没有指定 Tool ID,部署命令会使用部署 AK/SK 并行创建 Codex、
OpenClaw、Hermes 和 Skill Creator 四个独立 Tool,并为前三个 Tool 开启自动快照。
也可以使用对应的 `--sandbox-*-tool-id` 参数指定已有 Tool;部署命令同样会检查并开启
其快照能力。部署命令会通过 AgentKit 凭据托管将 Ark 凭据保存到 KMS,并把代理地址
和可撤销票据绑定到 Tool;VeFaaS Function 只接收 Tool ID,不接收模型凭据:

```bash
veadk studio deploy \
Expand Down
49 changes: 35 additions & 14 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ server that `veadk frontend` launches — no separate backend.
explicit deletion. Codex streams reasoning, tool activity, and replies into
the normal conversation renderer; leaving the conversation only disconnects
it, so the Agent remains available until the user deletes it. OpenClaw and
Hermes expose their main interface and Terminal through Studio.
Hermes expose their main interface and Terminal through Studio. If a running
Session has been reclaimed after AgentKit saved it, Studio presents its latest
snapshot as **Wakeable** and restores it before opening the Agent.
- **AgentKit Skill center**: browse Skill Spaces and their skills with
server-side pagination by region, then inspect the selected Skill content.
- **Automation directory**: browse development and message-channel integrations
Expand Down Expand Up @@ -397,18 +399,37 @@ assistant messages returned by the generator. Private chain-of-thought and
credentials never enter the UI. Completed candidates still support preview,
ZIP download, and AgentKit publish.

Configure separate ready AgentKit Tools for Codex, OpenClaw, Hermes, and Skill
creation before starting the server. The Tool IDs are intentionally server-only
and cannot be supplied by the browser:
Configure a temporary and a snapshot-enabled AgentKit Tool for each of Codex,
OpenClaw, and Hermes, plus a separate Skill Creator Tool, before starting the
server. The Tool IDs are intentionally server-only and cannot be supplied by
the browser. Studio defaults new agents to a recoverable session; users can
instead select a temporary session that is destroyed at expiry without keeping
a workspace snapshot. Skill Creator does not use snapshots:

```bash
export SANDBOX_CHAT_CODEX=<chat-code-env-tool-id>
export SANDBOX_CHAT_OPENCLAW=<openclaw-env-tool-id>
export SANDBOX_CHAT_HERMES=<hermes-env-tool-id>
export SANDBOX_CHAT_CODEX=<temporary-code-env-tool-id>
export SANDBOX_CHAT_CODEX_SNAPSHOT=<recoverable-code-env-tool-id>
export SANDBOX_CHAT_OPENCLAW=<temporary-openclaw-env-tool-id>
export SANDBOX_CHAT_OPENCLAW_SNAPSHOT=<recoverable-openclaw-env-tool-id>
export SANDBOX_CHAT_HERMES=<temporary-hermes-env-tool-id>
export SANDBOX_CHAT_HERMES_SNAPSHOT=<recoverable-hermes-env-tool-id>
export SANDBOX_SKILL_CREATOR=<skill-code-env-tool-id>
veadk frontend --agents-dir examples
```

For backward compatibility, when only a legacy `SANDBOX_CHAT_*` Tool is
configured, Studio treats it as the recoverable Tool and disables the temporary
choice. A new cloud deployment automatically provisions the missing temporary
Tool.

Studio creates new Agent Sandbox sessions with a
`studio-<safe-user-name>-<uuid>` UserSessionId. When listing snapshots, the
server follows every `NextToken` page and filters this prefix for the signed-in
user before returning results to the browser. Local administrators can see all
snapshots from the configured Tool, including legacy `studio2-*` snapshots.
Multiple restorable historical snapshots are kept in the result instead of
being collapsed to only the latest one.

Publishing a generated Skill uses TOS and the AgentKit Skills API. Set
`VEADK_SKILL_CREATOR_TOS_BUCKET`, `VEADK_SKILL_CREATOR_TOS_PREFIX`, and
`VEADK_SKILL_CREATOR_PROJECT_NAME` only when their defaults are unsuitable.
Expand All @@ -418,13 +439,13 @@ memory, so polling and downloads continue to work when FaaS requests reach a
different instance.

For local Studio, run the AgentKit `credential-hosting` command and bind its
result to both CodeEnv Tools. A cloud deployment creates both Tools in parallel
when their IDs are omitted. Alternatively, select existing Tools with
`--sandbox-chat-codex-tool-id`, `--sandbox-chat-openclaw-tool-id`,
`--sandbox-chat-hermes-tool-id`, and `--sandbox-skill-creator-tool-id`. The
deploy command obtains the Ark key with the deployer's Volcengine credentials,
stores it through AgentKit credential hosting, and binds only the returned
ticket and relay URL to the Tools:
result to the Tools. A cloud deployment creates all missing Tools in parallel,
with snapshots enabled only on the three recoverable Tools. Alternatively,
select existing Tools with the `--sandbox-chat-*-tool-id` and
`--sandbox-chat-*-snapshot-tool-id` option pairs, plus
`--sandbox-skill-creator-tool-id`. The deploy command obtains the Ark key with
the deployer's Volcengine credentials, stores it through AgentKit credential
hosting, and binds only the returned ticket and relay URL to the Tools:

```bash
veadk studio deploy \
Expand Down
94 changes: 83 additions & 11 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,12 @@ import {
sandboxClient,
type SandboxApproval,
type SandboxApprovalDecision,
type SandboxAgentResource,
type SandboxAgentKind,
type SandboxAgentWorkspace as SandboxAgentWorkspaceData,
type SandboxLaunchCapabilities,
type SandboxPermissions,
type SandboxRetentionMode,
type SandboxSession as SandboxSessionInfo,
type SandboxSkill,
type SandboxToolLaunch,
Expand Down Expand Up @@ -727,15 +730,23 @@ export default function App() {
const [sandboxLaunchState, setSandboxLaunchState] =
useState<SandboxLaunchState>("confirm");
const [sandboxLaunchError, setSandboxLaunchError] = useState("");
const [sandboxLaunchCapabilities, setSandboxLaunchCapabilities] =
useState<SandboxLaunchCapabilities | null>(null);
const [sandboxLaunchCapabilitiesLoading, setSandboxLaunchCapabilitiesLoading] =
useState(false);
const [sandboxLaunchCapabilitiesError, setSandboxLaunchCapabilitiesError] =
useState("");
const [sandboxLaunchKind, setSandboxLaunchKind] =
useState<"codex" | SandboxAgentKind>("codex");
const [sandboxLaunchFromAgents, setSandboxLaunchFromAgents] = useState(false);
const [sandboxAgentRefreshKey, setSandboxAgentRefreshKey] = useState(0);
const [sandboxAgentDetailTarget, setSandboxAgentDetailTarget] =
useState<SandboxSessionInfo | null>(null);
useState<SandboxAgentResource | null>(null);
const [sandboxAgentWorkspace, setSandboxAgentWorkspace] =
useState<SandboxAgentWorkspaceData | null>(null);
const sandboxLaunchAbortRef = useRef<AbortController | null>(null);
const sandboxLaunchCapabilitiesAbortRef =
useRef<AbortController | null>(null);
const sandboxMessageAbortRef = useRef<AbortController | null>(null);
const sandboxSessionIdRef = useRef(sandboxSession?.id ?? "");
const sandboxActiveAssistantTurnIdRef = useRef("");
Expand Down Expand Up @@ -2161,6 +2172,7 @@ export default function App() {
useEffect(
() => () => {
sandboxLaunchAbortRef.current?.abort();
sandboxLaunchCapabilitiesAbortRef.current?.abort();
sandboxMessageAbortRef.current?.abort();
},
[],
Expand Down Expand Up @@ -2252,14 +2264,51 @@ export default function App() {
setSandboxLaunchKind(kind);
setSandboxLaunchFromAgents(fromAgents);
setSandboxLaunchOpen(true);
loadSandboxLaunchCapabilities(kind);
}

function loadSandboxLaunchCapabilities(
kind: "codex" | SandboxAgentKind = sandboxLaunchKind,
) {
sandboxLaunchCapabilitiesAbortRef.current?.abort();
const controller = new AbortController();
sandboxLaunchCapabilitiesAbortRef.current = controller;
setSandboxLaunchCapabilities(null);
setSandboxLaunchCapabilitiesLoading(true);
setSandboxLaunchCapabilitiesError("");
void sandboxClient
.getLaunchCapabilities(kind, { signal: controller.signal })
.then((capabilities) => {
if (sandboxLaunchCapabilitiesAbortRef.current !== controller) return;
setSandboxLaunchCapabilities(capabilities);
})
.catch((capabilityError) => {
if ((capabilityError as Error)?.name === "AbortError") return;
if (sandboxLaunchCapabilitiesAbortRef.current !== controller) return;
setSandboxLaunchCapabilitiesError(
capabilityError instanceof Error
? capabilityError.message
: String(capabilityError),
);
})
.finally(() => {
if (sandboxLaunchCapabilitiesAbortRef.current !== controller) return;
sandboxLaunchCapabilitiesAbortRef.current = null;
setSandboxLaunchCapabilitiesLoading(false);
});
}

function cancelSandboxLaunch() {
sandboxLaunchAbortRef.current?.abort();
sandboxLaunchAbortRef.current = null;
sandboxLaunchCapabilitiesAbortRef.current?.abort();
sandboxLaunchCapabilitiesAbortRef.current = null;
setSandboxLaunchOpen(false);
setSandboxLaunchState("confirm");
setSandboxLaunchError("");
setSandboxLaunchCapabilities(null);
setSandboxLaunchCapabilitiesLoading(false);
setSandboxLaunchCapabilitiesError("");
if (
!sandboxSession &&
newChatMode === "temporary" &&
Expand All @@ -2269,20 +2318,25 @@ export default function App() {
}
}

async function launchSandboxSession(displayName: string) {
async function launchSandboxSession(
displayName: string,
retentionMode: SandboxRetentionMode,
) {
sandboxLaunchAbortRef.current?.abort();
const controller = new AbortController();
sandboxLaunchAbortRef.current = controller;
setSandboxLaunchState("loading");
setSandboxLaunchError("");
try {
const createdSession = sandboxLaunchKind === "codex"
? await sandboxClient.startSession({
? await sandboxClient.startSession({
displayName,
retentionMode,
signal: controller.signal,
})
: await sandboxClient.startAgentSession(sandboxLaunchKind, {
displayName,
retentionMode,
signal: controller.signal,
});
if (sandboxLaunchAbortRef.current !== controller) return;
Expand Down Expand Up @@ -2349,8 +2403,14 @@ export default function App() {
}
}

async function openSandboxAgent(session: SandboxSessionInfo) {
async function openSandboxAgent(resource: SandboxAgentResource) {
setError("");
const session = resource.resourceType === "snapshot"
? await sandboxClient.resumeSnapshot(resource.toolName, resource.snapshotId)
: resource;
if (resource.resourceType === "snapshot") {
setSandboxAgentRefreshKey((current) => current + 1);
}
if (session.toolName === "codex") {
const connected = await sandboxClient.connectSession(session.id);
viewSidRef.current = "";
Expand All @@ -2377,20 +2437,24 @@ export default function App() {
setManageAgents(false);
}

function openSandboxAgentDetails(session: SandboxSessionInfo) {
function openSandboxAgentDetails(session: SandboxAgentResource) {
setSandboxAgentDetailTarget(session);
setSandboxAgentWorkspace(null);
setMyAgents(false);
setManageAgents(false);
setError("");
}

async function deleteSandboxAgent(session: SandboxSessionInfo) {
if (sandboxSession?.id === session.id) exitSandboxSession();
if (session.toolName === "codex") {
await sandboxClient.deleteSession(session.id);
async function deleteSandboxAgent(session: SandboxAgentResource) {
if (session.resourceType === "snapshot") {
await sandboxClient.deleteSnapshot(session.toolName, session.snapshotId);
} else {
await sandboxClient.deleteAgentSession(session.toolName, session.id);
if (sandboxSession?.id === session.id) exitSandboxSession();
if (session.toolName === "codex") {
await sandboxClient.deleteSession(session.id);
} else {
await sandboxClient.deleteAgentSession(session.toolName, session.id);
}
}
setSandboxAgentDetailTarget(null);
setSandboxAgentWorkspace(null);
Expand Down Expand Up @@ -5037,8 +5101,16 @@ export default function App() {
state={sandboxLaunchState}
agentKind={sandboxLaunchKind}
error={sandboxLaunchError}
capabilities={sandboxLaunchCapabilities}
capabilitiesLoading={sandboxLaunchCapabilitiesLoading}
capabilitiesError={sandboxLaunchCapabilitiesError}
onRetryCapabilities={() => {
loadSandboxLaunchCapabilities();
}}
onCancel={cancelSandboxLaunch}
onConfirm={(displayName) => void launchSandboxSession(displayName)}
onConfirm={(displayName, retentionMode) => {
void launchSandboxSession(displayName, retentionMode);
}}
/>

{sandboxSession ? (
Expand Down
Loading
Loading