refactor(server): 消除与 Core 的重复类型定义
- 删除 Server 中 60+ 个与 Core 重复的类型定义 - 将动态导入 (await import) 改为静态类型导入 (import type) - 保留必要的运行时静态导入 - 修复测试文件中的 mock 初始化问题 - 净删除约 960 行重复代码 重构文件: - routes/checkpoints.ts: 删除 155 行重复类型 - routes/agents.ts: 删除 93 行重复类型 - routes/commands.ts: 删除 83 行重复类型 - routes/mcp.ts: 修复类型窄化 - routes/hooks.ts: 已使用静态导入 - routes/providers.ts: 删除 63 行重复类型 - session/manager.ts: 删除 41 行重复类型 - routes/sessions.ts: 添加类型导入 - permission/handler.ts: 添加类型导入
This commit is contained in:
@@ -44,7 +44,9 @@ export type {
|
||||
MessageInfo,
|
||||
Part,
|
||||
PartType,
|
||||
ToolPart,
|
||||
ToolStatus,
|
||||
ToolState,
|
||||
TodoItem,
|
||||
TodoList,
|
||||
} from './session/index.js';
|
||||
@@ -193,6 +195,11 @@ export type {
|
||||
AgentModelConfig,
|
||||
AgentToolConfig,
|
||||
AgentPermission,
|
||||
AgentBashPermission,
|
||||
AgentFilePermission,
|
||||
AgentGitPermission,
|
||||
PermissionAction,
|
||||
PermissionRule,
|
||||
} from './agent/index.js';
|
||||
|
||||
// Agent Events (for subagent progress tracking)
|
||||
@@ -213,6 +220,15 @@ export {
|
||||
getMCPManager,
|
||||
loadMCPConfig,
|
||||
createMCPToolAdapter,
|
||||
MCPManager,
|
||||
} from './mcp/index.js';
|
||||
|
||||
export type {
|
||||
MCPConfig,
|
||||
MCPServerConfig,
|
||||
MCPServerStatus,
|
||||
MCPServerStatusType,
|
||||
MCPTool,
|
||||
} from './mcp/index.js';
|
||||
|
||||
// Provider
|
||||
|
||||
@@ -11,24 +11,7 @@ import type {
|
||||
PermissionRequestContext,
|
||||
ServerMessage,
|
||||
} from '../types.js';
|
||||
|
||||
/**
|
||||
* 权限决策结果
|
||||
*/
|
||||
export interface PermissionDecision {
|
||||
allow: boolean;
|
||||
remember?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限上下文(来自 core 模块)
|
||||
*/
|
||||
export interface PermissionContext {
|
||||
command: string;
|
||||
workdir: string;
|
||||
patterns?: string[];
|
||||
externalPaths?: string[];
|
||||
}
|
||||
import type { PermissionDecision, PermissionContext } from '@ai-assistant/core';
|
||||
|
||||
// 等待中的权限请求
|
||||
interface PendingRequest {
|
||||
|
||||
@@ -6,83 +6,20 @@
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import { getConfig } from './config.js';
|
||||
|
||||
// Agent 类型定义(与 Core 对应)
|
||||
type AgentMode = 'primary' | 'subagent' | 'all';
|
||||
|
||||
interface AgentModelConfig {
|
||||
provider?: 'anthropic' | 'deepseek' | 'openai';
|
||||
model?: string;
|
||||
temperature?: number;
|
||||
topP?: number;
|
||||
maxTokens?: number;
|
||||
}
|
||||
|
||||
interface AgentToolConfig {
|
||||
disabled?: string[];
|
||||
enabled?: string[];
|
||||
noTask?: boolean;
|
||||
}
|
||||
|
||||
interface PermissionRule {
|
||||
pattern: string;
|
||||
action: 'allow' | 'deny';
|
||||
}
|
||||
|
||||
interface AgentBashPermission {
|
||||
rules?: PermissionRule[];
|
||||
defaultAction?: 'allow' | 'deny';
|
||||
}
|
||||
|
||||
interface AgentFilePermission {
|
||||
read?: { rules?: PermissionRule[]; defaultAction?: 'allow' | 'deny' };
|
||||
write?: { rules?: PermissionRule[]; defaultAction?: 'allow' | 'deny' };
|
||||
}
|
||||
|
||||
interface AgentGitPermission {
|
||||
commands?: string[];
|
||||
allowPush?: boolean;
|
||||
allowForce?: boolean;
|
||||
}
|
||||
|
||||
interface AgentPermission {
|
||||
bash?: AgentBashPermission;
|
||||
file?: AgentFilePermission;
|
||||
git?: AgentGitPermission;
|
||||
}
|
||||
|
||||
interface AgentInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
mode: AgentMode;
|
||||
prompt?: string;
|
||||
model?: AgentModelConfig;
|
||||
tools?: AgentToolConfig;
|
||||
permission?: AgentPermission;
|
||||
maxSteps?: number;
|
||||
}
|
||||
|
||||
interface AgentConfigFile {
|
||||
defaults?: {
|
||||
maxSteps?: number;
|
||||
model?: AgentModelConfig;
|
||||
permission?: AgentPermission;
|
||||
};
|
||||
agents?: Record<string, Omit<AgentInfo, 'name'>>;
|
||||
}
|
||||
|
||||
// Core Agent 模块类型
|
||||
interface AgentModule {
|
||||
agentRegistry: {
|
||||
init: () => Promise<void>;
|
||||
get: (name: string) => AgentInfo | undefined;
|
||||
list: () => AgentInfo[];
|
||||
};
|
||||
loadAgentConfig: (workdir: string) => Promise<AgentConfigFile | null>;
|
||||
saveAgentConfig: (workdir: string, config: AgentConfigFile, format?: 'json' | 'yaml') => Promise<void>;
|
||||
presetAgents: Record<string, Omit<AgentInfo, 'name'>>;
|
||||
isPresetAgent: (name: string) => boolean;
|
||||
}
|
||||
import type {
|
||||
AgentMode,
|
||||
AgentInfo,
|
||||
AgentConfigFile,
|
||||
AgentModelConfig,
|
||||
AgentPermission,
|
||||
} from '@ai-assistant/core';
|
||||
import {
|
||||
agentRegistry,
|
||||
loadAgentConfig,
|
||||
saveAgentConfig,
|
||||
presetAgents,
|
||||
isPresetAgent,
|
||||
} from '@ai-assistant/core';
|
||||
|
||||
// API 响应类型
|
||||
interface AgentListItem {
|
||||
@@ -103,72 +40,30 @@ interface AgentDefaults {
|
||||
|
||||
export const agentsRouter = new Hono();
|
||||
|
||||
// Core 模块缓存
|
||||
let agentModule: AgentModule | null = null;
|
||||
// 初始化状态
|
||||
let initialized = false;
|
||||
|
||||
/**
|
||||
* 初始化 Agent 模块
|
||||
*/
|
||||
async function initAgentModule(): Promise<AgentModule | null> {
|
||||
if (agentModule) return agentModule;
|
||||
|
||||
try {
|
||||
const corePath = '@ai-assistant/core';
|
||||
const core = (await import(corePath)) as Record<string, unknown>;
|
||||
|
||||
if (
|
||||
!core.agentRegistry ||
|
||||
typeof core.loadAgentConfig !== 'function' ||
|
||||
typeof core.saveAgentConfig !== 'function' ||
|
||||
!core.presetAgents ||
|
||||
typeof core.isPresetAgent !== 'function'
|
||||
) {
|
||||
console.warn('[Agents] Core module missing Agent exports');
|
||||
return null;
|
||||
}
|
||||
|
||||
agentModule = {
|
||||
agentRegistry: core.agentRegistry as AgentModule['agentRegistry'],
|
||||
loadAgentConfig: core.loadAgentConfig as AgentModule['loadAgentConfig'],
|
||||
saveAgentConfig: core.saveAgentConfig as AgentModule['saveAgentConfig'],
|
||||
presetAgents: core.presetAgents as AgentModule['presetAgents'],
|
||||
isPresetAgent: core.isPresetAgent as AgentModule['isPresetAgent'],
|
||||
};
|
||||
|
||||
console.log('[Agents] Agent module initialized');
|
||||
return agentModule;
|
||||
} catch (error) {
|
||||
console.warn('[Agents] Failed to load Agent module:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保 AgentRegistry 已初始化
|
||||
*/
|
||||
async function ensureRegistryInitialized(): Promise<boolean> {
|
||||
const module = await initAgentModule();
|
||||
if (!module) return false;
|
||||
|
||||
if (!initialized) {
|
||||
await module.agentRegistry.init();
|
||||
await agentRegistry.init();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 AgentInfo 转换为 AgentListItem
|
||||
*/
|
||||
function toListItem(agent: AgentInfo, module: AgentModule, customAgentNames: Set<string>): AgentListItem {
|
||||
const isPreset = module.isPresetAgent(agent.name);
|
||||
function toListItem(agent: AgentInfo, customAgentNames: Set<string>): AgentListItem {
|
||||
const isPresetAgent_ = isPresetAgent(agent.name);
|
||||
return {
|
||||
name: agent.name,
|
||||
description: agent.description,
|
||||
mode: agent.mode,
|
||||
isPreset,
|
||||
isPreset: isPresetAgent_,
|
||||
isCustomized: customAgentNames.has(agent.name),
|
||||
model: agent.model?.model,
|
||||
maxSteps: agent.maxSteps,
|
||||
@@ -179,18 +74,6 @@ function toListItem(agent: AgentInfo, module: AgentModule, customAgentNames: Set
|
||||
* GET /agents - 获取所有 Agent 列表
|
||||
*/
|
||||
agentsRouter.get('/', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
if (!(await ensureRegistryInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
@@ -202,11 +85,11 @@ agentsRouter.get('/', async (c) => {
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const userConfig = await module.loadAgentConfig(config.workdir);
|
||||
const userConfig = await loadAgentConfig(config.workdir);
|
||||
const customAgentNames = new Set(Object.keys(userConfig?.agents || {}));
|
||||
|
||||
const agents = module.agentRegistry.list();
|
||||
const items = agents.map((agent) => toListItem(agent, module, customAgentNames));
|
||||
const agents = agentRegistry.list();
|
||||
const items = agents.map((agent) => toListItem(agent, customAgentNames));
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
@@ -218,19 +101,7 @@ agentsRouter.get('/', async (c) => {
|
||||
* GET /agents/presets - 获取预设 Agent 列表
|
||||
*/
|
||||
agentsRouter.get('/presets', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
const presets = Object.entries(module.presetAgents).map(([name, agent]) => ({
|
||||
const presets = Object.entries(presetAgents).map(([name, agent]) => ({
|
||||
name,
|
||||
description: agent.description,
|
||||
mode: agent.mode,
|
||||
@@ -250,20 +121,8 @@ agentsRouter.get('/presets', async (c) => {
|
||||
* GET /agents/defaults - 获取全局默认配置
|
||||
*/
|
||||
agentsRouter.get('/defaults', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const userConfig = await module.loadAgentConfig(config.workdir);
|
||||
const userConfig = await loadAgentConfig(config.workdir);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
@@ -275,24 +134,12 @@ agentsRouter.get('/defaults', async (c) => {
|
||||
* PUT /agents/defaults - 更新全局默认配置
|
||||
*/
|
||||
agentsRouter.put('/defaults', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const newDefaults = await c.req.json<AgentDefaults>();
|
||||
const config = getConfig();
|
||||
|
||||
// 加载现有配置
|
||||
let userConfig = await module.loadAgentConfig(config.workdir);
|
||||
let userConfig = await loadAgentConfig(config.workdir);
|
||||
if (!userConfig) {
|
||||
userConfig = {};
|
||||
}
|
||||
@@ -301,7 +148,7 @@ agentsRouter.put('/defaults', async (c) => {
|
||||
userConfig.defaults = newDefaults;
|
||||
|
||||
// 保存配置
|
||||
await module.saveAgentConfig(config.workdir, userConfig);
|
||||
await saveAgentConfig(config.workdir, userConfig);
|
||||
|
||||
// 重新初始化 registry 以应用新配置
|
||||
initialized = false;
|
||||
@@ -326,18 +173,6 @@ agentsRouter.put('/defaults', async (c) => {
|
||||
* GET /agents/:name - 获取单个 Agent 详情
|
||||
*/
|
||||
agentsRouter.get('/:name', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
if (!(await ensureRegistryInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
@@ -349,7 +184,7 @@ agentsRouter.get('/:name', async (c) => {
|
||||
}
|
||||
|
||||
const name = c.req.param('name');
|
||||
const agent = module.agentRegistry.get(name);
|
||||
const agent = agentRegistry.get(name);
|
||||
|
||||
if (!agent) {
|
||||
return c.json(
|
||||
@@ -362,15 +197,15 @@ agentsRouter.get('/:name', async (c) => {
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const userConfig = await module.loadAgentConfig(config.workdir);
|
||||
const isPreset = module.isPresetAgent(name);
|
||||
const userConfig = await loadAgentConfig(config.workdir);
|
||||
const isPresetAgent_ = isPresetAgent(name);
|
||||
const isCustomized = !!(userConfig?.agents && name in userConfig.agents);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: {
|
||||
...agent,
|
||||
isPreset,
|
||||
isPreset: isPresetAgent_,
|
||||
isCustomized,
|
||||
},
|
||||
});
|
||||
@@ -380,18 +215,6 @@ agentsRouter.get('/:name', async (c) => {
|
||||
* POST /agents - 创建新 Agent
|
||||
*/
|
||||
agentsRouter.post('/', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await c.req.json<{ name: string } & Omit<AgentInfo, 'name'>>();
|
||||
const { name, ...agentConfig } = body;
|
||||
@@ -407,7 +230,7 @@ agentsRouter.post('/', async (c) => {
|
||||
}
|
||||
|
||||
// 检查名称是否与预设冲突
|
||||
if (module.isPresetAgent(name)) {
|
||||
if (isPresetAgent(name)) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -418,7 +241,7 @@ agentsRouter.post('/', async (c) => {
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
let userConfig = await module.loadAgentConfig(config.workdir);
|
||||
let userConfig = await loadAgentConfig(config.workdir);
|
||||
if (!userConfig) {
|
||||
userConfig = {};
|
||||
}
|
||||
@@ -441,13 +264,13 @@ agentsRouter.post('/', async (c) => {
|
||||
userConfig.agents[name] = agentConfig;
|
||||
|
||||
// 保存配置
|
||||
await module.saveAgentConfig(config.workdir, userConfig);
|
||||
await saveAgentConfig(config.workdir, userConfig);
|
||||
|
||||
// 重新初始化 registry
|
||||
initialized = false;
|
||||
await ensureRegistryInitialized();
|
||||
|
||||
const createdAgent = module.agentRegistry.get(name);
|
||||
const createdAgent = agentRegistry.get(name);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
@@ -474,24 +297,12 @@ agentsRouter.post('/', async (c) => {
|
||||
* PUT /agents/:name - 更新 Agent
|
||||
*/
|
||||
agentsRouter.put('/:name', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const name = c.req.param('name');
|
||||
const agentConfig = await c.req.json<Omit<AgentInfo, 'name'>>();
|
||||
|
||||
const config = getConfig();
|
||||
let userConfig = await module.loadAgentConfig(config.workdir);
|
||||
let userConfig = await loadAgentConfig(config.workdir);
|
||||
if (!userConfig) {
|
||||
userConfig = {};
|
||||
}
|
||||
@@ -503,24 +314,24 @@ agentsRouter.put('/:name', async (c) => {
|
||||
userConfig.agents[name] = agentConfig;
|
||||
|
||||
// 保存配置
|
||||
await module.saveAgentConfig(config.workdir, userConfig);
|
||||
await saveAgentConfig(config.workdir, userConfig);
|
||||
|
||||
// 重新初始化 registry
|
||||
initialized = false;
|
||||
await ensureRegistryInitialized();
|
||||
|
||||
const updatedAgent = module.agentRegistry.get(name);
|
||||
const isPreset = module.isPresetAgent(name);
|
||||
const updatedAgent = agentRegistry.get(name);
|
||||
const isPresetAgent_ = isPresetAgent(name);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: updatedAgent
|
||||
? {
|
||||
...updatedAgent,
|
||||
isPreset,
|
||||
isPreset: isPresetAgent_,
|
||||
isCustomized: true,
|
||||
}
|
||||
: { name, ...agentConfig, isPreset, isCustomized: true },
|
||||
: { name, ...agentConfig, isPreset: isPresetAgent_, isCustomized: true },
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
@@ -537,26 +348,14 @@ agentsRouter.put('/:name', async (c) => {
|
||||
* DELETE /agents/:name - 删除 Agent
|
||||
*/
|
||||
agentsRouter.delete('/:name', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const name = c.req.param('name');
|
||||
const config = getConfig();
|
||||
const userConfig = await module.loadAgentConfig(config.workdir);
|
||||
const userConfig = await loadAgentConfig(config.workdir);
|
||||
|
||||
if (!userConfig?.agents || !(name in userConfig.agents)) {
|
||||
// 如果是预设 Agent,返回特定错误
|
||||
if (module.isPresetAgent(name)) {
|
||||
if (isPresetAgent(name)) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -579,7 +378,7 @@ agentsRouter.delete('/:name', async (c) => {
|
||||
delete userConfig.agents[name];
|
||||
|
||||
// 保存配置
|
||||
await module.saveAgentConfig(config.workdir, userConfig);
|
||||
await saveAgentConfig(config.workdir, userConfig);
|
||||
|
||||
// 重新初始化 registry
|
||||
initialized = false;
|
||||
|
||||
@@ -6,157 +6,26 @@
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import { getConfig } from './config.js';
|
||||
|
||||
// Core Checkpoint 模块类型
|
||||
interface CheckpointModule {
|
||||
getCheckpointManager: () => CheckpointManager;
|
||||
initCheckpointManager: (
|
||||
workDir: string,
|
||||
config?: Partial<CheckpointConfig>
|
||||
) => Promise<CheckpointManager>;
|
||||
RestoreMode: typeof RestoreMode;
|
||||
}
|
||||
|
||||
interface CheckpointManager {
|
||||
initialize(): Promise<void>;
|
||||
isEnabled(): boolean;
|
||||
getConfig(): CheckpointConfig;
|
||||
listCheckpoints(): Promise<CheckpointMetadata[]>;
|
||||
getCheckpoint(idOrHash: string): Promise<CheckpointMetadata | null>;
|
||||
getLatestCheckpoint(): Promise<CheckpointMetadata | null>;
|
||||
createCheckpoint(options: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
trigger?: CheckpointTrigger;
|
||||
}): Promise<CheckpointMetadata>;
|
||||
deleteCheckpoint(id: string): Promise<boolean>;
|
||||
getDiff(checkpointId: string): Promise<DiffInfo>;
|
||||
getFileDiff(checkpointId: string, filePath: string): Promise<FileDiff>;
|
||||
rollback(options: RollbackOptions): Promise<RollbackResult>;
|
||||
checkSafety(checkpointId: string): Promise<SafetyCheckResult>;
|
||||
unrevert(): Promise<UnrevertResult>;
|
||||
canUnrevert(): boolean;
|
||||
getLastRollback(): RollbackRecord | null;
|
||||
cleanup(): Promise<number>;
|
||||
getStats(): Promise<CheckpointStats>;
|
||||
getSessionCheckpoints(sessionId: string): Promise<CheckpointMetadata[]>;
|
||||
getMessageCheckpoints(messageId: string): Promise<CheckpointMetadata[]>;
|
||||
}
|
||||
|
||||
interface CheckpointConfig {
|
||||
enabled: boolean;
|
||||
autoCheckpoint: {
|
||||
beforeWrite: boolean;
|
||||
beforeEdit: boolean;
|
||||
beforeDelete: boolean;
|
||||
beforeMove: boolean;
|
||||
beforeBash: boolean;
|
||||
};
|
||||
maxCheckpoints: number;
|
||||
maxAge: number;
|
||||
storageDir: string;
|
||||
}
|
||||
|
||||
type CheckpointTrigger =
|
||||
| 'auto'
|
||||
| 'manual'
|
||||
| 'tool:write_file'
|
||||
| 'tool:edit_file'
|
||||
| 'tool:delete_file'
|
||||
| 'tool:move_file'
|
||||
| 'tool:copy_file'
|
||||
| 'tool:bash'
|
||||
| 'task_start'
|
||||
| 'task_complete'
|
||||
| 'pre_rollback'
|
||||
| 'session_start'
|
||||
| 'session_end';
|
||||
|
||||
interface CheckpointMetadata {
|
||||
id: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
timestamp: number;
|
||||
trigger: CheckpointTrigger;
|
||||
toolCall?: {
|
||||
tool: string;
|
||||
params: Record<string, unknown>;
|
||||
};
|
||||
commitHash: string;
|
||||
filesChanged: number;
|
||||
messageId?: string;
|
||||
sessionId?: string;
|
||||
turnIndex?: number;
|
||||
}
|
||||
|
||||
type FileChangeType = 'added' | 'modified' | 'deleted' | 'renamed';
|
||||
|
||||
interface FileChange {
|
||||
path: string;
|
||||
type: FileChangeType;
|
||||
oldPath?: string;
|
||||
insertions?: number;
|
||||
deletions?: number;
|
||||
}
|
||||
|
||||
interface DiffInfo {
|
||||
from: string;
|
||||
to: string;
|
||||
files: FileChange[];
|
||||
totalInsertions: number;
|
||||
totalDeletions: number;
|
||||
}
|
||||
|
||||
interface FileDiff {
|
||||
path: string;
|
||||
type: FileChangeType;
|
||||
oldContent?: string;
|
||||
newContent?: string;
|
||||
patch?: string;
|
||||
}
|
||||
|
||||
enum RestoreMode {
|
||||
AI_CHANGES_ONLY = 'ai_changes_only',
|
||||
WORKSPACE_ONLY = 'workspace_only',
|
||||
FULL = 'full',
|
||||
}
|
||||
|
||||
interface RollbackOptions {
|
||||
target: string;
|
||||
files?: string[];
|
||||
dryRun?: boolean;
|
||||
mode?: RestoreMode;
|
||||
skipSafetyCheck?: boolean;
|
||||
}
|
||||
|
||||
interface RollbackResult {
|
||||
success: boolean;
|
||||
restoredFiles: string[];
|
||||
errors: Array<{ file: string; error: string }>;
|
||||
previousCommit?: string;
|
||||
}
|
||||
|
||||
interface SafetyCheckResult {
|
||||
safe: boolean;
|
||||
warnings: string[];
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
interface RollbackRecord {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
targetCheckpoint: string;
|
||||
previousCommit: string;
|
||||
restoredFiles: string[];
|
||||
canUnrevert: boolean;
|
||||
}
|
||||
|
||||
interface UnrevertResult {
|
||||
success: boolean;
|
||||
restoredCommit: string;
|
||||
filesRestored: number;
|
||||
error?: string;
|
||||
}
|
||||
import type {
|
||||
CheckpointMetadata,
|
||||
CheckpointConfig,
|
||||
CheckpointTrigger,
|
||||
FileChange,
|
||||
FileChangeType,
|
||||
DiffInfo,
|
||||
FileDiff,
|
||||
RollbackOptions,
|
||||
RollbackResult,
|
||||
RollbackRecord,
|
||||
SafetyCheckResult,
|
||||
UnrevertResult,
|
||||
} from '@ai-assistant/core';
|
||||
import {
|
||||
CheckpointManager,
|
||||
getCheckpointManager,
|
||||
initCheckpointManager,
|
||||
RestoreMode,
|
||||
} from '@ai-assistant/core';
|
||||
|
||||
interface CheckpointStats {
|
||||
count: number;
|
||||
@@ -166,46 +35,25 @@ interface CheckpointStats {
|
||||
|
||||
export const checkpointsRouter = new Hono();
|
||||
|
||||
// Core 模块缓存
|
||||
let checkpointModule: CheckpointModule | null = null;
|
||||
// Manager 初始化状态
|
||||
let managerInitialized = false;
|
||||
|
||||
/**
|
||||
* 初始化 Checkpoint 模块
|
||||
*/
|
||||
async function initCheckpointModule(): Promise<CheckpointModule | null> {
|
||||
if (checkpointModule && managerInitialized) return checkpointModule;
|
||||
async function ensureCheckpointManager(): Promise<CheckpointManager | null> {
|
||||
if (managerInitialized) {
|
||||
return getCheckpointManager();
|
||||
}
|
||||
|
||||
try {
|
||||
const corePath = '@ai-assistant/core';
|
||||
const core = (await import(corePath)) as Record<string, unknown>;
|
||||
|
||||
if (
|
||||
typeof core.getCheckpointManager !== 'function' ||
|
||||
typeof core.initCheckpointManager !== 'function'
|
||||
) {
|
||||
console.warn('[Checkpoints] Core module missing Checkpoint exports');
|
||||
return null;
|
||||
}
|
||||
|
||||
checkpointModule = {
|
||||
getCheckpointManager: core.getCheckpointManager as () => CheckpointManager,
|
||||
initCheckpointManager: core.initCheckpointManager as (
|
||||
workDir: string,
|
||||
config?: Partial<CheckpointConfig>
|
||||
) => Promise<CheckpointManager>,
|
||||
RestoreMode: core.RestoreMode as typeof RestoreMode,
|
||||
};
|
||||
|
||||
// 初始化 Checkpoint Manager
|
||||
const config = getConfig();
|
||||
await checkpointModule.initCheckpointManager(config.workdir);
|
||||
await initCheckpointManager(config.workdir);
|
||||
managerInitialized = true;
|
||||
|
||||
console.log('[Checkpoints] Checkpoint module initialized');
|
||||
return checkpointModule;
|
||||
return getCheckpointManager();
|
||||
} catch (error) {
|
||||
console.warn('[Checkpoints] Failed to load Checkpoint module:', error);
|
||||
console.warn('[Checkpoints] Failed to initialize Checkpoint module:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -214,9 +62,9 @@ async function initCheckpointModule(): Promise<CheckpointModule | null> {
|
||||
* GET /checkpoints - 获取所有检查点列表
|
||||
*/
|
||||
checkpointsRouter.get('/', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -227,7 +75,6 @@ checkpointsRouter.get('/', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const checkpoints = await manager.listCheckpoints();
|
||||
|
||||
return c.json({
|
||||
@@ -259,9 +106,9 @@ checkpointsRouter.get('/', async (c) => {
|
||||
* GET /checkpoints/stats - 获取检查点统计信息
|
||||
*/
|
||||
checkpointsRouter.get('/stats', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -272,7 +119,6 @@ checkpointsRouter.get('/stats', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const stats = await manager.getStats();
|
||||
|
||||
return c.json({
|
||||
@@ -294,9 +140,9 @@ checkpointsRouter.get('/stats', async (c) => {
|
||||
* GET /checkpoints/latest - 获取最新检查点
|
||||
*/
|
||||
checkpointsRouter.get('/latest', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -307,7 +153,6 @@ checkpointsRouter.get('/latest', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const checkpoint = await manager.getLatestCheckpoint();
|
||||
|
||||
return c.json({
|
||||
@@ -329,9 +174,9 @@ checkpointsRouter.get('/latest', async (c) => {
|
||||
* GET /checkpoints/unrevert/status - 检查是否可撤销回滚
|
||||
*/
|
||||
checkpointsRouter.get('/unrevert/status', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -342,7 +187,6 @@ checkpointsRouter.get('/unrevert/status', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const canUnrevert = manager.canUnrevert();
|
||||
const lastRollback = manager.getLastRollback();
|
||||
|
||||
@@ -376,9 +220,9 @@ checkpointsRouter.get('/unrevert/status', async (c) => {
|
||||
*/
|
||||
checkpointsRouter.get('/:id', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -389,7 +233,6 @@ checkpointsRouter.get('/:id', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const checkpoint = await manager.getCheckpoint(id);
|
||||
|
||||
if (!checkpoint) {
|
||||
@@ -421,9 +264,9 @@ checkpointsRouter.get('/:id', async (c) => {
|
||||
* POST /checkpoints - 创建手动检查点
|
||||
*/
|
||||
checkpointsRouter.post('/', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -435,7 +278,6 @@ checkpointsRouter.post('/', async (c) => {
|
||||
|
||||
try {
|
||||
const body = await c.req.json<{ name?: string; description?: string }>();
|
||||
const manager = module.getCheckpointManager();
|
||||
|
||||
const checkpoint = await manager.createCheckpoint({
|
||||
name: body.name,
|
||||
@@ -463,9 +305,9 @@ checkpointsRouter.post('/', async (c) => {
|
||||
*/
|
||||
checkpointsRouter.delete('/:id', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -476,7 +318,6 @@ checkpointsRouter.delete('/:id', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const deleted = await manager.deleteCheckpoint(id);
|
||||
|
||||
if (!deleted) {
|
||||
@@ -509,9 +350,9 @@ checkpointsRouter.delete('/:id', async (c) => {
|
||||
*/
|
||||
checkpointsRouter.get('/:id/diff', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -522,7 +363,6 @@ checkpointsRouter.get('/:id/diff', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const diff = await manager.getDiff(id);
|
||||
|
||||
return c.json({
|
||||
@@ -546,9 +386,9 @@ checkpointsRouter.get('/:id/diff', async (c) => {
|
||||
checkpointsRouter.get('/:id/file-diff', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const filePath = c.req.query('path');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -569,7 +409,6 @@ checkpointsRouter.get('/:id/file-diff', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const fileDiff = await manager.getFileDiff(id, filePath);
|
||||
|
||||
return c.json({
|
||||
@@ -592,9 +431,9 @@ checkpointsRouter.get('/:id/file-diff', async (c) => {
|
||||
*/
|
||||
checkpointsRouter.post('/:id/restore', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -611,20 +450,18 @@ checkpointsRouter.post('/:id/restore', async (c) => {
|
||||
skipSafetyCheck?: boolean;
|
||||
}>();
|
||||
|
||||
const manager = module.getCheckpointManager();
|
||||
|
||||
// 转换 mode 字符串为枚举值
|
||||
let mode: RestoreMode | undefined;
|
||||
if (body.mode) {
|
||||
switch (body.mode) {
|
||||
case 'ai_changes_only':
|
||||
mode = module.RestoreMode.AI_CHANGES_ONLY;
|
||||
mode = RestoreMode.AI_CHANGES_ONLY;
|
||||
break;
|
||||
case 'workspace_only':
|
||||
mode = module.RestoreMode.WORKSPACE_ONLY;
|
||||
mode = RestoreMode.WORKSPACE_ONLY;
|
||||
break;
|
||||
case 'full':
|
||||
mode = module.RestoreMode.FULL;
|
||||
mode = RestoreMode.FULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -658,9 +495,9 @@ checkpointsRouter.get('/:id/restore/preview', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const modeParam = c.req.query('mode');
|
||||
const filesParam = c.req.query('files');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -671,20 +508,18 @@ checkpointsRouter.get('/:id/restore/preview', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
|
||||
// 转换 mode 字符串为枚举值
|
||||
let mode: RestoreMode | undefined;
|
||||
if (modeParam) {
|
||||
switch (modeParam) {
|
||||
case 'ai_changes_only':
|
||||
mode = module.RestoreMode.AI_CHANGES_ONLY;
|
||||
mode = RestoreMode.AI_CHANGES_ONLY;
|
||||
break;
|
||||
case 'workspace_only':
|
||||
mode = module.RestoreMode.WORKSPACE_ONLY;
|
||||
mode = RestoreMode.WORKSPACE_ONLY;
|
||||
break;
|
||||
case 'full':
|
||||
mode = module.RestoreMode.FULL;
|
||||
mode = RestoreMode.FULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -717,9 +552,9 @@ checkpointsRouter.get('/:id/restore/preview', async (c) => {
|
||||
* POST /checkpoints/unrevert - 撤销最近一次回滚
|
||||
*/
|
||||
checkpointsRouter.post('/unrevert', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -730,7 +565,6 @@ checkpointsRouter.post('/unrevert', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const result = await manager.unrevert();
|
||||
|
||||
if (!result.success) {
|
||||
@@ -763,9 +597,9 @@ checkpointsRouter.post('/unrevert', async (c) => {
|
||||
*/
|
||||
checkpointsRouter.get('/:id/safety-check', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -776,7 +610,6 @@ checkpointsRouter.get('/:id/safety-check', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const result = await manager.checkSafety(id);
|
||||
|
||||
return c.json({
|
||||
@@ -798,9 +631,9 @@ checkpointsRouter.get('/:id/safety-check', async (c) => {
|
||||
* POST /checkpoints/cleanup - 清理过期检查点
|
||||
*/
|
||||
checkpointsRouter.post('/cleanup', async (c) => {
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -811,7 +644,6 @@ checkpointsRouter.post('/cleanup', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const deleted = await manager.cleanup();
|
||||
|
||||
return c.json({
|
||||
@@ -834,9 +666,9 @@ checkpointsRouter.post('/cleanup', async (c) => {
|
||||
*/
|
||||
checkpointsRouter.get('/sessions/:sessionId', async (c) => {
|
||||
const sessionId = c.req.param('sessionId');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -847,7 +679,6 @@ checkpointsRouter.get('/sessions/:sessionId', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const checkpoints = await manager.getSessionCheckpoints(sessionId);
|
||||
|
||||
return c.json({
|
||||
@@ -870,9 +701,9 @@ checkpointsRouter.get('/sessions/:sessionId', async (c) => {
|
||||
*/
|
||||
checkpointsRouter.get('/messages/:messageId', async (c) => {
|
||||
const messageId = c.req.param('messageId');
|
||||
const module = await initCheckpointModule();
|
||||
const manager = await ensureCheckpointManager();
|
||||
|
||||
if (!module) {
|
||||
if (!manager) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -883,7 +714,6 @@ checkpointsRouter.get('/messages/:messageId', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const manager = module.getCheckpointManager();
|
||||
const checkpoints = await manager.getMessageCheckpoints(messageId);
|
||||
|
||||
return c.json({
|
||||
|
||||
@@ -7,6 +7,16 @@
|
||||
import { Hono } from 'hono';
|
||||
import { z } from 'zod';
|
||||
import { getConfig } from './config.js';
|
||||
import type {
|
||||
Command,
|
||||
CommandInput,
|
||||
CommandExecutionResult,
|
||||
} from '@ai-assistant/core';
|
||||
import {
|
||||
getCommandRegistry,
|
||||
createCommandExecutor,
|
||||
createCommandManager,
|
||||
} from '@ai-assistant/core';
|
||||
|
||||
// Zod schemas
|
||||
const ExecuteCommandInputSchema = z.object({
|
||||
@@ -39,129 +49,25 @@ const UpdateCommandInputSchema = z.object({
|
||||
subtask: z.boolean().optional(),
|
||||
});
|
||||
|
||||
// Core 模块类型
|
||||
interface CommandModule {
|
||||
getCommandRegistry: () => CommandRegistry;
|
||||
createCommandExecutor: (workdir: string) => CommandExecutor;
|
||||
createCommandManager: (workdir: string) => CommandManager;
|
||||
}
|
||||
|
||||
interface CommandRegistry {
|
||||
initialize(workdir: string): Promise<void>;
|
||||
reload(workdir: string): Promise<void>;
|
||||
get(name: string): Command | undefined;
|
||||
getAll(): Command[];
|
||||
list(): Array<{ name: string; description?: string; source: string }>;
|
||||
search(query: string, limit?: number): Array<{ command: Command; score: number }>;
|
||||
getStats(): { total: number; bySource: Record<string, number> };
|
||||
}
|
||||
|
||||
interface CommandExecutor {
|
||||
execute(input: CommandInput): Promise<CommandExecutionResult>;
|
||||
}
|
||||
|
||||
interface Command {
|
||||
name: string;
|
||||
description?: string;
|
||||
template: string;
|
||||
agent?: string;
|
||||
model?: string;
|
||||
subtask?: boolean;
|
||||
source: 'builtin' | 'user' | 'project';
|
||||
sourcePath?: string;
|
||||
}
|
||||
|
||||
interface CommandInput {
|
||||
command: string;
|
||||
arguments: string;
|
||||
args: string[];
|
||||
workdir: string;
|
||||
}
|
||||
|
||||
interface CommandExecutionResult {
|
||||
success: boolean;
|
||||
prompt?: string;
|
||||
agent?: string;
|
||||
model?: string;
|
||||
subtask?: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface CommandManager {
|
||||
create(input: {
|
||||
name: string;
|
||||
description?: string;
|
||||
template: string;
|
||||
agent?: string;
|
||||
model?: string;
|
||||
subtask?: boolean;
|
||||
scope: 'user' | 'project';
|
||||
}): Promise<{ success: boolean; path?: string; error?: string }>;
|
||||
update(
|
||||
name: string,
|
||||
input: {
|
||||
description?: string;
|
||||
template?: string;
|
||||
agent?: string;
|
||||
model?: string;
|
||||
subtask?: boolean;
|
||||
}
|
||||
): Promise<{ success: boolean; path?: string; error?: string }>;
|
||||
delete(name: string): Promise<{ success: boolean; path?: string; error?: string }>;
|
||||
getContent(name: string): Promise<{
|
||||
success: boolean;
|
||||
data?: {
|
||||
name: string;
|
||||
description?: string;
|
||||
template: string;
|
||||
agent?: string;
|
||||
model?: string;
|
||||
subtask?: boolean;
|
||||
source: string;
|
||||
sourcePath?: string;
|
||||
};
|
||||
error?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
// Core 模块缓存
|
||||
let commandModule: CommandModule | null = null;
|
||||
// Registry 初始化状态
|
||||
let registryInitialized = false;
|
||||
|
||||
/**
|
||||
* 初始化 Command 模块
|
||||
* 确保 CommandRegistry 已初始化
|
||||
*/
|
||||
async function initCommandModule(): Promise<CommandModule | null> {
|
||||
if (commandModule) return commandModule;
|
||||
async function ensureRegistryInitialized(): Promise<boolean> {
|
||||
if (registryInitialized) return true;
|
||||
|
||||
try {
|
||||
const corePath = '@ai-assistant/core';
|
||||
const core = (await import(corePath)) as Record<string, unknown>;
|
||||
|
||||
if (
|
||||
typeof core.getCommandRegistry !== 'function' ||
|
||||
typeof core.createCommandExecutor !== 'function' ||
|
||||
typeof core.createCommandManager !== 'function'
|
||||
) {
|
||||
console.warn('[Commands] Core module missing command exports');
|
||||
return null;
|
||||
}
|
||||
|
||||
commandModule = {
|
||||
getCommandRegistry: core.getCommandRegistry as () => CommandRegistry,
|
||||
createCommandExecutor: core.createCommandExecutor as (workdir: string) => CommandExecutor,
|
||||
createCommandManager: core.createCommandManager as (workdir: string) => CommandManager,
|
||||
};
|
||||
|
||||
// Initialize registry with server workdir
|
||||
const config = getConfig();
|
||||
const registry = commandModule.getCommandRegistry();
|
||||
const registry = getCommandRegistry();
|
||||
await registry.initialize(config.workdir);
|
||||
|
||||
console.log('[Commands] Command module initialized');
|
||||
return commandModule;
|
||||
registryInitialized = true;
|
||||
console.log('[Commands] Command registry initialized');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn('[Commands] Failed to load core module:', error);
|
||||
return null;
|
||||
console.warn('[Commands] Failed to initialize command registry:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,9 +75,7 @@ async function initCommandModule(): Promise<CommandModule | null> {
|
||||
* GET /commands - 列出所有命令
|
||||
*/
|
||||
commandsRouter.get('/', async (c) => {
|
||||
const module = await initCommandModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureRegistryInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -181,7 +85,7 @@ commandsRouter.get('/', async (c) => {
|
||||
);
|
||||
}
|
||||
|
||||
const registry = module.getCommandRegistry();
|
||||
const registry = getCommandRegistry();
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
@@ -197,9 +101,7 @@ commandsRouter.get('/', async (c) => {
|
||||
* 注意:这个路由必须在 /:name 之前定义,否则会被匹配为命令名
|
||||
*/
|
||||
commandsRouter.post('/search', async (c) => {
|
||||
const module = await initCommandModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureRegistryInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -213,7 +115,7 @@ commandsRouter.post('/search', async (c) => {
|
||||
const body = await c.req.json();
|
||||
const input = SearchCommandInputSchema.parse(body);
|
||||
|
||||
const registry = module.getCommandRegistry();
|
||||
const registry = getCommandRegistry();
|
||||
const results = registry.search(input.query, input.limit);
|
||||
|
||||
return c.json({
|
||||
@@ -240,9 +142,7 @@ commandsRouter.post('/search', async (c) => {
|
||||
* POST /commands/reload - 重新加载命令
|
||||
*/
|
||||
commandsRouter.post('/reload', async (c) => {
|
||||
const module = await initCommandModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureRegistryInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -254,7 +154,7 @@ commandsRouter.post('/reload', async (c) => {
|
||||
|
||||
try {
|
||||
const config = getConfig();
|
||||
const registry = module.getCommandRegistry();
|
||||
const registry = getCommandRegistry();
|
||||
await registry.reload(config.workdir);
|
||||
|
||||
return c.json({
|
||||
@@ -281,9 +181,8 @@ commandsRouter.post('/reload', async (c) => {
|
||||
*/
|
||||
commandsRouter.post('/:name{.+}/execute', async (c) => {
|
||||
const name = c.req.param('name');
|
||||
const module = await initCommandModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureRegistryInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -298,7 +197,7 @@ commandsRouter.post('/:name{.+}/execute', async (c) => {
|
||||
const input = ExecuteCommandInputSchema.parse(body);
|
||||
|
||||
const config = getConfig();
|
||||
const executor = module.createCommandExecutor(config.workdir);
|
||||
const executor = createCommandExecutor(config.workdir);
|
||||
|
||||
// Parse arguments
|
||||
const args = input.arguments ? input.arguments.split(/\s+/).filter(Boolean) : [];
|
||||
@@ -342,9 +241,8 @@ commandsRouter.post('/:name{.+}/execute', async (c) => {
|
||||
*/
|
||||
commandsRouter.get('/:name{.+}/content', async (c) => {
|
||||
const name = c.req.param('name');
|
||||
const module = await initCommandModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureRegistryInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -355,7 +253,7 @@ commandsRouter.get('/:name{.+}/content', async (c) => {
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const manager = module.createCommandManager(config.workdir);
|
||||
const manager = createCommandManager(config.workdir);
|
||||
const result = await manager.getContent(name);
|
||||
|
||||
if (result.success) {
|
||||
@@ -381,9 +279,8 @@ commandsRouter.get('/:name{.+}/content', async (c) => {
|
||||
*/
|
||||
commandsRouter.get('/:name{.+}', async (c) => {
|
||||
const name = c.req.param('name');
|
||||
const module = await initCommandModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureRegistryInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -393,7 +290,7 @@ commandsRouter.get('/:name{.+}', async (c) => {
|
||||
);
|
||||
}
|
||||
|
||||
const registry = module.getCommandRegistry();
|
||||
const registry = getCommandRegistry();
|
||||
const command = registry.get(name);
|
||||
|
||||
if (!command) {
|
||||
@@ -429,9 +326,7 @@ commandsRouter.get('/:name{.+}', async (c) => {
|
||||
* POST /commands - 创建命令
|
||||
*/
|
||||
commandsRouter.post('/', async (c) => {
|
||||
const module = await initCommandModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureRegistryInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -446,7 +341,7 @@ commandsRouter.post('/', async (c) => {
|
||||
const input = CreateCommandInputSchema.parse(body);
|
||||
|
||||
const config = getConfig();
|
||||
const manager = module.createCommandManager(config.workdir);
|
||||
const manager = createCommandManager(config.workdir);
|
||||
const result = await manager.create(input);
|
||||
|
||||
if (result.success) {
|
||||
@@ -482,9 +377,8 @@ commandsRouter.post('/', async (c) => {
|
||||
*/
|
||||
commandsRouter.put('/:name{.+}', async (c) => {
|
||||
const name = c.req.param('name');
|
||||
const module = await initCommandModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureRegistryInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -499,7 +393,7 @@ commandsRouter.put('/:name{.+}', async (c) => {
|
||||
const input = UpdateCommandInputSchema.parse(body);
|
||||
|
||||
const config = getConfig();
|
||||
const manager = module.createCommandManager(config.workdir);
|
||||
const manager = createCommandManager(config.workdir);
|
||||
const result = await manager.update(name, input);
|
||||
|
||||
if (result.success) {
|
||||
@@ -535,9 +429,8 @@ commandsRouter.put('/:name{.+}', async (c) => {
|
||||
*/
|
||||
commandsRouter.delete('/:name{.+}', async (c) => {
|
||||
const name = c.req.param('name');
|
||||
const module = await initCommandModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureRegistryInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -548,7 +441,7 @@ commandsRouter.delete('/:name{.+}', async (c) => {
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const manager = module.createCommandManager(config.workdir);
|
||||
const manager = createCommandManager(config.workdir);
|
||||
const result = await manager.delete(name);
|
||||
|
||||
if (result.success) {
|
||||
|
||||
@@ -9,38 +9,18 @@ import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import { spawn } from 'child_process';
|
||||
import { getConfig } from './config.js';
|
||||
|
||||
// Core Hooks 模块类型
|
||||
interface HooksModule {
|
||||
loadProjectConfig: (directory: string) => Promise<ProjectConfig | null>;
|
||||
loadHookConfig: (directory: string) => Promise<HookConfig | null>;
|
||||
getConfigFilePath: (directory: string) => Promise<string | null>;
|
||||
createDefaultConfig: (directory: string) => Promise<void>;
|
||||
}
|
||||
|
||||
interface ShellCommandConfig {
|
||||
command: string[];
|
||||
environment?: Record<string, string>;
|
||||
timeout?: number;
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
interface FileHookConfig {
|
||||
[pattern: string]: ShellCommandConfig[];
|
||||
}
|
||||
|
||||
interface HookConfig {
|
||||
file_edited?: FileHookConfig;
|
||||
file_created?: FileHookConfig;
|
||||
file_deleted?: FileHookConfig;
|
||||
session_completed?: ShellCommandConfig[];
|
||||
}
|
||||
|
||||
interface ProjectConfig {
|
||||
hooks?: HookConfig;
|
||||
plugins?: string[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import type {
|
||||
HookConfig,
|
||||
ProjectConfig,
|
||||
ShellCommandConfig,
|
||||
FileHookConfig,
|
||||
} from '@ai-assistant/core';
|
||||
import {
|
||||
loadProjectConfig,
|
||||
loadHookConfig,
|
||||
getConfigFilePath,
|
||||
createDefaultConfig,
|
||||
} from '@ai-assistant/core';
|
||||
|
||||
interface HookTestResult {
|
||||
success: boolean;
|
||||
@@ -52,44 +32,6 @@ interface HookTestResult {
|
||||
|
||||
export const hooksRouter = new Hono();
|
||||
|
||||
// Core 模块缓存
|
||||
let hooksModule: HooksModule | null = null;
|
||||
|
||||
/**
|
||||
* 初始化 Hooks 模块
|
||||
*/
|
||||
async function initHooksModule(): Promise<HooksModule | null> {
|
||||
if (hooksModule) return hooksModule;
|
||||
|
||||
try {
|
||||
const corePath = '@ai-assistant/core';
|
||||
const core = (await import(corePath)) as Record<string, unknown>;
|
||||
|
||||
if (
|
||||
typeof core.loadProjectConfig !== 'function' ||
|
||||
typeof core.loadHookConfig !== 'function' ||
|
||||
typeof core.getConfigFilePath !== 'function' ||
|
||||
typeof core.createDefaultConfig !== 'function'
|
||||
) {
|
||||
console.warn('[Hooks] Core module missing Hooks exports');
|
||||
return null;
|
||||
}
|
||||
|
||||
hooksModule = {
|
||||
loadProjectConfig: core.loadProjectConfig as HooksModule['loadProjectConfig'],
|
||||
loadHookConfig: core.loadHookConfig as HooksModule['loadHookConfig'],
|
||||
getConfigFilePath: core.getConfigFilePath as HooksModule['getConfigFilePath'],
|
||||
createDefaultConfig: core.createDefaultConfig as HooksModule['createDefaultConfig'],
|
||||
};
|
||||
|
||||
console.log('[Hooks] Hooks module initialized');
|
||||
return hooksModule;
|
||||
} catch (error) {
|
||||
console.warn('[Hooks] Failed to load Hooks module:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除 JSON 中的注释(支持 JSONC 格式)
|
||||
*/
|
||||
@@ -123,11 +65,8 @@ async function writeConfigFile(configPath: string, config: ProjectConfig): Promi
|
||||
* 获取或创建配置文件路径
|
||||
*/
|
||||
async function getOrCreateConfigPath(workdir: string): Promise<string> {
|
||||
const module = await initHooksModule();
|
||||
if (module) {
|
||||
const existingPath = await module.getConfigFilePath(workdir);
|
||||
if (existingPath) return existingPath;
|
||||
}
|
||||
const existingPath = await getConfigFilePath(workdir);
|
||||
if (existingPath) return existingPath;
|
||||
return path.join(workdir, '.ai-assistant.json');
|
||||
}
|
||||
|
||||
@@ -135,20 +74,8 @@ async function getOrCreateConfigPath(workdir: string): Promise<string> {
|
||||
* GET /hooks/config - 获取完整钩子配置
|
||||
*/
|
||||
hooksRouter.get('/config', async (c) => {
|
||||
const module = await initHooksModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Hooks module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const hookConfig = await module.loadHookConfig(config.workdir);
|
||||
const hookConfig = await loadHookConfig(config.workdir);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
@@ -165,18 +92,6 @@ hooksRouter.get('/config', async (c) => {
|
||||
* PUT /hooks/config - 更新完整钩子配置
|
||||
*/
|
||||
hooksRouter.put('/config', async (c) => {
|
||||
const module = await initHooksModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Hooks module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const newHookConfig = await c.req.json<HookConfig>();
|
||||
const config = getConfig();
|
||||
@@ -213,20 +128,8 @@ hooksRouter.put('/config', async (c) => {
|
||||
* GET /hooks/file-edited - 获取 file_edited 钩子
|
||||
*/
|
||||
hooksRouter.get('/file-edited', async (c) => {
|
||||
const module = await initHooksModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Hooks module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const hookConfig = await module.loadHookConfig(config.workdir);
|
||||
const hookConfig = await loadHookConfig(config.workdir);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
@@ -238,18 +141,6 @@ hooksRouter.get('/file-edited', async (c) => {
|
||||
* PUT /hooks/file-edited - 更新 file_edited 钩子
|
||||
*/
|
||||
hooksRouter.put('/file-edited', async (c) => {
|
||||
const module = await initHooksModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Hooks module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const newFileEditedHooks = await c.req.json<FileHookConfig>();
|
||||
const config = getConfig();
|
||||
@@ -285,20 +176,8 @@ hooksRouter.put('/file-edited', async (c) => {
|
||||
* GET /hooks/file-created - 获取 file_created 钩子
|
||||
*/
|
||||
hooksRouter.get('/file-created', async (c) => {
|
||||
const module = await initHooksModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Hooks module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const hookConfig = await module.loadHookConfig(config.workdir);
|
||||
const hookConfig = await loadHookConfig(config.workdir);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
@@ -310,18 +189,6 @@ hooksRouter.get('/file-created', async (c) => {
|
||||
* PUT /hooks/file-created - 更新 file_created 钩子
|
||||
*/
|
||||
hooksRouter.put('/file-created', async (c) => {
|
||||
const module = await initHooksModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Hooks module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const newFileCreatedHooks = await c.req.json<FileHookConfig>();
|
||||
const config = getConfig();
|
||||
@@ -357,20 +224,8 @@ hooksRouter.put('/file-created', async (c) => {
|
||||
* GET /hooks/file-deleted - 获取 file_deleted 钩子
|
||||
*/
|
||||
hooksRouter.get('/file-deleted', async (c) => {
|
||||
const module = await initHooksModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Hooks module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const hookConfig = await module.loadHookConfig(config.workdir);
|
||||
const hookConfig = await loadHookConfig(config.workdir);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
@@ -382,18 +237,6 @@ hooksRouter.get('/file-deleted', async (c) => {
|
||||
* PUT /hooks/file-deleted - 更新 file_deleted 钩子
|
||||
*/
|
||||
hooksRouter.put('/file-deleted', async (c) => {
|
||||
const module = await initHooksModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Hooks module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const newFileDeletedHooks = await c.req.json<FileHookConfig>();
|
||||
const config = getConfig();
|
||||
@@ -429,20 +272,8 @@ hooksRouter.put('/file-deleted', async (c) => {
|
||||
* GET /hooks/session-completed - 获取 session_completed 钩子
|
||||
*/
|
||||
hooksRouter.get('/session-completed', async (c) => {
|
||||
const module = await initHooksModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Hooks module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const hookConfig = await module.loadHookConfig(config.workdir);
|
||||
const hookConfig = await loadHookConfig(config.workdir);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
@@ -454,18 +285,6 @@ hooksRouter.get('/session-completed', async (c) => {
|
||||
* PUT /hooks/session-completed - 更新 session_completed 钩子
|
||||
*/
|
||||
hooksRouter.put('/session-completed', async (c) => {
|
||||
const module = await initHooksModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Hooks module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const newSessionCompletedHooks = await c.req.json<ShellCommandConfig[]>();
|
||||
const config = getConfig();
|
||||
|
||||
@@ -6,100 +6,44 @@
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import { getConfig } from './config.js';
|
||||
|
||||
// Core MCP 模块类型
|
||||
interface MCPModule {
|
||||
getMCPManager: () => MCPManager;
|
||||
loadMCPConfig: (workdir: string) => Promise<MCPConfig>;
|
||||
}
|
||||
|
||||
interface MCPManager {
|
||||
initialize(config: MCPConfig): Promise<void>;
|
||||
shutdown(): Promise<void>;
|
||||
reconnect(serverName: string): Promise<void>;
|
||||
setServerEnabled(serverName: string, enabled: boolean): Promise<void>;
|
||||
getServerStatuses(): MCPServerStatus[];
|
||||
getServerStatus(name: string): MCPServerStatus | undefined;
|
||||
getTools(): MCPTool[];
|
||||
getTool(name: string): MCPTool | undefined;
|
||||
isInitialized(): boolean;
|
||||
}
|
||||
|
||||
interface MCPConfig {
|
||||
mcp?: Record<string, MCPServerConfig>;
|
||||
tools?: Record<string, boolean>;
|
||||
}
|
||||
|
||||
interface MCPServerConfig {
|
||||
type: 'local' | 'remote';
|
||||
command?: string[];
|
||||
url?: string;
|
||||
env?: Record<string, string>;
|
||||
cwd?: string;
|
||||
enabled?: boolean;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
interface MCPServerStatus {
|
||||
name: string;
|
||||
type: 'local' | 'remote';
|
||||
status: 'connected' | 'connecting' | 'disconnected' | 'disabled' | 'error';
|
||||
toolCount: number;
|
||||
error?: string;
|
||||
lastConnected?: Date;
|
||||
}
|
||||
|
||||
interface MCPTool {
|
||||
server: string;
|
||||
name: string;
|
||||
originalName: string;
|
||||
description: string;
|
||||
inputSchema: Record<string, unknown>;
|
||||
}
|
||||
import type {
|
||||
MCPConfig,
|
||||
MCPServerConfig,
|
||||
MCPServerStatus,
|
||||
MCPTool,
|
||||
} from '@ai-assistant/core';
|
||||
import {
|
||||
getMCPManager,
|
||||
loadMCPConfig,
|
||||
} from '@ai-assistant/core';
|
||||
|
||||
export const mcpRouter = new Hono();
|
||||
|
||||
// Core 模块缓存
|
||||
let mcpModule: MCPModule | null = null;
|
||||
// 配置缓存
|
||||
let currentConfig: MCPConfig | null = null;
|
||||
let managerInitialized = false;
|
||||
|
||||
/**
|
||||
* 初始化 MCP 模块
|
||||
* 初始化 MCP Manager
|
||||
*/
|
||||
async function initMCPModule(): Promise<MCPModule | null> {
|
||||
if (mcpModule) return mcpModule;
|
||||
async function ensureMCPInitialized(): Promise<boolean> {
|
||||
if (managerInitialized) return true;
|
||||
|
||||
try {
|
||||
const corePath = '@ai-assistant/core';
|
||||
const core = (await import(corePath)) as Record<string, unknown>;
|
||||
|
||||
if (
|
||||
typeof core.getMCPManager !== 'function' ||
|
||||
typeof core.loadMCPConfig !== 'function'
|
||||
) {
|
||||
console.warn('[MCP] Core module missing MCP exports');
|
||||
return null;
|
||||
}
|
||||
|
||||
mcpModule = {
|
||||
getMCPManager: core.getMCPManager as () => MCPManager,
|
||||
loadMCPConfig: core.loadMCPConfig as (workdir: string) => Promise<MCPConfig>,
|
||||
};
|
||||
|
||||
// 初始化 MCP Manager
|
||||
const config = getConfig();
|
||||
currentConfig = await mcpModule.loadMCPConfig(config.workdir);
|
||||
const manager = mcpModule.getMCPManager();
|
||||
currentConfig = await loadMCPConfig(config.workdir);
|
||||
const manager = getMCPManager();
|
||||
|
||||
if (!manager.isInitialized() && currentConfig.mcp) {
|
||||
await manager.initialize(currentConfig);
|
||||
}
|
||||
|
||||
managerInitialized = true;
|
||||
console.log('[MCP] MCP module initialized');
|
||||
return mcpModule;
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn('[MCP] Failed to load MCP module:', error);
|
||||
return null;
|
||||
console.warn('[MCP] Failed to initialize MCP module:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,9 +51,7 @@ async function initMCPModule(): Promise<MCPModule | null> {
|
||||
* GET /mcp/servers - 获取所有服务器状态
|
||||
*/
|
||||
mcpRouter.get('/servers', async (c) => {
|
||||
const module = await initMCPModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureMCPInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -119,7 +61,7 @@ mcpRouter.get('/servers', async (c) => {
|
||||
);
|
||||
}
|
||||
|
||||
const manager = module.getMCPManager();
|
||||
const manager = getMCPManager();
|
||||
const statuses = manager.getServerStatuses();
|
||||
|
||||
// 添加配置信息
|
||||
@@ -149,9 +91,8 @@ mcpRouter.get('/servers', async (c) => {
|
||||
*/
|
||||
mcpRouter.get('/servers/:name', async (c) => {
|
||||
const name = c.req.param('name');
|
||||
const module = await initMCPModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureMCPInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -161,7 +102,7 @@ mcpRouter.get('/servers/:name', async (c) => {
|
||||
);
|
||||
}
|
||||
|
||||
const manager = module.getMCPManager();
|
||||
const manager = getMCPManager();
|
||||
const status = manager.getServerStatus(name);
|
||||
|
||||
if (!status) {
|
||||
@@ -206,9 +147,8 @@ mcpRouter.get('/servers/:name', async (c) => {
|
||||
*/
|
||||
mcpRouter.post('/servers/:name/connect', async (c) => {
|
||||
const name = c.req.param('name');
|
||||
const module = await initMCPModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureMCPInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -218,7 +158,7 @@ mcpRouter.post('/servers/:name/connect', async (c) => {
|
||||
);
|
||||
}
|
||||
|
||||
const manager = module.getMCPManager();
|
||||
const manager = getMCPManager();
|
||||
|
||||
try {
|
||||
await manager.reconnect(name);
|
||||
@@ -246,9 +186,8 @@ mcpRouter.post('/servers/:name/connect', async (c) => {
|
||||
*/
|
||||
mcpRouter.post('/servers/:name/disconnect', async (c) => {
|
||||
const name = c.req.param('name');
|
||||
const module = await initMCPModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureMCPInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -258,7 +197,7 @@ mcpRouter.post('/servers/:name/disconnect', async (c) => {
|
||||
);
|
||||
}
|
||||
|
||||
const manager = module.getMCPManager();
|
||||
const manager = getMCPManager();
|
||||
|
||||
try {
|
||||
// 通过禁用来断开连接
|
||||
@@ -287,9 +226,8 @@ mcpRouter.post('/servers/:name/disconnect', async (c) => {
|
||||
*/
|
||||
mcpRouter.post('/servers/:name/enable', async (c) => {
|
||||
const name = c.req.param('name');
|
||||
const module = await initMCPModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureMCPInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -299,7 +237,7 @@ mcpRouter.post('/servers/:name/enable', async (c) => {
|
||||
);
|
||||
}
|
||||
|
||||
const manager = module.getMCPManager();
|
||||
const manager = getMCPManager();
|
||||
|
||||
try {
|
||||
await manager.setServerEnabled(name, true);
|
||||
@@ -327,9 +265,8 @@ mcpRouter.post('/servers/:name/enable', async (c) => {
|
||||
*/
|
||||
mcpRouter.post('/servers/:name/disable', async (c) => {
|
||||
const name = c.req.param('name');
|
||||
const module = await initMCPModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureMCPInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -339,7 +276,7 @@ mcpRouter.post('/servers/:name/disable', async (c) => {
|
||||
);
|
||||
}
|
||||
|
||||
const manager = module.getMCPManager();
|
||||
const manager = getMCPManager();
|
||||
|
||||
try {
|
||||
await manager.setServerEnabled(name, false);
|
||||
@@ -366,9 +303,7 @@ mcpRouter.post('/servers/:name/disable', async (c) => {
|
||||
* GET /mcp/tools - 获取所有 MCP 工具
|
||||
*/
|
||||
mcpRouter.get('/tools', async (c) => {
|
||||
const module = await initMCPModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureMCPInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -378,7 +313,7 @@ mcpRouter.get('/tools', async (c) => {
|
||||
);
|
||||
}
|
||||
|
||||
const manager = module.getMCPManager();
|
||||
const manager = getMCPManager();
|
||||
const tools = manager.getTools();
|
||||
|
||||
return c.json({
|
||||
@@ -398,9 +333,8 @@ mcpRouter.get('/tools', async (c) => {
|
||||
*/
|
||||
mcpRouter.get('/tools/:name', async (c) => {
|
||||
const name = c.req.param('name');
|
||||
const module = await initMCPModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureMCPInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -410,7 +344,7 @@ mcpRouter.get('/tools/:name', async (c) => {
|
||||
);
|
||||
}
|
||||
|
||||
const manager = module.getMCPManager();
|
||||
const manager = getMCPManager();
|
||||
const tool = manager.getTool(name);
|
||||
|
||||
if (!tool) {
|
||||
@@ -439,9 +373,7 @@ mcpRouter.get('/tools/:name', async (c) => {
|
||||
* GET /mcp/config - 获取 MCP 配置
|
||||
*/
|
||||
mcpRouter.get('/config', async (c) => {
|
||||
const module = await initMCPModule();
|
||||
|
||||
if (!module) {
|
||||
if (!(await ensureMCPInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -459,13 +391,21 @@ mcpRouter.get('/config', async (c) => {
|
||||
|
||||
if (currentConfig?.mcp) {
|
||||
for (const [name, config] of Object.entries(currentConfig.mcp)) {
|
||||
safeConfig.mcp![name] = {
|
||||
type: config.type,
|
||||
command: config.type === 'local' ? config.command : undefined,
|
||||
url: config.type === 'remote' ? config.url : undefined,
|
||||
enabled: config.enabled,
|
||||
timeout: config.timeout,
|
||||
};
|
||||
if (config.type === 'local') {
|
||||
safeConfig.mcp![name] = {
|
||||
type: 'local',
|
||||
command: config.command,
|
||||
enabled: config.enabled,
|
||||
timeout: config.timeout,
|
||||
};
|
||||
} else {
|
||||
safeConfig.mcp![name] = {
|
||||
type: 'remote',
|
||||
url: config.url,
|
||||
enabled: config.enabled,
|
||||
timeout: config.timeout,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,103 +6,24 @@
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import { getConfig } from './config.js';
|
||||
|
||||
// Types from core - dynamically import to avoid build dependency
|
||||
interface ProviderListItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
builtin: boolean;
|
||||
enabled: boolean;
|
||||
hasApiKey: boolean;
|
||||
modelCount: number;
|
||||
}
|
||||
|
||||
interface ModelInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
capabilities?: {
|
||||
vision?: boolean;
|
||||
functionCalling?: boolean;
|
||||
streaming?: boolean;
|
||||
};
|
||||
contextWindow?: number;
|
||||
maxOutput?: number;
|
||||
}
|
||||
|
||||
interface ProviderDetail {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
builtin: boolean;
|
||||
baseUrl?: string;
|
||||
apiKeyEnvVar?: string;
|
||||
models: ModelInfo[];
|
||||
allowCustomModels: boolean;
|
||||
config: {
|
||||
enabled: boolean;
|
||||
hasApiKey: boolean;
|
||||
baseUrl?: string;
|
||||
customModels: ModelInfo[];
|
||||
};
|
||||
}
|
||||
|
||||
interface CustomProviderDefinition {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
baseUrl: string;
|
||||
apiKeyEnvVar?: string;
|
||||
models?: ModelInfo[];
|
||||
allowCustomModels?: boolean;
|
||||
}
|
||||
|
||||
interface ProviderConfig {
|
||||
id?: string;
|
||||
apiKey?: string;
|
||||
apiKeyEnvVar?: string;
|
||||
baseUrl?: string;
|
||||
enabled?: boolean;
|
||||
customModels?: ModelInfo[];
|
||||
}
|
||||
|
||||
interface ConnectionTestResult {
|
||||
success: boolean;
|
||||
latency?: number;
|
||||
error?: string;
|
||||
}
|
||||
import type {
|
||||
ProviderListItem,
|
||||
ModelInfo,
|
||||
ProviderDetail,
|
||||
CustomProviderDefinition,
|
||||
ProviderConfig,
|
||||
ConnectionTestResult,
|
||||
} from '@ai-assistant/core';
|
||||
import { getProviderRegistry } from '@ai-assistant/core';
|
||||
|
||||
export const providersRouter = new Hono();
|
||||
|
||||
// Core module reference
|
||||
let coreModule: any = null;
|
||||
|
||||
/**
|
||||
* Load core module dynamically
|
||||
*/
|
||||
async function getCoreModule() {
|
||||
if (!coreModule) {
|
||||
try {
|
||||
const corePath = '@ai-assistant/core';
|
||||
coreModule = await import(corePath);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return coreModule;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /providers - List all providers
|
||||
*/
|
||||
providersRouter.get('/', async (c) => {
|
||||
const core = await getCoreModule();
|
||||
if (!core) {
|
||||
return c.json({ success: false, error: 'Core module not available' }, 503);
|
||||
}
|
||||
|
||||
try {
|
||||
const registry = core.getProviderRegistry();
|
||||
const registry = getProviderRegistry();
|
||||
const providers: ProviderListItem[] = registry.listForApi();
|
||||
|
||||
return c.json({
|
||||
@@ -124,15 +45,10 @@ providersRouter.get('/', async (c) => {
|
||||
* GET /providers/:id - Get provider detail
|
||||
*/
|
||||
providersRouter.get('/:id', async (c) => {
|
||||
const core = await getCoreModule();
|
||||
if (!core) {
|
||||
return c.json({ success: false, error: 'Core module not available' }, 503);
|
||||
}
|
||||
|
||||
const id = c.req.param('id');
|
||||
|
||||
try {
|
||||
const registry = core.getProviderRegistry();
|
||||
const registry = getProviderRegistry();
|
||||
const detail: ProviderDetail | undefined = registry.getDetail(id);
|
||||
|
||||
if (!detail) {
|
||||
@@ -158,15 +74,10 @@ providersRouter.get('/:id', async (c) => {
|
||||
* GET /providers/:id/models - Get provider's model list
|
||||
*/
|
||||
providersRouter.get('/:id/models', async (c) => {
|
||||
const core = await getCoreModule();
|
||||
if (!core) {
|
||||
return c.json({ success: false, error: 'Core module not available' }, 503);
|
||||
}
|
||||
|
||||
const id = c.req.param('id');
|
||||
|
||||
try {
|
||||
const registry = core.getProviderRegistry();
|
||||
const registry = getProviderRegistry();
|
||||
|
||||
if (!registry.has(id)) {
|
||||
return c.json({ success: false, error: `Provider not found: ${id}` }, 404);
|
||||
@@ -193,18 +104,13 @@ providersRouter.get('/:id/models', async (c) => {
|
||||
* POST /providers/:id/test - Test provider connection
|
||||
*/
|
||||
providersRouter.post('/:id/test', async (c) => {
|
||||
const core = await getCoreModule();
|
||||
if (!core) {
|
||||
return c.json({ success: false, error: 'Core module not available' }, 503);
|
||||
}
|
||||
|
||||
const id = c.req.param('id');
|
||||
|
||||
try {
|
||||
const body = await c.req.json().catch(() => ({}));
|
||||
const apiKey = body.apiKey as string | undefined;
|
||||
|
||||
const registry = core.getProviderRegistry();
|
||||
const registry = getProviderRegistry();
|
||||
const result: ConnectionTestResult = await registry.testConnection(id, apiKey);
|
||||
|
||||
return c.json({
|
||||
@@ -226,11 +132,6 @@ providersRouter.post('/:id/test', async (c) => {
|
||||
* POST /providers - Register custom provider
|
||||
*/
|
||||
providersRouter.post('/', async (c) => {
|
||||
const core = await getCoreModule();
|
||||
if (!core) {
|
||||
return c.json({ success: false, error: 'Core module not available' }, 503);
|
||||
}
|
||||
|
||||
try {
|
||||
const body: CustomProviderDefinition = await c.req.json();
|
||||
|
||||
@@ -245,7 +146,7 @@ providersRouter.post('/', async (c) => {
|
||||
);
|
||||
}
|
||||
|
||||
const registry = core.getProviderRegistry();
|
||||
const registry = getProviderRegistry();
|
||||
registry.registerCustom(body);
|
||||
await registry.saveConfig();
|
||||
|
||||
@@ -268,17 +169,12 @@ providersRouter.post('/', async (c) => {
|
||||
* PUT /providers/:id - Update provider config
|
||||
*/
|
||||
providersRouter.put('/:id', async (c) => {
|
||||
const core = await getCoreModule();
|
||||
if (!core) {
|
||||
return c.json({ success: false, error: 'Core module not available' }, 503);
|
||||
}
|
||||
|
||||
const id = c.req.param('id');
|
||||
|
||||
try {
|
||||
const body: ProviderConfig = await c.req.json();
|
||||
|
||||
const registry = core.getProviderRegistry();
|
||||
const registry = getProviderRegistry();
|
||||
|
||||
if (!registry.has(id)) {
|
||||
return c.json({ success: false, error: `Provider not found: ${id}` }, 404);
|
||||
@@ -306,15 +202,10 @@ providersRouter.put('/:id', async (c) => {
|
||||
* DELETE /providers/:id - Delete custom provider
|
||||
*/
|
||||
providersRouter.delete('/:id', async (c) => {
|
||||
const core = await getCoreModule();
|
||||
if (!core) {
|
||||
return c.json({ success: false, error: 'Core module not available' }, 503);
|
||||
}
|
||||
|
||||
const id = c.req.param('id');
|
||||
|
||||
try {
|
||||
const registry = core.getProviderRegistry();
|
||||
const registry = getProviderRegistry();
|
||||
const removed = registry.removeCustom(id);
|
||||
|
||||
if (!removed) {
|
||||
@@ -342,11 +233,6 @@ providersRouter.delete('/:id', async (c) => {
|
||||
* POST /providers/:id/models - Add custom model
|
||||
*/
|
||||
providersRouter.post('/:id/models', async (c) => {
|
||||
const core = await getCoreModule();
|
||||
if (!core) {
|
||||
return c.json({ success: false, error: 'Core module not available' }, 503);
|
||||
}
|
||||
|
||||
const providerId = c.req.param('id');
|
||||
|
||||
try {
|
||||
@@ -363,7 +249,7 @@ providersRouter.post('/:id/models', async (c) => {
|
||||
);
|
||||
}
|
||||
|
||||
const registry = core.getProviderRegistry();
|
||||
const registry = getProviderRegistry();
|
||||
registry.addCustomModel(providerId, body);
|
||||
await registry.saveConfig();
|
||||
|
||||
@@ -386,16 +272,11 @@ providersRouter.post('/:id/models', async (c) => {
|
||||
* DELETE /providers/:id/models/:modelId - Delete custom model
|
||||
*/
|
||||
providersRouter.delete('/:id/models/:modelId', async (c) => {
|
||||
const core = await getCoreModule();
|
||||
if (!core) {
|
||||
return c.json({ success: false, error: 'Core module not available' }, 503);
|
||||
}
|
||||
|
||||
const providerId = c.req.param('id');
|
||||
const modelId = c.req.param('modelId');
|
||||
|
||||
try {
|
||||
const registry = core.getProviderRegistry();
|
||||
const registry = getProviderRegistry();
|
||||
const removed = registry.removeCustomModel(providerId, modelId);
|
||||
|
||||
if (!removed) {
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
type MergedMessage,
|
||||
type MessagePart,
|
||||
} from '../types.js';
|
||||
import type { MessageInfo, Part, ToolPart } from '@ai-assistant/core';
|
||||
import { MessageStorage, PartStorage } from '@ai-assistant/core';
|
||||
|
||||
export const sessionsRouter = new Hono();
|
||||
|
||||
@@ -125,41 +127,6 @@ sessionsRouter.get('/:id/messages', async (c) => {
|
||||
}
|
||||
|
||||
try {
|
||||
// 动态导入 Core 存储 API
|
||||
const corePath = '@ai-assistant/core';
|
||||
type MessageInfo = {
|
||||
id: string;
|
||||
sessionId: string;
|
||||
role: 'user' | 'assistant';
|
||||
parentId?: string;
|
||||
createdAt: number;
|
||||
partIds: string[];
|
||||
};
|
||||
type Part = {
|
||||
id: string;
|
||||
createdAt: number;
|
||||
type: string;
|
||||
text?: string;
|
||||
toolCallId?: string;
|
||||
toolName?: string;
|
||||
state?: {
|
||||
status: 'pending' | 'running' | 'completed' | 'error';
|
||||
input?: Record<string, unknown>;
|
||||
output?: unknown;
|
||||
error?: string;
|
||||
time?: { start: number; end?: number };
|
||||
};
|
||||
};
|
||||
|
||||
const { MessageStorage, PartStorage } = (await import(/* webpackIgnore: true */ corePath)) as {
|
||||
MessageStorage: {
|
||||
listBySession(sessionId: string): Promise<MessageInfo[]>;
|
||||
};
|
||||
PartStorage: {
|
||||
getByIds(messageId: string, partIds: string[]): Promise<Part[]>;
|
||||
};
|
||||
};
|
||||
|
||||
// 获取消息列表(按创建时间排序)
|
||||
const messageInfos = await MessageStorage.listBySession(id);
|
||||
|
||||
@@ -178,17 +145,18 @@ sessionsRouter.get('/:id/messages', async (c) => {
|
||||
if (p.type === 'reasoning') {
|
||||
return { type: 'reasoning', id: p.id, text: p.text ?? '' };
|
||||
}
|
||||
// tool
|
||||
const state = p.state!;
|
||||
const startTime = state.time?.start;
|
||||
const endTime = state.time?.end;
|
||||
// tool - 使用类型断言
|
||||
const toolPart = p as ToolPart;
|
||||
const state = toolPart.state;
|
||||
const startTime = state.status !== 'pending' ? state.time?.start : undefined;
|
||||
const endTime = state.status === 'completed' || state.status === 'error' ? state.time?.end : undefined;
|
||||
return {
|
||||
type: 'tool',
|
||||
id: p.id,
|
||||
toolCallId: p.toolCallId ?? '',
|
||||
toolName: p.toolName ?? '',
|
||||
toolCallId: toolPart.toolCallId ?? '',
|
||||
toolName: toolPart.toolName ?? '',
|
||||
status: state.status,
|
||||
arguments: state.input ?? {},
|
||||
arguments: state.status !== 'pending' ? (state.input as Record<string, unknown>) : {},
|
||||
result: state.status === 'completed' ? state.output : undefined,
|
||||
error: state.status === 'error' ? state.error : undefined,
|
||||
duration: startTime && endTime ? endTime - startTime : undefined,
|
||||
@@ -203,15 +171,15 @@ sessionsRouter.get('/:id/messages', async (c) => {
|
||||
|
||||
// 兼容字段:提取工具调用
|
||||
const toolCalls: ToolCallInfo[] = parts
|
||||
.filter((p) => p.type === 'tool' && p.state)
|
||||
.filter((p): p is ToolPart => p.type === 'tool')
|
||||
.map((p) => {
|
||||
const state = p.state!;
|
||||
const startTime = state.time?.start;
|
||||
const endTime = state.time?.end;
|
||||
const state = p.state;
|
||||
const startTime = state.status !== 'pending' ? state.time?.start : undefined;
|
||||
const endTime = state.status === 'completed' || state.status === 'error' ? state.time?.end : undefined;
|
||||
return {
|
||||
id: p.toolCallId ?? '',
|
||||
name: p.toolName ?? '',
|
||||
arguments: state.input ?? {},
|
||||
arguments: state.status !== 'pending' ? (state.input as Record<string, unknown>) : {},
|
||||
status: state.status,
|
||||
result: state.status === 'completed' ? state.output : undefined,
|
||||
error: state.status === 'error' ? state.error : undefined,
|
||||
|
||||
@@ -6,68 +6,24 @@
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import type { Session, CreateSessionInput, SessionStatus } from '../types.js';
|
||||
|
||||
// ============================================================================
|
||||
// Core 模块接口定义(避免构建时依赖)
|
||||
// ============================================================================
|
||||
|
||||
interface SessionSummary {
|
||||
id: string;
|
||||
title: string;
|
||||
workdir: string;
|
||||
messageCount: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface ProjectMetadata {
|
||||
id: string;
|
||||
workdir: string;
|
||||
createdAt: string;
|
||||
isGitRepo: boolean;
|
||||
}
|
||||
|
||||
interface SessionData {
|
||||
id: string;
|
||||
projectId: string;
|
||||
parentId?: string;
|
||||
agentName?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
workdir: string;
|
||||
title?: string;
|
||||
messages: Array<{ role: string; content: unknown }>;
|
||||
discoveredTools: string[];
|
||||
todos: unknown[];
|
||||
}
|
||||
|
||||
interface SessionManagerInterface {
|
||||
init(workdir: string): Promise<SessionData>;
|
||||
getSession(): SessionData | null;
|
||||
getProject(): ProjectMetadata | null;
|
||||
listSessions(): Promise<SessionSummary[]>;
|
||||
listAllSessions(): Promise<SessionSummary[]>;
|
||||
deleteSession(sessionId: string): Promise<boolean>;
|
||||
newSession(workdir?: string): Promise<SessionData>;
|
||||
restoreSession(sessionId: string): Promise<SessionData | null>;
|
||||
getSessionId(): string | undefined;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SessionManager 类
|
||||
// ============================================================================
|
||||
import type {
|
||||
SessionData,
|
||||
SessionSummary,
|
||||
ProjectMetadata,
|
||||
} from '@ai-assistant/core';
|
||||
import { SessionManager as CoreSessionManager } from '@ai-assistant/core';
|
||||
|
||||
export class SessionManager {
|
||||
private sessions: Map<string, Session> = new Map();
|
||||
private sessionProjects: Map<string, string> = new Map(); // sessionId -> projectId
|
||||
private coreManager: SessionManagerInterface | null = null;
|
||||
private coreManager: CoreSessionManager | null = null;
|
||||
private currentProject: ProjectMetadata | null = null;
|
||||
private initialized = false;
|
||||
|
||||
/**
|
||||
* 获取 Core SessionManager 实例(供外部使用)
|
||||
*/
|
||||
getCoreManager(): SessionManagerInterface | null {
|
||||
getCoreManager(): CoreSessionManager | null {
|
||||
return this.coreManager;
|
||||
}
|
||||
|
||||
@@ -85,13 +41,7 @@ export class SessionManager {
|
||||
if (this.initialized) return;
|
||||
|
||||
try {
|
||||
// 动态导入 Core 模块,避免构建时依赖
|
||||
const corePath = '@ai-assistant/core';
|
||||
const core = (await import(/* webpackIgnore: true */ corePath)) as {
|
||||
SessionManager: new (storageDir?: string) => SessionManagerInterface;
|
||||
};
|
||||
|
||||
this.coreManager = new core.SessionManager();
|
||||
this.coreManager = new CoreSessionManager();
|
||||
await this.coreManager.init(process.cwd());
|
||||
|
||||
this.currentProject = this.coreManager.getProject();
|
||||
|
||||
@@ -34,15 +34,7 @@ const mockAgentModule = vi.hoisted(() => ({
|
||||
isPresetAgent: vi.fn((name: string) => name in mockPresetAgents),
|
||||
}));
|
||||
|
||||
// Track if module should be available
|
||||
let moduleAvailable = true;
|
||||
|
||||
vi.mock('@ai-assistant/core', () => {
|
||||
if (!moduleAvailable) {
|
||||
throw new Error('Module not found');
|
||||
}
|
||||
return mockAgentModule;
|
||||
});
|
||||
vi.mock('@ai-assistant/core', () => mockAgentModule);
|
||||
|
||||
vi.mock('../../../src/routes/config.js', () => ({
|
||||
getConfig: vi.fn(() => ({ workdir: '/test/workdir' })),
|
||||
@@ -57,7 +49,6 @@ app.route('/agents', agentsRouter);
|
||||
describe('Agents Route', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
moduleAvailable = true;
|
||||
mockAgentModule.loadAgentConfig.mockResolvedValue(null);
|
||||
mockAgentModule.saveAgentConfig.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
@@ -35,15 +35,7 @@ const mockCommandModule = vi.hoisted(() => ({
|
||||
createCommandManager: vi.fn(() => mockCommandManager),
|
||||
}));
|
||||
|
||||
// Track if module should be available
|
||||
let moduleAvailable = true;
|
||||
|
||||
vi.mock('@ai-assistant/core', () => {
|
||||
if (!moduleAvailable) {
|
||||
throw new Error('Module not found');
|
||||
}
|
||||
return mockCommandModule;
|
||||
});
|
||||
vi.mock('@ai-assistant/core', () => mockCommandModule);
|
||||
|
||||
vi.mock('../../../src/routes/config.js', () => ({
|
||||
getConfig: vi.fn(() => ({ workdir: '/test/workdir' })),
|
||||
@@ -58,7 +50,6 @@ app.route('/commands', commandsRouter);
|
||||
describe('Commands Route', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
moduleAvailable = true;
|
||||
mockCommandRegistry.list.mockReturnValue([]);
|
||||
mockCommandRegistry.getStats.mockReturnValue({ total: 0, bySource: {} });
|
||||
});
|
||||
|
||||
@@ -15,15 +15,7 @@ const mockHooksModule = vi.hoisted(() => ({
|
||||
createDefaultConfig: vi.fn(),
|
||||
}));
|
||||
|
||||
// Track if module should be available
|
||||
let moduleAvailable = true;
|
||||
|
||||
vi.mock('@ai-assistant/core', () => {
|
||||
if (!moduleAvailable) {
|
||||
throw new Error('Module not found');
|
||||
}
|
||||
return mockHooksModule;
|
||||
});
|
||||
vi.mock('@ai-assistant/core', () => mockHooksModule);
|
||||
|
||||
vi.mock('../../../src/routes/config.js', () => ({
|
||||
getConfig: vi.fn(() => ({ workdir: '/test/workdir' })),
|
||||
@@ -44,7 +36,6 @@ app.route('/hooks', hooksRouter);
|
||||
describe('Hooks Route', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
moduleAvailable = true;
|
||||
mockHooksModule.loadHookConfig.mockResolvedValue(null);
|
||||
mockHooksModule.getConfigFilePath.mockResolvedValue('/test/workdir/.ai-assistant.json');
|
||||
});
|
||||
|
||||
@@ -26,15 +26,7 @@ const mockCoreModule = vi.hoisted(() => ({
|
||||
getProviderRegistry: vi.fn(() => mockRegistry),
|
||||
}));
|
||||
|
||||
// Track if core module should be available
|
||||
let coreModuleAvailable = true;
|
||||
|
||||
vi.mock('@ai-assistant/core', () => {
|
||||
if (!coreModuleAvailable) {
|
||||
throw new Error('Module not found');
|
||||
}
|
||||
return mockCoreModule;
|
||||
});
|
||||
vi.mock('@ai-assistant/core', () => mockCoreModule);
|
||||
|
||||
import { providersRouter } from '../../../src/routes/providers.js';
|
||||
|
||||
@@ -45,7 +37,6 @@ app.route('/providers', providersRouter);
|
||||
describe('Providers Route', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
coreModuleAvailable = true;
|
||||
});
|
||||
|
||||
describe('GET /providers - 列出所有提供商', () => {
|
||||
|
||||
Reference in New Issue
Block a user