From 0d17d56628fbe6a7614cfe1edf7f991df6c76600 Mon Sep 17 00:00:00 2001 From: abearxiong Date: Fri, 30 Jan 2026 23:32:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E7=A9=BA=E9=97=B4=E4=BF=9D=E6=8C=81=E5=AD=98=E6=B4=BB=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E6=9B=B4=E6=96=B0=E7=9B=B8=E5=85=B3=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E5=92=8C=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +- agent/app.ts | 6 +- agent/opencode.ts | 2 +- agent/routes/index.ts | 6 +- agent/routes/workspace/index.ts | 3 +- agent/routes/workspace/keep.ts | 121 ++++++++++++++++ bun.config.ts | 3 +- package.json | 9 +- pnpm-lock.yaml | 238 ++++++++++++++++++++++++++++++-- src/workspace/keep-live.ts | 14 +- src/workspace/keep-worker.ts | 13 ++ tsconfig.json | 4 +- 12 files changed, 394 insertions(+), 29 deletions(-) create mode 100644 agent/routes/workspace/keep.ts create mode 100644 src/workspace/keep-worker.ts diff --git a/.gitignore b/.gitignore index 494bdae..2c0d4d8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ node_modules .pnpm-store -dist \ No newline at end of file +dist + +storage \ No newline at end of file diff --git a/agent/app.ts b/agent/app.ts index 6642937..a73519e 100644 --- a/agent/app.ts +++ b/agent/app.ts @@ -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', () => { @@ -13,9 +12,6 @@ export const cnb = useContextKey('cnb', () => { const cookie = useKey('CNB_COOKIE') as string return new CNB({ token: token, cookie: cookie }); }) -export const appId = nanoid(); export const app = useContextKey('app', () => { - return new App({ - appId - }) + return new App({}) }) \ No newline at end of file diff --git a/agent/opencode.ts b/agent/opencode.ts index ee10465..d90526f 100644 --- a/agent/opencode.ts +++ b/agent/opencode.ts @@ -1,4 +1,4 @@ -import { app} from './index.ts'; +import { app } from './index.ts'; import { createRouterAgentPluginFn } from '@kevisual/router/opencode' export const CnbPlugin = createRouterAgentPluginFn({ diff --git a/agent/routes/index.ts b/agent/routes/index.ts index d976f8b..3d08e01 100644 --- a/agent/routes/index.ts +++ b/agent/routes/index.ts @@ -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); diff --git a/agent/routes/workspace/index.ts b/agent/routes/workspace/index.ts index 1df17a3..8075356 100644 --- a/agent/routes/workspace/index.ts +++ b/agent/routes/workspace/index.ts @@ -2,6 +2,7 @@ import { createSkill, tool } from '@kevisual/router'; import { app, cnb } from '../../app.ts'; import z from 'zod'; import './skills.ts'; +import './keep.ts'; // 启动工作空间 app.route({ @@ -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('批量流水线构建号'), }, }) } diff --git a/agent/routes/workspace/keep.ts b/agent/routes/workspace/keep.ts new file mode 100644 index 0000000..eb11974 --- /dev/null +++ b/agent/routes/workspace/keep.ts @@ -0,0 +1,121 @@ +import { createSkill, tool } from '@kevisual/router'; +import { app, cnb } from '../../app.ts'; + +import { createKeepAlive } from '../../../src/keep.ts'; + +type AliveInfo = { + startTime: number; + updatedTime?: number; + KeepAlive: ReturnType; +} + +const keepAliveMap = new Map(); + +// 保持工作空间存活技能 +app.route({ + path: 'cnb', + key: 'keep-workspace-alive', + description: '保持工作空间存活技能,参数wsUrl:工作空间访问URL,cookie:访问工作空间所需的cookie', + middleware: ['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参数'); + } + + // 检测是否已在运行 + const existing = keepAliveMap.get(wsUrl); + if (existing) { + ctx.body = { message: `工作空间 ${wsUrl} 的保持存活任务已在运行中` }; + return; + } + + console.log(`启动保持工作空间 ${wsUrl} 存活的任务`); + const keep = createKeepAlive({ + wsUrl, + cookie, + onConnect: () => { + console.log(`工作空间 ${wsUrl} 保持存活任务已连接`); + }, + onMessage: (data) => { + // 可选:处理收到的消息 + // console.log(`工作空间 ${wsUrl} 收到消息: ${data}`); + const aliveInfo = keepAliveMap.get(wsUrl); + if (aliveInfo) { + aliveInfo.updatedTime = Date.now(); + } + }, + debug: true, + onExit: (code) => { + console.log(`工作空间 ${wsUrl} 保持存活任务已退出,退出码: ${code}`); + keepAliveMap.delete(wsUrl); + } + }); + + keepAliveMap.set(wsUrl, { startTime: Date.now(), updatedTime: Date.now(), KeepAlive: keep }); + + ctx.body = { message: `已启动保持工作空间 ${wsUrl} 存活的任务` }; +}).addTo(app); + +// 获取保持工作空间存活任务列表技能 +app.route({ + path: 'cnb', + key: 'list-keep-alive-tasks', + description: '获取保持工作空间存活任务列表技能', + middleware: ['auth'], + metadata: { + tags: [], + } +}).define(async (ctx) => { + const list = Array.from(keepAliveMap.entries()).map(([wsUrl, info]) => ({ + wsUrl, + startTime: info.startTime, + updatedTime: info.updatedTime + })); + ctx.body = { list }; +}).addTo(app); + +// 停止保持工作空间存活技能 +app.route({ + path: 'cnb', + key: 'stop-keep-workspace-alive', + description: '停止保持工作空间存活技能, 参数wsUrl:工作空间访问URL', + middleware: ['auth'], + metadata: { + tags: [], + ...({ + args: { + wsUrl: tool.schema.string().describe('工作空间的访问URL'), + } + }) + } +}).define(async (ctx) => { + const wsUrl = ctx.query?.wsUrl as string; + if (!wsUrl) { + ctx.throw(400, '缺少工作空间访问URL参数'); + } + + const keepAlive = keepAliveMap.get(wsUrl); + if (keepAlive) { + const endTime = Date.now(); + const duration = endTime - keepAlive.startTime; + keepAlive?.KeepAlive?.disconnect(); + keepAliveMap.delete(wsUrl); + ctx.body = { message: `已停止保持工作空间 ${wsUrl} 存活的任务,持续时间: ${duration}ms` }; + } else { + ctx.body = { message: `没有找到工作空间 ${wsUrl} 的保持存活任务` }; + } +}).addTo(app); diff --git a/bun.config.ts b/bun.config.ts index adb31a2..7de5637 100644 --- a/bun.config.ts +++ b/bun.config.ts @@ -20,4 +20,5 @@ const buildFn = async (opts: { entry?: string, naming?: string }) => { }; await buildFn({ naming: 'opencode', entry: 'agent/opencode.ts' }); -await buildFn({ naming: 'keep', entry: 'src/keep.ts' }); \ No newline at end of file +await buildFn({ naming: 'keep', entry: 'src/keep.ts' }); +await buildFn({ naming: 'routes', entry: 'agent/index.ts' }); \ No newline at end of file diff --git a/package.json b/package.json index 51e1e55..537b409 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kevisual/cnb", - "version": "0.0.10", + "version": "0.0.12", "description": "", "main": "index.js", "scripts": { @@ -30,12 +30,16 @@ "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" }, @@ -43,6 +47,7 @@ ".": "./mod.ts", "./opencode": "./dist/opencode.js", "./keep": "./dist/keep.js", + "./routes": "./dist/routes.js", "./src/*": "./src/*", "./agent/*": "./agent/*" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2969a5..85c3cae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 @@ -30,6 +39,9 @@ importers: '@kevisual/ai': specifier: ^0.0.24 version: 0.0.24 + '@kevisual/cnb': + specifier: ^0.0.10 + version: 0.0.10(dotenv@17.2.3)(typescript@5.9.3) '@kevisual/context': specifier: ^0.0.4 version: 0.0.4 @@ -51,9 +63,6 @@ importers: dotenv: specifier: ^17.2.3 version: 17.2.3 - ws: - specifier: npm:@kevisual/ws - version: '@kevisual/ws@8.19.0' packages: @@ -71,6 +80,9 @@ packages: '@kevisual/ai@0.0.24': resolution: {integrity: sha512-7jvZk1/L//VIClK7usuNgN4ZA9Etgbooka1Sj5quE/0UywR+NNnwqXVZ89Y1fBhI1TkhauDsdJBAtcQ7r/vbVw==} + '@kevisual/cnb@0.0.10': + resolution: {integrity: sha512-zwdJOj64FAkbUIGy+3CC+uQMUptWGf1uuQZXy0ZLfBfdFO2BcoHuzvomWhYxF0eexOBsLrz1F9jNWS0heBrp2Q==} + '@kevisual/context@0.0.4': resolution: {integrity: sha512-HJeLeZQLU+7tCluSfOyvkgKLs0HjCZrdJlZgEgKRSa8XTwZfMAUt6J7qZTbrZAHBlPtX68EPu/PI8JMCeu3WAQ==} @@ -93,6 +105,9 @@ packages: '@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==} @@ -304,16 +319,36 @@ packages: '@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==} + 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'} @@ -344,6 +379,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'} @@ -352,6 +390,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'} @@ -365,6 +406,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==} @@ -373,16 +418,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'} @@ -412,11 +481,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==} @@ -441,6 +575,19 @@ snapshots: '@kevisual/permission': 0.0.3 '@kevisual/query': 0.0.38 + '@kevisual/cnb@0.0.10(dotenv@17.2.3)(typescript@5.9.3)': + dependencies: + '@kevisual/query': 0.0.38 + '@kevisual/router': 0.0.63(typescript@5.9.3) + '@kevisual/use-config': 1.0.28(dotenv@17.2.3) + es-toolkit: 1.44.0 + nanoid: 5.1.6 + ws: '@kevisual/ws@8.19.0' + zod: 4.3.6 + transitivePeerDependencies: + - dotenv + - typescript + '@kevisual/context@0.0.4': {} '@kevisual/dts@0.0.3(typescript@5.9.3)': @@ -473,6 +620,13 @@ snapshots: transitivePeerDependencies: - typescript + '@kevisual/router@0.0.64(typescript@5.9.3)': + dependencies: + '@kevisual/dts': 0.0.3(typescript@5.9.3) + hono: 4.11.7 + transitivePeerDependencies: + - typescript + '@kevisual/types@0.0.12': {} '@kevisual/use-config@1.0.28(dotenv@17.2.3)': @@ -485,7 +639,7 @@ snapshots: '@opencode-ai/plugin@1.1.44': dependencies: '@opencode-ai/sdk': 1.1.44 - zod: 4.1.8 + zod: 4.3.6 '@opencode-ai/sdk@1.1.44': {} @@ -619,14 +773,33 @@ snapshots: 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 + deepmerge@4.3.1: {} + defu@6.1.4: {} + + destr@2.0.5: {} + dotenv@17.2.3: {} es-toolkit@1.44.0: {} @@ -644,12 +817,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 @@ -663,19 +850,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 @@ -727,8 +934,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: {} diff --git a/src/workspace/keep-live.ts b/src/workspace/keep-live.ts index 95d52bc..2c7edbe 100644 --- a/src/workspace/keep-live.ts +++ b/src/workspace/keep-live.ts @@ -12,6 +12,7 @@ export interface KeepAliveConfig { onDisconnect?: (code: number) => void; onError?: (error: Error) => void; onSign?: (data: { type: string; data: string; signedData: string }) => void; + onExit?: (code?: number) => void; debug?: boolean; } @@ -38,11 +39,12 @@ export class WSKeepAlive { 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 ?? (() => {}), + 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); @@ -158,6 +160,7 @@ export class WSKeepAlive { 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++; @@ -174,6 +177,7 @@ export class WSKeepAlive { this.stopPing(); if (this.ws) { this.ws.close(); + this.config.onExit(0); this.ws = null; } } diff --git a/src/workspace/keep-worker.ts b/src/workspace/keep-worker.ts new file mode 100644 index 0000000..b7efbbd --- /dev/null +++ b/src/workspace/keep-worker.ts @@ -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)); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 8e5e072..caabde9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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/**/*" ], } \ No newline at end of file