feat: update package dependencies and add new routes for CNB environment management
- Updated package.json and pnpm-lock.yaml with new dependencies and versions. - Removed outdated readme files from requirements. - Enhanced CNB environment configuration in cnb-env.ts with new VS Code remote SSH settings. - Modified KnowledgeBase class to return structured results. - Updated Workspace class to return structured results. - Implemented new routes for managing CNB cookies and VS Code proxy URIs. - Added AI chat functionality for querying knowledge base. - Created skills for cleaning up closed workspaces.
This commit is contained in:
@@ -1 +0,0 @@
|
||||
调用 path: cnb key: get-repo-list payload: { page: 1, per_page: 10 }
|
||||
48
agent/routes/cnb-env/env.ts
Normal file
48
agent/routes/cnb-env/env.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { createSkill, tool } from '@kevisual/router';
|
||||
import { app, cnb } from '../../app.ts';
|
||||
|
||||
// 设置 CNB_COOKIE环境变量和获取环境变量,用于界面操作定制模块功能
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'set-cnb-cookie',
|
||||
description: '设置当前cnb工作空间的cookie环境变量',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'set-cnb-cookie',
|
||||
title: '设置当前cnb工作空间的cookie环境变量',
|
||||
summary: '设置当前cnb工作空间的cookie环境变量,用于界面操作定制模块功能,例子:CNBSESSION=xxxx;csrfkey=2222xxxx;',
|
||||
args: {
|
||||
cookie: tool.schema.string().describe('cnb的cookie值'),
|
||||
}
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const cookie = ctx.query?.cookie;
|
||||
if (!cookie) {
|
||||
ctx.body = { content: '请提供有效的cookie值' };
|
||||
return;
|
||||
}
|
||||
cnb.cookie = cookie;
|
||||
ctx.body = { content: '已成功设置cnb的cookie环境变量' };
|
||||
}).addTo(app);
|
||||
|
||||
// 获取 CNB_COOKIE环境变量
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'get-cnb-cookie',
|
||||
description: '获取当前cnb工作空间的cookie环境变量',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'get-cnb-cookie',
|
||||
title: '获取当前cnb工作空间的cookie环境变量',
|
||||
summary: '获取当前cnb工作空间的cookie环境变量,用于界面操作定制模块功能',
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const cookie = cnb.cookie || '未设置cookie环境变量';
|
||||
ctx.body = { content: `当前cnb工作空间的cookie环境变量为:${cookie}` };
|
||||
}).addTo(app);
|
||||
@@ -1 +1,3 @@
|
||||
// 根据环境变量获取当前的 cnb 启动环境
|
||||
// 根据环境变量获取当前的 cnb 启动环境
|
||||
import './vscode.ts';
|
||||
import './env.ts';
|
||||
94
agent/routes/cnb-env/vscode.ts
Normal file
94
agent/routes/cnb-env/vscode.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { createSkill, tool } from '@kevisual/router';
|
||||
import { app, cnb } from '../../app.ts';
|
||||
|
||||
import { CNB_ENV } from "@/common/cnb-env.ts";
|
||||
|
||||
// 执行技能 get-cnb-port-uri,端口为4096
|
||||
// 执行技能 get-cnb-port-uri,端口为51515
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'get-cnb-port-uri',
|
||||
description: '获取当前cnb工作空间的port代理uri',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'get-cnb-port-uri',
|
||||
title: '获取当前cnb工作空间的port代理uri',
|
||||
summary: '获取当前cnb工作空间的port代理uri,用于端口转发',
|
||||
args: {
|
||||
port: tool.schema.number().optional().describe('端口号,默认为4096'),
|
||||
}
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const port = ctx.query?.port || 4096;
|
||||
const uri = CNB_ENV?.CNB_VSCODE_PROXY_URI as string || '';
|
||||
const finalUri = uri.replace('{{port}}', port.toString());
|
||||
let content = `
|
||||
cnb工作空间的访问uri为:${finalUri}
|
||||
`
|
||||
ctx.body = { content };
|
||||
}).addTo(app);
|
||||
|
||||
// 获取当前cnb工作空间的vscode代理uri,执行技能 get-cnb-vscode-uri
|
||||
// 包括 web 访问uri,vscode 访问uri,codebuddy 访问uri,cursor 访问uri,ssh 连接字符串
|
||||
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'get-cnb-vscode-uri',
|
||||
description: '获取当前cnb工作空间的vscode代理uri, 包括多种访问方式, 如web、vscode、codebuddy、cursor、ssh',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'get-cnb-vscode-uri',
|
||||
title: '获取当前cnb工作空间的编辑器访问地址',
|
||||
summary: '获取当前cnb工作空间的vscode代理uri,用于在浏览器中访问vscode,包含多种访问方式,如web、vscode、codebuddy、cursor、ssh',
|
||||
args: {
|
||||
web: tool.schema.boolean().optional().describe('是否获取vscode web的访问uri,默认为false'),
|
||||
vscode: tool.schema.boolean().optional().describe('是否获取vscode的代理uri,默认为true'),
|
||||
codebuddy: tool.schema.boolean().optional().describe('是否获取codebuddy的代理uri,默认为false'),
|
||||
cursor: tool.schema.boolean().optional().describe('是否获取cursor的代理uri,默认为false'),
|
||||
// trae: tool.schema.boolean().optional().describe('是否获取trae的代理uri,默认为false'),
|
||||
ssh: tool.schema.boolean().optional().describe('是否获取vscode remote ssh的连接字符串,默认为false'),
|
||||
}
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const web = ctx.query?.web ?? false;
|
||||
const vscode = ctx.query?.vscode ?? true; // 默认true
|
||||
const codebuddy = ctx.query?.codebuddy ?? false;
|
||||
const cursor = ctx.query?.cursor ?? false;
|
||||
// const trae = ctx.query?.trae ?? false;
|
||||
const ssh = ctx.query?.ssh ?? false;
|
||||
|
||||
const webUri = CNB_ENV?.CNB_VSCODE_WEB_URL as string || '';
|
||||
const vscodeSchma = CNB_ENV?.CNB_VSCODE_REMOTE_SSH_SCHEMA as string || '';
|
||||
let content = `
|
||||
当前的cnb 仓库为:${CNB_ENV?.CNB_REPO_SLUG}
|
||||
|
||||
`
|
||||
if (web) {
|
||||
content += `VS Code Web 访问 URI:${webUri}\n\n`;
|
||||
}
|
||||
if (vscode) {
|
||||
content += `VS Code 访问 URI:${vscodeSchma}\n\n`;
|
||||
}
|
||||
if (codebuddy) {
|
||||
const codebuddyUri = vscodeSchma.replace('vscode://vscode-remote/ssh-remote+', 'codebuddycn://vscode-remote/codebuddy-remote');
|
||||
content += `CodeBuddy 访问 URI:${codebuddyUri}\n\n`;
|
||||
}
|
||||
if (cursor) {
|
||||
const cursorUri = vscodeSchma.replace('vscode://', 'cursor://');
|
||||
content += `Cursor 访问 URI:${cursorUri}\n\n`;
|
||||
}
|
||||
// if (trae) {
|
||||
// const traeUri = vscodeSchma.replace('vscode://vscode-remote/ssh-remote+', 'traecn://ssh-remote+');
|
||||
// content += `Trae 访问 URI:${traeUri}\n\n`;
|
||||
// }
|
||||
if (ssh) {
|
||||
content += `VS Code Remote SSH 连接字符串:ssh ${CNB_ENV.CNB_PIPELINE_ID}.${CNB_ENV.CNB_VSCODE_SSH_TOKEN}@cnb.space`;
|
||||
}
|
||||
ctx.body = { content };
|
||||
}).addTo(app);
|
||||
@@ -3,6 +3,8 @@ import './user/check.ts'
|
||||
import './repo/index.ts'
|
||||
import './workspace/index.ts'
|
||||
import './call/index.ts'
|
||||
import './cnb-env/index.ts'
|
||||
import './knowledge/index.ts'
|
||||
|
||||
import { isEqual } from 'es-toolkit'
|
||||
/**
|
||||
|
||||
142
agent/routes/knowledge/ai.ts
Normal file
142
agent/routes/knowledge/ai.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { createSkill, tool } from '@kevisual/router';
|
||||
import { app, cnb } from '../../app.ts';
|
||||
import { CNBChat } from '@kevisual/ai/browser'
|
||||
|
||||
/**
|
||||
|
||||
调用cnb-ai-chat技能, repo为kevisual/starred-auto.
|
||||
问题是:用户提供的问题是OpenListTeam/OpenList是什么,有多少star
|
||||
|
||||
*/
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'cnb-ai-chat',
|
||||
description: '调用cnb的知识库ai对话功能进行聊天',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'cnb-ai-chat',
|
||||
title: '调用cnb的知识库ai对话功能进行聊天',
|
||||
summary: '调用cnb的知识库ai对话功能进行聊天,基于cnb提供的ai能力',
|
||||
args: {
|
||||
question: tool.schema.string().describe('用户输入的消息内容'),
|
||||
repo: tool.schema.string().optional().describe('知识库仓库ID,默认为空表示使用默认知识库'),
|
||||
}
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const question = ctx.query?.question;
|
||||
if (!question) {
|
||||
ctx.body = { content: '请提供有效的消息内容' };
|
||||
return;
|
||||
}
|
||||
let repo = ctx.query?.repo;
|
||||
if (!repo) {
|
||||
// 如果未指定知识库仓库ID,则使用默认知识库
|
||||
const res = await cnb.repo.getRepoList({ flags: 'KnowledgeBase' });
|
||||
if (res.code === 200 && res.data.length > 0) {
|
||||
repo = res.data[0].path;
|
||||
}
|
||||
}
|
||||
console.log("Using knowledge base repo:", repo);
|
||||
const ragRes = await cnb.knowledgeBase.queryKnowledgeBase(repo || '', {
|
||||
query: question,
|
||||
score_threshold: 0.62,
|
||||
top_k: 10,
|
||||
});
|
||||
if (ragRes.code !== 200) {
|
||||
ctx.body = { content: `查询知识库失败,错误信息:${ragRes.message}` };
|
||||
return;
|
||||
}
|
||||
const list = ragRes.data || [];
|
||||
// 构建RAG上下文,包含文件来源和相似度信息
|
||||
const ragContext = list.map((item, index) => {
|
||||
const source = item.metadata?.path || item.metadata?.name || '未知来源';
|
||||
const type = item.metadata?.type === 'code' ? '〔代码〕' : '〔文档〕';
|
||||
const scorePercent = Math.round((item.score || 0) * 100);
|
||||
const url = item.metadata?.url || '无';
|
||||
return `〔来源${index + 1}〕${type} ${source} (相似度: ${scorePercent}%)\n${item.chunk}\n访问地址: ${url}`;
|
||||
}).join('\n\n---\n\n');
|
||||
// hunyuan-a13b
|
||||
// enable_thinking
|
||||
const chat = new CNBChat({
|
||||
repo,
|
||||
token: cnb.token,
|
||||
model: 'hunyuan-a13b'
|
||||
});
|
||||
const messages = [
|
||||
{
|
||||
role: 'system',
|
||||
content: `[角色定义]='''\n你是一个专业的技术助手,擅长基于提供的知识库内容进行准确、有条理的分析和回答。你的特点是:\n1. 严格基于RAG检索到的上下文内容进行回答,不添加未经验证的信息\n2. 回答时清晰标注信息来源,便于用户追溯查证\n3. 面对不确定的信息,明确标注「根据提供的内容无法确定」\n4. 代码相关问题注重可执行性和最佳实践\n'''[要求]='''\n1. 严格遵循用户的提问要求,优先解决用户的核心问题\n2. 避免侵犯版权的内容,不复制原文超过100字(技术术语和函数名除外)\n3. 使用中文进行响应,语言专业且易于理解\n4. 如果上下文存在多个来源,优先使用相似度更高的内容\n5. 对于代码片段,确保完整且可直接使用\n6. 当上下文中没有相关信息时,直接说明「知识库中未找到相关内容」\n7. 在思考过程中分析:用户的真实意图是什么?提供的上下文是否足够回答?\n'''[回答格式]='''\n- 先简要说明回答的核心结论\n- 如有必要,分点阐述详细分析过程\n- 标注关键信息来源(标注【来源X】即可)\n- 提供可操作的建议或代码示例\n'''`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `[上下文]='''\n${ragContext}\n'''\n\n[用户问题]='''\n${question}\n'''\n\n请基于以上上下文知识库内容回答用户问题。`
|
||||
}
|
||||
] as Array<{ role: 'system' | 'user' | 'assistant', content: string }>;
|
||||
const response = await chat.chat({
|
||||
messages
|
||||
});
|
||||
const txt = chat.responseText;
|
||||
ctx.body = { content: txt, response };
|
||||
}).addTo(app);
|
||||
|
||||
|
||||
// RAG知识库查询技能: 查询openlist有多少star
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'cnb-rag-query',
|
||||
description: '调用cnb的知识库RAG查询功能进行问答',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'cnb-rag-query',
|
||||
title: '调用cnb的知识库RAG查询功能进行问答',
|
||||
summary: '调用cnb的知识库RAG查询功能进行问答,基于cnb提供的知识库能力',
|
||||
args: {
|
||||
question: tool.schema.string().describe('用户输入的消息内容'),
|
||||
repo: tool.schema.string().optional().describe('知识库仓库ID,默认为空表示使用默认知识库'),
|
||||
}
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const question = ctx.query?.question;
|
||||
if (!question) {
|
||||
ctx.body = { content: '请提供有效的消息内容' };
|
||||
return;
|
||||
}
|
||||
let repo = ctx.query?.repo;
|
||||
if (!repo) {
|
||||
// 如果未指定知识库仓库ID,则使用默认知识库
|
||||
const res = await cnb.repo.getRepoList({ flags: 'KnowledgeBase' });
|
||||
if (res.code === 200 && res.data.length > 0) {
|
||||
repo = res.data[0].path;
|
||||
}
|
||||
}
|
||||
console.log("Using knowledge base repo:", repo);
|
||||
const ragRes = await cnb.knowledgeBase.queryKnowledgeBase(repo || '', {
|
||||
query: question,
|
||||
score_threshold: 0.62,
|
||||
top_k: 10,
|
||||
});
|
||||
if (ragRes.code !== 200) {
|
||||
ctx.body = { content: `查询知识库失败,错误信息:${ragRes.message}` };
|
||||
return;
|
||||
}
|
||||
const list = ragRes.data || [];
|
||||
let answer = `基于知识库「${repo}」的查询结果:\n\n`;
|
||||
if (list.length === 0) {
|
||||
answer += '知识库中未找到相关内容。';
|
||||
} else {
|
||||
list.forEach((item, index) => {
|
||||
const source = item.metadata?.path || item.metadata?.name || '未知来源';
|
||||
const type = item.metadata?.type === 'code' ? '〔代码〕' : '〔文档〕';
|
||||
const scorePercent = Math.round((item.score || 0) * 100);
|
||||
const url = item.metadata?.url || '无';
|
||||
answer += `【来源${index + 1}】${type} ${source} (相似度: ${scorePercent}%)\n${item.chunk}\n访问地址: ${url}\n\n`;
|
||||
});
|
||||
}
|
||||
ctx.body = { content: answer };
|
||||
}).addTo(app);
|
||||
1
agent/routes/knowledge/index.ts
Normal file
1
agent/routes/knowledge/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './ai.ts'
|
||||
@@ -2,6 +2,8 @@ import { createSkill } from '@kevisual/router';
|
||||
import { app, cnb } from '../../app.ts';
|
||||
import { tool } from "@opencode-ai/plugin/tool"
|
||||
|
||||
// "列出我的代码仓库,search blog"
|
||||
// 列出我的知识库的代码仓库
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'list-repos',
|
||||
@@ -11,18 +13,24 @@ app.route({
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'list-repos',
|
||||
title: '列出代码仓库',
|
||||
summary: '列出代码仓库',
|
||||
title: '列出cnb代码仓库',
|
||||
summary: '列出cnb代码仓库, 可选flags参数,如 KnowledgeBase',
|
||||
args: {
|
||||
search: tool.schema.string().optional().describe('搜索关键词'),
|
||||
pageSize: tool.schema.number().optional().describe('每页数量,默认999'),
|
||||
flags: tool.schema.string().optional().describe('仓库标记,如果是知识库则填写 KnowledgeBase'),
|
||||
},
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const search = ctx.query?.search;
|
||||
const pageSize = ctx.query?.pageSize || 9999;
|
||||
const res = await cnb.repo.getRepoList({ search, page_size: pageSize, role: 'developer' });
|
||||
const flags = ctx.query?.flags;
|
||||
const params: any = {};
|
||||
if (flags) {
|
||||
params.flags = flags;
|
||||
}
|
||||
const res = await cnb.repo.getRepoList({ search, page_size: pageSize, role: 'developer', ...params });
|
||||
if (res.code === 200) {
|
||||
const repos = res.data.map((item) => ({
|
||||
name: item.name,
|
||||
@@ -30,7 +38,7 @@ app.route({
|
||||
description: item.description,
|
||||
web_url: item.web_url,
|
||||
}));
|
||||
ctx.body = { content: JSON.stringify(repos) };
|
||||
ctx.body = { content: JSON.stringify(repos), list: res.data };
|
||||
} else {
|
||||
ctx.throw(500, '获取仓库列表失败');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { app, cnb } from '../../app.ts';
|
||||
import { createSkill, Skill } from '@kevisual/router'
|
||||
import { tool } from "@opencode-ai/plugin/tool"
|
||||
|
||||
// 创建一个仓库 kevisual/test-repo
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'create-repo',
|
||||
@@ -79,7 +81,7 @@ app.route({
|
||||
ctx.forward(res);
|
||||
}).addTo(app);
|
||||
|
||||
|
||||
// 删除一个仓库 kevisual/test-repo
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'delete-repo',
|
||||
|
||||
0
agent/routes/repo/skills.ts
Normal file
0
agent/routes/repo/skills.ts
Normal file
@@ -1,12 +1,26 @@
|
||||
import { createSkill, tool } from '@kevisual/router';
|
||||
import { app, cnb } from '../../app.ts';
|
||||
import z from 'zod';
|
||||
import './skills.ts';
|
||||
|
||||
// 启动工作空间
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'start-workspace',
|
||||
description: '启动开发工作空间, 参数 repo',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode']
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'start-workspace',
|
||||
title: '启动cnb工作空间',
|
||||
summary: '启动cnb工作空间',
|
||||
args: {
|
||||
repo: tool.schema.string().describe('代码仓库路径,例如 user/repo'),
|
||||
branch: tool.schema.string().optional().describe('分支名称,默认主分支'),
|
||||
ref: tool.schema.string().optional().describe('提交引用,例如 commit sha'),
|
||||
},
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const repo = ctx.query?.repo;
|
||||
@@ -21,3 +35,133 @@ app.route({
|
||||
});
|
||||
ctx.forward(res);
|
||||
}).addTo(app);
|
||||
|
||||
// 获取cnb工作空间列表
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'list-workspace',
|
||||
description: '获取cnb开发工作空间列表,可选参数 status=running 获取运行中的环境',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'list-workspace',
|
||||
title: '列出cnb工作空间',
|
||||
summary: '列出cnb工作空间列表,支持按状态过滤, status 可选值 running 或 closed',
|
||||
args: {
|
||||
status: tool.schema.string().optional().describe('开发环境状态,running: 运行中,closed: 已关闭和停止的'),
|
||||
page: tool.schema.number().optional().describe('分页页码,默认 1'),
|
||||
pageSize: tool.schema.number().optional().describe('分页大小,默认 20,最大 100'),
|
||||
slug: tool.schema.string().optional().describe('仓库路径,例如 groupname/reponame'),
|
||||
branch: tool.schema.string().optional().describe('分支名称'),
|
||||
},
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const { status = 'running', page, pageSize, slug, branch } = ctx.query || {};
|
||||
const res = await cnb.workspace.list({
|
||||
status: status as 'running' | 'closed' | undefined,
|
||||
page: page ?? 1,
|
||||
pageSize: pageSize ?? 100,
|
||||
});
|
||||
ctx.forward({ code: 200, message: 'success', data: res });
|
||||
}).addTo(app);
|
||||
|
||||
// 获取工作空间详情
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'get-workspace',
|
||||
description: '获取工作空间详情,通过 repo 和 sn 获取',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'get-workspace',
|
||||
title: '获取工作空间详情',
|
||||
summary: '获取工作空间详细信息',
|
||||
args: {
|
||||
repo: tool.schema.string().describe('代码仓库路径,例如 user/repo'),
|
||||
sn: tool.schema.string().describe('工作空间流水线的 sn'),
|
||||
},
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const repo = ctx.query?.repo;
|
||||
const sn = ctx.query?.sn;
|
||||
if (!repo) {
|
||||
ctx.throw(400, '缺少参数 repo');
|
||||
}
|
||||
if (!sn) {
|
||||
ctx.throw(400, '缺少参数 sn');
|
||||
}
|
||||
const res = await cnb.workspace.getDetail(repo, sn);
|
||||
ctx.forward({ code: 200, message: 'success', data: res });
|
||||
}).addTo(app);
|
||||
|
||||
// 删除工作空间
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'delete-workspace',
|
||||
description: '删除工作空间,通过 pipelineId 或 sn',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'delete-workspace',
|
||||
title: '删除工作空间',
|
||||
summary: '删除工作空间,pipelineId 和 sn 二选一',
|
||||
args: {
|
||||
pipelineId: tool.schema.string().optional().describe('流水线 ID,优先使用'),
|
||||
sn: tool.schema.string().optional().describe('流水线构建号'),
|
||||
sns: tool.schema.array(z.string()).optional().describe('流水线构建号'),
|
||||
},
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const pipelineId = ctx.query?.pipelineId;
|
||||
const sn = ctx.query?.sn;
|
||||
const sns = ctx.query?.sns;
|
||||
if (!pipelineId && !sn && (!sns || sns.length === 0)) {
|
||||
ctx.throw(400, 'pipelineId 和 sn 必须提供其中一个');
|
||||
}
|
||||
if (sns && sns.length > 0) {
|
||||
const results = [];
|
||||
for (const snItem of sns) {
|
||||
const res = await cnb.workspace.deleteWorkspace({ sn: snItem });
|
||||
results.push(res);
|
||||
}
|
||||
ctx.forward({ code: 200, message: 'success', data: results });
|
||||
return;
|
||||
}
|
||||
const res = await cnb.workspace.deleteWorkspace({ pipelineId, sn });
|
||||
ctx.forward(res);
|
||||
}).addTo(app);
|
||||
|
||||
// 停止工作空间
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'stop-workspace',
|
||||
description: '停止工作空间,通过 pipelineId 或 sn',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'stop-workspace',
|
||||
title: '停止工作空间',
|
||||
summary: '停止运行中的工作空间',
|
||||
args: {
|
||||
pipelineId: tool.schema.string().optional().describe('流水线 ID,优先使用'),
|
||||
sn: tool.schema.string().optional().describe('流水线构建号'),
|
||||
},
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const pipelineId = ctx.query?.pipelineId;
|
||||
const sn = ctx.query?.sn;
|
||||
if (!pipelineId && !sn) {
|
||||
ctx.throw(400, 'pipelineId 和 sn 必须提供其中一个');
|
||||
}
|
||||
const res = await cnb.workspace.stopWorkspace({ pipelineId, sn });
|
||||
ctx.forward({ code: 200, message: 'success', data: res });
|
||||
}).addTo(app);
|
||||
|
||||
|
||||
64
agent/routes/workspace/skills.ts
Normal file
64
agent/routes/workspace/skills.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { createSkill, tool } from '@kevisual/router';
|
||||
import { app, cnb } from '../../app.ts';
|
||||
|
||||
// 批量删除已停止的cnb工作空间
|
||||
// app.route({
|
||||
// path: 'cnb',
|
||||
// key: 'clean-closed-workspace-skill',
|
||||
// description: '批量删除已停止的cnb工作空间',
|
||||
// middleware: ['auth'],
|
||||
// metadata: {
|
||||
// tags: ['opencode'],
|
||||
// ...createSkill({
|
||||
// skill: 'clean-closed-workspace-skill',
|
||||
// title: '清理已关闭的cnb工作空间',
|
||||
// summary: '批量删除已停止的cnb工作空间,释放资源',
|
||||
// args: {
|
||||
// question: tool.schema.string().optional().describe('具体的要求的信息'),
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }).define(async (ctx) => {
|
||||
// const question = ctx.query?.question || '';
|
||||
// let content = `这是一个技能任务, 批量删除已停止的cnb工作空间,释放资源
|
||||
// 执行步骤:
|
||||
// 1. 执行list-workspace,获取状态为 closed 的工作空间列表,提取sn
|
||||
// 2. 执行delete-workspace技能,传入sns列表的数组,批量删除工作空间`
|
||||
// if (question) {
|
||||
// content += `\n注意用户的具体要求是:${question}`;
|
||||
// }
|
||||
// ctx.body = { content }
|
||||
// }).addTo(app);
|
||||
|
||||
// 批量删除已停止的cnb工作空间
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'clean-closed-workspace',
|
||||
description: '批量删除已停止的cnb工作空间',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'clean-closed-workspace',
|
||||
title: '清理已关闭的cnb工作空间',
|
||||
summary: '批量删除已停止的cnb工作空间,释放资源',
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const closedWorkspaces = await cnb.workspace.list({ status: 'closed' });
|
||||
if (closedWorkspaces.code !== 200) {
|
||||
ctx.throw(500, '获取已关闭工作空间列表失败');
|
||||
}
|
||||
const list = closedWorkspaces.data?.list || [];
|
||||
if (list.length === 0) {
|
||||
ctx.forward({ code: 200, message: '没有已关闭的工作空间需要删除', data: [] });
|
||||
return;
|
||||
}
|
||||
const sns = list.map(ws => ws.sn);
|
||||
const results = [];
|
||||
for (const sn of sns) {
|
||||
const res = await cnb.workspace.deleteWorkspace({ sn });
|
||||
results.push(res);
|
||||
}
|
||||
ctx.forward({ code: 200, message: '已关闭的工作空间删除完成', data: results });
|
||||
}).addTo(app);
|
||||
Reference in New Issue
Block a user