feat: Implement LiveCode module with WebSocket and SSE support
- Added config management using `useConfig` for environment variables. - Created `LiveCode` class to manage WebSocket connections and routing. - Implemented `SSEManager` for Server-Sent Events handling. - Developed `WSSManager` for managing WebSocket connections with heartbeat functionality. - Introduced `ReconnectingWebSocket` class for robust WebSocket client with automatic reconnection. - Added test files for live application demonstrating WebSocket and TCP server integration.
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
"dev:share": "bun --watch src/test/remote-app.ts ",
|
"dev:share": "bun --watch src/test/remote-app.ts ",
|
||||||
"build:lib": "bun run bun-lib.config.mjs",
|
"build:lib": "bun run bun-lib.config.mjs",
|
||||||
"postbuild:lib": "dts -i src/lib.ts -o assistant-lib.d.ts -d libs -t",
|
"postbuild:lib": "dts -i src/lib.ts -o assistant-lib.d.ts -d libs -t",
|
||||||
|
"dev:live": "bun --watch src/test/live-app.ts ",
|
||||||
"build": "rimraf dist && bun run bun.config.mjs"
|
"build": "rimraf dist && bun run bun.config.mjs"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -43,19 +44,18 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@inquirer/prompts": "^8.2.0",
|
"@inquirer/prompts": "^8.2.0",
|
||||||
"@kevisual/ai": "^0.0.24",
|
"@kevisual/ai": "^0.0.24",
|
||||||
"@kevisual/api": "^0.0.35",
|
"@kevisual/api": "^0.0.42",
|
||||||
"@kevisual/cnb": "^0.0.13",
|
|
||||||
"@kevisual/load": "^0.0.6",
|
"@kevisual/load": "^0.0.6",
|
||||||
"@kevisual/local-app-manager": "^0.1.32",
|
"@kevisual/local-app-manager": "^0.1.32",
|
||||||
"@kevisual/logger": "^0.0.4",
|
"@kevisual/logger": "^0.0.4",
|
||||||
"@kevisual/query": "0.0.38",
|
"@kevisual/query": "0.0.39",
|
||||||
"@kevisual/query-login": "0.0.7",
|
"@kevisual/query-login": "0.0.7",
|
||||||
"@kevisual/router": "^0.0.64",
|
"@kevisual/router": "^0.0.67",
|
||||||
"@kevisual/types": "^0.0.12",
|
"@kevisual/types": "^0.0.12",
|
||||||
"@kevisual/use-config": "^1.0.28",
|
"@kevisual/use-config": "^1.0.30",
|
||||||
"@opencode-ai/plugin": "^1.1.47",
|
"@opencode-ai/plugin": "^1.1.48",
|
||||||
"@types/bun": "^1.3.8",
|
"@types/bun": "^1.3.8",
|
||||||
"@types/node": "^25.1.0",
|
"@types/node": "^25.2.0",
|
||||||
"@types/send": "^1.2.1",
|
"@types/send": "^1.2.1",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
@@ -78,11 +78,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.980.0",
|
"@aws-sdk/client-s3": "^3.980.0",
|
||||||
"@kevisual/ha-api": "^0.0.8",
|
|
||||||
"@kevisual/js-filter": "^0.0.5",
|
"@kevisual/js-filter": "^0.0.5",
|
||||||
"@kevisual/oss": "^0.0.18",
|
"@kevisual/oss": "^0.0.19",
|
||||||
"@kevisual/video-tools": "^0.0.13",
|
"@kevisual/video-tools": "^0.0.13",
|
||||||
"@opencode-ai/sdk": "^1.1.47",
|
"@opencode-ai/sdk": "^1.1.48",
|
||||||
"es-toolkit": "^1.44.0",
|
"es-toolkit": "^1.44.0",
|
||||||
"eventemitter3": "^5.0.4",
|
"eventemitter3": "^5.0.4",
|
||||||
"lowdb": "^7.0.1",
|
"lowdb": "^7.0.1",
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import { AssistantInit, parseHomeArg } from '@/services/init/index.ts';
|
|||||||
import { configDir as HomeConfigDir } from '@/module/assistant/config/index.ts';
|
import { configDir as HomeConfigDir } from '@/module/assistant/config/index.ts';
|
||||||
import { useContextKey } from '@kevisual/use-config/context';
|
import { useContextKey } from '@kevisual/use-config/context';
|
||||||
import { AssistantQuery } from '@/module/assistant/query/index.ts';
|
import { AssistantQuery } from '@/module/assistant/query/index.ts';
|
||||||
|
import { config } from '@/module/config.ts';
|
||||||
|
export { config };
|
||||||
const manualParse = parseHomeArg(HomeConfigDir);
|
const manualParse = parseHomeArg(HomeConfigDir);
|
||||||
const _configDir = manualParse.configDir;
|
const _configDir = manualParse.configDir;
|
||||||
export const configDir = AssistantInit.detectConfigDir(_configDir);
|
export const configDir = AssistantInit.detectConfigDir(_configDir);
|
||||||
@@ -29,7 +30,7 @@ type Runtime = {
|
|||||||
isServer?: boolean;
|
isServer?: boolean;
|
||||||
}
|
}
|
||||||
export const runtime: Runtime = useContextKey('runtime', () => {
|
export const runtime: Runtime = useContextKey('runtime', () => {
|
||||||
console.log('Runtime detected:', manualParse);
|
console.log('Runtime detected:', manualParse.isDev);
|
||||||
return {
|
return {
|
||||||
type: 'client',
|
type: 'client',
|
||||||
isServer: manualParse.isServer,
|
isServer: manualParse.isServer,
|
||||||
|
|||||||
@@ -107,11 +107,13 @@ export type AssistantConfigData = {
|
|||||||
* 例子: { proxy: [ { type: 'router', api: 'https://localhost:50002/api/router' } ] }
|
* 例子: { proxy: [ { type: 'router', api: 'https://localhost:50002/api/router' } ] }
|
||||||
* base: 是否使用 /api/router的基础路径,默认false
|
* base: 是否使用 /api/router的基础路径,默认false
|
||||||
* lightcode: 是否启用lightcode路由,默认false
|
* lightcode: 是否启用lightcode路由,默认false
|
||||||
|
* livecode: 是否启用livecode路由,实时的注册和销毁,默认false
|
||||||
*/
|
*/
|
||||||
router?: {
|
router?: {
|
||||||
proxy: ProxyInfo[];
|
proxy: ProxyInfo[];
|
||||||
base?: boolean;
|
base?: boolean;
|
||||||
lightcode?: boolean;
|
lightcode?: boolean;
|
||||||
|
livecode?: boolean;
|
||||||
}
|
}
|
||||||
routes?: AssistantRoutes[],
|
routes?: AssistantRoutes[],
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -36,7 +36,23 @@ export class ModuleResolver {
|
|||||||
|
|
||||||
// 相对路径 ./xxx 或 ../xxx
|
// 相对路径 ./xxx 或 ../xxx
|
||||||
const localFullPath = path.resolve(this.root, routePath);
|
const localFullPath = path.resolve(this.root, routePath);
|
||||||
return this.fileIsExists(localFullPath) ? localFullPath : routePath;
|
if (!this.fileIsExists(localFullPath)) {
|
||||||
|
return routePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是目录,解析入口文件
|
||||||
|
if (fs.statSync(localFullPath).isDirectory()) {
|
||||||
|
const pkgJsonPath = path.join(localFullPath, 'package.json');
|
||||||
|
const pkg = this.readPackageJson(pkgJsonPath);
|
||||||
|
if (pkg) {
|
||||||
|
const entryPath = this.resolvePackageExport(pkg, '');
|
||||||
|
return path.join(localFullPath, entryPath);
|
||||||
|
}
|
||||||
|
// 没有 package.json,默认使用 index.ts
|
||||||
|
return path.join(localFullPath, 'index.ts');
|
||||||
|
}
|
||||||
|
|
||||||
|
return localFullPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 解析 scoped 包 */
|
/** 解析 scoped 包 */
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ export class AssistantApp extends Manager {
|
|||||||
const routeStr = typeof route === 'string' ? route : route.path;
|
const routeStr = typeof route === 'string' ? route : route.path;
|
||||||
const resolvedPath = this.resolver.resolve(routeStr);
|
const resolvedPath = this.resolver.resolve(routeStr);
|
||||||
await import(resolvedPath);
|
await import(resolvedPath);
|
||||||
console.log('路由已初始化', route);
|
console.log('[routes] 路由已初始化', route, resolvedPath);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('初始化路由失败', route, err);
|
console.error('初始化路由失败', route, err);
|
||||||
}
|
}
|
||||||
|
|||||||
10
assistant/src/module/config.ts
Normal file
10
assistant/src/module/config.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { useConfig } from '@kevisual/use-config';
|
||||||
|
import { HomeConfigDir } from './assistant/config/index.ts';
|
||||||
|
import path from 'node:path';
|
||||||
|
export const config = useConfig({
|
||||||
|
dotenvOpts: {
|
||||||
|
path: [path.join(HomeConfigDir, '.env'), '.env'],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// console.log('配置文件目录:', config, HomeConfigDir);
|
||||||
@@ -165,7 +165,11 @@ export const initLightCode = async (opts: opts) => {
|
|||||||
} else {
|
} else {
|
||||||
ctx.throw(runRes2.error || 'Lightcode 路由执行失败');
|
ctx.throw(runRes2.error || 'Lightcode 路由执行失败');
|
||||||
}
|
}
|
||||||
}).addTo(app);
|
}).addTo(app, {
|
||||||
|
override: false,
|
||||||
|
// @ts-ignore
|
||||||
|
overwrite: false
|
||||||
|
});// 不允许覆盖已存在的路由
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
131
assistant/src/module/livecode/index.ts
Normal file
131
assistant/src/module/livecode/index.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import { WSSManager } from './wss.ts';
|
||||||
|
import { App, Route } from '@kevisual/router'
|
||||||
|
import { WebSocketReq } from '@kevisual/router'
|
||||||
|
import { EventEmitter } from 'eventemitter3';
|
||||||
|
import { customAlphabet } from 'nanoid';
|
||||||
|
|
||||||
|
const letter = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
const customId = customAlphabet(letter, 16);
|
||||||
|
export class LiveCode {
|
||||||
|
wssManager: WSSManager;
|
||||||
|
app: App;
|
||||||
|
emitter: EventEmitter;
|
||||||
|
constructor(app: App) {
|
||||||
|
this.wssManager = new WSSManager({ heartbeatInterval: 5000 });
|
||||||
|
this.app = app;
|
||||||
|
this.emitter = new EventEmitter();
|
||||||
|
console.log('[LiveCode] 模块已初始化');
|
||||||
|
}
|
||||||
|
async conn(req: WebSocketReq) {
|
||||||
|
const { ws, emitter, id } = req;
|
||||||
|
const that = this;
|
||||||
|
// @ts-ignore
|
||||||
|
let wid = ws.data?.wid;
|
||||||
|
if (!wid) {
|
||||||
|
const _id = this.wssManager.addConnection(req, { userId: id });
|
||||||
|
// @ts-ignore
|
||||||
|
ws.data.wid = _id;
|
||||||
|
emitter.once('close--' + id, () => {
|
||||||
|
that.wssManager.closeConnection(_id);
|
||||||
|
this.deinitAppRoutes(_id);
|
||||||
|
});
|
||||||
|
console.log('[LiveCode]新的 WebSocket 连接已打开', _id);
|
||||||
|
const res = await that.init(_id);
|
||||||
|
if (res.code === 200) {
|
||||||
|
console.log('[LiveCode]初始化路由列表完成');
|
||||||
|
that.initAppRoutes(res.data?.list || [], _id);
|
||||||
|
} else {
|
||||||
|
console.error('[LiveCode]初始化路由列表失败:', res?.message);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
that.onMessage(req);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
getWss(id: string) {
|
||||||
|
return this.wssManager.getConnection(id)
|
||||||
|
}
|
||||||
|
async init(id: string): Promise<{ code: number, message?: string, data?: any }> {
|
||||||
|
return this.sendData({ path: 'router', key: 'list', }, id);
|
||||||
|
}
|
||||||
|
sendData(data: any, id: string): Promise<{ code: number, message?: string, data?: any }> {
|
||||||
|
const reqId = customId()
|
||||||
|
const wss = this.getWss(id);
|
||||||
|
if (!wss) {
|
||||||
|
return Promise.resolve({ code: 500, message: '连接不存在或已关闭' });
|
||||||
|
}
|
||||||
|
const emitter = this.emitter;
|
||||||
|
const wsReq = wss.wsReq;
|
||||||
|
try {
|
||||||
|
wsReq.ws.send(JSON.stringify({
|
||||||
|
type: 'router',
|
||||||
|
id: reqId,
|
||||||
|
data: data
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[LiveCode]发送数据失败:', error);
|
||||||
|
return Promise.resolve({ code: 500, message: '发送数据失败' });
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
resolve({ code: 500, message: '请求超时' });
|
||||||
|
emitter.off(reqId, listenOnce);
|
||||||
|
}, 5000);
|
||||||
|
const listenOnce = (resData: any) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve(resData);
|
||||||
|
emitter.off(reqId, listenOnce);
|
||||||
|
}
|
||||||
|
emitter.once(reqId, listenOnce);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onMessage(req: WebSocketReq) {
|
||||||
|
const { data } = req;
|
||||||
|
if (data?.id) {
|
||||||
|
// console.log('LiveCode 收到消息:', data);
|
||||||
|
this.emitter.emit(data.id, data.data);
|
||||||
|
} else {
|
||||||
|
console.warn('[LiveCode] 未知的消息格式', data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initAppRoutes(list: Route[], wid: string) {
|
||||||
|
for (const route of list) {
|
||||||
|
const path = route.path || '';
|
||||||
|
const id = route.id || '';
|
||||||
|
if (path.startsWith('router') || path.startsWith('auth') || path.startsWith('admin-autu') || path.startsWith('call')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// console.log('注册路由:', route.path, route.description, route.metadata, route.id);
|
||||||
|
this.app.route({
|
||||||
|
path: route.id,
|
||||||
|
key: route.key,
|
||||||
|
description: route.description,
|
||||||
|
metadata: {
|
||||||
|
...route.metadata,
|
||||||
|
liveCodeId: wid
|
||||||
|
},
|
||||||
|
middleware: ['auth'],
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const { token, cookie, ...rest } = ctx.query;
|
||||||
|
const tokenUser = ctx.state.tokernUser;
|
||||||
|
const res = await this.sendData({
|
||||||
|
id: route.id,
|
||||||
|
tokenUser,
|
||||||
|
payload: rest,
|
||||||
|
}, wid);
|
||||||
|
// console.log('路由响应数据:', res);
|
||||||
|
ctx.forward(res)
|
||||||
|
}).addTo(this.app, {
|
||||||
|
// override: false,
|
||||||
|
// // @ts-ignore
|
||||||
|
// overwrite: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deinitAppRoutes(wid: string) {
|
||||||
|
const routesToRemove = this.app.routes.filter(route => route.metadata?.liveCodeId === wid);
|
||||||
|
for (const route of routesToRemove) {
|
||||||
|
this.app.removeById(route.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
134
assistant/src/module/livecode/sse.ts
Normal file
134
assistant/src/module/livecode/sse.ts
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import { nanoid } from "nanoid";
|
||||||
|
type ConnectionInfo = {
|
||||||
|
id: string;
|
||||||
|
writer: WritableStreamDefaultWriter;
|
||||||
|
stream: ReadableStream<any>;
|
||||||
|
connectedAt: Date;
|
||||||
|
heartbeatInterval: NodeJS.Timeout | null;
|
||||||
|
userId?: string;
|
||||||
|
};
|
||||||
|
export class SSEManager {
|
||||||
|
private connections: Map<string, ConnectionInfo> = new Map();
|
||||||
|
private userConnections: Map<string, Set<string>> = new Map(); // userId -> connectionIds
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// 初始化逻辑
|
||||||
|
}
|
||||||
|
createConnection(info?: { userId?: string }): ConnectionInfo {
|
||||||
|
const connectionId = nanoid(16);
|
||||||
|
const { readable, writable } = new TransformStream();
|
||||||
|
const writer = writable.getWriter();
|
||||||
|
|
||||||
|
// 存储连接信息
|
||||||
|
const connectionInfo = {
|
||||||
|
id: connectionId,
|
||||||
|
writer,
|
||||||
|
stream: readable,
|
||||||
|
connectedAt: new Date(),
|
||||||
|
heartbeatInterval: null,
|
||||||
|
userId: info?.userId
|
||||||
|
};
|
||||||
|
|
||||||
|
this.connections.set(connectionId, connectionInfo);
|
||||||
|
|
||||||
|
// 添加到用户索引
|
||||||
|
if (info?.userId) {
|
||||||
|
const userSet = this.userConnections.get(info.userId) || new Set();
|
||||||
|
userSet.add(connectionId);
|
||||||
|
this.userConnections.set(info.userId, userSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
return connectionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToConnection(connectionId: string, data: any) {
|
||||||
|
const connection = this.connections.get(connectionId);
|
||||||
|
if (connection) {
|
||||||
|
const message = `data: ${JSON.stringify(data)}\n\n`;
|
||||||
|
return connection.writer.write(new TextEncoder().encode(message));
|
||||||
|
}
|
||||||
|
throw new Error(`Connection ${connectionId} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getConnection(connectionId: string) {
|
||||||
|
return this.connections.get(connectionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast(data: any, opts?: { userId?: string }) {
|
||||||
|
const message = `data: ${JSON.stringify(data)}\n\n`;
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
// 指定 userId:只发送给目标用户(通过索引快速查找)
|
||||||
|
if (opts?.userId) {
|
||||||
|
const userConnIds = this.userConnections.get(opts.userId);
|
||||||
|
if (userConnIds) {
|
||||||
|
for (const connId of userConnIds) {
|
||||||
|
const conn = this.connections.get(connId);
|
||||||
|
if (conn) {
|
||||||
|
promises.push(
|
||||||
|
conn.writer.write(new TextEncoder().encode(message))
|
||||||
|
.catch(() => {
|
||||||
|
this.closeConnection(connId);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未指定 userId:广播给所有人
|
||||||
|
for (const [id, connection] of this.connections) {
|
||||||
|
promises.push(
|
||||||
|
connection.writer.write(new TextEncoder().encode(message))
|
||||||
|
.catch(() => {
|
||||||
|
this.closeConnection(id);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeConnection(connectionId: string) {
|
||||||
|
const connection = this.connections.get(connectionId);
|
||||||
|
if (connection) {
|
||||||
|
// 清理心跳定时器
|
||||||
|
if (connection.heartbeatInterval) {
|
||||||
|
clearInterval(connection.heartbeatInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从用户索引中移除
|
||||||
|
if (connection.userId) {
|
||||||
|
const userSet = this.userConnections.get(connection.userId);
|
||||||
|
if (userSet) {
|
||||||
|
userSet.delete(connectionId);
|
||||||
|
if (userSet.size === 0) {
|
||||||
|
this.userConnections.delete(connection.userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭写入器
|
||||||
|
connection.writer.close().catch(console.error);
|
||||||
|
|
||||||
|
// 从管理器中移除
|
||||||
|
this.connections.delete(connectionId);
|
||||||
|
|
||||||
|
console.log(`Connection ${connectionId} closed`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAllConnections() {
|
||||||
|
for (const [connectionId, connection] of this.connections) {
|
||||||
|
this.closeConnection(connectionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveConnections() {
|
||||||
|
return Array.from(this.connections.keys());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
213
assistant/src/module/livecode/wss.ts
Normal file
213
assistant/src/module/livecode/wss.ts
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import { nanoid } from "nanoid";
|
||||||
|
import { WebSocketReq } from '@kevisual/router'
|
||||||
|
type ConnectionInfo = {
|
||||||
|
id: string;
|
||||||
|
wsReq: WebSocketReq;
|
||||||
|
connectedAt: Date;
|
||||||
|
heartbeatInterval: NodeJS.Timeout | null;
|
||||||
|
userId?: string;
|
||||||
|
lastHeartbeat: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class WSSManager {
|
||||||
|
private connections: Map<string, ConnectionInfo> = new Map();
|
||||||
|
private userConnections: Map<string, Set<string>> = new Map();
|
||||||
|
private heartbeatInterval: number = 30000; // 默认30秒
|
||||||
|
|
||||||
|
constructor(opts?: { heartbeatInterval?: number }) {
|
||||||
|
if (opts?.heartbeatInterval) {
|
||||||
|
this.heartbeatInterval = opts.heartbeatInterval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加 WebSocket 连接
|
||||||
|
*/
|
||||||
|
addConnection(wsReq: WebSocketReq, info?: { userId?: string }): string {
|
||||||
|
const connectionId = nanoid(16);
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const connectionInfo: ConnectionInfo = {
|
||||||
|
id: connectionId,
|
||||||
|
wsReq: wsReq,
|
||||||
|
connectedAt: now,
|
||||||
|
heartbeatInterval: null,
|
||||||
|
userId: info?.userId,
|
||||||
|
lastHeartbeat: now,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 启动心跳
|
||||||
|
this.startHeartbeat(connectionInfo);
|
||||||
|
|
||||||
|
// 存储连接
|
||||||
|
this.connections.set(connectionId, connectionInfo);
|
||||||
|
|
||||||
|
// 添加到用户索引
|
||||||
|
if (info?.userId) {
|
||||||
|
const userSet = this.userConnections.get(info.userId) || new Set();
|
||||||
|
userSet.add(connectionId);
|
||||||
|
this.userConnections.set(info.userId, userSet);
|
||||||
|
}
|
||||||
|
return connectionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动心跳
|
||||||
|
*/
|
||||||
|
private startHeartbeat(connection: ConnectionInfo) {
|
||||||
|
connection.heartbeatInterval = setInterval(() => {
|
||||||
|
const ws = connection.wsReq.ws;
|
||||||
|
ws.send(JSON.stringify({ type: 'heartbeat', timestamp: new Date().toISOString() }));
|
||||||
|
connection.lastHeartbeat = new Date();
|
||||||
|
console.log(`[LiveCode] 发送心跳给连接 ${connection.id}`);
|
||||||
|
}, this.heartbeatInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息到指定连接
|
||||||
|
*/
|
||||||
|
sendToConnection(connectionId: string, data: any): boolean {
|
||||||
|
const connection = this.connections.get(connectionId);
|
||||||
|
if (connection) {
|
||||||
|
// 发送消息
|
||||||
|
connection.wsReq.ws.send(JSON.stringify(data));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息到指定用户的所有连接
|
||||||
|
*/
|
||||||
|
sendToUser(userId: string, data: any): number {
|
||||||
|
const userConnIds = this.userConnections.get(userId);
|
||||||
|
if (!userConnIds) return 0;
|
||||||
|
|
||||||
|
let sentCount = 0;
|
||||||
|
for (const connId of userConnIds) {
|
||||||
|
if (this.sendToConnection(connId, data)) {
|
||||||
|
sentCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sentCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 广播消息到所有连接
|
||||||
|
*/
|
||||||
|
broadcast(data: any, opts?: { userId?: string; excludeConnectionId?: string }): number {
|
||||||
|
if (opts?.userId) {
|
||||||
|
// 发送给指定用户
|
||||||
|
return this.sendToUser(opts.userId, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sentCount = 0;
|
||||||
|
for (const [connId, connection] of this.connections) {
|
||||||
|
// 跳过排除的连接
|
||||||
|
if (opts?.excludeConnectionId && connId === opts.excludeConnectionId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sendToConnection(connId, data)) {
|
||||||
|
sentCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sentCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取连接信息
|
||||||
|
*/
|
||||||
|
getConnection(connectionId: string): ConnectionInfo | undefined {
|
||||||
|
return this.connections.get(connectionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户的所有连接
|
||||||
|
*/
|
||||||
|
getUserConnections(userId: string): ConnectionInfo[] {
|
||||||
|
const userConnIds = this.userConnections.get(userId);
|
||||||
|
if (!userConnIds) return [];
|
||||||
|
|
||||||
|
return Array.from(userConnIds)
|
||||||
|
.map((id) => this.connections.get(id))
|
||||||
|
.filter((conn): conn is ConnectionInfo => conn !== undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查连接是否活跃(基于心跳)
|
||||||
|
*/
|
||||||
|
isConnectionAlive(connectionId: string, timeout: number = 60000): boolean {
|
||||||
|
const connection = this.connections.get(connectionId);
|
||||||
|
if (!connection) return false;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const timeSinceLastHeartbeat = now.getTime() - connection.lastHeartbeat.getTime();
|
||||||
|
return timeSinceLastHeartbeat < timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭指定连接
|
||||||
|
*/
|
||||||
|
closeConnection(connectionId: string): boolean {
|
||||||
|
const connection = this.connections.get(connectionId);
|
||||||
|
if (connection) {
|
||||||
|
// 清理心跳定时器
|
||||||
|
if (connection.heartbeatInterval) {
|
||||||
|
clearInterval(connection.heartbeatInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从用户索引中移除
|
||||||
|
if (connection.userId) {
|
||||||
|
const userSet = this.userConnections.get(connection.userId);
|
||||||
|
if (userSet) {
|
||||||
|
userSet.delete(connectionId);
|
||||||
|
if (userSet.size === 0) {
|
||||||
|
this.userConnections.delete(connection.userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
connection.wsReq.ws.close();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error closing WebSocket for connection ${connectionId}:`, error);
|
||||||
|
}
|
||||||
|
// 从管理器中移除
|
||||||
|
this.connections.delete(connectionId);
|
||||||
|
|
||||||
|
console.log(`WebSocket connection ${connectionId} closed`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭所有连接
|
||||||
|
*/
|
||||||
|
closeAllConnections(): void {
|
||||||
|
for (const [connectionId] of this.connections) {
|
||||||
|
this.closeConnection(connectionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取活跃连接列表
|
||||||
|
*/
|
||||||
|
getActiveConnections(): string[] {
|
||||||
|
return Array.from(this.connections.keys());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取连接数量
|
||||||
|
*/
|
||||||
|
getConnectionCount(): number {
|
||||||
|
return this.connections.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户连接数量
|
||||||
|
*/
|
||||||
|
getUserConnectionCount(userId: string): number {
|
||||||
|
return this.userConnections.get(userId)?.size || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
|
|||||||
return fileProxy(req, res, {
|
return fileProxy(req, res, {
|
||||||
path: localProxyProxy.path,
|
path: localProxyProxy.path,
|
||||||
rootPath: localProxy.pagesDir,
|
rootPath: localProxy.pagesDir,
|
||||||
indexPath: localProxyProxy.indexPath,
|
indexPath: localProxyProxy.file?.indexPath,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
res.statusCode = 404;
|
res.statusCode = 404;
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import { app } from '@/app.ts';
|
|
||||||
// import { Hotkeys } from '@kevisual/hot-api';
|
|
||||||
import { Hotkeys } from './lib.ts';
|
|
||||||
import { useContextKey } from '@kevisual/context';
|
|
||||||
app.route({
|
|
||||||
path: 'key-sender',
|
|
||||||
// middleware: ['admin-auth']
|
|
||||||
}).define(async (ctx) => {
|
|
||||||
let keys = ctx.query.keys;
|
|
||||||
if (keys.includes(' ')) {
|
|
||||||
keys = keys.replace(/\s+/g, '+');
|
|
||||||
}
|
|
||||||
const hotKeys: Hotkeys = useContextKey('hotkeys', () => new Hotkeys());
|
|
||||||
if (typeof keys === 'string') {
|
|
||||||
await hotKeys.pressHotkey({
|
|
||||||
hotkey: keys,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ctx.body = 'ok';
|
|
||||||
}).addTo(app);
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import { keyboard, Key } from "@nut-tree-fork/nut-js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 控制功能部分的案件映射
|
|
||||||
*/
|
|
||||||
export const keyMap: Record<string, Key> = {
|
|
||||||
'ctrl': Key.LeftControl,
|
|
||||||
'leftctrl': Key.LeftControl,
|
|
||||||
'rightctrl': Key.RightControl,
|
|
||||||
'alt': Key.LeftAlt,
|
|
||||||
'leftalt': Key.LeftAlt,
|
|
||||||
'rightalt': Key.RightAlt,
|
|
||||||
'shift': Key.LeftShift,
|
|
||||||
'leftshift': Key.LeftShift,
|
|
||||||
'rightshift': Key.RightShift,
|
|
||||||
'meta': Key.LeftSuper,
|
|
||||||
'cmd': Key.LeftCmd,
|
|
||||||
'win': Key.LeftWin,
|
|
||||||
// 根据操作系统选择 Ctrl 或 Command 键
|
|
||||||
'ctrlorcommand': process.platform === 'darwin' ? Key.LeftCmd : Key.LeftControl,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将快捷键字符串转换为 Key 枚举值
|
|
||||||
* @param hotkey
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const parseHotkey = (hotkey: string): Key[] => {
|
|
||||||
return hotkey
|
|
||||||
.toLowerCase()
|
|
||||||
.split('+')
|
|
||||||
.map(key => {
|
|
||||||
const trimmed = key.trim().toLowerCase();
|
|
||||||
// 如果是修饰键,从映射表中获取
|
|
||||||
if (keyMap[trimmed]) {
|
|
||||||
return keyMap[trimmed];
|
|
||||||
}
|
|
||||||
// 如果是字母,转换为大写并查找对应的 Key
|
|
||||||
if (trimmed.length === 1 && /[a-z]/.test(trimmed)) {
|
|
||||||
const upperKey = trimmed.toUpperCase();
|
|
||||||
return Key[upperKey as keyof typeof Key] as Key;
|
|
||||||
}
|
|
||||||
// 其他情况直接查找
|
|
||||||
return Key[trimmed as keyof typeof Key] as Key;
|
|
||||||
})
|
|
||||||
.filter((key): key is Key => key !== undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
type PressHostKeysOptions = {
|
|
||||||
hotkey: string;
|
|
||||||
durationMs?: number;
|
|
||||||
}
|
|
||||||
export const pressHotkey = async (opts: PressHostKeysOptions): Promise<boolean> => {
|
|
||||||
const { hotkey, durationMs = 100 } = opts;
|
|
||||||
const keys = parseHotkey(hotkey);
|
|
||||||
|
|
||||||
console.log('准备模拟按下快捷键:', hotkey);
|
|
||||||
// 同时按下所有键
|
|
||||||
await keyboard.pressKey(...keys);
|
|
||||||
// 短暂延迟后释放
|
|
||||||
await new Promise(resolve => setTimeout(resolve, durationMs));
|
|
||||||
// 释放所有键
|
|
||||||
await keyboard.releaseKey(...keys);
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模拟按下一组快捷键,支持逗号分隔的多个快捷键
|
|
||||||
* @param opts
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const pressHotkeys = async (opts: PressHostKeysOptions): Promise<boolean> => {
|
|
||||||
let { hotkey } = opts;
|
|
||||||
hotkey = hotkey.replace(/\s+/g, ''); // 去除所有空格
|
|
||||||
const hotkeyList = hotkey.split(',').map(hk => hk.trim());
|
|
||||||
if (hotkeyList.length === 0) {
|
|
||||||
return await pressHotkey({ ...opts, hotkey });
|
|
||||||
}
|
|
||||||
for (const hk of hotkeyList) {
|
|
||||||
await pressHotkey({ ...opts, hotkey: hk });
|
|
||||||
// 每个快捷键之间稍作延迟
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 200));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
export class Hotkeys {
|
|
||||||
pressHotkey = pressHotkey;
|
|
||||||
pressHotkeys = pressHotkeys;
|
|
||||||
}
|
|
||||||
@@ -5,8 +5,6 @@ import './ai/index.ts';
|
|||||||
import './user/index.ts';
|
import './user/index.ts';
|
||||||
import './call/index.ts'
|
import './call/index.ts'
|
||||||
|
|
||||||
// TODO: 移除
|
|
||||||
// import './hot-api/key-sender/index.ts';
|
|
||||||
import './opencode/index.ts';
|
import './opencode/index.ts';
|
||||||
import './remote/index.ts';
|
import './remote/index.ts';
|
||||||
import './kevisual/index.ts'
|
import './kevisual/index.ts'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useContextKey } from '@kevisual/context';
|
import { useContextKey } from '@kevisual/context';
|
||||||
import { app, assistantConfig, runtime } from './app.ts';
|
import { app, assistantConfig, runtime } from './app.ts';
|
||||||
import { proxyRoute, proxyWs } from './services/proxy/proxy-page-index.ts';
|
import { proxyLivecodeWs, proxyRoute, proxyWs } from './services/proxy/proxy-page-index.ts';
|
||||||
import './routes/index.ts';
|
import './routes/index.ts';
|
||||||
import './routes-simple/index.ts';
|
import './routes-simple/index.ts';
|
||||||
|
|
||||||
@@ -49,6 +49,7 @@ export const runServer = async (port: number = 51515, listenPath = '127.0.0.1')
|
|||||||
func: proxyRoute as any,
|
func: proxyRoute as any,
|
||||||
},
|
},
|
||||||
...proxyWs(),
|
...proxyWs(),
|
||||||
|
...proxyLivecodeWs(),
|
||||||
qwenAsr,
|
qwenAsr,
|
||||||
]);
|
]);
|
||||||
const manager = useContextKey('manager', new AssistantApp(assistantConfig, app));
|
const manager = useContextKey('manager', new AssistantApp(assistantConfig, app));
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { createApiProxy, ProxyInfo, proxy } from '@/module/assistant/index.ts';
|
import { createApiProxy, ProxyInfo, proxy } from '@/module/assistant/index.ts';
|
||||||
import http from 'node:http';
|
import http from 'node:http';
|
||||||
import { LocalProxy } from './local-proxy.ts';
|
import { LocalProxy } from './local-proxy.ts';
|
||||||
import { assistantConfig, simpleRouter } from '@/app.ts';
|
import { assistantConfig, simpleRouter, app } from '@/app.ts';
|
||||||
import { log, logger } from '@/module/logger.ts';
|
import { log, logger } from '@/module/logger.ts';
|
||||||
import { getToken } from '@/module/http-token.ts';
|
import { getToken } from '@/module/http-token.ts';
|
||||||
import { getTokenUserCache } from '@/routes/index.ts';
|
import { getTokenUserCache } from '@/routes/index.ts';
|
||||||
import type { WebSocketListenerFun } from "@kevisual/router";
|
import type { WebSocketListenerFun } from "@kevisual/router";
|
||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws';
|
||||||
import { renderNoAuthAndLogin } from '@/module/assistant/html/login.ts';
|
import { renderNoAuthAndLogin } from '@/module/assistant/html/login.ts';
|
||||||
|
import { LiveCode } from '@/module/livecode/index.ts';
|
||||||
const localProxy = new LocalProxy({});
|
const localProxy = new LocalProxy({});
|
||||||
localProxy.initFromAssistantConfig(assistantConfig);
|
localProxy.initFromAssistantConfig(assistantConfig);
|
||||||
|
|
||||||
@@ -234,6 +235,27 @@ export const proxyWs = () => {
|
|||||||
}
|
}
|
||||||
return proxyApi.map(createProxyInfo);
|
return proxyApi.map(createProxyInfo);
|
||||||
};
|
};
|
||||||
|
const liveCode = new LiveCode(app)
|
||||||
|
export const proxyLivecodeWs = () => {
|
||||||
|
const livecode = assistantConfig.getCacheAssistantConfig()?.router?.livecode ?? true;
|
||||||
|
if (!livecode) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const fun: WebSocketListenerFun = async (req, res) => {
|
||||||
|
const { ws, emitter, id, data } = req;
|
||||||
|
// if (!id) {
|
||||||
|
// ws.send(JSON.stringify({ type: 'error', message: 'not found id' }));
|
||||||
|
// ws.close();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
liveCode.conn(req)
|
||||||
|
}
|
||||||
|
return [{
|
||||||
|
path: '/livecode/ws',
|
||||||
|
io: true,
|
||||||
|
func: fun
|
||||||
|
}]
|
||||||
|
}
|
||||||
export const createProxyInfo = (proxyApiItem: ProxyInfo) => {
|
export const createProxyInfo = (proxyApiItem: ProxyInfo) => {
|
||||||
const func: WebSocketListenerFun = async (req, res) => {
|
const func: WebSocketListenerFun = async (req, res) => {
|
||||||
const { ws, emitter, id, data } = req;
|
const { ws, emitter, id, data } = req;
|
||||||
|
|||||||
215
assistant/src/test/live-app-origin.ts
Normal file
215
assistant/src/test/live-app-origin.ts
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
import { App } from '@kevisual/router'
|
||||||
|
import { WebSocket } from 'ws'
|
||||||
|
import net from 'net';
|
||||||
|
|
||||||
|
type ReconnectConfig = {
|
||||||
|
maxRetries?: number; // 最大重试次数,默认无限
|
||||||
|
retryDelay?: number; // 重试延迟(ms),默认1000
|
||||||
|
maxDelay?: number; // 最大延迟(ms),默认30000
|
||||||
|
backoffMultiplier?: number; // 退避倍数,默认2
|
||||||
|
};
|
||||||
|
|
||||||
|
class ReconnectingWebSocket {
|
||||||
|
private ws: WebSocket | null = null;
|
||||||
|
private url: string;
|
||||||
|
private config: Required<ReconnectConfig>;
|
||||||
|
private retryCount: number = 0;
|
||||||
|
private reconnectTimer: NodeJS.Timeout | null = null;
|
||||||
|
private isManualClose: boolean = false;
|
||||||
|
private messageHandlers: Array<(data: any) => void> = [];
|
||||||
|
private openHandlers: Array<() => void> = [];
|
||||||
|
private closeHandlers: Array<(code: number, reason: Buffer) => void> = [];
|
||||||
|
private errorHandlers: Array<(error: Error) => void> = [];
|
||||||
|
|
||||||
|
constructor(url: string, config: ReconnectConfig = {}) {
|
||||||
|
this.url = url;
|
||||||
|
this.config = {
|
||||||
|
maxRetries: config.maxRetries ?? Infinity,
|
||||||
|
retryDelay: config.retryDelay ?? 1000,
|
||||||
|
maxDelay: config.maxDelay ?? 30000,
|
||||||
|
backoffMultiplier: config.backoffMultiplier ?? 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(): void {
|
||||||
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`正在连接到 ${this.url}...`);
|
||||||
|
this.ws = new WebSocket(this.url);
|
||||||
|
|
||||||
|
this.ws.on('open', () => {
|
||||||
|
console.log('WebSocket 连接已打开');
|
||||||
|
this.retryCount = 0;
|
||||||
|
this.openHandlers.forEach(handler => handler());
|
||||||
|
this.send({ type: 'heartbeat', timestamp: new Date().toISOString() });
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ws.on('message', (data: any) => {
|
||||||
|
this.messageHandlers.forEach(handler => {
|
||||||
|
try {
|
||||||
|
const message = JSON.parse(data.toString());
|
||||||
|
handler(message);
|
||||||
|
} catch {
|
||||||
|
handler(data.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ws.on('close', (code: number, reason: Buffer) => {
|
||||||
|
console.log(`WebSocket 连接已关闭: code=${code}, reason=${reason.toString()}`);
|
||||||
|
this.closeHandlers.forEach(handler => handler(code, reason));
|
||||||
|
|
||||||
|
if (!this.isManualClose) {
|
||||||
|
this.scheduleReconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ws.on('error', (error: Error) => {
|
||||||
|
console.error('WebSocket 错误:', error.message);
|
||||||
|
this.errorHandlers.forEach(handler => handler(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private scheduleReconnect(): void {
|
||||||
|
if (this.reconnectTimer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.retryCount >= this.config.maxRetries) {
|
||||||
|
console.error(`已达到最大重试次数 (${this.config.maxRetries}),停止重连`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算延迟(指数退避)
|
||||||
|
const delay = Math.min(
|
||||||
|
this.config.retryDelay * Math.pow(this.config.backoffMultiplier, this.retryCount),
|
||||||
|
this.config.maxDelay
|
||||||
|
);
|
||||||
|
|
||||||
|
this.retryCount++;
|
||||||
|
console.log(`将在 ${delay}ms 后进行第 ${this.retryCount} 次重连尝试...`);
|
||||||
|
|
||||||
|
this.reconnectTimer = setTimeout(() => {
|
||||||
|
this.reconnectTimer = null;
|
||||||
|
this.connect();
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data: any): boolean {
|
||||||
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||||
|
this.ws.send(JSON.stringify(data));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
console.warn('WebSocket 未连接,无法发送消息');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage(handler: (data: any) => void): void {
|
||||||
|
this.messageHandlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpen(handler: () => void): void {
|
||||||
|
this.openHandlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose(handler: (code: number, reason: Buffer) => void): void {
|
||||||
|
this.closeHandlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
onError(handler: (error: Error) => void): void {
|
||||||
|
this.errorHandlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
this.isManualClose = true;
|
||||||
|
if (this.reconnectTimer) {
|
||||||
|
clearTimeout(this.reconnectTimer);
|
||||||
|
this.reconnectTimer = null;
|
||||||
|
}
|
||||||
|
if (this.ws) {
|
||||||
|
this.ws.close();
|
||||||
|
this.ws = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getReadyState(): number {
|
||||||
|
return this.ws?.readyState ?? WebSocket.CLOSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRetryCount(): number {
|
||||||
|
return this.retryCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = new App();
|
||||||
|
|
||||||
|
app.route({
|
||||||
|
path: 'livecode-status',
|
||||||
|
description: 'LiveCode 状态路由',
|
||||||
|
metadata: {
|
||||||
|
tags: ['livecode', 'status'],
|
||||||
|
},
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
ctx.body = {
|
||||||
|
status: 'LiveCode 模块运行正常',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
}).addTo(app)
|
||||||
|
|
||||||
|
app.createRouteList();
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
|
// 创建支持断开重连的 WebSocket 客户端
|
||||||
|
const ws = new ReconnectingWebSocket('ws://localhost:51516/livecode/ws?id=test-live-app', {
|
||||||
|
maxRetries: Infinity, // 无限重试
|
||||||
|
retryDelay: 1000, // 初始重试延迟 1 秒
|
||||||
|
maxDelay: 30000, // 最大延迟 30 秒
|
||||||
|
backoffMultiplier: 2, // 指数退避倍数
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.onMessage(async (message) => {
|
||||||
|
console.log('收到消息:', message);
|
||||||
|
if (message.type === 'router' && message.id) {
|
||||||
|
console.log('收到路由响应:', message);
|
||||||
|
const data = message?.data;
|
||||||
|
if (!data) {
|
||||||
|
ws.send({
|
||||||
|
type: 'router',
|
||||||
|
id: message.id,
|
||||||
|
data: { code: 500, message: 'No data received' }
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await app.run(message.data);
|
||||||
|
console.log('路由处理结果:', res);
|
||||||
|
ws.send({
|
||||||
|
type: 'router',
|
||||||
|
id: message.id,
|
||||||
|
data: res
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.onOpen(() => {
|
||||||
|
console.log('连接已建立,可以开始通信');
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.onError((error) => {
|
||||||
|
console.error('连接错误:', error.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.onClose((code, reason) => {
|
||||||
|
console.log(`连接关闭: ${code} - ${reason.toString()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 启动连接
|
||||||
|
ws.connect();
|
||||||
|
|
||||||
|
|
||||||
|
net.createServer((socket) => {
|
||||||
|
console.log('TCP 客户端已连接');
|
||||||
|
}).listen(61616, () => {
|
||||||
|
console.log('TCP 服务器正在监听端口 61616');
|
||||||
|
});
|
||||||
75
assistant/src/test/live-app.ts
Normal file
75
assistant/src/test/live-app.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
|
||||||
|
import { App } from '@kevisual/router'
|
||||||
|
import { WebSocket } from 'ws'
|
||||||
|
import { ReconnectingWebSocket, handleCallApp } from '@kevisual/router/ws'
|
||||||
|
import net from 'net';
|
||||||
|
|
||||||
|
const app = new App();
|
||||||
|
|
||||||
|
app.route({
|
||||||
|
path: 'livecode-status',
|
||||||
|
description: 'LiveCode 状态路由',
|
||||||
|
metadata: {
|
||||||
|
tags: ['livecode', 'status'],
|
||||||
|
},
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
ctx.body = {
|
||||||
|
status: 'LiveCode 模块运行正常',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
}).addTo(app)
|
||||||
|
|
||||||
|
app.createRouteList();
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
|
// 创建支持断开重连的 WebSocket 客户端
|
||||||
|
const ws = new ReconnectingWebSocket('ws://localhost:51516/livecode/ws?id=test-live-app', {
|
||||||
|
maxRetries: Infinity, // 无限重试
|
||||||
|
retryDelay: 1000, // 初始重试延迟 1 秒
|
||||||
|
maxDelay: 30000, // 最大延迟 30 秒
|
||||||
|
backoffMultiplier: 2, // 指数退避倍数
|
||||||
|
});
|
||||||
|
ws.onMessage(async (message) => {
|
||||||
|
console.log('收到消息:', message);
|
||||||
|
if (message.type === 'router' && message.id) {
|
||||||
|
console.log('收到路由响应:', message);
|
||||||
|
const data = message?.data;
|
||||||
|
if (!data) {
|
||||||
|
ws.send({
|
||||||
|
type: 'router',
|
||||||
|
id: message.id,
|
||||||
|
data: { code: 500, message: 'No data received' }
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await app.run(message.data);
|
||||||
|
console.log('路由处理结果:', res);
|
||||||
|
ws.send({
|
||||||
|
type: 'router',
|
||||||
|
id: message.id,
|
||||||
|
data: res
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.onOpen(() => {
|
||||||
|
console.log('连接已建立,可以开始通信');
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.onError((error) => {
|
||||||
|
console.error('连接错误:', error.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.onClose((code, reason) => {
|
||||||
|
console.log(`连接关闭: ${code} - ${reason.toString()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 启动连接
|
||||||
|
ws.connect();
|
||||||
|
|
||||||
|
|
||||||
|
net.createServer((socket) => {
|
||||||
|
console.log('TCP 客户端已连接');
|
||||||
|
}).listen(61616, () => {
|
||||||
|
console.log('TCP 服务器正在监听端口 61616');
|
||||||
|
});
|
||||||
286
pnpm-lock.yaml
generated
286
pnpm-lock.yaml
generated
@@ -129,21 +129,18 @@ importers:
|
|||||||
'@aws-sdk/client-s3':
|
'@aws-sdk/client-s3':
|
||||||
specifier: ^3.980.0
|
specifier: ^3.980.0
|
||||||
version: 3.980.0
|
version: 3.980.0
|
||||||
'@kevisual/ha-api':
|
|
||||||
specifier: ^0.0.8
|
|
||||||
version: 0.0.8
|
|
||||||
'@kevisual/js-filter':
|
'@kevisual/js-filter':
|
||||||
specifier: ^0.0.5
|
specifier: ^0.0.5
|
||||||
version: 0.0.5
|
version: 0.0.5
|
||||||
'@kevisual/oss':
|
'@kevisual/oss':
|
||||||
specifier: ^0.0.18
|
specifier: ^0.0.19
|
||||||
version: 0.0.18
|
version: 0.0.19
|
||||||
'@kevisual/video-tools':
|
'@kevisual/video-tools':
|
||||||
specifier: ^0.0.13
|
specifier: ^0.0.13
|
||||||
version: 0.0.13(dotenv@17.2.3)(supports-color@10.2.2)
|
version: 0.0.13(dotenv@17.2.3)(supports-color@10.2.2)
|
||||||
'@opencode-ai/sdk':
|
'@opencode-ai/sdk':
|
||||||
specifier: ^1.1.47
|
specifier: ^1.1.48
|
||||||
version: 1.1.47
|
version: 1.1.48
|
||||||
es-toolkit:
|
es-toolkit:
|
||||||
specifier: ^1.44.0
|
specifier: ^1.44.0
|
||||||
version: 1.44.0
|
version: 1.44.0
|
||||||
@@ -168,16 +165,13 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
'@inquirer/prompts':
|
'@inquirer/prompts':
|
||||||
specifier: ^8.2.0
|
specifier: ^8.2.0
|
||||||
version: 8.2.0(@types/node@25.1.0)
|
version: 8.2.0(@types/node@25.2.0)
|
||||||
'@kevisual/ai':
|
'@kevisual/ai':
|
||||||
specifier: ^0.0.24
|
specifier: ^0.0.24
|
||||||
version: 0.0.24
|
version: 0.0.24
|
||||||
'@kevisual/api':
|
'@kevisual/api':
|
||||||
specifier: ^0.0.35
|
specifier: ^0.0.42
|
||||||
version: 0.0.35
|
version: 0.0.42
|
||||||
'@kevisual/cnb':
|
|
||||||
specifier: ^0.0.13
|
|
||||||
version: 0.0.13(dotenv@17.2.3)(idb-keyval@6.2.2)(typescript@5.8.2)
|
|
||||||
'@kevisual/load':
|
'@kevisual/load':
|
||||||
specifier: ^0.0.6
|
specifier: ^0.0.6
|
||||||
version: 0.0.6
|
version: 0.0.6
|
||||||
@@ -188,29 +182,29 @@ importers:
|
|||||||
specifier: ^0.0.4
|
specifier: ^0.0.4
|
||||||
version: 0.0.4
|
version: 0.0.4
|
||||||
'@kevisual/query':
|
'@kevisual/query':
|
||||||
specifier: 0.0.38
|
specifier: 0.0.39
|
||||||
version: 0.0.38
|
version: 0.0.39
|
||||||
'@kevisual/query-login':
|
'@kevisual/query-login':
|
||||||
specifier: 0.0.7
|
specifier: 0.0.7
|
||||||
version: 0.0.7(@kevisual/query@0.0.38)
|
version: 0.0.7(@kevisual/query@0.0.39)
|
||||||
'@kevisual/router':
|
'@kevisual/router':
|
||||||
specifier: ^0.0.64
|
specifier: ^0.0.67
|
||||||
version: 0.0.64(typescript@5.8.2)
|
version: 0.0.67
|
||||||
'@kevisual/types':
|
'@kevisual/types':
|
||||||
specifier: ^0.0.12
|
specifier: ^0.0.12
|
||||||
version: 0.0.12
|
version: 0.0.12
|
||||||
'@kevisual/use-config':
|
'@kevisual/use-config':
|
||||||
specifier: ^1.0.28
|
specifier: ^1.0.30
|
||||||
version: 1.0.28(dotenv@17.2.3)
|
version: 1.0.30(dotenv@17.2.3)
|
||||||
'@opencode-ai/plugin':
|
'@opencode-ai/plugin':
|
||||||
specifier: ^1.1.47
|
specifier: ^1.1.48
|
||||||
version: 1.1.47
|
version: 1.1.48
|
||||||
'@types/bun':
|
'@types/bun':
|
||||||
specifier: ^1.3.8
|
specifier: ^1.3.8
|
||||||
version: 1.3.8
|
version: 1.3.8
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.1.0
|
specifier: ^25.2.0
|
||||||
version: 25.1.0
|
version: 25.2.0
|
||||||
'@types/send':
|
'@types/send':
|
||||||
specifier: ^1.2.1
|
specifier: ^1.2.1
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
@@ -1306,9 +1300,6 @@ packages:
|
|||||||
'@kevisual/api@0.0.28':
|
'@kevisual/api@0.0.28':
|
||||||
resolution: {integrity: sha512-WQluRlu2qGM1qktIhPLODie8x382a6jEMfFOcay/rnkCgXK0BRpnqOKwlX7IMLdMqka7GY/BD69kSMnK1Exf5g==}
|
resolution: {integrity: sha512-WQluRlu2qGM1qktIhPLODie8x382a6jEMfFOcay/rnkCgXK0BRpnqOKwlX7IMLdMqka7GY/BD69kSMnK1Exf5g==}
|
||||||
|
|
||||||
'@kevisual/api@0.0.35':
|
|
||||||
resolution: {integrity: sha512-NbaOasecbG+O9Ju2/LWC2eWeqcPc5yZYXXyT4vHpU2W5SoPzBf7H3W7+i3py/JcEXF6adcHZVofftCYpecmGMQ==}
|
|
||||||
|
|
||||||
'@kevisual/api@0.0.42':
|
'@kevisual/api@0.0.42':
|
||||||
resolution: {integrity: sha512-Bn5G+ZzGEPoJdvd5U3xWHGY0oidQj23gt1YAWvTqjm0frDJfJ4Q2WT9Xjb1ZdJ/YBcfaNe9yEoMCpFNdUls/mw==}
|
resolution: {integrity: sha512-Bn5G+ZzGEPoJdvd5U3xWHGY0oidQj23gt1YAWvTqjm0frDJfJ4Q2WT9Xjb1ZdJ/YBcfaNe9yEoMCpFNdUls/mw==}
|
||||||
|
|
||||||
@@ -1324,12 +1315,6 @@ packages:
|
|||||||
'@kevisual/cache@0.0.3':
|
'@kevisual/cache@0.0.3':
|
||||||
resolution: {integrity: sha512-BWEck69KYL96/ywjYVkML974RHjDJTj2ITQND1zFPR+hlBV1H1p55QZgSYRJCObg3EAV1S9Zic/fR2T4pfe8yg==}
|
resolution: {integrity: sha512-BWEck69KYL96/ywjYVkML974RHjDJTj2ITQND1zFPR+hlBV1H1p55QZgSYRJCObg3EAV1S9Zic/fR2T4pfe8yg==}
|
||||||
|
|
||||||
'@kevisual/cache@0.0.5':
|
|
||||||
resolution: {integrity: sha512-fgtUYGUUq/DY0KFV4CkWszNqvQUaA8XvMTUjoR9ZXRpau5IIDolD/Wen2TFsZ7G3Rfy+lef5dnaiZVDkZwdVKg==}
|
|
||||||
|
|
||||||
'@kevisual/cnb@0.0.13':
|
|
||||||
resolution: {integrity: sha512-n98lwnlVHz8YqceR/fcorYUaBzcvwwqehyOAGVrqCVwVLsltYmYuHUhzVy1bK3NJ6zwhVdoDrkq7+bv3ZqDT3g==}
|
|
||||||
|
|
||||||
'@kevisual/context@0.0.4':
|
'@kevisual/context@0.0.4':
|
||||||
resolution: {integrity: sha512-HJeLeZQLU+7tCluSfOyvkgKLs0HjCZrdJlZgEgKRSa8XTwZfMAUt6J7qZTbrZAHBlPtX68EPu/PI8JMCeu3WAQ==}
|
resolution: {integrity: sha512-HJeLeZQLU+7tCluSfOyvkgKLs0HjCZrdJlZgEgKRSa8XTwZfMAUt6J7qZTbrZAHBlPtX68EPu/PI8JMCeu3WAQ==}
|
||||||
|
|
||||||
@@ -1337,9 +1322,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-4T/m2LqhtwWEW+lWmg7jLxKFW7VtIAftsWFDDZvh10bZunqFf8iXxChHcVSQWikghJb4cq1IkWzPkvc2l+Asdw==}
|
resolution: {integrity: sha512-4T/m2LqhtwWEW+lWmg7jLxKFW7VtIAftsWFDDZvh10bZunqFf8iXxChHcVSQWikghJb4cq1IkWzPkvc2l+Asdw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@kevisual/ha-api@0.0.8':
|
|
||||||
resolution: {integrity: sha512-RVBrHOXx471lwVmoP5lnAw4XAwyBN3BsJvwaJKLTpAaefvZ2slZEuJZY7lAX7OVEAZJLrfjH+QeErLQ+EvpdVA==}
|
|
||||||
|
|
||||||
'@kevisual/js-filter@0.0.5':
|
'@kevisual/js-filter@0.0.5':
|
||||||
resolution: {integrity: sha512-+S+Sf3K/aP6XtZI2s7TgKOr35UuvUvtpJ9YDW30a+mY0/N8gRuzyKhieBzQN7Ykayzz70uoMavBXut2rUlLgzw==}
|
resolution: {integrity: sha512-+S+Sf3K/aP6XtZI2s7TgKOr35UuvUvtpJ9YDW30a+mY0/N8gRuzyKhieBzQN7Ykayzz70uoMavBXut2rUlLgzw==}
|
||||||
|
|
||||||
@@ -1355,8 +1337,8 @@ packages:
|
|||||||
'@kevisual/logger@0.0.4':
|
'@kevisual/logger@0.0.4':
|
||||||
resolution: {integrity: sha512-+fpr92eokSxoGOW1SIRl/27lPuO+zyY+feR5o2Q4YCNlAdt2x64NwC/w8r/3NEC5QenLgd4K0azyKTI2mHbARw==}
|
resolution: {integrity: sha512-+fpr92eokSxoGOW1SIRl/27lPuO+zyY+feR5o2Q4YCNlAdt2x64NwC/w8r/3NEC5QenLgd4K0azyKTI2mHbARw==}
|
||||||
|
|
||||||
'@kevisual/oss@0.0.18':
|
'@kevisual/oss@0.0.19':
|
||||||
resolution: {integrity: sha512-vTdXe41inq4oc+bfYIR3xMDm8GZyOAaWq3DBh+Eur9uNOJcIUdgZBVPOm2uSigmjl3PvqekUw8bE/vbWWJAY7w==}
|
resolution: {integrity: sha512-4Y5krJTqLQOsEwJf7K7a/88t9YHm8PQNuZ5SJDTMopYDOflJlwVjvqiu0lapQ0UrpI+wG6FdfmdmnWpXdQsa1Q==}
|
||||||
|
|
||||||
'@kevisual/permission@0.0.3':
|
'@kevisual/permission@0.0.3':
|
||||||
resolution: {integrity: sha512-8JsA/5O5Ax/z+M+MYpFYdlioHE6jNmWMuFSokBWYs9CCAHNiSKMR01YLkoVDoPvncfH/Y8F5K/IEXRCbptuMNA==}
|
resolution: {integrity: sha512-8JsA/5O5Ax/z+M+MYpFYdlioHE6jNmWMuFSokBWYs9CCAHNiSKMR01YLkoVDoPvncfH/Y8F5K/IEXRCbptuMNA==}
|
||||||
@@ -1390,17 +1372,12 @@ packages:
|
|||||||
'@kevisual/router@0.0.51':
|
'@kevisual/router@0.0.51':
|
||||||
resolution: {integrity: sha512-i9qYBeS/um78oC912oWJD3iElB+5NTKyTrz1Hzf4DckiUFnjLL81UPwjIh5I2l9+ul0IZ/Pxx+sFSF99fJkzKg==}
|
resolution: {integrity: sha512-i9qYBeS/um78oC912oWJD3iElB+5NTKyTrz1Hzf4DckiUFnjLL81UPwjIh5I2l9+ul0IZ/Pxx+sFSF99fJkzKg==}
|
||||||
|
|
||||||
'@kevisual/router@0.0.64':
|
'@kevisual/router@0.0.67':
|
||||||
resolution: {integrity: sha512-EYz1MZxrltgySUL0Y+/MtZf2FEmqC5U8GmFAqvHNjgtS5FJdHpxRjo6zab4+0wSUlVyCxCpZXFY5vHB/g+nQBw==}
|
resolution: {integrity: sha512-SKQDc9RUSUqpcVA4Y05rl525zmHcyl4JlHdFyBhatNRMBQdKCVd8rBAojnyz4gNmUU9bY+gxM87f30dHsQkRAw==}
|
||||||
|
|
||||||
'@kevisual/types@0.0.12':
|
'@kevisual/types@0.0.12':
|
||||||
resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==}
|
resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==}
|
||||||
|
|
||||||
'@kevisual/use-config@1.0.28':
|
|
||||||
resolution: {integrity: sha512-ngF+LDbjxpXWrZNmnShIKF/jPpAa+ezV+DcgoZIIzHlRnIjE+rr9sLkN/B7WJbiH9C/j1tQXOILY8ujBqILrow==}
|
|
||||||
peerDependencies:
|
|
||||||
dotenv: ^17
|
|
||||||
|
|
||||||
'@kevisual/use-config@1.0.30':
|
'@kevisual/use-config@1.0.30':
|
||||||
resolution: {integrity: sha512-kPdna0FW/X7D600aMdiZ5UTjbCo6d8d4jjauSc8RMmBwUU6WliFDSPUNKVpzm2BsDX5Nth1IXFPYMqH+wxqAmw==}
|
resolution: {integrity: sha512-kPdna0FW/X7D600aMdiZ5UTjbCo6d8d4jjauSc8RMmBwUU6WliFDSPUNKVpzm2BsDX5Nth1IXFPYMqH+wxqAmw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1416,10 +1393,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-jlFxSlXUEz93cFW+UYT5BXv/rFVgiMQnIfqRYZ0gj1hSP8PMGRqMqUoHSLfKvfRRS4jseLSvTTeEKSQpZJtURg==}
|
resolution: {integrity: sha512-jlFxSlXUEz93cFW+UYT5BXv/rFVgiMQnIfqRYZ0gj1hSP8PMGRqMqUoHSLfKvfRRS4jseLSvTTeEKSQpZJtURg==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
'@kevisual/ws@8.19.0':
|
|
||||||
resolution: {integrity: sha512-jLsL80wBBKkrJZrfk3SQpJ9JA/zREdlUROj7eCkmzqduAWKSI0wVcXuCKf+mLFCHB0Q0Tkh2rgzjSlurt3JQgw==}
|
|
||||||
engines: {node: '>=10.0.0'}
|
|
||||||
|
|
||||||
'@lezer/common@1.4.0':
|
'@lezer/common@1.4.0':
|
||||||
resolution: {integrity: sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==}
|
resolution: {integrity: sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==}
|
||||||
|
|
||||||
@@ -1466,11 +1439,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
'@opencode-ai/plugin@1.1.47':
|
'@opencode-ai/plugin@1.1.48':
|
||||||
resolution: {integrity: sha512-gNMPz72altieDfLhUw3VAT1xbduKi3w3wZ57GLeS7qU9W474HdvdIiLBnt2Xq3U7Ko0/0tvK3nzCker6IIDqmQ==}
|
resolution: {integrity: sha512-KkaSMevXmz7tOwYDMJeWiXE5N8LmRP18qWI5Xhv3+c+FdGPL+l1hQrjSgyv3k7Co7qpCyW3kAUESBB7BzIOl2w==}
|
||||||
|
|
||||||
'@opencode-ai/sdk@1.1.47':
|
|
||||||
resolution: {integrity: sha512-s3PBHwk1sP6Zt/lJxIWSBWZ1TnrI1nFxSP97LCODUytouAQgbygZ1oDH7O2sGMBEuGdA8B1nNSPla0aRSN3IpA==}
|
|
||||||
|
|
||||||
'@opencode-ai/sdk@1.1.48':
|
'@opencode-ai/sdk@1.1.48':
|
||||||
resolution: {integrity: sha512-j5/79X45fUPWVD2Ffm/qvwLclDCdPeV+TYMDrm9to0p4pmzhmeKevCsyiRdLg0o0HE3AFRUnOo2rdO9NetN79A==}
|
resolution: {integrity: sha512-j5/79X45fUPWVD2Ffm/qvwLclDCdPeV+TYMDrm9to0p4pmzhmeKevCsyiRdLg0o0HE3AFRUnOo2rdO9NetN79A==}
|
||||||
@@ -2402,9 +2372,6 @@ packages:
|
|||||||
'@types/node@17.0.45':
|
'@types/node@17.0.45':
|
||||||
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
|
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
|
||||||
|
|
||||||
'@types/node@25.1.0':
|
|
||||||
resolution: {integrity: sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==}
|
|
||||||
|
|
||||||
'@types/node@25.2.0':
|
'@types/node@25.2.0':
|
||||||
resolution: {integrity: sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==}
|
resolution: {integrity: sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==}
|
||||||
|
|
||||||
@@ -3427,10 +3394,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
|
resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
hono@4.11.7:
|
|
||||||
resolution: {integrity: sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==}
|
|
||||||
engines: {node: '>=16.9.0'}
|
|
||||||
|
|
||||||
hookable@5.5.3:
|
hookable@5.5.3:
|
||||||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||||
|
|
||||||
@@ -6314,15 +6277,6 @@ snapshots:
|
|||||||
|
|
||||||
'@inquirer/ansi@2.0.3': {}
|
'@inquirer/ansi@2.0.3': {}
|
||||||
|
|
||||||
'@inquirer/checkbox@5.0.4(@types/node@25.1.0)':
|
|
||||||
dependencies:
|
|
||||||
'@inquirer/ansi': 2.0.3
|
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.1.0)
|
|
||||||
'@inquirer/figures': 2.0.3
|
|
||||||
'@inquirer/type': 4.0.3(@types/node@25.1.0)
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.1.0
|
|
||||||
|
|
||||||
'@inquirer/checkbox@5.0.4(@types/node@25.2.0)':
|
'@inquirer/checkbox@5.0.4(@types/node@25.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@inquirer/ansi': 2.0.3
|
'@inquirer/ansi': 2.0.3
|
||||||
@@ -6332,13 +6286,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.2.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
'@inquirer/confirm@6.0.4(@types/node@25.1.0)':
|
|
||||||
dependencies:
|
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.1.0)
|
|
||||||
'@inquirer/type': 4.0.3(@types/node@25.1.0)
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.1.0
|
|
||||||
|
|
||||||
'@inquirer/confirm@6.0.4(@types/node@25.2.0)':
|
'@inquirer/confirm@6.0.4(@types/node@25.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.2.0)
|
'@inquirer/core': 11.1.1(@types/node@25.2.0)
|
||||||
@@ -6346,18 +6293,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.2.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
'@inquirer/core@11.1.1(@types/node@25.1.0)':
|
|
||||||
dependencies:
|
|
||||||
'@inquirer/ansi': 2.0.3
|
|
||||||
'@inquirer/figures': 2.0.3
|
|
||||||
'@inquirer/type': 4.0.3(@types/node@25.1.0)
|
|
||||||
cli-width: 4.1.0
|
|
||||||
mute-stream: 3.0.0
|
|
||||||
signal-exit: 4.1.0
|
|
||||||
wrap-ansi: 9.0.2
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.1.0
|
|
||||||
|
|
||||||
'@inquirer/core@11.1.1(@types/node@25.2.0)':
|
'@inquirer/core@11.1.1(@types/node@25.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@inquirer/ansi': 2.0.3
|
'@inquirer/ansi': 2.0.3
|
||||||
@@ -6370,14 +6305,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.2.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
'@inquirer/editor@5.0.4(@types/node@25.1.0)':
|
|
||||||
dependencies:
|
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.1.0)
|
|
||||||
'@inquirer/external-editor': 2.0.3(@types/node@25.1.0)
|
|
||||||
'@inquirer/type': 4.0.3(@types/node@25.1.0)
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.1.0
|
|
||||||
|
|
||||||
'@inquirer/editor@5.0.4(@types/node@25.2.0)':
|
'@inquirer/editor@5.0.4(@types/node@25.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.2.0)
|
'@inquirer/core': 11.1.1(@types/node@25.2.0)
|
||||||
@@ -6386,13 +6313,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.2.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
'@inquirer/expand@5.0.4(@types/node@25.1.0)':
|
|
||||||
dependencies:
|
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.1.0)
|
|
||||||
'@inquirer/type': 4.0.3(@types/node@25.1.0)
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.1.0
|
|
||||||
|
|
||||||
'@inquirer/expand@5.0.4(@types/node@25.2.0)':
|
'@inquirer/expand@5.0.4(@types/node@25.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.2.0)
|
'@inquirer/core': 11.1.1(@types/node@25.2.0)
|
||||||
@@ -6400,13 +6320,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.2.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
'@inquirer/external-editor@2.0.3(@types/node@25.1.0)':
|
|
||||||
dependencies:
|
|
||||||
chardet: 2.1.1
|
|
||||||
iconv-lite: 0.7.2
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.1.0
|
|
||||||
|
|
||||||
'@inquirer/external-editor@2.0.3(@types/node@25.2.0)':
|
'@inquirer/external-editor@2.0.3(@types/node@25.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
chardet: 2.1.1
|
chardet: 2.1.1
|
||||||
@@ -6416,13 +6329,6 @@ snapshots:
|
|||||||
|
|
||||||
'@inquirer/figures@2.0.3': {}
|
'@inquirer/figures@2.0.3': {}
|
||||||
|
|
||||||
'@inquirer/input@5.0.4(@types/node@25.1.0)':
|
|
||||||
dependencies:
|
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.1.0)
|
|
||||||
'@inquirer/type': 4.0.3(@types/node@25.1.0)
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.1.0
|
|
||||||
|
|
||||||
'@inquirer/input@5.0.4(@types/node@25.2.0)':
|
'@inquirer/input@5.0.4(@types/node@25.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.2.0)
|
'@inquirer/core': 11.1.1(@types/node@25.2.0)
|
||||||
@@ -6430,13 +6336,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.2.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
'@inquirer/number@4.0.4(@types/node@25.1.0)':
|
|
||||||
dependencies:
|
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.1.0)
|
|
||||||
'@inquirer/type': 4.0.3(@types/node@25.1.0)
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.1.0
|
|
||||||
|
|
||||||
'@inquirer/number@4.0.4(@types/node@25.2.0)':
|
'@inquirer/number@4.0.4(@types/node@25.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.2.0)
|
'@inquirer/core': 11.1.1(@types/node@25.2.0)
|
||||||
@@ -6444,14 +6343,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.2.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
'@inquirer/password@5.0.4(@types/node@25.1.0)':
|
|
||||||
dependencies:
|
|
||||||
'@inquirer/ansi': 2.0.3
|
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.1.0)
|
|
||||||
'@inquirer/type': 4.0.3(@types/node@25.1.0)
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.1.0
|
|
||||||
|
|
||||||
'@inquirer/password@5.0.4(@types/node@25.2.0)':
|
'@inquirer/password@5.0.4(@types/node@25.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@inquirer/ansi': 2.0.3
|
'@inquirer/ansi': 2.0.3
|
||||||
@@ -6460,21 +6351,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.2.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
'@inquirer/prompts@8.2.0(@types/node@25.1.0)':
|
|
||||||
dependencies:
|
|
||||||
'@inquirer/checkbox': 5.0.4(@types/node@25.1.0)
|
|
||||||
'@inquirer/confirm': 6.0.4(@types/node@25.1.0)
|
|
||||||
'@inquirer/editor': 5.0.4(@types/node@25.1.0)
|
|
||||||
'@inquirer/expand': 5.0.4(@types/node@25.1.0)
|
|
||||||
'@inquirer/input': 5.0.4(@types/node@25.1.0)
|
|
||||||
'@inquirer/number': 4.0.4(@types/node@25.1.0)
|
|
||||||
'@inquirer/password': 5.0.4(@types/node@25.1.0)
|
|
||||||
'@inquirer/rawlist': 5.2.0(@types/node@25.1.0)
|
|
||||||
'@inquirer/search': 4.1.0(@types/node@25.1.0)
|
|
||||||
'@inquirer/select': 5.0.4(@types/node@25.1.0)
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.1.0
|
|
||||||
|
|
||||||
'@inquirer/prompts@8.2.0(@types/node@25.2.0)':
|
'@inquirer/prompts@8.2.0(@types/node@25.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@inquirer/checkbox': 5.0.4(@types/node@25.2.0)
|
'@inquirer/checkbox': 5.0.4(@types/node@25.2.0)
|
||||||
@@ -6490,13 +6366,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.2.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
'@inquirer/rawlist@5.2.0(@types/node@25.1.0)':
|
|
||||||
dependencies:
|
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.1.0)
|
|
||||||
'@inquirer/type': 4.0.3(@types/node@25.1.0)
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.1.0
|
|
||||||
|
|
||||||
'@inquirer/rawlist@5.2.0(@types/node@25.2.0)':
|
'@inquirer/rawlist@5.2.0(@types/node@25.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.2.0)
|
'@inquirer/core': 11.1.1(@types/node@25.2.0)
|
||||||
@@ -6504,14 +6373,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.2.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
'@inquirer/search@4.1.0(@types/node@25.1.0)':
|
|
||||||
dependencies:
|
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.1.0)
|
|
||||||
'@inquirer/figures': 2.0.3
|
|
||||||
'@inquirer/type': 4.0.3(@types/node@25.1.0)
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.1.0
|
|
||||||
|
|
||||||
'@inquirer/search@4.1.0(@types/node@25.2.0)':
|
'@inquirer/search@4.1.0(@types/node@25.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.2.0)
|
'@inquirer/core': 11.1.1(@types/node@25.2.0)
|
||||||
@@ -6520,15 +6381,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.2.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
'@inquirer/select@5.0.4(@types/node@25.1.0)':
|
|
||||||
dependencies:
|
|
||||||
'@inquirer/ansi': 2.0.3
|
|
||||||
'@inquirer/core': 11.1.1(@types/node@25.1.0)
|
|
||||||
'@inquirer/figures': 2.0.3
|
|
||||||
'@inquirer/type': 4.0.3(@types/node@25.1.0)
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.1.0
|
|
||||||
|
|
||||||
'@inquirer/select@5.0.4(@types/node@25.2.0)':
|
'@inquirer/select@5.0.4(@types/node@25.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@inquirer/ansi': 2.0.3
|
'@inquirer/ansi': 2.0.3
|
||||||
@@ -6538,10 +6390,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.2.0
|
'@types/node': 25.2.0
|
||||||
|
|
||||||
'@inquirer/type@4.0.3(@types/node@25.1.0)':
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/node': 25.1.0
|
|
||||||
|
|
||||||
'@inquirer/type@4.0.3(@types/node@25.2.0)':
|
'@inquirer/type@4.0.3(@types/node@25.2.0)':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.2.0
|
'@types/node': 25.2.0
|
||||||
@@ -6592,16 +6440,6 @@ snapshots:
|
|||||||
fuse.js: 7.1.0
|
fuse.js: 7.1.0
|
||||||
nanoid: 5.1.6
|
nanoid: 5.1.6
|
||||||
|
|
||||||
'@kevisual/api@0.0.35':
|
|
||||||
dependencies:
|
|
||||||
'@kevisual/js-filter': 0.0.5
|
|
||||||
'@kevisual/load': 0.0.6
|
|
||||||
es-toolkit: 1.44.0
|
|
||||||
eventemitter3: 5.0.4
|
|
||||||
fuse.js: 7.1.0
|
|
||||||
nanoid: 5.1.6
|
|
||||||
path-browserify-esm: 1.0.6
|
|
||||||
|
|
||||||
'@kevisual/api@0.0.42':
|
'@kevisual/api@0.0.42':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@kevisual/js-filter': 0.0.5
|
'@kevisual/js-filter': 0.0.5
|
||||||
@@ -6645,45 +6483,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
idb-keyval: 6.2.1
|
idb-keyval: 6.2.1
|
||||||
|
|
||||||
'@kevisual/cache@0.0.5':
|
|
||||||
dependencies:
|
|
||||||
idb-keyval: 6.2.2
|
|
||||||
lru-cache: 11.2.5
|
|
||||||
nanoid: 5.1.6
|
|
||||||
|
|
||||||
'@kevisual/cnb@0.0.13(dotenv@17.2.3)(idb-keyval@6.2.2)(typescript@5.8.2)':
|
|
||||||
dependencies:
|
|
||||||
'@kevisual/query': 0.0.38
|
|
||||||
'@kevisual/router': 0.0.64(typescript@5.8.2)
|
|
||||||
'@kevisual/use-config': 1.0.30(dotenv@17.2.3)
|
|
||||||
es-toolkit: 1.44.0
|
|
||||||
nanoid: 5.1.6
|
|
||||||
unstorage: 1.17.4(idb-keyval@6.2.2)
|
|
||||||
ws: '@kevisual/ws@8.19.0'
|
|
||||||
zod: 4.3.6
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@azure/app-configuration'
|
|
||||||
- '@azure/cosmos'
|
|
||||||
- '@azure/data-tables'
|
|
||||||
- '@azure/identity'
|
|
||||||
- '@azure/keyvault-secrets'
|
|
||||||
- '@azure/storage-blob'
|
|
||||||
- '@capacitor/preferences'
|
|
||||||
- '@deno/kv'
|
|
||||||
- '@netlify/blobs'
|
|
||||||
- '@planetscale/database'
|
|
||||||
- '@upstash/redis'
|
|
||||||
- '@vercel/blob'
|
|
||||||
- '@vercel/functions'
|
|
||||||
- '@vercel/kv'
|
|
||||||
- aws4fetch
|
|
||||||
- db0
|
|
||||||
- dotenv
|
|
||||||
- idb-keyval
|
|
||||||
- ioredis
|
|
||||||
- typescript
|
|
||||||
- uploadthing
|
|
||||||
|
|
||||||
'@kevisual/context@0.0.4': {}
|
'@kevisual/context@0.0.4': {}
|
||||||
|
|
||||||
'@kevisual/dts@0.0.3(typescript@5.8.2)':
|
'@kevisual/dts@0.0.3(typescript@5.8.2)':
|
||||||
@@ -6697,12 +6496,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
'@kevisual/ha-api@0.0.8':
|
|
||||||
dependencies:
|
|
||||||
'@kevisual/cache': 0.0.5
|
|
||||||
fuse.js: 7.1.0
|
|
||||||
lru-cache: 11.2.5
|
|
||||||
|
|
||||||
'@kevisual/js-filter@0.0.5': {}
|
'@kevisual/js-filter@0.0.5': {}
|
||||||
|
|
||||||
'@kevisual/kv-code@0.0.4(@types/react@19.2.10)(dotenv@17.2.3)':
|
'@kevisual/kv-code@0.0.4(@types/react@19.2.10)(dotenv@17.2.3)':
|
||||||
@@ -6748,7 +6541,7 @@ snapshots:
|
|||||||
|
|
||||||
'@kevisual/logger@0.0.4': {}
|
'@kevisual/logger@0.0.4': {}
|
||||||
|
|
||||||
'@kevisual/oss@0.0.18': {}
|
'@kevisual/oss@0.0.19': {}
|
||||||
|
|
||||||
'@kevisual/permission@0.0.3': {}
|
'@kevisual/permission@0.0.3': {}
|
||||||
|
|
||||||
@@ -6818,20 +6611,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@kevisual/router@0.0.64(typescript@5.8.2)':
|
'@kevisual/router@0.0.67': {}
|
||||||
dependencies:
|
|
||||||
'@kevisual/dts': 0.0.3(typescript@5.8.2)
|
|
||||||
hono: 4.11.7
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- typescript
|
|
||||||
|
|
||||||
'@kevisual/types@0.0.12': {}
|
'@kevisual/types@0.0.12': {}
|
||||||
|
|
||||||
'@kevisual/use-config@1.0.28(dotenv@17.2.3)':
|
|
||||||
dependencies:
|
|
||||||
'@kevisual/load': 0.0.6
|
|
||||||
dotenv: 17.2.3
|
|
||||||
|
|
||||||
'@kevisual/use-config@1.0.30(dotenv@17.2.3)':
|
'@kevisual/use-config@1.0.30(dotenv@17.2.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@kevisual/load': 0.0.6
|
'@kevisual/load': 0.0.6
|
||||||
@@ -6856,8 +6639,6 @@ snapshots:
|
|||||||
|
|
||||||
'@kevisual/ws@8.0.0': {}
|
'@kevisual/ws@8.0.0': {}
|
||||||
|
|
||||||
'@kevisual/ws@8.19.0': {}
|
|
||||||
|
|
||||||
'@lezer/common@1.4.0': {}
|
'@lezer/common@1.4.0': {}
|
||||||
|
|
||||||
'@lezer/css@1.3.0':
|
'@lezer/css@1.3.0':
|
||||||
@@ -6943,13 +6724,11 @@ snapshots:
|
|||||||
'@nodelib/fs.scandir': 2.1.5
|
'@nodelib/fs.scandir': 2.1.5
|
||||||
fastq: 1.17.1
|
fastq: 1.17.1
|
||||||
|
|
||||||
'@opencode-ai/plugin@1.1.47':
|
'@opencode-ai/plugin@1.1.48':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@opencode-ai/sdk': 1.1.47
|
'@opencode-ai/sdk': 1.1.48
|
||||||
zod: 4.1.8
|
zod: 4.1.8
|
||||||
|
|
||||||
'@opencode-ai/sdk@1.1.47': {}
|
|
||||||
|
|
||||||
'@opencode-ai/sdk@1.1.48': {}
|
'@opencode-ai/sdk@1.1.48': {}
|
||||||
|
|
||||||
'@oslojs/encoding@1.1.0': {}
|
'@oslojs/encoding@1.1.0': {}
|
||||||
@@ -8087,10 +7866,6 @@ snapshots:
|
|||||||
|
|
||||||
'@types/node@17.0.45': {}
|
'@types/node@17.0.45': {}
|
||||||
|
|
||||||
'@types/node@25.1.0':
|
|
||||||
dependencies:
|
|
||||||
undici-types: 7.16.0
|
|
||||||
|
|
||||||
'@types/node@25.2.0':
|
'@types/node@25.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.16.0
|
undici-types: 7.16.0
|
||||||
@@ -9390,8 +9165,6 @@ snapshots:
|
|||||||
|
|
||||||
highlight.js@11.11.1: {}
|
highlight.js@11.11.1: {}
|
||||||
|
|
||||||
hono@4.11.7: {}
|
|
||||||
|
|
||||||
hookable@5.5.3: {}
|
hookable@5.5.3: {}
|
||||||
|
|
||||||
html-escaper@3.0.3: {}
|
html-escaper@3.0.3: {}
|
||||||
@@ -9456,7 +9229,8 @@ snapshots:
|
|||||||
|
|
||||||
idb-keyval@6.2.1: {}
|
idb-keyval@6.2.1: {}
|
||||||
|
|
||||||
idb-keyval@6.2.2: {}
|
idb-keyval@6.2.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
ignore@7.0.5: {}
|
ignore@7.0.5: {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user