@kevisual/cnb 0.0.12 → 0.0.14
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/agent/routes/call/index.ts +30 -28
- package/agent/routes/cnb-env/check.ts +1 -1
- package/agent/routes/cnb-env/env.ts +2 -2
- package/agent/routes/cnb-env/vscode.ts +2 -2
- package/agent/routes/issues/issue.ts +2 -2
- package/agent/routes/issues/list.ts +1 -1
- package/agent/routes/knowledge/ai.ts +2 -2
- package/agent/routes/repo/list.ts +1 -1
- package/agent/routes/repo/repo.ts +3 -3
- package/agent/routes/workspace/index.ts +5 -5
- package/agent/routes/workspace/keep.ts +121 -28
- package/agent/routes/workspace/skills.ts +1 -1
- package/dist/keep.js +2850 -4
- package/dist/opencode.js +3562 -212
- package/dist/routes.d.ts +11 -2
- package/dist/routes.js +3558 -208
- package/package.json +8 -6
- package/src/cnb-core.ts +8 -0
- package/src/git/index.ts +676 -0
- package/src/repo/index.ts +2 -2
- package/src/user/index.ts +4 -4
|
@@ -2,31 +2,33 @@ import { createSkill } from '@kevisual/router'
|
|
|
2
2
|
import { app } from '../../app.ts'
|
|
3
3
|
import { tool } from '@opencode-ai/plugin/tool'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
5
|
+
if (!app.hasRoute('call')) {
|
|
6
|
+
// "调用 path: cnb key: list-repos"
|
|
7
|
+
app.route({
|
|
8
|
+
path: 'call',
|
|
9
|
+
key: '',
|
|
10
|
+
description: '调用',
|
|
11
|
+
middleware: ['admin-auth'],
|
|
12
|
+
metadata: {
|
|
13
|
+
tags: ['opencode'],
|
|
14
|
+
...createSkill({
|
|
15
|
+
skill: 'call-app',
|
|
16
|
+
title: '调用app应用',
|
|
17
|
+
summary: '调用router的应用, 参数path, key, payload',
|
|
18
|
+
args: {
|
|
19
|
+
path: tool.schema.string().describe('应用路径,例如 cnb'),
|
|
20
|
+
key: tool.schema.string().optional().describe('应用key,例如 list-repos'),
|
|
21
|
+
payload: tool.schema.object({}).optional().describe('调用参数'),
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
},
|
|
25
|
+
}).define(async (ctx) => {
|
|
26
|
+
const { path, key } = ctx.query;
|
|
27
|
+
console.log('call app', ctx.query);
|
|
28
|
+
if (!path) {
|
|
29
|
+
ctx.throw('路径path不能为空');
|
|
30
|
+
}
|
|
31
|
+
const res = await ctx.run({ path, key, payload: ctx.query.payload || {} });
|
|
32
|
+
ctx.forward(res);
|
|
33
|
+
}).addTo(app)
|
|
34
|
+
}
|
|
@@ -6,7 +6,7 @@ app.route({
|
|
|
6
6
|
path: 'cnb',
|
|
7
7
|
key: 'set-cnb-cookie',
|
|
8
8
|
description: '设置当前cnb工作空间的cookie环境变量',
|
|
9
|
-
middleware: ['auth'],
|
|
9
|
+
middleware: ['admin-auth'],
|
|
10
10
|
metadata: {
|
|
11
11
|
tags: ['opencode'],
|
|
12
12
|
...createSkill({
|
|
@@ -33,7 +33,7 @@ app.route({
|
|
|
33
33
|
path: 'cnb',
|
|
34
34
|
key: 'get-cnb-cookie',
|
|
35
35
|
description: '获取当前cnb工作空间的cookie环境变量',
|
|
36
|
-
middleware: ['auth'],
|
|
36
|
+
middleware: ['admin-auth'],
|
|
37
37
|
metadata: {
|
|
38
38
|
tags: ['opencode'],
|
|
39
39
|
...createSkill({
|
|
@@ -11,7 +11,7 @@ app.route({
|
|
|
11
11
|
path: 'cnb',
|
|
12
12
|
key: 'get-cnb-port-uri',
|
|
13
13
|
description: '获取当前cnb工作空间的port代理uri',
|
|
14
|
-
middleware: ['auth'],
|
|
14
|
+
middleware: ['admin-auth'],
|
|
15
15
|
metadata: {
|
|
16
16
|
tags: ['opencode'],
|
|
17
17
|
...createSkill({
|
|
@@ -40,7 +40,7 @@ app.route({
|
|
|
40
40
|
path: 'cnb',
|
|
41
41
|
key: 'get-cnb-vscode-uri',
|
|
42
42
|
description: '获取当前cnb工作空间的vscode代理uri, 包括多种访问方式, 如web、vscode、codebuddy、cursor、ssh',
|
|
43
|
-
middleware: ['auth'],
|
|
43
|
+
middleware: ['admin-auth'],
|
|
44
44
|
metadata: {
|
|
45
45
|
tags: ['opencode'],
|
|
46
46
|
...createSkill({
|
|
@@ -7,7 +7,7 @@ app.route({
|
|
|
7
7
|
path: 'cnb',
|
|
8
8
|
key: 'create-issue',
|
|
9
9
|
description: '创建 Issue, 参数 repo, title, body, assignees, labels, priority',
|
|
10
|
-
middleware: ['auth'],
|
|
10
|
+
middleware: ['admin-auth'],
|
|
11
11
|
metadata: {
|
|
12
12
|
tags: ['opencode'],
|
|
13
13
|
...createSkill({
|
|
@@ -51,7 +51,7 @@ app.route({
|
|
|
51
51
|
path: 'cnb',
|
|
52
52
|
key: 'complete-issue',
|
|
53
53
|
description: '完成 Issue, 参数 repo, issueNumber',
|
|
54
|
-
middleware: ['auth'],
|
|
54
|
+
middleware: ['admin-auth'],
|
|
55
55
|
metadata: {
|
|
56
56
|
tags: ['opencode'],
|
|
57
57
|
...createSkill({
|
|
@@ -12,7 +12,7 @@ app.route({
|
|
|
12
12
|
path: 'cnb',
|
|
13
13
|
key: 'cnb-ai-chat',
|
|
14
14
|
description: '调用cnb的知识库ai对话功能进行聊天',
|
|
15
|
-
middleware: ['auth'],
|
|
15
|
+
middleware: ['admin-auth'],
|
|
16
16
|
metadata: {
|
|
17
17
|
tags: ['opencode'],
|
|
18
18
|
...createSkill({
|
|
@@ -88,7 +88,7 @@ app.route({
|
|
|
88
88
|
path: 'cnb',
|
|
89
89
|
key: 'cnb-rag-query',
|
|
90
90
|
description: '调用cnb的知识库RAG查询功能进行问答',
|
|
91
|
-
middleware: ['auth'],
|
|
91
|
+
middleware: ['admin-auth'],
|
|
92
92
|
metadata: {
|
|
93
93
|
tags: ['opencode'],
|
|
94
94
|
...createSkill({
|
|
@@ -7,7 +7,7 @@ app.route({
|
|
|
7
7
|
path: 'cnb',
|
|
8
8
|
key: 'create-repo',
|
|
9
9
|
description: '创建代码仓库, 参数name, visibility, description',
|
|
10
|
-
middleware: ['auth'],
|
|
10
|
+
middleware: ['admin-auth'],
|
|
11
11
|
metadata: {
|
|
12
12
|
tags: ['opencode'],
|
|
13
13
|
...createSkill({
|
|
@@ -47,7 +47,7 @@ app.route({
|
|
|
47
47
|
path: 'cnb',
|
|
48
48
|
key: 'create-repo-file',
|
|
49
49
|
description: '在代码仓库中创建文件, repoName, filePath, content, encoding',
|
|
50
|
-
middleware: ['auth'],
|
|
50
|
+
middleware: ['admin-auth'],
|
|
51
51
|
metadata: {
|
|
52
52
|
tags: ['opencode'],
|
|
53
53
|
...createSkill({
|
|
@@ -86,7 +86,7 @@ app.route({
|
|
|
86
86
|
path: 'cnb',
|
|
87
87
|
key: 'delete-repo',
|
|
88
88
|
description: '删除代码仓库, 参数name',
|
|
89
|
-
middleware: ['auth'],
|
|
89
|
+
middleware: ['admin-auth'],
|
|
90
90
|
metadata: {
|
|
91
91
|
tags: ['opencode'],
|
|
92
92
|
...createSkill({
|
|
@@ -9,7 +9,7 @@ app.route({
|
|
|
9
9
|
path: 'cnb',
|
|
10
10
|
key: 'start-workspace',
|
|
11
11
|
description: '启动开发工作空间, 参数 repo',
|
|
12
|
-
middleware: ['auth'],
|
|
12
|
+
middleware: ['admin-auth'],
|
|
13
13
|
metadata: {
|
|
14
14
|
tags: ['opencode'],
|
|
15
15
|
...createSkill({
|
|
@@ -42,7 +42,7 @@ app.route({
|
|
|
42
42
|
path: 'cnb',
|
|
43
43
|
key: 'list-workspace',
|
|
44
44
|
description: '获取cnb开发工作空间列表,可选参数 status=running 获取运行中的环境',
|
|
45
|
-
middleware: ['auth'],
|
|
45
|
+
middleware: ['admin-auth'],
|
|
46
46
|
metadata: {
|
|
47
47
|
tags: ['opencode'],
|
|
48
48
|
...createSkill({
|
|
@@ -73,7 +73,7 @@ app.route({
|
|
|
73
73
|
path: 'cnb',
|
|
74
74
|
key: 'get-workspace',
|
|
75
75
|
description: '获取工作空间详情,通过 repo 和 sn 获取',
|
|
76
|
-
middleware: ['auth'],
|
|
76
|
+
middleware: ['admin-auth'],
|
|
77
77
|
metadata: {
|
|
78
78
|
tags: ['opencode'],
|
|
79
79
|
...createSkill({
|
|
@@ -104,7 +104,7 @@ app.route({
|
|
|
104
104
|
path: 'cnb',
|
|
105
105
|
key: 'delete-workspace',
|
|
106
106
|
description: '删除工作空间,通过 pipelineId 或 sn',
|
|
107
|
-
middleware: ['auth'],
|
|
107
|
+
middleware: ['admin-auth'],
|
|
108
108
|
metadata: {
|
|
109
109
|
tags: ['opencode'],
|
|
110
110
|
...createSkill({
|
|
@@ -143,7 +143,7 @@ app.route({
|
|
|
143
143
|
path: 'cnb',
|
|
144
144
|
key: 'stop-workspace',
|
|
145
145
|
description: '停止工作空间,通过 pipelineId 或 sn',
|
|
146
|
-
middleware: ['auth'],
|
|
146
|
+
middleware: ['admin-auth'],
|
|
147
147
|
metadata: {
|
|
148
148
|
tags: ['opencode'],
|
|
149
149
|
...createSkill({
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { createSkill, tool } from '@kevisual/router';
|
|
2
2
|
import { app, cnb } from '../../app.ts';
|
|
3
|
-
|
|
3
|
+
import { nanoid } from 'nanoid';
|
|
4
|
+
import dayjs from 'dayjs';
|
|
4
5
|
import { createKeepAlive } from '../../../src/keep.ts';
|
|
5
6
|
|
|
6
7
|
type AliveInfo = {
|
|
7
8
|
startTime: number;
|
|
8
9
|
updatedTime?: number;
|
|
9
10
|
KeepAlive: ReturnType<typeof createKeepAlive>;
|
|
11
|
+
id: string;// 6位唯一标识符
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
const keepAliveMap = new Map<string, AliveInfo>();
|
|
@@ -16,7 +18,7 @@ app.route({
|
|
|
16
18
|
path: 'cnb',
|
|
17
19
|
key: 'keep-workspace-alive',
|
|
18
20
|
description: '保持工作空间存活技能,参数wsUrl:工作空间访问URL,cookie:访问工作空间所需的cookie',
|
|
19
|
-
middleware: ['auth'],
|
|
21
|
+
middleware: ['admin-auth'],
|
|
20
22
|
metadata: {
|
|
21
23
|
tags: [],
|
|
22
24
|
...({
|
|
@@ -36,10 +38,10 @@ app.route({
|
|
|
36
38
|
ctx.throw(400, '缺少访问工作空间所需的cookie参数');
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
//
|
|
40
|
-
const existing = keepAliveMap.
|
|
41
|
+
// 检测是否已在运行(通过 wsUrl 遍历检查)
|
|
42
|
+
const existing = Array.from(keepAliveMap.values()).find(info => (info as AliveInfo).id && (info as any).KeepAlive?.wsUrl === wsUrl);
|
|
41
43
|
if (existing) {
|
|
42
|
-
ctx.body = { message: `工作空间 ${wsUrl}
|
|
44
|
+
ctx.body = { message: `工作空间 ${wsUrl} 的保持存活任务已在运行中`, id: (existing as AliveInfo).id };
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
47
|
|
|
@@ -53,21 +55,31 @@ app.route({
|
|
|
53
55
|
onMessage: (data) => {
|
|
54
56
|
// 可选:处理收到的消息
|
|
55
57
|
// console.log(`工作空间 ${wsUrl} 收到消息: ${data}`);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
// 通过 wsUrl 找到对应的 id 并更新时间
|
|
59
|
+
for (const info of keepAliveMap.values()) {
|
|
60
|
+
if ((info as any).KeepAlive?.wsUrl === wsUrl) {
|
|
61
|
+
info.updatedTime = Date.now();
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
59
64
|
}
|
|
60
65
|
},
|
|
61
66
|
debug: true,
|
|
62
67
|
onExit: (code) => {
|
|
63
68
|
console.log(`工作空间 ${wsUrl} 保持存活任务已退出,退出码: ${code}`);
|
|
64
|
-
|
|
69
|
+
// 通过 wsUrl 找到对应的 id 并删除
|
|
70
|
+
for (const [id, info] of keepAliveMap.entries()) {
|
|
71
|
+
if ((info as any).KeepAlive?.wsUrl === wsUrl) {
|
|
72
|
+
keepAliveMap.delete(id);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
65
76
|
}
|
|
66
77
|
});
|
|
67
78
|
|
|
68
|
-
|
|
79
|
+
const id = nanoid(6).toLowerCase();
|
|
80
|
+
keepAliveMap.set(id, { startTime: Date.now(), updatedTime: Date.now(), KeepAlive: keep, id });
|
|
69
81
|
|
|
70
|
-
ctx.body = {
|
|
82
|
+
ctx.body = { content: `已启动保持工作空间 ${wsUrl} 存活的任务`, id };
|
|
71
83
|
}).addTo(app);
|
|
72
84
|
|
|
73
85
|
// 获取保持工作空间存活任务列表技能
|
|
@@ -75,16 +87,24 @@ app.route({
|
|
|
75
87
|
path: 'cnb',
|
|
76
88
|
key: 'list-keep-alive-tasks',
|
|
77
89
|
description: '获取保持工作空间存活任务列表技能',
|
|
78
|
-
middleware: ['auth'],
|
|
90
|
+
middleware: ['admin-auth'],
|
|
79
91
|
metadata: {
|
|
80
92
|
tags: [],
|
|
81
93
|
}
|
|
82
94
|
}).define(async (ctx) => {
|
|
83
|
-
const list = Array.from(keepAliveMap.entries()).map(([
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
const list = Array.from(keepAliveMap.entries()).map(([id, info]) => {
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
const duration = Math.floor((now - info.startTime) / 60000); // 分钟
|
|
98
|
+
return {
|
|
99
|
+
id,
|
|
100
|
+
wsUrl: (info as any).KeepAlive?.wsUrl,
|
|
101
|
+
startTime: info.startTime,
|
|
102
|
+
startTimeStr: dayjs(info.startTime).format('YYYY-MM-DD HH:mm'),
|
|
103
|
+
updatedTime: info.updatedTime,
|
|
104
|
+
updatedTimeStr: dayjs(info.updatedTime).format('YYYY-MM-DD HH:mm'),
|
|
105
|
+
duration,
|
|
106
|
+
}
|
|
107
|
+
});
|
|
88
108
|
ctx.body = { list };
|
|
89
109
|
}).addTo(app);
|
|
90
110
|
|
|
@@ -92,30 +112,103 @@ app.route({
|
|
|
92
112
|
app.route({
|
|
93
113
|
path: 'cnb',
|
|
94
114
|
key: 'stop-keep-workspace-alive',
|
|
95
|
-
description: '停止保持工作空间存活技能, 参数wsUrl:工作空间访问URL',
|
|
96
|
-
middleware: ['auth'],
|
|
115
|
+
description: '停止保持工作空间存活技能, 参数wsUrl:工作空间访问URL或者id',
|
|
116
|
+
middleware: ['admin-auth'],
|
|
97
117
|
metadata: {
|
|
98
118
|
tags: [],
|
|
99
119
|
...({
|
|
100
120
|
args: {
|
|
101
|
-
wsUrl: tool.schema.string().describe('工作空间的访问URL'),
|
|
121
|
+
wsUrl: tool.schema.string().optional().describe('工作空间的访问URL'),
|
|
122
|
+
id: tool.schema.string().optional().describe('保持存活任务的唯一标识符'),
|
|
102
123
|
}
|
|
103
124
|
})
|
|
104
125
|
}
|
|
105
126
|
}).define(async (ctx) => {
|
|
106
127
|
const wsUrl = ctx.query?.wsUrl as string;
|
|
107
|
-
|
|
108
|
-
|
|
128
|
+
const id = ctx.query?.id as string;
|
|
129
|
+
if (!wsUrl && !id) {
|
|
130
|
+
ctx.throw(400, '缺少工作空间访问URL参数或唯一标识符');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let targetId: string | undefined;
|
|
134
|
+
let wsUrlFound: string | undefined;
|
|
135
|
+
|
|
136
|
+
if (id) {
|
|
137
|
+
const info = keepAliveMap.get(id);
|
|
138
|
+
if (info) {
|
|
139
|
+
targetId = id;
|
|
140
|
+
wsUrlFound = (info as any).KeepAlive?.wsUrl;
|
|
141
|
+
}
|
|
142
|
+
} else if (wsUrl) {
|
|
143
|
+
for (const [key, info] of keepAliveMap.entries()) {
|
|
144
|
+
if ((info as any).KeepAlive?.wsUrl === wsUrl) {
|
|
145
|
+
targetId = key;
|
|
146
|
+
wsUrlFound = wsUrl;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
109
150
|
}
|
|
110
151
|
|
|
111
|
-
|
|
112
|
-
|
|
152
|
+
if (targetId) {
|
|
153
|
+
const keepAlive = keepAliveMap.get(targetId);
|
|
113
154
|
const endTime = Date.now();
|
|
114
|
-
const duration = endTime - keepAlive
|
|
155
|
+
const duration = endTime - keepAlive!.startTime;
|
|
115
156
|
keepAlive?.KeepAlive?.disconnect();
|
|
116
|
-
keepAliveMap.delete(
|
|
117
|
-
ctx.body = {
|
|
157
|
+
keepAliveMap.delete(targetId);
|
|
158
|
+
ctx.body = { content: `已停止保持工作空间 ${wsUrlFound} 存活的任务,持续时间: ${duration}ms`, id: targetId };
|
|
118
159
|
} else {
|
|
119
|
-
ctx.body = {
|
|
160
|
+
ctx.body = { content: `没有找到对应的工作空间保持存活任务` };
|
|
161
|
+
}
|
|
162
|
+
}).addTo(app);
|
|
163
|
+
|
|
164
|
+
app.route({
|
|
165
|
+
path: 'cnb',
|
|
166
|
+
key: 'reset-keep-workspace-alive',
|
|
167
|
+
description: '对存活的工作空间,startTime进行重置',
|
|
168
|
+
middleware: ['admin-auth'],
|
|
169
|
+
metadata: {
|
|
170
|
+
tags: [],
|
|
171
|
+
}
|
|
172
|
+
}).define(async (ctx) => {
|
|
173
|
+
const now = Date.now();
|
|
174
|
+
for (const info of keepAliveMap.values()) {
|
|
175
|
+
info.startTime = now;
|
|
176
|
+
}
|
|
177
|
+
ctx.body = { content: `已重置所有存活工作空间的开始时间` };
|
|
178
|
+
}).addTo(app);
|
|
179
|
+
|
|
180
|
+
app.route({
|
|
181
|
+
path: 'cnb',
|
|
182
|
+
key: 'clear-keep-workspace-alive',
|
|
183
|
+
description: '对存活的工作空间,超过5小时的进行清理',
|
|
184
|
+
middleware: ['admin-auth'],
|
|
185
|
+
metadata: {
|
|
186
|
+
tags: [],
|
|
120
187
|
}
|
|
188
|
+
}).define(async (ctx) => {
|
|
189
|
+
const res = clearKeepAlive();
|
|
190
|
+
ctx.body = {
|
|
191
|
+
content: `已清理所有存活工作空间中超过5小时的任务` + (res.length ? `,清理项:${res.map(i => i.wsUrl).join(', ')}` : ''),
|
|
192
|
+
list: res
|
|
193
|
+
};
|
|
121
194
|
}).addTo(app);
|
|
195
|
+
|
|
196
|
+
const clearKeepAlive = () => {
|
|
197
|
+
const now = Date.now();
|
|
198
|
+
let clearedArr: { id: string; wsUrl: string }[] = [];
|
|
199
|
+
for (const [id, info] of keepAliveMap.entries()) {
|
|
200
|
+
if (now - info.startTime > FIVE_HOURS) {
|
|
201
|
+
console.log(`工作空间 ${(info as any).KeepAlive?.wsUrl} 超过5小时,自动停止`);
|
|
202
|
+
info.KeepAlive?.disconnect?.();
|
|
203
|
+
keepAliveMap.delete(id);
|
|
204
|
+
clearedArr.push({ id, wsUrl: (info as any).KeepAlive?.wsUrl });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return clearedArr;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 每5小时自动清理超时的keepAlive任务
|
|
211
|
+
const FIVE_HOURS = 5 * 60 * 60 * 1000;
|
|
212
|
+
setInterval(() => {
|
|
213
|
+
clearKeepAlive();
|
|
214
|
+
}, FIVE_HOURS);
|