@kevisual/cnb 0.0.10 → 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.
- package/agent/app.ts +1 -5
- package/agent/opencode.ts +1 -1
- 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/index.ts +3 -3
- 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 +7 -6
- package/agent/routes/workspace/keep.ts +214 -0
- package/agent/routes/workspace/skills.ts +1 -1
- package/dist/keep.d.ts +1 -0
- package/dist/keep.js +3 -0
- package/dist/opencode.js +8388 -20067
- package/dist/routes.d.ts +744 -0
- package/dist/routes.js +54750 -0
- package/package.json +8 -2
- package/src/workspace/keep-live.ts +9 -5
- package/src/workspace/keep-worker.ts +13 -0
package/agent/app.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { QueryRouterServer as App } from '@kevisual/router'
|
|
|
2
2
|
import { useContextKey } from '@kevisual/context'
|
|
3
3
|
import { useConfig, useKey } from '@kevisual/use-config'
|
|
4
4
|
import { CNB } from '../src/index.ts';
|
|
5
|
-
import { nanoid } from 'nanoid';
|
|
6
5
|
|
|
7
6
|
export const config = useConfig()
|
|
8
7
|
export const cnb = useContextKey<CNB>('cnb', () => {
|
|
@@ -13,9 +12,6 @@ export const cnb = useContextKey<CNB>('cnb', () => {
|
|
|
13
12
|
const cookie = useKey('CNB_COOKIE') as string
|
|
14
13
|
return new CNB({ token: token, cookie: cookie });
|
|
15
14
|
})
|
|
16
|
-
export const appId = nanoid();
|
|
17
15
|
export const app = useContextKey<App>('app', () => {
|
|
18
|
-
return new App({
|
|
19
|
-
appId
|
|
20
|
-
})
|
|
16
|
+
return new App({})
|
|
21
17
|
})
|
package/agent/opencode.ts
CHANGED
|
@@ -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({
|
package/agent/routes/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { app
|
|
1
|
+
import { app } from '../app.ts';
|
|
2
2
|
import './cnb-env/check.ts'
|
|
3
3
|
import './repo/index.ts'
|
|
4
4
|
import './workspace/index.ts'
|
|
@@ -31,7 +31,7 @@ if (!app.hasRoute('auth')) {
|
|
|
31
31
|
path: 'auth',
|
|
32
32
|
}).define(async (ctx) => {
|
|
33
33
|
// ctx.body = 'Auth Route';
|
|
34
|
-
if (checkAppId(ctx, appId)) {
|
|
34
|
+
if (checkAppId(ctx, app.appId)) {
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
}).addTo(app);
|
|
@@ -42,7 +42,7 @@ if (!app.hasRoute('auth')) {
|
|
|
42
42
|
middleware: ['auth'],
|
|
43
43
|
}).define(async (ctx) => {
|
|
44
44
|
// ctx.body = 'Admin Auth Route';
|
|
45
|
-
if (checkAppId(ctx, appId)) {
|
|
45
|
+
if (checkAppId(ctx, app.appId)) {
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
}).addTo(app);
|
|
@@ -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({
|
|
@@ -2,13 +2,14 @@ import { createSkill, tool } from '@kevisual/router';
|
|
|
2
2
|
import { app, cnb } from '../../app.ts';
|
|
3
3
|
import z from 'zod';
|
|
4
4
|
import './skills.ts';
|
|
5
|
+
import './keep.ts';
|
|
5
6
|
|
|
6
7
|
// 启动工作空间
|
|
7
8
|
app.route({
|
|
8
9
|
path: 'cnb',
|
|
9
10
|
key: 'start-workspace',
|
|
10
11
|
description: '启动开发工作空间, 参数 repo',
|
|
11
|
-
middleware: ['auth'],
|
|
12
|
+
middleware: ['admin-auth'],
|
|
12
13
|
metadata: {
|
|
13
14
|
tags: ['opencode'],
|
|
14
15
|
...createSkill({
|
|
@@ -41,7 +42,7 @@ app.route({
|
|
|
41
42
|
path: 'cnb',
|
|
42
43
|
key: 'list-workspace',
|
|
43
44
|
description: '获取cnb开发工作空间列表,可选参数 status=running 获取运行中的环境',
|
|
44
|
-
middleware: ['auth'],
|
|
45
|
+
middleware: ['admin-auth'],
|
|
45
46
|
metadata: {
|
|
46
47
|
tags: ['opencode'],
|
|
47
48
|
...createSkill({
|
|
@@ -72,7 +73,7 @@ app.route({
|
|
|
72
73
|
path: 'cnb',
|
|
73
74
|
key: 'get-workspace',
|
|
74
75
|
description: '获取工作空间详情,通过 repo 和 sn 获取',
|
|
75
|
-
middleware: ['auth'],
|
|
76
|
+
middleware: ['admin-auth'],
|
|
76
77
|
metadata: {
|
|
77
78
|
tags: ['opencode'],
|
|
78
79
|
...createSkill({
|
|
@@ -103,7 +104,7 @@ app.route({
|
|
|
103
104
|
path: 'cnb',
|
|
104
105
|
key: 'delete-workspace',
|
|
105
106
|
description: '删除工作空间,通过 pipelineId 或 sn',
|
|
106
|
-
middleware: ['auth'],
|
|
107
|
+
middleware: ['admin-auth'],
|
|
107
108
|
metadata: {
|
|
108
109
|
tags: ['opencode'],
|
|
109
110
|
...createSkill({
|
|
@@ -113,7 +114,7 @@ app.route({
|
|
|
113
114
|
args: {
|
|
114
115
|
pipelineId: tool.schema.string().optional().describe('流水线 ID,优先使用'),
|
|
115
116
|
sn: tool.schema.string().optional().describe('流水线构建号'),
|
|
116
|
-
sns: tool.schema.array(z.string()).optional().describe('
|
|
117
|
+
sns: tool.schema.array(z.string()).optional().describe('批量流水线构建号'),
|
|
117
118
|
},
|
|
118
119
|
})
|
|
119
120
|
}
|
|
@@ -142,7 +143,7 @@ app.route({
|
|
|
142
143
|
path: 'cnb',
|
|
143
144
|
key: 'stop-workspace',
|
|
144
145
|
description: '停止工作空间,通过 pipelineId 或 sn',
|
|
145
|
-
middleware: ['auth'],
|
|
146
|
+
middleware: ['admin-auth'],
|
|
146
147
|
metadata: {
|
|
147
148
|
tags: ['opencode'],
|
|
148
149
|
...createSkill({
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { createSkill, tool } from '@kevisual/router';
|
|
2
|
+
import { app, cnb } from '../../app.ts';
|
|
3
|
+
import { nanoid } from 'nanoid';
|
|
4
|
+
import dayjs from 'dayjs';
|
|
5
|
+
import { createKeepAlive } from '../../../src/keep.ts';
|
|
6
|
+
|
|
7
|
+
type AliveInfo = {
|
|
8
|
+
startTime: number;
|
|
9
|
+
updatedTime?: number;
|
|
10
|
+
KeepAlive: ReturnType<typeof createKeepAlive>;
|
|
11
|
+
id: string;// 6位唯一标识符
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const keepAliveMap = new Map<string, AliveInfo>();
|
|
15
|
+
|
|
16
|
+
// 保持工作空间存活技能
|
|
17
|
+
app.route({
|
|
18
|
+
path: 'cnb',
|
|
19
|
+
key: 'keep-workspace-alive',
|
|
20
|
+
description: '保持工作空间存活技能,参数wsUrl:工作空间访问URL,cookie:访问工作空间所需的cookie',
|
|
21
|
+
middleware: ['admin-auth'],
|
|
22
|
+
metadata: {
|
|
23
|
+
tags: [],
|
|
24
|
+
...({
|
|
25
|
+
args: {
|
|
26
|
+
wsUrl: tool.schema.string().describe('工作空间的访问URL'),
|
|
27
|
+
cookie: tool.schema.string().describe('访问工作空间所需的cookie')
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
}).define(async (ctx) => {
|
|
32
|
+
const wsUrl = ctx.query?.wsUrl as string;
|
|
33
|
+
const cookie = ctx.query?.cookie as string;
|
|
34
|
+
if (!wsUrl) {
|
|
35
|
+
ctx.throw(400, '缺少工作空间访问URL参数');
|
|
36
|
+
}
|
|
37
|
+
if (!cookie) {
|
|
38
|
+
ctx.throw(400, '缺少访问工作空间所需的cookie参数');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 检测是否已在运行(通过 wsUrl 遍历检查)
|
|
42
|
+
const existing = Array.from(keepAliveMap.values()).find(info => (info as AliveInfo).id && (info as any).KeepAlive?.wsUrl === wsUrl);
|
|
43
|
+
if (existing) {
|
|
44
|
+
ctx.body = { message: `工作空间 ${wsUrl} 的保持存活任务已在运行中`, id: (existing as AliveInfo).id };
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log(`启动保持工作空间 ${wsUrl} 存活的任务`);
|
|
49
|
+
const keep = createKeepAlive({
|
|
50
|
+
wsUrl,
|
|
51
|
+
cookie,
|
|
52
|
+
onConnect: () => {
|
|
53
|
+
console.log(`工作空间 ${wsUrl} 保持存活任务已连接`);
|
|
54
|
+
},
|
|
55
|
+
onMessage: (data) => {
|
|
56
|
+
// 可选:处理收到的消息
|
|
57
|
+
// console.log(`工作空间 ${wsUrl} 收到消息: ${data}`);
|
|
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
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
debug: true,
|
|
67
|
+
onExit: (code) => {
|
|
68
|
+
console.log(`工作空间 ${wsUrl} 保持存活任务已退出,退出码: ${code}`);
|
|
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
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const id = nanoid(6).toLowerCase();
|
|
80
|
+
keepAliveMap.set(id, { startTime: Date.now(), updatedTime: Date.now(), KeepAlive: keep, id });
|
|
81
|
+
|
|
82
|
+
ctx.body = { content: `已启动保持工作空间 ${wsUrl} 存活的任务`, id };
|
|
83
|
+
}).addTo(app);
|
|
84
|
+
|
|
85
|
+
// 获取保持工作空间存活任务列表技能
|
|
86
|
+
app.route({
|
|
87
|
+
path: 'cnb',
|
|
88
|
+
key: 'list-keep-alive-tasks',
|
|
89
|
+
description: '获取保持工作空间存活任务列表技能',
|
|
90
|
+
middleware: ['admin-auth'],
|
|
91
|
+
metadata: {
|
|
92
|
+
tags: [],
|
|
93
|
+
}
|
|
94
|
+
}).define(async (ctx) => {
|
|
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
|
+
});
|
|
108
|
+
ctx.body = { list };
|
|
109
|
+
}).addTo(app);
|
|
110
|
+
|
|
111
|
+
// 停止保持工作空间存活技能
|
|
112
|
+
app.route({
|
|
113
|
+
path: 'cnb',
|
|
114
|
+
key: 'stop-keep-workspace-alive',
|
|
115
|
+
description: '停止保持工作空间存活技能, 参数wsUrl:工作空间访问URL或者id',
|
|
116
|
+
middleware: ['admin-auth'],
|
|
117
|
+
metadata: {
|
|
118
|
+
tags: [],
|
|
119
|
+
...({
|
|
120
|
+
args: {
|
|
121
|
+
wsUrl: tool.schema.string().optional().describe('工作空间的访问URL'),
|
|
122
|
+
id: tool.schema.string().optional().describe('保持存活任务的唯一标识符'),
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
}).define(async (ctx) => {
|
|
127
|
+
const wsUrl = ctx.query?.wsUrl as string;
|
|
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
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (targetId) {
|
|
153
|
+
const keepAlive = keepAliveMap.get(targetId);
|
|
154
|
+
const endTime = Date.now();
|
|
155
|
+
const duration = endTime - keepAlive!.startTime;
|
|
156
|
+
keepAlive?.KeepAlive?.disconnect();
|
|
157
|
+
keepAliveMap.delete(targetId);
|
|
158
|
+
ctx.body = { content: `已停止保持工作空间 ${wsUrlFound} 存活的任务,持续时间: ${duration}ms`, id: targetId };
|
|
159
|
+
} else {
|
|
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: [],
|
|
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
|
+
};
|
|
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);
|
package/dist/keep.d.ts
CHANGED
package/dist/keep.js
CHANGED
|
@@ -20,6 +20,7 @@ class WSKeepAlive {
|
|
|
20
20
|
onDisconnect: config.onDisconnect ?? (() => {}),
|
|
21
21
|
onError: config.onError ?? (() => {}),
|
|
22
22
|
onSign: config.onSign ?? (() => {}),
|
|
23
|
+
onExit: config.onExit ?? (() => {}),
|
|
23
24
|
debug: config.debug ?? false
|
|
24
25
|
};
|
|
25
26
|
this.url = new URL(this.config.wsUrl);
|
|
@@ -119,6 +120,7 @@ class WSKeepAlive {
|
|
|
119
120
|
handleReconnect() {
|
|
120
121
|
if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
|
|
121
122
|
this.log(`Max reconnect attempts (${this.config.maxReconnectAttempts}) reached. Giving up.`);
|
|
123
|
+
this.config.onExit(1);
|
|
122
124
|
return;
|
|
123
125
|
}
|
|
124
126
|
this.reconnectAttempts++;
|
|
@@ -133,6 +135,7 @@ class WSKeepAlive {
|
|
|
133
135
|
this.stopPing();
|
|
134
136
|
if (this.ws) {
|
|
135
137
|
this.ws.close();
|
|
138
|
+
this.config.onExit(0);
|
|
136
139
|
this.ws = null;
|
|
137
140
|
}
|
|
138
141
|
}
|