Compare commits
7 Commits
1d4a27d1b2
...
412c057756
| Author | SHA1 | Date | |
|---|---|---|---|
| 412c057756 | |||
| 7a01339ef2 | |||
| 6e5a642ab2 | |||
| 0d17d56628 | |||
| 972d68b87e | |||
| 28484baab3 | |||
| d7a4bcf58f |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,4 +3,6 @@
|
||||
node_modules
|
||||
.pnpm-store
|
||||
|
||||
dist
|
||||
dist
|
||||
|
||||
storage
|
||||
@@ -2,7 +2,6 @@ import { QueryRouterServer as App } from '@kevisual/router'
|
||||
import { useContextKey } from '@kevisual/context'
|
||||
import { useConfig, useKey } from '@kevisual/use-config'
|
||||
import { CNB } from '../src/index.ts';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export const config = useConfig()
|
||||
export const cnb = useContextKey<CNB>('cnb', () => {
|
||||
@@ -13,9 +12,6 @@ export const cnb = useContextKey<CNB>('cnb', () => {
|
||||
const cookie = useKey('CNB_COOKIE') as string
|
||||
return new CNB({ token: token, cookie: cookie });
|
||||
})
|
||||
export const appId = nanoid();
|
||||
export const app = useContextKey<App>('app', () => {
|
||||
return new App({
|
||||
appId
|
||||
})
|
||||
return new App({})
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import { app} from './index.ts';
|
||||
import { app } from './index.ts';
|
||||
import { createRouterAgentPluginFn } from '@kevisual/router/opencode'
|
||||
|
||||
export const CnbPlugin = createRouterAgentPluginFn({
|
||||
|
||||
@@ -2,31 +2,33 @@ import { createSkill } from '@kevisual/router'
|
||||
import { app } from '../../app.ts'
|
||||
import { tool } from '@opencode-ai/plugin/tool'
|
||||
|
||||
// "调用 path: cnb key: list-repos"
|
||||
app.route({
|
||||
path: 'call',
|
||||
key: '',
|
||||
description: '调用',
|
||||
middleware: ['auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'call-app',
|
||||
title: '调用app应用',
|
||||
summary: '调用router的应用, 参数path, key, payload',
|
||||
args: {
|
||||
path: tool.schema.string().describe('应用路径,例如 cnb'),
|
||||
key: tool.schema.string().optional().describe('应用key,例如 list-repos'),
|
||||
payload: tool.schema.object({}).optional().describe('调用参数'),
|
||||
}
|
||||
})
|
||||
},
|
||||
}).define(async (ctx) => {
|
||||
const { path, key } = ctx.query;
|
||||
console.log('call app', ctx.query);
|
||||
if (!path) {
|
||||
ctx.throw('路径path不能为空');
|
||||
}
|
||||
const res = await ctx.run({ path, key, payload: ctx.query.payload || {} });
|
||||
ctx.forward(res);
|
||||
}).addTo(app)
|
||||
if (!app.hasRoute('call')) {
|
||||
// "调用 path: cnb key: list-repos"
|
||||
app.route({
|
||||
path: 'call',
|
||||
key: '',
|
||||
description: '调用',
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
skill: 'call-app',
|
||||
title: '调用app应用',
|
||||
summary: '调用router的应用, 参数path, key, payload',
|
||||
args: {
|
||||
path: tool.schema.string().describe('应用路径,例如 cnb'),
|
||||
key: tool.schema.string().optional().describe('应用key,例如 list-repos'),
|
||||
payload: tool.schema.object({}).optional().describe('调用参数'),
|
||||
}
|
||||
})
|
||||
},
|
||||
}).define(async (ctx) => {
|
||||
const { path, key } = ctx.query;
|
||||
console.log('call app', ctx.query);
|
||||
if (!path) {
|
||||
ctx.throw('路径path不能为空');
|
||||
}
|
||||
const res = await ctx.run({ path, key, payload: ctx.query.payload || {} });
|
||||
ctx.forward(res);
|
||||
}).addTo(app)
|
||||
}
|
||||
@@ -7,7 +7,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'user-check',
|
||||
description: '检查用户登录状态,参数checkToken,default true; checkCookie, default false',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
|
||||
@@ -6,7 +6,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'set-cnb-cookie',
|
||||
description: '设置当前cnb工作空间的cookie环境变量',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
@@ -33,7 +33,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'get-cnb-cookie',
|
||||
description: '获取当前cnb工作空间的cookie环境变量',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
|
||||
@@ -11,7 +11,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'get-cnb-port-uri',
|
||||
description: '获取当前cnb工作空间的port代理uri',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
@@ -40,7 +40,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'get-cnb-vscode-uri',
|
||||
description: '获取当前cnb工作空间的vscode代理uri, 包括多种访问方式, 如web、vscode、codebuddy、cursor、ssh',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { app, appId } from '../app.ts';
|
||||
import { app } from '../app.ts';
|
||||
import './cnb-env/check.ts'
|
||||
import './repo/index.ts'
|
||||
import './workspace/index.ts'
|
||||
@@ -31,7 +31,7 @@ if (!app.hasRoute('auth')) {
|
||||
path: 'auth',
|
||||
}).define(async (ctx) => {
|
||||
// ctx.body = 'Auth Route';
|
||||
if (checkAppId(ctx, appId)) {
|
||||
if (checkAppId(ctx, app.appId)) {
|
||||
return;
|
||||
}
|
||||
}).addTo(app);
|
||||
@@ -42,7 +42,7 @@ if (!app.hasRoute('auth')) {
|
||||
middleware: ['auth'],
|
||||
}).define(async (ctx) => {
|
||||
// ctx.body = 'Admin Auth Route';
|
||||
if (checkAppId(ctx, appId)) {
|
||||
if (checkAppId(ctx, app.appId)) {
|
||||
return;
|
||||
}
|
||||
}).addTo(app);
|
||||
|
||||
@@ -7,7 +7,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'create-issue',
|
||||
description: '创建 Issue, 参数 repo, title, body, assignees, labels, priority',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
@@ -51,7 +51,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'complete-issue',
|
||||
description: '完成 Issue, 参数 repo, issueNumber',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
|
||||
@@ -6,7 +6,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'list-issues',
|
||||
description: '查询 Issue 列表, 参数 repo, state, keyword, labels, page, page_size 等',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
|
||||
@@ -12,7 +12,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'cnb-ai-chat',
|
||||
description: '调用cnb的知识库ai对话功能进行聊天',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
@@ -88,7 +88,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'cnb-rag-query',
|
||||
description: '调用cnb的知识库RAG查询功能进行问答',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
|
||||
@@ -8,7 +8,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'list-repos',
|
||||
description: '列出我的代码仓库',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
|
||||
@@ -7,7 +7,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'create-repo',
|
||||
description: '创建代码仓库, 参数name, visibility, description',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
@@ -47,7 +47,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'create-repo-file',
|
||||
description: '在代码仓库中创建文件, repoName, filePath, content, encoding',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
@@ -86,7 +86,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'delete-repo',
|
||||
description: '删除代码仓库, 参数name',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
|
||||
@@ -2,13 +2,14 @@ import { createSkill, tool } from '@kevisual/router';
|
||||
import { app, cnb } from '../../app.ts';
|
||||
import z from 'zod';
|
||||
import './skills.ts';
|
||||
import './keep.ts';
|
||||
|
||||
// 启动工作空间
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'start-workspace',
|
||||
description: '启动开发工作空间, 参数 repo',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
@@ -41,7 +42,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'list-workspace',
|
||||
description: '获取cnb开发工作空间列表,可选参数 status=running 获取运行中的环境',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
@@ -72,7 +73,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'get-workspace',
|
||||
description: '获取工作空间详情,通过 repo 和 sn 获取',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
@@ -103,7 +104,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'delete-workspace',
|
||||
description: '删除工作空间,通过 pipelineId 或 sn',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
@@ -113,7 +114,7 @@ app.route({
|
||||
args: {
|
||||
pipelineId: tool.schema.string().optional().describe('流水线 ID,优先使用'),
|
||||
sn: tool.schema.string().optional().describe('流水线构建号'),
|
||||
sns: tool.schema.array(z.string()).optional().describe('流水线构建号'),
|
||||
sns: tool.schema.array(z.string()).optional().describe('批量流水线构建号'),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -142,7 +143,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'stop-workspace',
|
||||
description: '停止工作空间,通过 pipelineId 或 sn',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
|
||||
214
agent/routes/workspace/keep.ts
Normal file
214
agent/routes/workspace/keep.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import { createSkill, tool } from '@kevisual/router';
|
||||
import { app, cnb } from '../../app.ts';
|
||||
import { nanoid } from 'nanoid';
|
||||
import dayjs from 'dayjs';
|
||||
import { createKeepAlive } from '../../../src/keep.ts';
|
||||
|
||||
type AliveInfo = {
|
||||
startTime: number;
|
||||
updatedTime?: number;
|
||||
KeepAlive: ReturnType<typeof createKeepAlive>;
|
||||
id: string;// 6位唯一标识符
|
||||
}
|
||||
|
||||
const keepAliveMap = new Map<string, AliveInfo>();
|
||||
|
||||
// 保持工作空间存活技能
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'keep-workspace-alive',
|
||||
description: '保持工作空间存活技能,参数wsUrl:工作空间访问URL,cookie:访问工作空间所需的cookie',
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: [],
|
||||
...({
|
||||
args: {
|
||||
wsUrl: tool.schema.string().describe('工作空间的访问URL'),
|
||||
cookie: tool.schema.string().describe('访问工作空间所需的cookie')
|
||||
}
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const wsUrl = ctx.query?.wsUrl as string;
|
||||
const cookie = ctx.query?.cookie as string;
|
||||
if (!wsUrl) {
|
||||
ctx.throw(400, '缺少工作空间访问URL参数');
|
||||
}
|
||||
if (!cookie) {
|
||||
ctx.throw(400, '缺少访问工作空间所需的cookie参数');
|
||||
}
|
||||
|
||||
// 检测是否已在运行(通过 wsUrl 遍历检查)
|
||||
const existing = Array.from(keepAliveMap.values()).find(info => (info as AliveInfo).id && (info as any).KeepAlive?.wsUrl === wsUrl);
|
||||
if (existing) {
|
||||
ctx.body = { message: `工作空间 ${wsUrl} 的保持存活任务已在运行中`, id: (existing as AliveInfo).id };
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`启动保持工作空间 ${wsUrl} 存活的任务`);
|
||||
const keep = createKeepAlive({
|
||||
wsUrl,
|
||||
cookie,
|
||||
onConnect: () => {
|
||||
console.log(`工作空间 ${wsUrl} 保持存活任务已连接`);
|
||||
},
|
||||
onMessage: (data) => {
|
||||
// 可选:处理收到的消息
|
||||
// console.log(`工作空间 ${wsUrl} 收到消息: ${data}`);
|
||||
// 通过 wsUrl 找到对应的 id 并更新时间
|
||||
for (const info of keepAliveMap.values()) {
|
||||
if ((info as any).KeepAlive?.wsUrl === wsUrl) {
|
||||
info.updatedTime = Date.now();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
debug: true,
|
||||
onExit: (code) => {
|
||||
console.log(`工作空间 ${wsUrl} 保持存活任务已退出,退出码: ${code}`);
|
||||
// 通过 wsUrl 找到对应的 id 并删除
|
||||
for (const [id, info] of keepAliveMap.entries()) {
|
||||
if ((info as any).KeepAlive?.wsUrl === wsUrl) {
|
||||
keepAliveMap.delete(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const id = nanoid(6).toLowerCase();
|
||||
keepAliveMap.set(id, { startTime: Date.now(), updatedTime: Date.now(), KeepAlive: keep, id });
|
||||
|
||||
ctx.body = { content: `已启动保持工作空间 ${wsUrl} 存活的任务`, id };
|
||||
}).addTo(app);
|
||||
|
||||
// 获取保持工作空间存活任务列表技能
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'list-keep-alive-tasks',
|
||||
description: '获取保持工作空间存活任务列表技能',
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: [],
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const list = Array.from(keepAliveMap.entries()).map(([id, info]) => {
|
||||
const now = Date.now();
|
||||
const duration = Math.floor((now - info.startTime) / 60000); // 分钟
|
||||
return {
|
||||
id,
|
||||
wsUrl: (info as any).KeepAlive?.wsUrl,
|
||||
startTime: info.startTime,
|
||||
startTimeStr: dayjs(info.startTime).format('YYYY-MM-DD HH:mm'),
|
||||
updatedTime: info.updatedTime,
|
||||
updatedTimeStr: dayjs(info.updatedTime).format('YYYY-MM-DD HH:mm'),
|
||||
duration,
|
||||
}
|
||||
});
|
||||
ctx.body = { list };
|
||||
}).addTo(app);
|
||||
|
||||
// 停止保持工作空间存活技能
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'stop-keep-workspace-alive',
|
||||
description: '停止保持工作空间存活技能, 参数wsUrl:工作空间访问URL或者id',
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: [],
|
||||
...({
|
||||
args: {
|
||||
wsUrl: tool.schema.string().optional().describe('工作空间的访问URL'),
|
||||
id: tool.schema.string().optional().describe('保持存活任务的唯一标识符'),
|
||||
}
|
||||
})
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const wsUrl = ctx.query?.wsUrl as string;
|
||||
const id = ctx.query?.id as string;
|
||||
if (!wsUrl && !id) {
|
||||
ctx.throw(400, '缺少工作空间访问URL参数或唯一标识符');
|
||||
}
|
||||
|
||||
let targetId: string | undefined;
|
||||
let wsUrlFound: string | undefined;
|
||||
|
||||
if (id) {
|
||||
const info = keepAliveMap.get(id);
|
||||
if (info) {
|
||||
targetId = id;
|
||||
wsUrlFound = (info as any).KeepAlive?.wsUrl;
|
||||
}
|
||||
} else if (wsUrl) {
|
||||
for (const [key, info] of keepAliveMap.entries()) {
|
||||
if ((info as any).KeepAlive?.wsUrl === wsUrl) {
|
||||
targetId = key;
|
||||
wsUrlFound = wsUrl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetId) {
|
||||
const keepAlive = keepAliveMap.get(targetId);
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - keepAlive!.startTime;
|
||||
keepAlive?.KeepAlive?.disconnect();
|
||||
keepAliveMap.delete(targetId);
|
||||
ctx.body = { content: `已停止保持工作空间 ${wsUrlFound} 存活的任务,持续时间: ${duration}ms`, id: targetId };
|
||||
} else {
|
||||
ctx.body = { content: `没有找到对应的工作空间保持存活任务` };
|
||||
}
|
||||
}).addTo(app);
|
||||
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'reset-keep-workspace-alive',
|
||||
description: '对存活的工作空间,startTime进行重置',
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: [],
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const now = Date.now();
|
||||
for (const info of keepAliveMap.values()) {
|
||||
info.startTime = now;
|
||||
}
|
||||
ctx.body = { content: `已重置所有存活工作空间的开始时间` };
|
||||
}).addTo(app);
|
||||
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'clear-keep-workspace-alive',
|
||||
description: '对存活的工作空间,超过5小时的进行清理',
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: [],
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const res = clearKeepAlive();
|
||||
ctx.body = {
|
||||
content: `已清理所有存活工作空间中超过5小时的任务` + (res.length ? `,清理项:${res.map(i => i.wsUrl).join(', ')}` : ''),
|
||||
list: res
|
||||
};
|
||||
}).addTo(app);
|
||||
|
||||
const clearKeepAlive = () => {
|
||||
const now = Date.now();
|
||||
let clearedArr: { id: string; wsUrl: string }[] = [];
|
||||
for (const [id, info] of keepAliveMap.entries()) {
|
||||
if (now - info.startTime > FIVE_HOURS) {
|
||||
console.log(`工作空间 ${(info as any).KeepAlive?.wsUrl} 超过5小时,自动停止`);
|
||||
info.KeepAlive?.disconnect?.();
|
||||
keepAliveMap.delete(id);
|
||||
clearedArr.push({ id, wsUrl: (info as any).KeepAlive?.wsUrl });
|
||||
}
|
||||
}
|
||||
return clearedArr;
|
||||
}
|
||||
|
||||
// 每5小时自动清理超时的keepAlive任务
|
||||
const FIVE_HOURS = 5 * 60 * 60 * 1000;
|
||||
setInterval(() => {
|
||||
clearKeepAlive();
|
||||
}, FIVE_HOURS);
|
||||
@@ -35,7 +35,7 @@ app.route({
|
||||
path: 'cnb',
|
||||
key: 'clean-closed-workspace',
|
||||
description: '批量删除已停止的cnb工作空间',
|
||||
middleware: ['auth'],
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import { resolvePath } from '@kevisual/use-config';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
const entry = 'agent/opencode.ts';
|
||||
const naming = 'opencode';
|
||||
const external: string[] = ["bun"];
|
||||
await Bun.build({
|
||||
target: 'node',
|
||||
format: 'esm',
|
||||
entrypoints: [resolvePath(entry, { meta: import.meta })],
|
||||
outdir: resolvePath('./dist', { meta: import.meta }),
|
||||
naming: {
|
||||
entry: `${naming}.js`,
|
||||
},
|
||||
external,
|
||||
});
|
||||
const buildFn = async (opts: { entry?: string, naming?: string }) => {
|
||||
const entry = opts.entry || 'agent/opencode.ts';
|
||||
const naming = opts.naming || 'opencode';
|
||||
const external: string[] = ["bun", "ws"];
|
||||
await Bun.build({
|
||||
target: 'node',
|
||||
format: 'esm',
|
||||
entrypoints: [resolvePath(entry, { meta: import.meta })],
|
||||
outdir: resolvePath('./dist', { meta: import.meta }),
|
||||
naming: {
|
||||
entry: `${naming}.js`,
|
||||
},
|
||||
external,
|
||||
});
|
||||
const cmd = `dts -i ${entry} -o ${naming}.d.ts`;
|
||||
execSync(cmd);
|
||||
};
|
||||
|
||||
const cmd = 'dts -i agent/opencode.ts -o opencode.d.ts';
|
||||
execSync(cmd);
|
||||
await buildFn({ naming: 'opencode', entry: 'agent/opencode.ts' });
|
||||
await buildFn({ naming: 'keep', entry: 'src/keep.ts' });
|
||||
await buildFn({ naming: 'routes', entry: 'agent/index.ts' });
|
||||
17
package.json
17
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kevisual/cnb",
|
||||
"version": "0.0.7",
|
||||
"version": "0.0.13",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -21,25 +21,34 @@
|
||||
"@kevisual/ai": "^0.0.24",
|
||||
"@kevisual/context": "^0.0.4",
|
||||
"@kevisual/types": "^0.0.12",
|
||||
"@opencode-ai/plugin": "^1.1.39",
|
||||
"@types/bun": "^1.3.7",
|
||||
"@opencode-ai/plugin": "^1.1.44",
|
||||
"@types/bun": "^1.3.8",
|
||||
"@types/node": "^25.1.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"dotenv": "^17.2.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"resolutions": {
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kevisual/query": "^0.0.38",
|
||||
"@kevisual/router": "^0.0.63",
|
||||
"@kevisual/router": "^0.0.64",
|
||||
"@kevisual/use-config": "^1.0.28",
|
||||
"es-toolkit": "^1.44.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"unstorage": "^1.17.4",
|
||||
"ws": "npm:@kevisual/ws",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"exports": {
|
||||
".": "./mod.ts",
|
||||
"./opencode": "./dist/opencode.js",
|
||||
"./keep": "./dist/keep.js",
|
||||
"./routes": "./dist/routes.js",
|
||||
"./src/*": "./src/*",
|
||||
"./agent/*": "./agent/*"
|
||||
}
|
||||
|
||||
274
pnpm-lock.yaml
generated
274
pnpm-lock.yaml
generated
@@ -4,6 +4,9 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
zod: ^4.3.6
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@@ -12,8 +15,8 @@ importers:
|
||||
specifier: ^0.0.38
|
||||
version: 0.0.38
|
||||
'@kevisual/router':
|
||||
specifier: ^0.0.63
|
||||
version: 0.0.63(typescript@5.9.3)
|
||||
specifier: ^0.0.64
|
||||
version: 0.0.64(typescript@5.9.3)
|
||||
'@kevisual/use-config':
|
||||
specifier: ^1.0.28
|
||||
version: 1.0.28(dotenv@17.2.3)
|
||||
@@ -23,6 +26,12 @@ importers:
|
||||
nanoid:
|
||||
specifier: ^5.1.6
|
||||
version: 5.1.6
|
||||
unstorage:
|
||||
specifier: ^1.17.4
|
||||
version: 1.17.4
|
||||
ws:
|
||||
specifier: npm:@kevisual/ws
|
||||
version: '@kevisual/ws@8.19.0'
|
||||
zod:
|
||||
specifier: ^4.3.6
|
||||
version: 4.3.6
|
||||
@@ -37,14 +46,20 @@ importers:
|
||||
specifier: ^0.0.12
|
||||
version: 0.0.12
|
||||
'@opencode-ai/plugin':
|
||||
specifier: ^1.1.39
|
||||
version: 1.1.39
|
||||
specifier: ^1.1.44
|
||||
version: 1.1.44
|
||||
'@types/bun':
|
||||
specifier: ^1.3.7
|
||||
version: 1.3.7
|
||||
specifier: ^1.3.8
|
||||
version: 1.3.8
|
||||
'@types/node':
|
||||
specifier: ^25.1.0
|
||||
version: 25.1.0
|
||||
'@types/ws':
|
||||
specifier: ^8.18.1
|
||||
version: 8.18.1
|
||||
dayjs:
|
||||
specifier: ^1.11.19
|
||||
version: 1.11.19
|
||||
dotenv:
|
||||
specifier: ^17.2.3
|
||||
version: 17.2.3
|
||||
@@ -84,8 +99,8 @@ packages:
|
||||
'@kevisual/query@0.0.38':
|
||||
resolution: {integrity: sha512-bfvbSodsZyMfwY+1T2SvDeOCKsT/AaIxlVe0+B1R/fNhlg2MDq2CP0L9HKiFkEm+OXrvXcYDMKPUituVUM5J6Q==}
|
||||
|
||||
'@kevisual/router@0.0.63':
|
||||
resolution: {integrity: sha512-rM/FELiNtTJkjb00sdQ2f8NpWHzfOwrtgQJhsX9Np9KdzAQ5dP6pI5nAHlWvU2QyrC1VH5IibUmsBIeMMV3nWg==}
|
||||
'@kevisual/router@0.0.64':
|
||||
resolution: {integrity: sha512-EYz1MZxrltgySUL0Y+/MtZf2FEmqC5U8GmFAqvHNjgtS5FJdHpxRjo6zab4+0wSUlVyCxCpZXFY5vHB/g+nQBw==}
|
||||
|
||||
'@kevisual/types@0.0.12':
|
||||
resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==}
|
||||
@@ -95,11 +110,15 @@ packages:
|
||||
peerDependencies:
|
||||
dotenv: ^17
|
||||
|
||||
'@opencode-ai/plugin@1.1.39':
|
||||
resolution: {integrity: sha512-PAdVYNZeRW9pCi4+t+Dp88hoPgEIiS5uJT31hUXLhZEfBvgaLNP38WhXwRNWbwSaNXudWOu/a5DzYNbU84uvHQ==}
|
||||
'@kevisual/ws@8.19.0':
|
||||
resolution: {integrity: sha512-jLsL80wBBKkrJZrfk3SQpJ9JA/zREdlUROj7eCkmzqduAWKSI0wVcXuCKf+mLFCHB0Q0Tkh2rgzjSlurt3JQgw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
'@opencode-ai/sdk@1.1.39':
|
||||
resolution: {integrity: sha512-EUYBZAci0bzG9+a7JVINmqAqis71ipG2/D3juvmvvKFyu0YBIT/6b+g3+p82Eb5CU2dujxpPdJJCaexZ1389eQ==}
|
||||
'@opencode-ai/plugin@1.1.44':
|
||||
resolution: {integrity: sha512-5w66Dq2Fugwgr2yrd8obvnlIEjBOuya82UgfR/3z3EzlyNDi2sitQSYbz7CcOtwd89eZ0n/tH/JX2KDGVuzxTQ==}
|
||||
|
||||
'@opencode-ai/sdk@1.1.44':
|
||||
resolution: {integrity: sha512-coQgtSSCbY46/GY+M5zG0rChiLSJWSjPERRt5L1hbjvDWvErelVV0ILPbd1+3CwJLFTedBYgotby2TcO8U0IfQ==}
|
||||
|
||||
'@rollup/plugin-commonjs@28.0.9':
|
||||
resolution: {integrity: sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA==}
|
||||
@@ -279,8 +298,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@types/bun@1.3.7':
|
||||
resolution: {integrity: sha512-lmNuMda+Z9b7tmhA0tohwy8ZWFSnmQm1UDWXtH5r9F7wZCfkeO3Jx7wKQ1EOiKq43yHts7ky6r8SDJQWRNupkA==}
|
||||
'@types/bun@1.3.8':
|
||||
resolution: {integrity: sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA==}
|
||||
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
@@ -291,16 +310,42 @@ packages:
|
||||
'@types/resolve@1.20.2':
|
||||
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
||||
|
||||
bun-types@1.3.7:
|
||||
resolution: {integrity: sha512-qyschsA03Qz+gou+apt6HNl6HnI+sJJLL4wLDke4iugsE6584CMupOtTY1n+2YC9nGVrEKUlTs99jjRLKgWnjQ==}
|
||||
'@types/ws@8.18.1':
|
||||
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
|
||||
|
||||
anymatch@3.1.3:
|
||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
bun-types@1.3.8:
|
||||
resolution: {integrity: sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q==}
|
||||
|
||||
chokidar@5.0.0:
|
||||
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
commondir@1.0.1:
|
||||
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
|
||||
|
||||
cookie-es@1.2.2:
|
||||
resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==}
|
||||
|
||||
crossws@0.3.5:
|
||||
resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==}
|
||||
|
||||
dayjs@1.11.19:
|
||||
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
|
||||
|
||||
deepmerge@4.3.1:
|
||||
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
defu@6.1.4:
|
||||
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
||||
|
||||
destr@2.0.5:
|
||||
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
|
||||
|
||||
dotenv@17.2.3:
|
||||
resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -331,6 +376,9 @@ packages:
|
||||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
h3@1.15.5:
|
||||
resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==}
|
||||
|
||||
hasown@2.0.2:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -339,6 +387,9 @@ packages:
|
||||
resolution: {integrity: sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
iron-webcrypto@1.2.1:
|
||||
resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
|
||||
|
||||
is-core-module@2.16.1:
|
||||
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -352,6 +403,10 @@ packages:
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
lru-cache@11.2.5:
|
||||
resolution: {integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
@@ -360,16 +415,40 @@ packages:
|
||||
engines: {node: ^18 || >=20}
|
||||
hasBin: true
|
||||
|
||||
node-fetch-native@1.6.7:
|
||||
resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
|
||||
|
||||
node-mock-http@1.0.4:
|
||||
resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==}
|
||||
|
||||
normalize-path@3.0.0:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
ofetch@1.5.1:
|
||||
resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==}
|
||||
|
||||
path-parse@1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
picomatch@2.3.1:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
picomatch@4.0.3:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
radix3@1.1.2:
|
||||
resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
|
||||
|
||||
readdirp@5.0.0:
|
||||
resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
resolve@1.22.11:
|
||||
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -399,11 +478,76 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
ufo@1.6.3:
|
||||
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
|
||||
|
||||
uncrypto@0.1.3:
|
||||
resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
|
||||
|
||||
undici-types@7.16.0:
|
||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||
|
||||
zod@4.1.8:
|
||||
resolution: {integrity: sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==}
|
||||
unstorage@1.17.4:
|
||||
resolution: {integrity: sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==}
|
||||
peerDependencies:
|
||||
'@azure/app-configuration': ^1.8.0
|
||||
'@azure/cosmos': ^4.2.0
|
||||
'@azure/data-tables': ^13.3.0
|
||||
'@azure/identity': ^4.6.0
|
||||
'@azure/keyvault-secrets': ^4.9.0
|
||||
'@azure/storage-blob': ^12.26.0
|
||||
'@capacitor/preferences': ^6 || ^7 || ^8
|
||||
'@deno/kv': '>=0.9.0'
|
||||
'@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0
|
||||
'@planetscale/database': ^1.19.0
|
||||
'@upstash/redis': ^1.34.3
|
||||
'@vercel/blob': '>=0.27.1'
|
||||
'@vercel/functions': ^2.2.12 || ^3.0.0
|
||||
'@vercel/kv': ^1 || ^2 || ^3
|
||||
aws4fetch: ^1.0.20
|
||||
db0: '>=0.2.1'
|
||||
idb-keyval: ^6.2.1
|
||||
ioredis: ^5.4.2
|
||||
uploadthing: ^7.4.4
|
||||
peerDependenciesMeta:
|
||||
'@azure/app-configuration':
|
||||
optional: true
|
||||
'@azure/cosmos':
|
||||
optional: true
|
||||
'@azure/data-tables':
|
||||
optional: true
|
||||
'@azure/identity':
|
||||
optional: true
|
||||
'@azure/keyvault-secrets':
|
||||
optional: true
|
||||
'@azure/storage-blob':
|
||||
optional: true
|
||||
'@capacitor/preferences':
|
||||
optional: true
|
||||
'@deno/kv':
|
||||
optional: true
|
||||
'@netlify/blobs':
|
||||
optional: true
|
||||
'@planetscale/database':
|
||||
optional: true
|
||||
'@upstash/redis':
|
||||
optional: true
|
||||
'@vercel/blob':
|
||||
optional: true
|
||||
'@vercel/functions':
|
||||
optional: true
|
||||
'@vercel/kv':
|
||||
optional: true
|
||||
aws4fetch:
|
||||
optional: true
|
||||
db0:
|
||||
optional: true
|
||||
idb-keyval:
|
||||
optional: true
|
||||
ioredis:
|
||||
optional: true
|
||||
uploadthing:
|
||||
optional: true
|
||||
|
||||
zod@4.3.6:
|
||||
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
|
||||
@@ -453,7 +597,7 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@kevisual/router@0.0.63(typescript@5.9.3)':
|
||||
'@kevisual/router@0.0.64(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@kevisual/dts': 0.0.3(typescript@5.9.3)
|
||||
hono: 4.11.7
|
||||
@@ -467,12 +611,14 @@ snapshots:
|
||||
'@kevisual/load': 0.0.6
|
||||
dotenv: 17.2.3
|
||||
|
||||
'@opencode-ai/plugin@1.1.39':
|
||||
dependencies:
|
||||
'@opencode-ai/sdk': 1.1.39
|
||||
zod: 4.1.8
|
||||
'@kevisual/ws@8.19.0': {}
|
||||
|
||||
'@opencode-ai/sdk@1.1.39': {}
|
||||
'@opencode-ai/plugin@1.1.44':
|
||||
dependencies:
|
||||
'@opencode-ai/sdk': 1.1.44
|
||||
zod: 4.3.6
|
||||
|
||||
'@opencode-ai/sdk@1.1.44': {}
|
||||
|
||||
'@rollup/plugin-commonjs@28.0.9(rollup@4.57.0)':
|
||||
dependencies:
|
||||
@@ -588,9 +734,9 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc@4.57.0':
|
||||
optional: true
|
||||
|
||||
'@types/bun@1.3.7':
|
||||
'@types/bun@1.3.8':
|
||||
dependencies:
|
||||
bun-types: 1.3.7
|
||||
bun-types: 1.3.8
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
@@ -600,14 +746,39 @@ snapshots:
|
||||
|
||||
'@types/resolve@1.20.2': {}
|
||||
|
||||
bun-types@1.3.7:
|
||||
'@types/ws@8.18.1':
|
||||
dependencies:
|
||||
'@types/node': 25.1.0
|
||||
|
||||
anymatch@3.1.3:
|
||||
dependencies:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
|
||||
bun-types@1.3.8:
|
||||
dependencies:
|
||||
'@types/node': 25.1.0
|
||||
|
||||
chokidar@5.0.0:
|
||||
dependencies:
|
||||
readdirp: 5.0.0
|
||||
|
||||
commondir@1.0.1: {}
|
||||
|
||||
cookie-es@1.2.2: {}
|
||||
|
||||
crossws@0.3.5:
|
||||
dependencies:
|
||||
uncrypto: 0.1.3
|
||||
|
||||
dayjs@1.11.19: {}
|
||||
|
||||
deepmerge@4.3.1: {}
|
||||
|
||||
defu@6.1.4: {}
|
||||
|
||||
destr@2.0.5: {}
|
||||
|
||||
dotenv@17.2.3: {}
|
||||
|
||||
es-toolkit@1.44.0: {}
|
||||
@@ -625,12 +796,26 @@ snapshots:
|
||||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
h3@1.15.5:
|
||||
dependencies:
|
||||
cookie-es: 1.2.2
|
||||
crossws: 0.3.5
|
||||
defu: 6.1.4
|
||||
destr: 2.0.5
|
||||
iron-webcrypto: 1.2.1
|
||||
node-mock-http: 1.0.4
|
||||
radix3: 1.1.2
|
||||
ufo: 1.6.3
|
||||
uncrypto: 0.1.3
|
||||
|
||||
hasown@2.0.2:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hono@4.11.7: {}
|
||||
|
||||
iron-webcrypto@1.2.1: {}
|
||||
|
||||
is-core-module@2.16.1:
|
||||
dependencies:
|
||||
hasown: 2.0.2
|
||||
@@ -644,19 +829,39 @@ snapshots:
|
||||
js-tokens@4.0.0:
|
||||
optional: true
|
||||
|
||||
lru-cache@11.2.5: {}
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
nanoid@5.1.6: {}
|
||||
|
||||
node-fetch-native@1.6.7: {}
|
||||
|
||||
node-mock-http@1.0.4: {}
|
||||
|
||||
normalize-path@3.0.0: {}
|
||||
|
||||
ofetch@1.5.1:
|
||||
dependencies:
|
||||
destr: 2.0.5
|
||||
node-fetch-native: 1.6.7
|
||||
ufo: 1.6.3
|
||||
|
||||
path-parse@1.0.7: {}
|
||||
|
||||
picocolors@1.1.1:
|
||||
optional: true
|
||||
|
||||
picomatch@2.3.1: {}
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
|
||||
radix3@1.1.2: {}
|
||||
|
||||
readdirp@5.0.0: {}
|
||||
|
||||
resolve@1.22.11:
|
||||
dependencies:
|
||||
is-core-module: 2.16.1
|
||||
@@ -708,8 +913,21 @@ snapshots:
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
ufo@1.6.3: {}
|
||||
|
||||
uncrypto@0.1.3: {}
|
||||
|
||||
undici-types@7.16.0: {}
|
||||
|
||||
zod@4.1.8: {}
|
||||
unstorage@1.17.4:
|
||||
dependencies:
|
||||
anymatch: 3.1.3
|
||||
chokidar: 5.0.0
|
||||
destr: 2.0.5
|
||||
h3: 1.15.5
|
||||
lru-cache: 11.2.5
|
||||
node-fetch-native: 1.6.7
|
||||
ofetch: 1.5.1
|
||||
ufo: 1.6.3
|
||||
|
||||
zod@4.3.6: {}
|
||||
|
||||
1
src/keep.ts
Normal file
1
src/keep.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './workspace/keep-live.ts';
|
||||
191
src/workspace/keep-live.ts
Normal file
191
src/workspace/keep-live.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
// WebSocket Keep-Alive Client Library
|
||||
import WebSocket from "ws";
|
||||
|
||||
export interface KeepAliveConfig {
|
||||
wsUrl: string;
|
||||
cookie: string;
|
||||
reconnectInterval?: number;
|
||||
maxReconnectAttempts?: number;
|
||||
pingInterval?: number;
|
||||
onMessage?: (data: Buffer | string) => void;
|
||||
onConnect?: () => void;
|
||||
onDisconnect?: (code: number) => void;
|
||||
onError?: (error: Error) => void;
|
||||
onSign?: (data: { type: string; data: string; signedData: string }) => void;
|
||||
onExit?: (code?: number) => void;
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
export interface ParsedMessage {
|
||||
type: string;
|
||||
raw: Buffer;
|
||||
payload?: any;
|
||||
}
|
||||
|
||||
type MessageHandler = (msg: ParsedMessage) => void;
|
||||
|
||||
export class WSKeepAlive {
|
||||
private ws: WebSocket | null = null;
|
||||
private config: Required<KeepAliveConfig>;
|
||||
private reconnectAttempts = 0;
|
||||
private pingTimer: NodeJS.Timeout | null = null;
|
||||
private messageHandlers: Set<MessageHandler> = new Set();
|
||||
private url: URL;
|
||||
|
||||
constructor(config: KeepAliveConfig) {
|
||||
this.config = {
|
||||
wsUrl: config.wsUrl,
|
||||
cookie: config.cookie,
|
||||
reconnectInterval: config.reconnectInterval ?? 5000,
|
||||
maxReconnectAttempts: config.maxReconnectAttempts ?? 3,
|
||||
pingInterval: config.pingInterval ?? 30000,
|
||||
onMessage: config.onMessage ?? (() => { }),
|
||||
onConnect: config.onConnect ?? (() => { }),
|
||||
onDisconnect: config.onDisconnect ?? (() => { }),
|
||||
onError: config.onError ?? (() => { }),
|
||||
onSign: config.onSign ?? (() => { }),
|
||||
onExit: config.onExit ?? (() => { }),
|
||||
debug: config.debug ?? false,
|
||||
};
|
||||
this.url = new URL(this.config.wsUrl);
|
||||
}
|
||||
|
||||
private log(message: string) {
|
||||
if (!this.config.debug) return;
|
||||
const timestamp = new Date().toISOString();
|
||||
const msg = `[${timestamp}] ${message}`;
|
||||
console.log(msg);
|
||||
}
|
||||
|
||||
private parseMessage(data: Buffer): ParsedMessage | null {
|
||||
const result: ParsedMessage = { type: "unknown", raw: data };
|
||||
|
||||
if (data.length < 14) {
|
||||
result.type = "raw";
|
||||
return result;
|
||||
}
|
||||
|
||||
const prefix = data.slice(0, 13);
|
||||
const msgType = prefix[0];
|
||||
const jsonStart = data.indexOf(0x71); // 0x71 = 'q'
|
||||
|
||||
if (jsonStart !== -1) {
|
||||
try {
|
||||
const jsonStr = data.slice(jsonStart + 1).toString();
|
||||
const payload = JSON.parse(jsonStr);
|
||||
result.type = `binary(0x${msgType.toString(16)})`;
|
||||
result.payload = payload;
|
||||
|
||||
// 特殊处理 sign 类型
|
||||
if (payload.type === "sign" && this.config.onSign) {
|
||||
this.config.onSign(payload);
|
||||
}
|
||||
return result;
|
||||
} catch {
|
||||
result.type = "binary(json-parse-error)";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result.type = "raw";
|
||||
return result;
|
||||
}
|
||||
|
||||
connect() {
|
||||
const { wsUrl, cookie, debug } = this.config;
|
||||
this.log(`Connecting to ${wsUrl}...`);
|
||||
|
||||
this.ws = new WebSocket(wsUrl, {
|
||||
headers: {
|
||||
"Origin": this.url.origin,
|
||||
"Cookie": cookie,
|
||||
"Cache-Control": "no-cache",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
"Pragma": "no-cache",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
||||
"Sec-WebSocket-Extensions": "permessage-deflate",
|
||||
}
|
||||
});
|
||||
|
||||
this.ws.on("open", () => {
|
||||
debug && this.log("Connected!");
|
||||
this.reconnectAttempts = 0;
|
||||
this.config.onConnect();
|
||||
this.startPing();
|
||||
});
|
||||
|
||||
this.ws.on("message", (data: any) => {
|
||||
if (Buffer.isBuffer(data)) {
|
||||
const parsed = this.parseMessage(data);
|
||||
this.config.onMessage(parsed?.raw ?? data);
|
||||
|
||||
this.messageHandlers.forEach(handler => {
|
||||
if (parsed) handler(parsed);
|
||||
});
|
||||
} else {
|
||||
this.config.onMessage(data);
|
||||
}
|
||||
});
|
||||
|
||||
this.ws.on("close", (code: number) => {
|
||||
debug && this.log(`Disconnected (code: ${code})`);
|
||||
this.stopPing();
|
||||
this.config.onDisconnect(code);
|
||||
this.handleReconnect();
|
||||
});
|
||||
|
||||
this.ws.on("error", (err: Error) => {
|
||||
debug && this.log(`Error: ${err.message}`);
|
||||
this.config.onError(err);
|
||||
});
|
||||
}
|
||||
|
||||
private startPing() {
|
||||
this.stopPing();
|
||||
this.pingTimer = setInterval(() => {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.ping();
|
||||
this.log("Sent ping");
|
||||
}
|
||||
}, this.config.pingInterval);
|
||||
}
|
||||
|
||||
private stopPing() {
|
||||
if (this.pingTimer) {
|
||||
clearInterval(this.pingTimer);
|
||||
this.pingTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private handleReconnect() {
|
||||
if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
|
||||
this.log(`Max reconnect attempts (${this.config.maxReconnectAttempts}) reached. Giving up.`);
|
||||
this.config.onExit(1);
|
||||
return;
|
||||
}
|
||||
this.reconnectAttempts++;
|
||||
this.log(`Reconnecting in ${this.config.reconnectInterval}ms... (attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts})`);
|
||||
setTimeout(() => this.connect(), this.config.reconnectInterval);
|
||||
}
|
||||
|
||||
onMessage(handler: MessageHandler) {
|
||||
this.messageHandlers.add(handler);
|
||||
return () => this.messageHandlers.delete(handler);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.stopPing();
|
||||
if (this.ws) {
|
||||
this.ws.close();
|
||||
this.config.onExit(0);
|
||||
this.ws = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 便捷函数:快速创建并启动
|
||||
export function createKeepAlive(config: KeepAliveConfig): WSKeepAlive {
|
||||
const client = new WSKeepAlive(config);
|
||||
client.connect();
|
||||
return client;
|
||||
}
|
||||
13
src/workspace/keep-worker.ts
Normal file
13
src/workspace/keep-worker.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createKeepAlive } from '@kevisual/cnb/keep';
|
||||
|
||||
const wsUrl = process.argv[2];
|
||||
const cookie = process.argv[3];
|
||||
|
||||
createKeepAlive({
|
||||
wsUrl,
|
||||
cookie,
|
||||
onConnect: () => console.log('已连接'),
|
||||
debug: true
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => process.exit(0));
|
||||
13
test/keep-test.ts
Normal file
13
test/keep-test.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createKeepAlive } from "@kevisual/cnb/keep";
|
||||
|
||||
const config = {
|
||||
"wss": "wss://cnb-dk4-1jgcjjqvc-001.cnb.space:443/stable-3c0b449c6e6e37b44a8a7938c0d8a3049926a64c?reconnectionToken=d70ab69b-5e92-471a-b3d2-31f554b468d4&reconnection=false&skipWebSocketFrames=false",
|
||||
"cookie": "orange:workspace:cookie-session:cnb-dk4-1jgcjjqvc-001=01fea6db-d73f-4ce8-8929-36903ee7a266",
|
||||
"url": "https://cnb-dk4-1jgcjjqvc-001.cnb.space/?folder=/workspace"
|
||||
}
|
||||
|
||||
createKeepAlive({
|
||||
wsUrl: config.wss,
|
||||
cookie: config.cookie,
|
||||
debug: true,
|
||||
});
|
||||
20
test/keep.ts
Normal file
20
test/keep.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
// WebSocket Keep-Alive Client with node+ws
|
||||
// import { createKeepAlive } from "../src/keep.ts";
|
||||
import { createKeepAlive } from "../dist/keep.js";
|
||||
|
||||
const WS_URL = "wss://cnb-l6o-1jg7aoevl-001.cnb.space/stable-3c0b449c6e6e37b44a8a7938c0d8a3049926a64c?reconnectionToken=a6517530-9911-406b-a65f-0d9d4b3f0d6f&reconnection=false&skipWebSocketFrames=false";
|
||||
const COOKIE = "orange:workspace:cookie-session:cnb-l6o-1jg7aoevl-001=1ba3d696-1805-4c6b-b109-222738be570f";
|
||||
|
||||
// 使用库创建客户端
|
||||
const client = createKeepAlive({
|
||||
wsUrl: WS_URL,
|
||||
cookie: COOKIE,
|
||||
debug: true,
|
||||
});
|
||||
|
||||
// 监听解析后的消息
|
||||
client.onMessage((msg) => {
|
||||
console.log(`[Received] ${msg.raw.length} bytes`);
|
||||
});
|
||||
|
||||
console.log("开始激活 WebSocket 连接...");
|
||||
@@ -3,6 +3,7 @@
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"target": "esnext",
|
||||
"rootDir": ".",
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
@@ -19,6 +20,7 @@
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"agent/**/*",
|
||||
"mod.ts",
|
||||
"agent/**/*"
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user