Compare commits

..

14 Commits

Author SHA1 Message Date
412c057756 update 2026-02-01 21:13:36 +08:00
7a01339ef2 feat: 添加工作空间存活管理功能,重置和清理超时任务,使用 dayjs 格式化时间 2026-01-31 00:40:29 +08:00
6e5a642ab2 feat: 将所有路由的中间件从 'auth' 更新为 'admin-auth',并更新版本至 0.0.13 2026-01-31 00:30:37 +08:00
0d17d56628 feat: 添加工作空间保持存活功能,更新相关依赖和配置 2026-01-30 23:32:40 +08:00
972d68b87e feat: 更新版本至 0.0.10,修复依赖项 ws 的位置 2026-01-30 21:25:21 +08:00
28484baab3 feat: 更新 WebSocket Keep-Alive 客户端,添加 ws 作为外部依赖,修正导入路径 2026-01-30 21:23:40 +08:00
d7a4bcf58f feat: 重构 WebSocket Keep-Alive 客户端,添加连接和消息处理功能,更新依赖版本,增加 keep.ts 文件 2026-01-30 21:16:06 +08:00
1d4a27d1b2 add:TODO 2026-01-29 13:59:18 +08:00
5db3b4a51b feat(cnb-env): 添加用户登录状态检查路由,更新版本至 0.0.7,移除旧的用户检查路由 2026-01-29 13:34:04 +08:00
86b24cc9ef feat(issue): 添加查询 Issue 列表功能,更新完成 Issue 的状态处理,版本升级至 0.0.6 2026-01-29 13:23:17 +08:00
e15cf4f7be add version 2026-01-29 13:06:25 +08:00
xiongxiao
20fcf2dca8 feat(issue): add body field to IssueItem type and implement issue creation and completion routes
- Added 'body' field to IssueItem type in index.ts
- Created new routes for issue creation and completion in issue.ts
- Implemented validation for required parameters in issue creation and completion
- Added a new list route for issues in list.ts
2026-01-29 02:50:28 +08:00
xiongxiao
8e1880a343 update 2026-01-29 00:42:16 +08:00
xiongxiao
7b31367c1d 编辑文件 template.yml 2026-01-28 10:39:04 +08:00
29 changed files with 1466 additions and 124 deletions

View File

@@ -21,7 +21,7 @@ $:
# stages: # stages:
# - name: pnpm install # - name: pnpm install
# script: pnpm install # script: pnpm install
stages: !reference [.dev_tempalte, stages] stages: !reference [.dev_template, stages]
.common_sync_to_gitea: &common_sync_to_gitea .common_sync_to_gitea: &common_sync_to_gitea
- <<: *common_env - <<: *common_env

View File

@@ -95,7 +95,7 @@
# 开发环境模版 # 开发环境模版
.dev_tempalte: &dev_tempalte .dev_template: &dev_template
services: services:
- vscode - vscode
docker: docker:

4
.gitignore vendored
View File

@@ -3,4 +3,6 @@
node_modules node_modules
.pnpm-store .pnpm-store
dist dist
storage

View File

@@ -2,7 +2,6 @@ import { QueryRouterServer as App } from '@kevisual/router'
import { useContextKey } from '@kevisual/context' import { useContextKey } from '@kevisual/context'
import { useConfig, useKey } from '@kevisual/use-config' import { useConfig, useKey } from '@kevisual/use-config'
import { CNB } from '../src/index.ts'; import { CNB } from '../src/index.ts';
import { nanoid } from 'nanoid';
export const config = useConfig() export const config = useConfig()
export const cnb = useContextKey<CNB>('cnb', () => { export const cnb = useContextKey<CNB>('cnb', () => {
@@ -13,9 +12,6 @@ export const cnb = useContextKey<CNB>('cnb', () => {
const cookie = useKey('CNB_COOKIE') as string const cookie = useKey('CNB_COOKIE') as string
return new CNB({ token: token, cookie: cookie }); return new CNB({ token: token, cookie: cookie });
}) })
export const appId = nanoid();
export const app = useContextKey<App>('app', () => { export const app = useContextKey<App>('app', () => {
return new App({ return new App({})
appId
})
}) })

View File

@@ -1,4 +1,4 @@
import { app} from './index.ts'; import { app } from './index.ts';
import { createRouterAgentPluginFn } from '@kevisual/router/opencode' import { createRouterAgentPluginFn } from '@kevisual/router/opencode'
export const CnbPlugin = createRouterAgentPluginFn({ export const CnbPlugin = createRouterAgentPluginFn({

View File

@@ -2,31 +2,33 @@ import { createSkill } from '@kevisual/router'
import { app } from '../../app.ts' import { app } from '../../app.ts'
import { tool } from '@opencode-ai/plugin/tool' import { tool } from '@opencode-ai/plugin/tool'
// "调用 path: cnb key: list-repos" if (!app.hasRoute('call')) {
app.route({ // "调用 path: cnb key: list-repos"
path: 'call', app.route({
key: '', path: 'call',
description: '调用', key: '',
middleware: ['auth'], description: '调用',
metadata: { middleware: ['admin-auth'],
tags: ['opencode'], metadata: {
...createSkill({ tags: ['opencode'],
skill: 'call-app', ...createSkill({
title: '调用app应用', skill: 'call-app',
summary: '调用router的应用, 参数path, key, payload', title: '调用app应用',
args: { summary: '调用router的应用, 参数path, key, payload',
path: tool.schema.string().describe('应用路径,例如 cnb'), args: {
key: tool.schema.string().optional().describe('应用key,例如 list-repos'), path: tool.schema.string().describe('应用路径,例如 cnb'),
payload: tool.schema.object({}).optional().describe('调用参数'), key: tool.schema.string().optional().describe('应用key例如 list-repos'),
} payload: tool.schema.object({}).optional().describe('调用参数'),
}) }
}, })
}).define(async (ctx) => { },
const { path, key } = ctx.query; }).define(async (ctx) => {
console.log('call app', ctx.query); const { path, key } = ctx.query;
if (!path) { console.log('call app', ctx.query);
ctx.throw('路径path不能为空'); if (!path) {
} ctx.throw('路径path不能为空');
const res = await ctx.run({ path, key, payload: ctx.query.payload || {} }); }
ctx.forward(res); const res = await ctx.run({ path, key, payload: ctx.query.payload || {} });
}).addTo(app) ctx.forward(res);
}).addTo(app)
}

View File

@@ -7,7 +7,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'user-check', key: 'user-check',
description: '检查用户登录状态参数checkToken,default true; checkCookie, default false', description: '检查用户登录状态参数checkToken,default true; checkCookie, default false',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({

View File

@@ -6,7 +6,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'set-cnb-cookie', key: 'set-cnb-cookie',
description: '设置当前cnb工作空间的cookie环境变量', description: '设置当前cnb工作空间的cookie环境变量',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -33,7 +33,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'get-cnb-cookie', key: 'get-cnb-cookie',
description: '获取当前cnb工作空间的cookie环境变量', description: '获取当前cnb工作空间的cookie环境变量',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({

View File

@@ -5,11 +5,13 @@ import { CNB_ENV } from "@/common/cnb-env.ts";
// 执行技能 get-cnb-port-uri端口为4096 // 执行技能 get-cnb-port-uri端口为4096
// 执行技能 get-cnb-port-uri端口为51515 // 执行技能 get-cnb-port-uri端口为51515
// TODO 获取仓库的
app.route({ app.route({
path: 'cnb', path: 'cnb',
key: 'get-cnb-port-uri', key: 'get-cnb-port-uri',
description: '获取当前cnb工作空间的port代理uri', description: '获取当前cnb工作空间的port代理uri',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -38,7 +40,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'get-cnb-vscode-uri', key: 'get-cnb-vscode-uri',
description: '获取当前cnb工作空间的vscode代理uri, 包括多种访问方式, 如web、vscode、codebuddy、cursor、ssh', description: '获取当前cnb工作空间的vscode代理uri, 包括多种访问方式, 如web、vscode、codebuddy、cursor、ssh',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({

View File

@@ -1,12 +1,12 @@
import { app, appId } from '../app.ts'; import { app } from '../app.ts';
import './user/check.ts' import './cnb-env/check.ts'
import './repo/index.ts' import './repo/index.ts'
import './workspace/index.ts' import './workspace/index.ts'
import './call/index.ts' import './call/index.ts'
import './cnb-env/index.ts' import './cnb-env/index.ts'
import './knowledge/index.ts' import './knowledge/index.ts'
import './issues/index.ts'
import { isEqual } from 'es-toolkit'
/** /**
* 验证上下文中的 App ID 是否与指定的 App ID 匹配 * 验证上下文中的 App ID 是否与指定的 App ID 匹配
* @param {any} ctx - 上下文对象,可能包含 appId 属性 * @param {any} ctx - 上下文对象,可能包含 appId 属性
@@ -31,7 +31,7 @@ if (!app.hasRoute('auth')) {
path: 'auth', path: 'auth',
}).define(async (ctx) => { }).define(async (ctx) => {
// ctx.body = 'Auth Route'; // ctx.body = 'Auth Route';
if (checkAppId(ctx, appId)) { if (checkAppId(ctx, app.appId)) {
return; return;
} }
}).addTo(app); }).addTo(app);
@@ -42,7 +42,7 @@ if (!app.hasRoute('auth')) {
middleware: ['auth'], middleware: ['auth'],
}).define(async (ctx) => { }).define(async (ctx) => {
// ctx.body = 'Admin Auth Route'; // ctx.body = 'Admin Auth Route';
if (checkAppId(ctx, appId)) { if (checkAppId(ctx, app.appId)) {
return; return;
} }
}).addTo(app); }).addTo(app);

View File

@@ -0,0 +1,2 @@
import './list.ts'
import './issue.ts'

View File

@@ -0,0 +1,82 @@
import { createSkill, tool } from '@kevisual/router';
import { app, cnb } from '../../app.ts';
import { IssueItem } from '@/index.ts';
// 创建cnb issue, 仓库为 kevisual/kevisual 标题为 "自动化测试创建issue", 内容为 "这是通过API创建的issue用于测试目的", body: "这是通过API创建的issue用于测试目的"
app.route({
path: 'cnb',
key: 'create-issue',
description: '创建 Issue, 参数 repo, title, body, assignees, labels, priority',
middleware: ['admin-auth'],
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'create-issue',
title: '创建 Issue',
args: {
repo: tool.schema.string().describe('代码仓库名称, 如 my-user/my-repo'),
title: tool.schema.string().describe('Issue 标题'),
body: tool.schema.string().optional().describe('Issue 描述内容'),
assignees: tool.schema.array(tool.schema.string()).optional().describe('指派人列表'),
labels: tool.schema.array(tool.schema.string()).optional().describe('标签列表'),
priority: tool.schema.string().optional().describe('优先级'),
},
summary: '创建一个新的 Issue',
})
}
}).define(async (ctx) => {
const repo = ctx.query?.repo;
const title = ctx.query?.title;
const body = ctx.query?.body;
const assignees = ctx.query?.assignees;
const labels = ctx.query?.labels;
const priority = ctx.query?.priority;
if (!repo || !title) {
ctx.throw(400, '缺少参数 repo 或 title');
}
const res = await cnb.issue.createIssue(repo, {
title,
body,
assignees,
labels,
priority,
});
ctx.forward(res);
}).addTo(app);
// 完成 issue 8 仓库是 kevisual/kevisaul
app.route({
path: 'cnb',
key: 'complete-issue',
description: '完成 Issue, 参数 repo, issueNumber',
middleware: ['admin-auth'],
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'complete-issue',
title: '完成 CNB的任务Issue',
args: {
repo: tool.schema.string().describe('代码仓库名称, 如 my-user/my-repo'),
issueNumber: tool.schema.union([tool.schema.string(), tool.schema.number()]).describe('Issue 编号'),
state: tool.schema.string().optional().describe('Issue 状态,默认为 closed'),
},
summary: '完成一个 Issue将 state 改为 closed',
})
}
}).define(async (ctx) => {
const repo = ctx.query?.repo;
const issueNumber = ctx.query?.issueNumber;
const state = ctx.query?.state ?? 'closed';
if (!repo || !issueNumber) {
ctx.throw(400, '缺少参数 repo 或 issueNumber');
}
const iss: Partial<IssueItem> = { state: state };
if (iss.state === 'closed') {
iss.state_reason = 'completed';
}
const res = await cnb.issue.updateIssue(repo, issueNumber, iss);
ctx.forward(res);
}).addTo(app);

View File

@@ -0,0 +1,50 @@
import { createSkill, tool } from '@kevisual/router';
import { app, cnb } from '../../app.ts';
// 查询 Issue 列表 repo是 kevisual/kevisual
app.route({
path: 'cnb',
key: 'list-issues',
description: '查询 Issue 列表, 参数 repo, state, keyword, labels, page, page_size 等',
middleware: ['admin-auth'],
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'list-issues',
title: '查询 Issue 列表',
args: {
repo: tool.schema.string().describe('代码仓库名称, 如 my-user/my-repo'),
state: tool.schema.string().optional().describe('Issue 状态open 或 closed'),
keyword: tool.schema.string().optional().describe('问题搜索关键词'),
labels: tool.schema.string().optional().describe('问题标签,多个用逗号分隔'),
page: tool.schema.number().optional().describe('分页页码,默认: 1'),
page_size: tool.schema.number().optional().describe('分页每页大小,默认: 30'),
order_by: tool.schema.string().optional().describe('排序方式,如 created_at, -updated_at'),
},
summary: '查询 Issue 列表',
})
}
}).define(async (ctx) => {
const repo = ctx.query?.repo;
const state = ctx.query?.state;
const keyword = ctx.query?.keyword;
const labels = ctx.query?.labels;
const page = ctx.query?.page ? Number(ctx.query.page) : undefined;
const page_size = ctx.query?.page_size ? Number(ctx.query.page_size) : undefined;
const order_by = ctx.query?.order_by;
if (!repo) {
ctx.throw(400, '缺少参数 repo');
}
const params: Record<string, any> = {};
if (state) params.state = state;
if (keyword) params.keyword = keyword;
if (labels) params.labels = labels;
if (page) params.page = page;
if (page_size) params.page_size = page_size;
if (order_by) params.order_by = order_by;
const res = await cnb.issue.getList(repo, params);
ctx.forward(res);
}).addTo(app);

View File

@@ -12,7 +12,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'cnb-ai-chat', key: 'cnb-ai-chat',
description: '调用cnb的知识库ai对话功能进行聊天', description: '调用cnb的知识库ai对话功能进行聊天',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -88,7 +88,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'cnb-rag-query', key: 'cnb-rag-query',
description: '调用cnb的知识库RAG查询功能进行问答', description: '调用cnb的知识库RAG查询功能进行问答',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({

View File

@@ -8,7 +8,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'list-repos', key: 'list-repos',
description: '列出我的代码仓库', description: '列出我的代码仓库',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({

View File

@@ -7,7 +7,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'create-repo', key: 'create-repo',
description: '创建代码仓库, 参数name, visibility, description', description: '创建代码仓库, 参数name, visibility, description',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -47,7 +47,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'create-repo-file', key: 'create-repo-file',
description: '在代码仓库中创建文件, repoName, filePath, content, encoding', description: '在代码仓库中创建文件, repoName, filePath, content, encoding',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -86,7 +86,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'delete-repo', key: 'delete-repo',
description: '删除代码仓库, 参数name', description: '删除代码仓库, 参数name',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({

View File

@@ -2,13 +2,14 @@ import { createSkill, tool } from '@kevisual/router';
import { app, cnb } from '../../app.ts'; import { app, cnb } from '../../app.ts';
import z from 'zod'; import z from 'zod';
import './skills.ts'; import './skills.ts';
import './keep.ts';
// 启动工作空间 // 启动工作空间
app.route({ app.route({
path: 'cnb', path: 'cnb',
key: 'start-workspace', key: 'start-workspace',
description: '启动开发工作空间, 参数 repo', description: '启动开发工作空间, 参数 repo',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -41,7 +42,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'list-workspace', key: 'list-workspace',
description: '获取cnb开发工作空间列表可选参数 status=running 获取运行中的环境', description: '获取cnb开发工作空间列表可选参数 status=running 获取运行中的环境',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -72,7 +73,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'get-workspace', key: 'get-workspace',
description: '获取工作空间详情,通过 repo 和 sn 获取', description: '获取工作空间详情,通过 repo 和 sn 获取',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -103,7 +104,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'delete-workspace', key: 'delete-workspace',
description: '删除工作空间,通过 pipelineId 或 sn', description: '删除工作空间,通过 pipelineId 或 sn',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -113,7 +114,7 @@ app.route({
args: { args: {
pipelineId: tool.schema.string().optional().describe('流水线 ID优先使用'), pipelineId: tool.schema.string().optional().describe('流水线 ID优先使用'),
sn: tool.schema.string().optional().describe('流水线构建号'), 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', path: 'cnb',
key: 'stop-workspace', key: 'stop-workspace',
description: '停止工作空间,通过 pipelineId 或 sn', description: '停止工作空间,通过 pipelineId 或 sn',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({

View 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:工作空间访问URLcookie:访问工作空间所需的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);

View File

@@ -35,7 +35,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'clean-closed-workspace', key: 'clean-closed-workspace',
description: '批量删除已停止的cnb工作空间', description: '批量删除已停止的cnb工作空间',
middleware: ['auth'], middleware: ['admin-auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({

View File

@@ -1,19 +1,24 @@
import { resolvePath } from '@kevisual/use-config'; import { resolvePath } from '@kevisual/use-config';
import { execSync } from 'node:child_process'; import { execSync } from 'node:child_process';
const entry = 'agent/opencode.ts'; const buildFn = async (opts: { entry?: string, naming?: string }) => {
const naming = 'opencode'; const entry = opts.entry || 'agent/opencode.ts';
const external: string[] = ["bun"]; const naming = opts.naming || 'opencode';
await Bun.build({ const external: string[] = ["bun", "ws"];
target: 'node', await Bun.build({
format: 'esm', target: 'node',
entrypoints: [resolvePath(entry, { meta: import.meta })], format: 'esm',
outdir: resolvePath('./dist', { meta: import.meta }), entrypoints: [resolvePath(entry, { meta: import.meta })],
naming: { outdir: resolvePath('./dist', { meta: import.meta }),
entry: `${naming}.js`, naming: {
}, entry: `${naming}.js`,
external, },
}); external,
});
const cmd = `dts -i ${entry} -o ${naming}.d.ts`;
execSync(cmd);
};
const cmd = 'dts -i agent/opencode.ts -o opencode.d.ts'; await buildFn({ naming: 'opencode', entry: 'agent/opencode.ts' });
execSync(cmd); await buildFn({ naming: 'keep', entry: 'src/keep.ts' });
await buildFn({ naming: 'routes', entry: 'agent/index.ts' });

View File

@@ -1,6 +1,6 @@
{ {
"name": "@kevisual/cnb", "name": "@kevisual/cnb",
"version": "0.0.3", "version": "0.0.13",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@@ -18,28 +18,37 @@
"packageManager": "pnpm@10.28.2", "packageManager": "pnpm@10.28.2",
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@kevisual/ai": "^0.0.22", "@kevisual/ai": "^0.0.24",
"@kevisual/context": "^0.0.4", "@kevisual/context": "^0.0.4",
"@kevisual/types": "^0.0.12", "@kevisual/types": "^0.0.12",
"@opencode-ai/plugin": "^1.1.36", "@opencode-ai/plugin": "^1.1.44",
"@types/bun": "^1.3.6", "@types/bun": "^1.3.8",
"@types/node": "^25.0.10", "@types/node": "^25.1.0",
"@types/ws": "^8.18.1",
"dayjs": "^1.11.19",
"dotenv": "^17.2.3" "dotenv": "^17.2.3"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
"resolutions": {
"zod": "^4.3.6"
},
"dependencies": { "dependencies": {
"@kevisual/query": "^0.0.38", "@kevisual/query": "^0.0.38",
"@kevisual/router": "^0.0.62", "@kevisual/router": "^0.0.64",
"@kevisual/use-config": "^1.0.28", "@kevisual/use-config": "^1.0.28",
"es-toolkit": "^1.44.0", "es-toolkit": "^1.44.0",
"nanoid": "^5.1.6", "nanoid": "^5.1.6",
"unstorage": "^1.17.4",
"ws": "npm:@kevisual/ws",
"zod": "^4.3.6" "zod": "^4.3.6"
}, },
"exports": { "exports": {
".": "./mod.ts", ".": "./mod.ts",
"./opencode": "./dist/opencode.js", "./opencode": "./dist/opencode.js",
"./keep": "./dist/keep.js",
"./routes": "./dist/routes.js",
"./src/*": "./src/*", "./src/*": "./src/*",
"./agent/*": "./agent/*" "./agent/*": "./agent/*"
} }

820
pnpm-lock.yaml generated
View File

@@ -4,6 +4,9 @@ settings:
autoInstallPeers: true autoInstallPeers: true
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
overrides:
zod: ^4.3.6
importers: importers:
.: .:
@@ -12,8 +15,8 @@ importers:
specifier: ^0.0.38 specifier: ^0.0.38
version: 0.0.38 version: 0.0.38
'@kevisual/router': '@kevisual/router':
specifier: ^0.0.62 specifier: ^0.0.64
version: 0.0.62 version: 0.0.64(typescript@5.9.3)
'@kevisual/use-config': '@kevisual/use-config':
specifier: ^1.0.28 specifier: ^1.0.28
version: 1.0.28(dotenv@17.2.3) version: 1.0.28(dotenv@17.2.3)
@@ -23,13 +26,19 @@ importers:
nanoid: nanoid:
specifier: ^5.1.6 specifier: ^5.1.6
version: 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: zod:
specifier: ^4.3.6 specifier: ^4.3.6
version: 4.3.6 version: 4.3.6
devDependencies: devDependencies:
'@kevisual/ai': '@kevisual/ai':
specifier: ^0.0.22 specifier: ^0.0.24
version: 0.0.22 version: 0.0.24
'@kevisual/context': '@kevisual/context':
specifier: ^0.0.4 specifier: ^0.0.4
version: 0.0.4 version: 0.0.4
@@ -37,26 +46,47 @@ importers:
specifier: ^0.0.12 specifier: ^0.0.12
version: 0.0.12 version: 0.0.12
'@opencode-ai/plugin': '@opencode-ai/plugin':
specifier: ^1.1.36 specifier: ^1.1.44
version: 1.1.36 version: 1.1.44
'@types/bun': '@types/bun':
specifier: ^1.3.6 specifier: ^1.3.8
version: 1.3.6 version: 1.3.8
'@types/node': '@types/node':
specifier: ^25.0.10 specifier: ^25.1.0
version: 25.0.10 version: 25.1.0
'@types/ws':
specifier: ^8.18.1
version: 8.18.1
dayjs:
specifier: ^1.11.19
version: 1.11.19
dotenv: dotenv:
specifier: ^17.2.3 specifier: ^17.2.3
version: 17.2.3 version: 17.2.3
packages: packages:
'@kevisual/ai@0.0.22': '@babel/code-frame@7.28.6':
resolution: {integrity: sha512-9kth0pvPD4jT8c6rJ0DzSuvUY0s0pdw+CnO+YU8bwpMg04k1jFkd1RSZdtFrVbc60e2cpooQMUt/oimkfs082A==} resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==}
engines: {node: '>=6.9.0'}
'@babel/helper-validator-identifier@7.28.5':
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@kevisual/ai@0.0.24':
resolution: {integrity: sha512-7jvZk1/L//VIClK7usuNgN4ZA9Etgbooka1Sj5quE/0UywR+NNnwqXVZ89Y1fBhI1TkhauDsdJBAtcQ7r/vbVw==}
'@kevisual/context@0.0.4': '@kevisual/context@0.0.4':
resolution: {integrity: sha512-HJeLeZQLU+7tCluSfOyvkgKLs0HjCZrdJlZgEgKRSa8XTwZfMAUt6J7qZTbrZAHBlPtX68EPu/PI8JMCeu3WAQ==} resolution: {integrity: sha512-HJeLeZQLU+7tCluSfOyvkgKLs0HjCZrdJlZgEgKRSa8XTwZfMAUt6J7qZTbrZAHBlPtX68EPu/PI8JMCeu3WAQ==}
'@kevisual/dts@0.0.3':
resolution: {integrity: sha512-4T/m2LqhtwWEW+lWmg7jLxKFW7VtIAftsWFDDZvh10bZunqFf8iXxChHcVSQWikghJb4cq1IkWzPkvc2l+Asdw==}
hasBin: true
'@kevisual/load@0.0.6': '@kevisual/load@0.0.6':
resolution: {integrity: sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA==} resolution: {integrity: sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA==}
@@ -69,8 +99,8 @@ packages:
'@kevisual/query@0.0.38': '@kevisual/query@0.0.38':
resolution: {integrity: sha512-bfvbSodsZyMfwY+1T2SvDeOCKsT/AaIxlVe0+B1R/fNhlg2MDq2CP0L9HKiFkEm+OXrvXcYDMKPUituVUM5J6Q==} resolution: {integrity: sha512-bfvbSodsZyMfwY+1T2SvDeOCKsT/AaIxlVe0+B1R/fNhlg2MDq2CP0L9HKiFkEm+OXrvXcYDMKPUituVUM5J6Q==}
'@kevisual/router@0.0.62': '@kevisual/router@0.0.64':
resolution: {integrity: sha512-kKQFYkJ/qemGAygGSKkM/TujvreoU9HxL7/2vx2RYDMRyRuZOYUuvrTF92XIffFnZHugFbs8uEt+Z8I11SrFUg==} resolution: {integrity: sha512-EYz1MZxrltgySUL0Y+/MtZf2FEmqC5U8GmFAqvHNjgtS5FJdHpxRjo6zab4+0wSUlVyCxCpZXFY5vHB/g+nQBw==}
'@kevisual/types@0.0.12': '@kevisual/types@0.0.12':
resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==} resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==}
@@ -80,20 +110,241 @@ packages:
peerDependencies: peerDependencies:
dotenv: ^17 dotenv: ^17
'@opencode-ai/plugin@1.1.36': '@kevisual/ws@8.19.0':
resolution: {integrity: sha512-b2XWeFZN7UzgwkkzTIi6qSntkpEA9En2zvpqakQzZAGQm6QBdGAlv6r1u5hEnmF12Gzyj5umTMWr5GzVbP/oAA==} resolution: {integrity: sha512-jLsL80wBBKkrJZrfk3SQpJ9JA/zREdlUROj7eCkmzqduAWKSI0wVcXuCKf+mLFCHB0Q0Tkh2rgzjSlurt3JQgw==}
engines: {node: '>=10.0.0'}
'@opencode-ai/sdk@1.1.36': '@opencode-ai/plugin@1.1.44':
resolution: {integrity: sha512-feNHWnbxhg03TI2QrWnw3Chc0eYrWSDSmHIy/ejpSVfcKlfXREw1Tpg0L4EjrpeSc4jB1eM673dh+WM/Ko2SFQ==} resolution: {integrity: sha512-5w66Dq2Fugwgr2yrd8obvnlIEjBOuya82UgfR/3z3EzlyNDi2sitQSYbz7CcOtwd89eZ0n/tH/JX2KDGVuzxTQ==}
'@types/bun@1.3.6': '@opencode-ai/sdk@1.1.44':
resolution: {integrity: sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA==} resolution: {integrity: sha512-coQgtSSCbY46/GY+M5zG0rChiLSJWSjPERRt5L1hbjvDWvErelVV0ILPbd1+3CwJLFTedBYgotby2TcO8U0IfQ==}
'@types/node@25.0.10': '@rollup/plugin-commonjs@28.0.9':
resolution: {integrity: sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==} resolution: {integrity: sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA==}
engines: {node: '>=16.0.0 || 14 >= 14.17'}
peerDependencies:
rollup: ^2.68.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
bun-types@1.3.6: '@rollup/plugin-node-resolve@16.0.3':
resolution: {integrity: sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ==} resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^2.78.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
'@rollup/plugin-typescript@12.3.0':
resolution: {integrity: sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^2.14.0||^3.0.0||^4.0.0
tslib: '*'
typescript: '>=3.7.0'
peerDependenciesMeta:
rollup:
optional: true
tslib:
optional: true
'@rollup/pluginutils@5.3.0':
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
'@rollup/rollup-android-arm-eabi@4.57.0':
resolution: {integrity: sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.57.0':
resolution: {integrity: sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.57.0':
resolution: {integrity: sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.57.0':
resolution: {integrity: sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-freebsd-arm64@4.57.0':
resolution: {integrity: sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==}
cpu: [arm64]
os: [freebsd]
'@rollup/rollup-freebsd-x64@4.57.0':
resolution: {integrity: sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==}
cpu: [x64]
os: [freebsd]
'@rollup/rollup-linux-arm-gnueabihf@4.57.0':
resolution: {integrity: sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.57.0':
resolution: {integrity: sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.57.0':
resolution: {integrity: sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.57.0':
resolution: {integrity: sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.57.0':
resolution: {integrity: sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.57.0':
resolution: {integrity: sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==}
cpu: [loong64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.57.0':
resolution: {integrity: sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.57.0':
resolution: {integrity: sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==}
cpu: [ppc64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.57.0':
resolution: {integrity: sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.57.0':
resolution: {integrity: sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.57.0':
resolution: {integrity: sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.57.0':
resolution: {integrity: sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.57.0':
resolution: {integrity: sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.57.0':
resolution: {integrity: sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==}
cpu: [x64]
os: [openbsd]
'@rollup/rollup-openharmony-arm64@4.57.0':
resolution: {integrity: sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==}
cpu: [arm64]
os: [openharmony]
'@rollup/rollup-win32-arm64-msvc@4.57.0':
resolution: {integrity: sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.57.0':
resolution: {integrity: sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-gnu@4.57.0':
resolution: {integrity: sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==}
cpu: [x64]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.57.0':
resolution: {integrity: sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==}
cpu: [x64]
os: [win32]
'@types/bun@1.3.8':
resolution: {integrity: sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/node@25.1.0':
resolution: {integrity: sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==}
'@types/resolve@1.20.2':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
'@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: dotenv@17.2.3:
resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
@@ -102,33 +353,220 @@ packages:
es-toolkit@1.44.0: es-toolkit@1.44.0:
resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==} resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==}
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
eventemitter3@5.0.1: eventemitter3@5.0.1:
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
hono@4.11.6: fdir@6.5.0:
resolution: {integrity: sha512-ofIiiHyl34SV6AuhE3YT2mhO5HRWokce+eUYE82TsP6z0/H3JeJcjVWEMSIAiw2QkjDOEpES/lYsg8eEbsLtdw==} resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
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'}
hono@4.11.7:
resolution: {integrity: sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==}
engines: {node: '>=16.9.0'} 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'}
is-module@1.0.0:
resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
is-reference@1.2.1:
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
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==}
nanoid@5.1.6: nanoid@5.1.6:
resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==}
engines: {node: ^18 || >=20} engines: {node: ^18 || >=20}
hasBin: true 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'}
hasBin: true
rollup-plugin-dts@6.3.0:
resolution: {integrity: sha512-d0UrqxYd8KyZ6i3M2Nx7WOMy708qsV/7fTHMHxCMCBOAe3V/U7OMPu5GkX8hC+cmkHhzGnfeYongl1IgiooddA==}
engines: {node: '>=16'}
peerDependencies:
rollup: ^3.29.4 || ^4
typescript: ^4.5 || ^5.0
rollup@4.57.0:
resolution: {integrity: sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
tslib@2.8.1: tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
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: undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
zod@4.1.8: unstorage@1.17.4:
resolution: {integrity: sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==} 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: zod@4.3.6:
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
snapshots: snapshots:
'@kevisual/ai@0.0.22': '@babel/code-frame@7.28.6':
dependencies:
'@babel/helper-validator-identifier': 7.28.5
js-tokens: 4.0.0
picocolors: 1.1.1
optional: true
'@babel/helper-validator-identifier@7.28.5':
optional: true
'@jridgewell/sourcemap-codec@1.5.5': {}
'@kevisual/ai@0.0.24':
dependencies: dependencies:
'@kevisual/logger': 0.0.4 '@kevisual/logger': 0.0.4
'@kevisual/permission': 0.0.3 '@kevisual/permission': 0.0.3
@@ -136,6 +574,17 @@ snapshots:
'@kevisual/context@0.0.4': {} '@kevisual/context@0.0.4': {}
'@kevisual/dts@0.0.3(typescript@5.9.3)':
dependencies:
'@rollup/plugin-commonjs': 28.0.9(rollup@4.57.0)
'@rollup/plugin-node-resolve': 16.0.3(rollup@4.57.0)
'@rollup/plugin-typescript': 12.3.0(rollup@4.57.0)(tslib@2.8.1)(typescript@5.9.3)
rollup: 4.57.0
rollup-plugin-dts: 6.3.0(rollup@4.57.0)(typescript@5.9.3)
tslib: 2.8.1
transitivePeerDependencies:
- typescript
'@kevisual/load@0.0.6': '@kevisual/load@0.0.6':
dependencies: dependencies:
eventemitter3: 5.0.1 eventemitter3: 5.0.1
@@ -148,9 +597,12 @@ snapshots:
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
'@kevisual/router@0.0.62': '@kevisual/router@0.0.64(typescript@5.9.3)':
dependencies: dependencies:
hono: 4.11.6 '@kevisual/dts': 0.0.3(typescript@5.9.3)
hono: 4.11.7
transitivePeerDependencies:
- typescript
'@kevisual/types@0.0.12': {} '@kevisual/types@0.0.12': {}
@@ -159,39 +611,323 @@ snapshots:
'@kevisual/load': 0.0.6 '@kevisual/load': 0.0.6
dotenv: 17.2.3 dotenv: 17.2.3
'@opencode-ai/plugin@1.1.36': '@kevisual/ws@8.19.0': {}
'@opencode-ai/plugin@1.1.44':
dependencies: dependencies:
'@opencode-ai/sdk': 1.1.36 '@opencode-ai/sdk': 1.1.44
zod: 4.1.8 zod: 4.3.6
'@opencode-ai/sdk@1.1.36': {} '@opencode-ai/sdk@1.1.44': {}
'@types/bun@1.3.6': '@rollup/plugin-commonjs@28.0.9(rollup@4.57.0)':
dependencies: dependencies:
bun-types: 1.3.6 '@rollup/pluginutils': 5.3.0(rollup@4.57.0)
commondir: 1.0.1
estree-walker: 2.0.2
fdir: 6.5.0(picomatch@4.0.3)
is-reference: 1.2.1
magic-string: 0.30.21
picomatch: 4.0.3
optionalDependencies:
rollup: 4.57.0
'@types/node@25.0.10': '@rollup/plugin-node-resolve@16.0.3(rollup@4.57.0)':
dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.57.0)
'@types/resolve': 1.20.2
deepmerge: 4.3.1
is-module: 1.0.0
resolve: 1.22.11
optionalDependencies:
rollup: 4.57.0
'@rollup/plugin-typescript@12.3.0(rollup@4.57.0)(tslib@2.8.1)(typescript@5.9.3)':
dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.57.0)
resolve: 1.22.11
typescript: 5.9.3
optionalDependencies:
rollup: 4.57.0
tslib: 2.8.1
'@rollup/pluginutils@5.3.0(rollup@4.57.0)':
dependencies:
'@types/estree': 1.0.8
estree-walker: 2.0.2
picomatch: 4.0.3
optionalDependencies:
rollup: 4.57.0
'@rollup/rollup-android-arm-eabi@4.57.0':
optional: true
'@rollup/rollup-android-arm64@4.57.0':
optional: true
'@rollup/rollup-darwin-arm64@4.57.0':
optional: true
'@rollup/rollup-darwin-x64@4.57.0':
optional: true
'@rollup/rollup-freebsd-arm64@4.57.0':
optional: true
'@rollup/rollup-freebsd-x64@4.57.0':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.57.0':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.57.0':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.57.0':
optional: true
'@rollup/rollup-linux-arm64-musl@4.57.0':
optional: true
'@rollup/rollup-linux-loong64-gnu@4.57.0':
optional: true
'@rollup/rollup-linux-loong64-musl@4.57.0':
optional: true
'@rollup/rollup-linux-ppc64-gnu@4.57.0':
optional: true
'@rollup/rollup-linux-ppc64-musl@4.57.0':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.57.0':
optional: true
'@rollup/rollup-linux-riscv64-musl@4.57.0':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.57.0':
optional: true
'@rollup/rollup-linux-x64-gnu@4.57.0':
optional: true
'@rollup/rollup-linux-x64-musl@4.57.0':
optional: true
'@rollup/rollup-openbsd-x64@4.57.0':
optional: true
'@rollup/rollup-openharmony-arm64@4.57.0':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.57.0':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.57.0':
optional: true
'@rollup/rollup-win32-x64-gnu@4.57.0':
optional: true
'@rollup/rollup-win32-x64-msvc@4.57.0':
optional: true
'@types/bun@1.3.8':
dependencies:
bun-types: 1.3.8
'@types/estree@1.0.8': {}
'@types/node@25.1.0':
dependencies: dependencies:
undici-types: 7.16.0 undici-types: 7.16.0
bun-types@1.3.6: '@types/resolve@1.20.2': {}
'@types/ws@8.18.1':
dependencies: dependencies:
'@types/node': 25.0.10 '@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: {} dotenv@17.2.3: {}
es-toolkit@1.44.0: {} es-toolkit@1.44.0: {}
estree-walker@2.0.2: {}
eventemitter3@5.0.1: {} eventemitter3@5.0.1: {}
hono@4.11.6: {} fdir@6.5.0(picomatch@4.0.3):
optionalDependencies:
picomatch: 4.0.3
fsevents@2.3.3:
optional: true
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
is-module@1.0.0: {}
is-reference@1.2.1:
dependencies:
'@types/estree': 1.0.8
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: {} 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
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
rollup-plugin-dts@6.3.0(rollup@4.57.0)(typescript@5.9.3):
dependencies:
magic-string: 0.30.21
rollup: 4.57.0
typescript: 5.9.3
optionalDependencies:
'@babel/code-frame': 7.28.6
rollup@4.57.0:
dependencies:
'@types/estree': 1.0.8
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.57.0
'@rollup/rollup-android-arm64': 4.57.0
'@rollup/rollup-darwin-arm64': 4.57.0
'@rollup/rollup-darwin-x64': 4.57.0
'@rollup/rollup-freebsd-arm64': 4.57.0
'@rollup/rollup-freebsd-x64': 4.57.0
'@rollup/rollup-linux-arm-gnueabihf': 4.57.0
'@rollup/rollup-linux-arm-musleabihf': 4.57.0
'@rollup/rollup-linux-arm64-gnu': 4.57.0
'@rollup/rollup-linux-arm64-musl': 4.57.0
'@rollup/rollup-linux-loong64-gnu': 4.57.0
'@rollup/rollup-linux-loong64-musl': 4.57.0
'@rollup/rollup-linux-ppc64-gnu': 4.57.0
'@rollup/rollup-linux-ppc64-musl': 4.57.0
'@rollup/rollup-linux-riscv64-gnu': 4.57.0
'@rollup/rollup-linux-riscv64-musl': 4.57.0
'@rollup/rollup-linux-s390x-gnu': 4.57.0
'@rollup/rollup-linux-x64-gnu': 4.57.0
'@rollup/rollup-linux-x64-musl': 4.57.0
'@rollup/rollup-openbsd-x64': 4.57.0
'@rollup/rollup-openharmony-arm64': 4.57.0
'@rollup/rollup-win32-arm64-msvc': 4.57.0
'@rollup/rollup-win32-ia32-msvc': 4.57.0
'@rollup/rollup-win32-x64-gnu': 4.57.0
'@rollup/rollup-win32-x64-msvc': 4.57.0
fsevents: 2.3.3
supports-preserve-symlinks-flag@1.0.0: {}
tslib@2.8.1: {} tslib@2.8.1: {}
typescript@5.9.3: {}
ufo@1.6.3: {}
uncrypto@0.1.3: {}
undici-types@7.16.0: {} 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: {} zod@4.3.6: {}

View File

@@ -26,6 +26,7 @@ export type IssueItem = {
author: IssueAuthor; author: IssueAuthor;
labels: IssueLabel[]; labels: IssueLabel[];
body: string;
last_acted_at: string; last_acted_at: string;
number: string; number: string;
priority: string; priority: string;

1
src/keep.ts Normal file
View File

@@ -0,0 +1 @@
export * from './workspace/keep-live.ts';

191
src/workspace/keep-live.ts Normal file
View 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;
}

View 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
View 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
View 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 连接...");

View File

@@ -3,6 +3,7 @@
"compilerOptions": { "compilerOptions": {
"module": "NodeNext", "module": "NodeNext",
"target": "esnext", "target": "esnext",
"rootDir": ".",
"baseUrl": ".", "baseUrl": ".",
"typeRoots": [ "typeRoots": [
"./node_modules/@types", "./node_modules/@types",
@@ -19,6 +20,7 @@
}, },
"include": [ "include": [
"src/**/*", "src/**/*",
"agent/**/*", "mod.ts",
"agent/**/*"
], ],
} }