@kevisual/cnb 0.0.12 → 0.0.13

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.
@@ -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
- // "调用 path: cnb key: list-repos"
6
- app.route({
7
- path: 'call',
8
- key: '',
9
- description: '调用',
10
- middleware: ['auth'],
11
- metadata: {
12
- tags: ['opencode'],
13
- ...createSkill({
14
- skill: 'call-app',
15
- title: '调用app应用',
16
- summary: '调用router的应用, 参数path, key, payload',
17
- args: {
18
- path: tool.schema.string().describe('应用路径,例如 cnb'),
19
- key: tool.schema.string().optional().describe('应用key,例如 list-repos'),
20
- payload: tool.schema.object({}).optional().describe('调用参数'),
21
- }
22
- })
23
- },
24
- }).define(async (ctx) => {
25
- const { path, key } = ctx.query;
26
- console.log('call app', ctx.query);
27
- if (!path) {
28
- ctx.throw('路径path不能为空');
29
- }
30
- const res = await ctx.run({ path, key, payload: ctx.query.payload || {} });
31
- ctx.forward(res);
32
- }).addTo(app)
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
+ }
@@ -7,7 +7,7 @@ app.route({
7
7
  path: 'cnb',
8
8
  key: 'user-check',
9
9
  description: '检查用户登录状态,参数checkToken,default true; checkCookie, default false',
10
- middleware: ['auth'],
10
+ middleware: ['admin-auth'],
11
11
  metadata: {
12
12
  tags: ['opencode'],
13
13
  ...createSkill({
@@ -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({
@@ -6,7 +6,7 @@ app.route({
6
6
  path: 'cnb',
7
7
  key: 'list-issues',
8
8
  description: '查询 Issue 列表, 参数 repo, state, keyword, labels, page, page_size 等',
9
- middleware: ['auth'],
9
+ middleware: ['admin-auth'],
10
10
  metadata: {
11
11
  tags: ['opencode'],
12
12
  ...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({
@@ -8,7 +8,7 @@ app.route({
8
8
  path: 'cnb',
9
9
  key: 'list-repos',
10
10
  description: '列出我的代码仓库',
11
- middleware: ['auth'],
11
+ middleware: ['admin-auth'],
12
12
  metadata: {
13
13
  tags: ['opencode'],
14
14
  ...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.get(wsUrl);
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
- const aliveInfo = keepAliveMap.get(wsUrl);
57
- if (aliveInfo) {
58
- aliveInfo.updatedTime = Date.now();
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
- keepAliveMap.delete(wsUrl);
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
- keepAliveMap.set(wsUrl, { startTime: Date.now(), updatedTime: Date.now(), KeepAlive: keep });
79
+ const id = nanoid(6).toLowerCase();
80
+ keepAliveMap.set(id, { startTime: Date.now(), updatedTime: Date.now(), KeepAlive: keep, id });
69
81
 
70
- ctx.body = { message: `已启动保持工作空间 ${wsUrl} 存活的任务` };
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(([wsUrl, info]) => ({
84
- wsUrl,
85
- startTime: info.startTime,
86
- updatedTime: info.updatedTime
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
- if (!wsUrl) {
108
- ctx.throw(400, '缺少工作空间访问URL参数');
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
- const keepAlive = keepAliveMap.get(wsUrl);
112
- if (keepAlive) {
152
+ if (targetId) {
153
+ const keepAlive = keepAliveMap.get(targetId);
113
154
  const endTime = Date.now();
114
- const duration = endTime - keepAlive.startTime;
155
+ const duration = endTime - keepAlive!.startTime;
115
156
  keepAlive?.KeepAlive?.disconnect();
116
- keepAliveMap.delete(wsUrl);
117
- ctx.body = { message: `已停止保持工作空间 ${wsUrl} 存活的任务,持续时间: ${duration}ms` };
157
+ keepAliveMap.delete(targetId);
158
+ ctx.body = { content: `已停止保持工作空间 ${wsUrlFound} 存活的任务,持续时间: ${duration}ms`, id: targetId };
118
159
  } else {
119
- ctx.body = { message: `没有找到工作空间 ${wsUrl} 的保持存活任务` };
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);
@@ -35,7 +35,7 @@ app.route({
35
35
  path: 'cnb',
36
36
  key: 'clean-closed-workspace',
37
37
  description: '批量删除已停止的cnb工作空间',
38
- middleware: ['auth'],
38
+ middleware: ['admin-auth'],
39
39
  metadata: {
40
40
  tags: ['opencode'],
41
41
  ...createSkill({