@maidang1/hataraku 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/.claude/CLAUDE.md +21 -0
  2. package/.codex/skills/beautiful-mermaid/SKILL.md +171 -0
  3. package/.codex/skills/beautiful-mermaid/references/mermaid-syntax.md +235 -0
  4. package/.codex/skills/beautiful-mermaid/scripts/create-html.ts +177 -0
  5. package/.codex/skills/beautiful-mermaid/scripts/render.ts +221 -0
  6. package/.codex/skills/find-skills/SKILL.md +133 -0
  7. package/.github/workflows/publish-github-packages.yml +58 -0
  8. package/.github/workflows/publish-npm.yml +46 -0
  9. package/.vscode/settings.json +2 -0
  10. package/AGENTS.md +41 -0
  11. package/LICENSE +21 -0
  12. package/README.md +119 -0
  13. package/bun.lock +327 -0
  14. package/docs/agent/architecture.md +28 -0
  15. package/docs/agent/development_commands.md +6 -0
  16. package/docs/plan/agent-plan-2026-02-05.md +136 -0
  17. package/docs/plan/core-agent-sdk-structure-2026-02-07.md +156 -0
  18. package/docs/plan/implementation-summary.md +303 -0
  19. package/docs/plan/mcp-2026-02-05.md +700 -0
  20. package/docs/plan/op.md +478 -0
  21. package/docs/plan/skills-2026-02-05.md +352 -0
  22. package/docs/plan/skills-flow.svg +120 -0
  23. package/docs/plan/tui-readability-2026-02-06.md +67 -0
  24. package/package.json +34 -0
  25. package/src/cli/index.tsx +4 -0
  26. package/src/cli/main.tsx +98 -0
  27. package/src/core/README.md +19 -0
  28. package/src/core/api/agent.ts +1 -0
  29. package/src/core/api/config.ts +1 -0
  30. package/src/core/api/index.ts +10 -0
  31. package/src/core/api/integrations.ts +1 -0
  32. package/src/core/api/observability.ts +1 -0
  33. package/src/core/api/policy.ts +1 -0
  34. package/src/core/api/providers.ts +1 -0
  35. package/src/core/api/runtime.ts +1 -0
  36. package/src/core/api/shared.ts +1 -0
  37. package/src/core/api/tools.ts +1 -0
  38. package/src/core/api/types.ts +1 -0
  39. package/src/core/index.ts +1 -0
  40. package/src/core/internal/config/defaults.ts +8 -0
  41. package/src/core/internal/config/index.ts +3 -0
  42. package/src/core/internal/config/loader.ts +97 -0
  43. package/src/core/internal/config/schema.ts +47 -0
  44. package/src/core/internal/integrations/index.ts +2 -0
  45. package/src/core/internal/integrations/mcp/connection-manager.ts +231 -0
  46. package/src/core/internal/integrations/mcp/health-checker.ts +91 -0
  47. package/src/core/internal/integrations/mcp/index.ts +197 -0
  48. package/src/core/internal/integrations/mcp/retry-strategy.ts +111 -0
  49. package/src/core/internal/integrations/mcp/tool-cache.ts +103 -0
  50. package/src/core/internal/integrations/mcp/transport.ts +58 -0
  51. package/src/core/internal/integrations/mcp/types.ts +95 -0
  52. package/src/core/internal/integrations/mcp/utils.ts +44 -0
  53. package/src/core/internal/integrations/skills/cache/index.ts +38 -0
  54. package/src/core/internal/integrations/skills/cache/interface.ts +9 -0
  55. package/src/core/internal/integrations/skills/cache/memory-cache.ts +118 -0
  56. package/src/core/internal/integrations/skills/config/defaults.ts +35 -0
  57. package/src/core/internal/integrations/skills/config/index.ts +71 -0
  58. package/src/core/internal/integrations/skills/config/schema.ts +31 -0
  59. package/src/core/internal/integrations/skills/core/errors.ts +36 -0
  60. package/src/core/internal/integrations/skills/core/events.ts +143 -0
  61. package/src/core/internal/integrations/skills/core/types.ts +83 -0
  62. package/src/core/internal/integrations/skills/dependency/conflict-detector.ts +126 -0
  63. package/src/core/internal/integrations/skills/dependency/graph.ts +91 -0
  64. package/src/core/internal/integrations/skills/dependency/resolver.ts +128 -0
  65. package/src/core/internal/integrations/skills/dependency/types.ts +51 -0
  66. package/src/core/internal/integrations/skills/discovery/index.ts +98 -0
  67. package/src/core/internal/integrations/skills/discovery/resolver.ts +39 -0
  68. package/src/core/internal/integrations/skills/discovery/scanner.ts +116 -0
  69. package/src/core/internal/integrations/skills/discovery/strategies/file-system.ts +16 -0
  70. package/src/core/internal/integrations/skills/index.ts +3 -0
  71. package/src/core/internal/integrations/skills/integration/lifecycle.ts +124 -0
  72. package/src/core/internal/integrations/skills/integration/mcp-loader.ts +100 -0
  73. package/src/core/internal/integrations/skills/integration/tool-mapper.ts +56 -0
  74. package/src/core/internal/integrations/skills/loaders/index.ts +5 -0
  75. package/src/core/internal/integrations/skills/loaders/skill-loader.ts +97 -0
  76. package/src/core/internal/integrations/skills/manager.ts +200 -0
  77. package/src/core/internal/integrations/skills/parsers/base.ts +134 -0
  78. package/src/core/internal/integrations/skills/parsers/factory.ts +42 -0
  79. package/src/core/internal/integrations/skills/parsers/index.ts +71 -0
  80. package/src/core/internal/integrations/skills/parsers/markdown.ts +111 -0
  81. package/src/core/internal/integrations/skills/parsers/yaml-metadata.ts +49 -0
  82. package/src/core/internal/integrations/skills/types.ts +15 -0
  83. package/src/core/internal/integrations/skills/utils/fs.ts +59 -0
  84. package/src/core/internal/integrations/skills/utils/logger.ts +109 -0
  85. package/src/core/internal/integrations/skills/utils/path.ts +27 -0
  86. package/src/core/internal/integrations/skills/validation/index.ts +43 -0
  87. package/src/core/internal/integrations/skills/validation/schema.ts +37 -0
  88. package/src/core/internal/integrations/skills/validation/skill-validator.ts +56 -0
  89. package/src/core/internal/observability/index.ts +2 -0
  90. package/src/core/internal/observability/logging/env.ts +32 -0
  91. package/src/core/internal/observability/logging/export.ts +55 -0
  92. package/src/core/internal/observability/logging/index.ts +4 -0
  93. package/src/core/internal/observability/logging/session-logger.ts +54 -0
  94. package/src/core/internal/observability/logging/types.ts +53 -0
  95. package/src/core/internal/policy/index.ts +1 -0
  96. package/src/core/internal/policy/safety/index.ts +2 -0
  97. package/src/core/internal/policy/safety/policy.ts +96 -0
  98. package/src/core/internal/policy/safety/types.ts +24 -0
  99. package/src/core/internal/providers/anthropic/client.ts +20 -0
  100. package/src/core/internal/providers/anthropic/index.ts +1 -0
  101. package/src/core/internal/providers/index.ts +1 -0
  102. package/src/core/internal/sdk/agent/agent.ts +691 -0
  103. package/src/core/internal/sdk/agent/index.ts +3 -0
  104. package/src/core/internal/sdk/agent/session.ts +9 -0
  105. package/src/core/internal/sdk/agent/tool-loop.ts +10 -0
  106. package/src/core/internal/sdk/index.ts +3 -0
  107. package/src/core/internal/sdk/runtime/context.ts +1 -0
  108. package/src/core/internal/sdk/runtime/errors.ts +9 -0
  109. package/src/core/internal/sdk/runtime/execution.ts +12 -0
  110. package/src/core/internal/sdk/runtime/index.ts +3 -0
  111. package/src/core/internal/sdk/types/api.ts +4 -0
  112. package/src/core/internal/sdk/types/index.ts +1 -0
  113. package/src/core/internal/sdk/types/internal.ts +1 -0
  114. package/src/core/internal/shared/fs.ts +10 -0
  115. package/src/core/internal/shared/index.ts +3 -0
  116. package/src/core/internal/shared/message.ts +12 -0
  117. package/src/core/internal/shared/path.ts +10 -0
  118. package/src/core/internal/tools/base/errors.ts +6 -0
  119. package/src/core/internal/tools/base/index.ts +3 -0
  120. package/src/core/internal/tools/base/schema.ts +1 -0
  121. package/src/core/internal/tools/base/tool.ts +42 -0
  122. package/src/core/internal/tools/builtins/architect.ts +45 -0
  123. package/src/core/internal/tools/builtins/bash.ts +135 -0
  124. package/src/core/internal/tools/builtins/fetch.ts +62 -0
  125. package/src/core/internal/tools/builtins/file-edit.ts +134 -0
  126. package/src/core/internal/tools/builtins/file-read.ts +75 -0
  127. package/src/core/internal/tools/builtins/fs.ts +254 -0
  128. package/src/core/internal/tools/builtins/glob.ts +75 -0
  129. package/src/core/internal/tools/builtins/grep.ts +104 -0
  130. package/src/core/internal/tools/builtins/index.ts +26 -0
  131. package/src/core/internal/tools/builtins/list-files.ts +64 -0
  132. package/src/core/internal/tools/builtins/search.ts +50 -0
  133. package/src/core/internal/tools/builtins/skills.ts +127 -0
  134. package/src/core/internal/tools/builtins/todo.ts +121 -0
  135. package/src/core/internal/tools/guards/file-edit-cache.ts +21 -0
  136. package/src/core/internal/tools/guards/limits.ts +43 -0
  137. package/src/core/internal/tools/index.ts +39 -0
  138. package/src/core/internal/tools/registry/index.ts +2 -0
  139. package/src/core/internal/tools/registry/presets.ts +28 -0
  140. package/src/core/internal/tools/registry/registry.ts +21 -0
  141. package/src/index.ts +3 -0
  142. package/src/render/commands/index.ts +113 -0
  143. package/src/render/commands/init.ts +45 -0
  144. package/src/render/components/ActivityPane.tsx +67 -0
  145. package/src/render/components/ChatBubble.tsx +58 -0
  146. package/src/render/components/ConfirmCard.tsx +100 -0
  147. package/src/render/components/ConfirmSelectMenu.tsx +56 -0
  148. package/src/render/components/ConversationPane.tsx +65 -0
  149. package/src/render/components/EventTimeline.tsx +30 -0
  150. package/src/render/components/MarkdownText.tsx +139 -0
  151. package/src/render/components/SlashCommandMenu.tsx +68 -0
  152. package/src/render/components/Spinner.tsx +18 -0
  153. package/src/render/components/StatusBar.tsx +72 -0
  154. package/src/render/components/Timeline.tsx +57 -0
  155. package/src/render/components/TimelineEvent.tsx +313 -0
  156. package/src/render/components/ToolCard.tsx +126 -0
  157. package/src/render/components/formatters/confirm.test.ts +34 -0
  158. package/src/render/components/formatters/confirm.ts +32 -0
  159. package/src/render/index.tsx +466 -0
  160. package/src/render/state/events.ts +301 -0
  161. package/src/render/state/history.ts +5 -0
  162. package/src/render/state/loading.ts +18 -0
  163. package/src/render/state/message.tsx +35 -0
  164. package/src/render/state/store.ts +7 -0
  165. package/src/render/theme.ts +52 -0
  166. package/test-e2e.ts +250 -0
  167. package/tsconfig.json +29 -0
@@ -0,0 +1,197 @@
1
+ import type Anthropic from "@anthropic-ai/sdk";
2
+ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
+ import type { Tool as McpToolDefinition } from "@modelcontextprotocol/sdk/types.js";
4
+ import { getEffectiveConfig, type McpServerConfig } from "../../config";
5
+ import { Tool } from "../../tools/base";
6
+ import type { ToolExecutionContext } from "../../tools/base";
7
+ import { ConnectionManager } from "./connection-manager";
8
+ import { McpToolCache } from "./tool-cache";
9
+ import { ConnectionState } from "./types";
10
+ import { MCP_CONSTANTS, normalizeError, createTimeoutPromise } from "./utils";
11
+
12
+ class McpTool extends Tool {
13
+ name: string;
14
+ description: string;
15
+ readonly = false;
16
+ private client: Client;
17
+ private toolName: string;
18
+ private inputSchema: Anthropic.Tool["input_schema"];
19
+ private config: McpServerConfig;
20
+
21
+ constructor(params: {
22
+ serverName: string;
23
+ tool: McpToolDefinition;
24
+ client: Client;
25
+ config: McpServerConfig;
26
+ }) {
27
+ super();
28
+ const { serverName, tool, client, config } = params;
29
+ this.name = `${serverName}.${tool.name}`;
30
+ this.description = tool.description ?? `MCP tool ${tool.name} from ${serverName}`;
31
+ this.client = client;
32
+ this.toolName = tool.name;
33
+ this.config = config;
34
+ this.inputSchema =
35
+ (tool.inputSchema as Anthropic.Tool["input_schema"]) ?? {
36
+ type: "object",
37
+ properties: {},
38
+ };
39
+ }
40
+
41
+ getSchema(): Anthropic.Tool {
42
+ return {
43
+ name: this.name,
44
+ description: this.description,
45
+ input_schema: this.inputSchema,
46
+ };
47
+ }
48
+
49
+ async execute(
50
+ input: Record<string, unknown>,
51
+ _context: ToolExecutionContext,
52
+ ): Promise<string> {
53
+ const timeoutMs = this.config.toolTimeoutSec
54
+ ? this.config.toolTimeoutSec * 1000
55
+ : MCP_CONSTANTS.DEFAULT_TOOL_TIMEOUT_SEC * 1000;
56
+
57
+ const result = await Promise.race([
58
+ this.client.callTool({
59
+ name: this.toolName,
60
+ arguments: input ?? {},
61
+ }),
62
+ createTimeoutPromise(timeoutMs, `Tool execution timeout after ${timeoutMs}ms`),
63
+ ]);
64
+
65
+ return formatToolResult(result);
66
+ }
67
+ }
68
+
69
+ function formatToolResult(result: unknown): string {
70
+ if (!result) return "";
71
+
72
+ const resultObj = result as Record<string, unknown>;
73
+ const content = resultObj.content ?? result;
74
+
75
+ if (typeof content === "string") return content;
76
+
77
+ if (Array.isArray(content)) {
78
+ const parts = content.map((part: unknown) => {
79
+ const partObj = part as Record<string, unknown> | null;
80
+ if (partObj?.type === "text" && typeof partObj.text === "string") {
81
+ return partObj.text;
82
+ }
83
+ return JSON.stringify(part, null, 2);
84
+ });
85
+ return parts.join("\n");
86
+ }
87
+
88
+ return JSON.stringify(content, null, 2);
89
+ }
90
+
91
+ /**
92
+ * 过滤工具列表
93
+ */
94
+ function filterTools(
95
+ tools: McpToolDefinition[],
96
+ config: McpServerConfig
97
+ ): McpToolDefinition[] {
98
+ let filtered = tools;
99
+
100
+ // 白名单过滤
101
+ if (config.enabledTools && config.enabledTools.length > 0) {
102
+ filtered = filtered.filter((tool) =>
103
+ config.enabledTools!.includes(tool.name)
104
+ );
105
+ }
106
+
107
+ // 黑名单过滤
108
+ if (config.disabledTools && config.disabledTools.length > 0) {
109
+ filtered = filtered.filter((tool) =>
110
+ !config.disabledTools!.includes(tool.name)
111
+ );
112
+ }
113
+
114
+ return filtered;
115
+ }
116
+
117
+ export async function loadMcpTools(
118
+ mcpServersOverride?: Record<string, McpServerConfig>,
119
+ hooks?: {
120
+ onServerConnectStart?: (serverName: string) => void;
121
+ onServerConnectSuccess?: (serverName: string, toolCount: number) => void;
122
+ onServerConnectError?: (serverName: string, error: Error) => void;
123
+ onReconnectAttempt?: (serverName: string, attempt: number, maxRetries: number) => void;
124
+ onHealthCheck?: (serverName: string, latency: number, healthy: boolean) => void;
125
+ onCacheHit?: (serverName: string) => void;
126
+ }
127
+ ): Promise<{
128
+ tools: Tool[];
129
+ cleanup: () => Promise<void>;
130
+ connectionManager: ConnectionManager;
131
+ }> {
132
+ const { mcpServers } = getEffectiveConfig();
133
+ const mcpServersToUse = mcpServersOverride ?? mcpServers;
134
+ const tools: Tool[] = [];
135
+ const connectionManager = new ConnectionManager();
136
+ const toolCache = new McpToolCache();
137
+
138
+ // 设置事件监听
139
+ connectionManager.on("reconnectAttempt", (serverName, attempt, maxRetries) => {
140
+ hooks?.onReconnectAttempt?.(serverName, attempt, maxRetries);
141
+ });
142
+
143
+ connectionManager.on("healthCheckCompleted", (serverName, latency, healthy) => {
144
+ hooks?.onHealthCheck?.(serverName, latency, healthy);
145
+ });
146
+
147
+ const serverEntries = Object.entries(mcpServersToUse);
148
+ for (const [serverName, config] of serverEntries) {
149
+ // 检查是否启用
150
+ if (config.enabled === false) {
151
+ continue;
152
+ }
153
+
154
+ try {
155
+ hooks?.onServerConnectStart?.(serverName);
156
+
157
+ // 连接到服务器
158
+ const client = await connectionManager.connect(serverName, config);
159
+
160
+ // 尝试从缓存获取工具列表
161
+ let serverTools = toolCache.get(serverName);
162
+ if (serverTools) {
163
+ hooks?.onCacheHit?.(serverName);
164
+ } else {
165
+ // 从服务器获取工具列表
166
+ const result = await client.listTools();
167
+ serverTools = result.tools ?? [];
168
+ toolCache.set(serverName, serverTools);
169
+ }
170
+
171
+ // 过滤工具
172
+ const filteredTools = filterTools(serverTools, config);
173
+
174
+ // 创建工具实例
175
+ for (const tool of filteredTools) {
176
+ tools.push(new McpTool({ serverName, tool, client, config }));
177
+ }
178
+
179
+ hooks?.onServerConnectSuccess?.(serverName, filteredTools.length);
180
+ } catch (error) {
181
+ const err = normalizeError(error);
182
+ console.warn(`Failed to load MCP server "${serverName}": ${err.message}`);
183
+ hooks?.onServerConnectError?.(serverName, err);
184
+ }
185
+ }
186
+
187
+ const cleanup = async () => {
188
+ toolCache.stopCleanup();
189
+ await connectionManager.disconnectAll();
190
+ };
191
+
192
+ return { tools, cleanup, connectionManager };
193
+ }
194
+
195
+ // 导出类型和工具
196
+ export { ConnectionManager, McpToolCache, ConnectionState };
197
+ export type { McpServerConfig };
@@ -0,0 +1,111 @@
1
+ import type { RetryConfig } from "./types";
2
+ import { ErrorType } from "./types";
3
+ import { MCP_CONSTANTS, normalizeError } from "./utils";
4
+
5
+ /**
6
+ * 可重试错误的正则模式
7
+ */
8
+ const RETRYABLE_PATTERNS =
9
+ /econnrefused|etimedout|enotfound|socket hang up|network error|connection timeout|connection reset/i;
10
+
11
+ /**
12
+ * 不可重试错误的正则模式
13
+ */
14
+ const NON_RETRYABLE_PATTERNS =
15
+ /unauthorized|forbidden|authentication|invalid config|protocol error/i;
16
+
17
+ /**
18
+ * 重试策略 - 使用指数退避算法
19
+ */
20
+ export class RetryStrategy {
21
+ private config: RetryConfig;
22
+
23
+ constructor(config?: Partial<RetryConfig>) {
24
+ this.config = {
25
+ maxRetries: config?.maxRetries ?? MCP_CONSTANTS.DEFAULT_MAX_RETRIES,
26
+ initialDelay: config?.initialDelay ?? MCP_CONSTANTS.DEFAULT_INITIAL_RETRY_DELAY_MS,
27
+ maxDelay: config?.maxDelay ?? MCP_CONSTANTS.DEFAULT_MAX_RETRY_DELAY_MS,
28
+ jitterFactor: config?.jitterFactor ?? MCP_CONSTANTS.DEFAULT_JITTER_FACTOR,
29
+ };
30
+ }
31
+
32
+ /**
33
+ * 计算下次重试的延迟时间(毫秒)
34
+ * 使用指数退避算法:delay = min(initialDelay * 2^attempt, maxDelay) + jitter
35
+ */
36
+ getDelay(attempt: number): number {
37
+ const exponentialDelay = this.config.initialDelay * Math.pow(2, attempt);
38
+ const cappedDelay = Math.min(exponentialDelay, this.config.maxDelay);
39
+
40
+ // 添加抖动避免雷鸣群效应
41
+ const jitter = cappedDelay * this.config.jitterFactor * Math.random();
42
+
43
+ return Math.floor(cappedDelay + jitter);
44
+ }
45
+
46
+ /**
47
+ * 判断是否应该重试
48
+ */
49
+ shouldRetry(attempt: number, error: Error): boolean {
50
+ if (attempt >= this.config.maxRetries) {
51
+ return false;
52
+ }
53
+
54
+ return this.classifyError(error) === ErrorType.RETRYABLE;
55
+ }
56
+
57
+ /**
58
+ * 分类错误类型
59
+ */
60
+ private classifyError(error: Error): ErrorType {
61
+ const message = error.message;
62
+
63
+ // 可重试的网络错误
64
+ if (RETRYABLE_PATTERNS.test(message)) {
65
+ return ErrorType.RETRYABLE;
66
+ }
67
+
68
+ // 不可重试的错误
69
+ if (NON_RETRYABLE_PATTERNS.test(message)) {
70
+ return ErrorType.NON_RETRYABLE;
71
+ }
72
+
73
+ // 默认可重试
74
+ return ErrorType.RETRYABLE;
75
+ }
76
+
77
+ /**
78
+ * 执行带重试的操作
79
+ */
80
+ async executeWithRetry<T>(
81
+ operation: () => Promise<T>,
82
+ onRetry?: (attempt: number, delay: number) => void
83
+ ): Promise<T> {
84
+ let lastError: Error | undefined;
85
+
86
+ for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
87
+ try {
88
+ return await operation();
89
+ } catch (error) {
90
+ const err = normalizeError(error);
91
+ lastError = err;
92
+
93
+ if (!this.shouldRetry(attempt, err)) {
94
+ throw err;
95
+ }
96
+
97
+ if (attempt < this.config.maxRetries) {
98
+ const delay = this.getDelay(attempt);
99
+ onRetry?.(attempt + 1, delay);
100
+ await this.sleep(delay);
101
+ }
102
+ }
103
+ }
104
+
105
+ throw lastError ?? new Error("Operation failed after retries");
106
+ }
107
+
108
+ private sleep(ms: number): Promise<void> {
109
+ return new Promise((resolve) => setTimeout(resolve, ms));
110
+ }
111
+ }
@@ -0,0 +1,103 @@
1
+ import type { Tool as McpToolDefinition } from "@modelcontextprotocol/sdk/types.js";
2
+ import { MCP_CONSTANTS } from "./utils";
3
+
4
+ /**
5
+ * MCP 工具缓存项
6
+ */
7
+ interface CacheEntry {
8
+ tools: McpToolDefinition[];
9
+ timestamp: number;
10
+ }
11
+
12
+ /**
13
+ * MCP 工具缓存
14
+ * 缓存 MCP 服务器的工具列表,减少重复的 listTools 调用
15
+ */
16
+ export class McpToolCache {
17
+ private cache: Map<string, CacheEntry> = new Map();
18
+ private ttl: number; // TTL in milliseconds
19
+ private cleanupInterval: NodeJS.Timeout | null = null;
20
+
21
+ constructor(ttlMinutes: number = MCP_CONSTANTS.DEFAULT_TOOL_CACHE_TTL_MINUTES) {
22
+ this.ttl = ttlMinutes * 60 * 1000;
23
+ this.startCleanup();
24
+ }
25
+
26
+ /**
27
+ * 获取缓存的工具列表
28
+ */
29
+ get(serverName: string): McpToolDefinition[] | null {
30
+ const entry = this.cache.get(serverName);
31
+ if (!entry) {
32
+ return null;
33
+ }
34
+
35
+ const now = Date.now();
36
+ if (now - entry.timestamp > this.ttl) {
37
+ // 缓存过期
38
+ this.cache.delete(serverName);
39
+ return null;
40
+ }
41
+
42
+ return entry.tools;
43
+ }
44
+
45
+ /**
46
+ * 设置缓存
47
+ */
48
+ set(serverName: string, tools: McpToolDefinition[]): void {
49
+ this.cache.set(serverName, {
50
+ tools,
51
+ timestamp: Date.now(),
52
+ });
53
+ }
54
+
55
+ /**
56
+ * 使缓存失效
57
+ */
58
+ invalidate(serverName: string): void {
59
+ this.cache.delete(serverName);
60
+ }
61
+
62
+ /**
63
+ * 清空所有缓存
64
+ */
65
+ clear(): void {
66
+ this.cache.clear();
67
+ }
68
+
69
+ /**
70
+ * 启动自动清理过期缓存
71
+ */
72
+ private startCleanup(): void {
73
+ // 每分钟清理一次过期缓存
74
+ this.cleanupInterval = setInterval(() => {
75
+ const now = Date.now();
76
+ for (const [serverName, entry] of this.cache.entries()) {
77
+ if (now - entry.timestamp > this.ttl) {
78
+ this.cache.delete(serverName);
79
+ }
80
+ }
81
+ }, MCP_CONSTANTS.CACHE_CLEANUP_INTERVAL_MS);
82
+ }
83
+
84
+ /**
85
+ * 停止自动清理
86
+ */
87
+ stopCleanup(): void {
88
+ if (this.cleanupInterval) {
89
+ clearInterval(this.cleanupInterval);
90
+ this.cleanupInterval = null;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * 获取缓存统计信息
96
+ */
97
+ getStats(): { size: number; entries: string[] } {
98
+ return {
99
+ size: this.cache.size,
100
+ entries: Array.from(this.cache.keys()),
101
+ };
102
+ }
103
+ }
@@ -0,0 +1,58 @@
1
+ import { URL } from "url";
2
+ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5
+ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
6
+ import type { McpServerConfig } from "./types";
7
+ import { MCP_CONSTANTS, createTimeoutPromise } from "./utils";
8
+
9
+ /**
10
+ * 创建传输层
11
+ */
12
+ export function createTransport(config: McpServerConfig): Transport {
13
+ if (config.command) {
14
+ return new StdioClientTransport({
15
+ command: config.command,
16
+ args: config.args ?? [],
17
+ env: config.env,
18
+ cwd: config.cwd,
19
+ });
20
+ }
21
+
22
+ if (config.url) {
23
+ const headers = { ...config.headers };
24
+
25
+ // 添加认证 header
26
+ if (config.auth) {
27
+ if (config.auth.type === "bearer" && config.auth.token) {
28
+ headers["Authorization"] = `Bearer ${config.auth.token}`;
29
+ } else if (config.auth.type === "basic" && config.auth.username && config.auth.password) {
30
+ const credentials = Buffer.from(
31
+ `${config.auth.username}:${config.auth.password}`
32
+ ).toString("base64");
33
+ headers["Authorization"] = `Basic ${credentials}`;
34
+ }
35
+ // OAuth 需要更复杂的实现,暂时不支持
36
+ }
37
+
38
+ return new StreamableHTTPClientTransport(new URL(config.url), {
39
+ requestInit: Object.keys(headers).length > 0 ? { headers } : undefined,
40
+ });
41
+ }
42
+
43
+ throw new Error("MCP server config must include command or url.");
44
+ }
45
+
46
+ /**
47
+ * 带超时的连接
48
+ */
49
+ export async function connectWithTimeout(
50
+ client: Client,
51
+ transport: Transport,
52
+ timeoutSec: number = MCP_CONSTANTS.DEFAULT_STARTUP_TIMEOUT_SEC
53
+ ): Promise<void> {
54
+ await Promise.race([
55
+ client.connect(transport),
56
+ createTimeoutPromise(timeoutSec * 1000, `Connection timeout after ${timeoutSec}s`),
57
+ ]);
58
+ }
@@ -0,0 +1,95 @@
1
+ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
3
+
4
+ /**
5
+ * MCP 服务器配置
6
+ */
7
+ export interface McpServerConfig {
8
+ // 传输方式配置
9
+ command?: string;
10
+ args?: string[];
11
+ env?: Record<string, string>;
12
+ cwd?: string;
13
+ url?: string;
14
+ headers?: Record<string, string>;
15
+
16
+ // 启用/禁用控制
17
+ enabled?: boolean; // 默认 true
18
+
19
+ // 超时配置
20
+ startupTimeoutSec?: number; // 启动超时(默认 30s)
21
+ toolTimeoutSec?: number; // 工具调用超时(默认 60s)
22
+
23
+ // 工具过滤
24
+ enabledTools?: string[]; // 工具白名单
25
+ disabledTools?: string[]; // 工具黑名单
26
+
27
+ // 重连配置
28
+ maxRetries?: number; // 最大重试次数(默认 3)
29
+ retryDelay?: number; // 初始重试延迟(默认 1000ms)
30
+
31
+ // 健康检查配置
32
+ healthCheckInterval?: number; // 健康检查间隔(默认 60000ms)
33
+
34
+ // 认证配置
35
+ auth?: {
36
+ type: "bearer" | "oauth" | "basic";
37
+ token?: string;
38
+ username?: string;
39
+ password?: string;
40
+ };
41
+ }
42
+
43
+ /**
44
+ * 连接状态
45
+ */
46
+ export enum ConnectionState {
47
+ DISCONNECTED = "DISCONNECTED",
48
+ CONNECTING = "CONNECTING",
49
+ CONNECTED = "CONNECTED",
50
+ RECONNECTING = "RECONNECTING",
51
+ FAILED = "FAILED",
52
+ }
53
+
54
+ /**
55
+ * 管理的连接
56
+ */
57
+ export interface ManagedConnection {
58
+ serverName: string;
59
+ client: Client;
60
+ transport: Transport;
61
+ config: McpServerConfig;
62
+ state: ConnectionState;
63
+ lastError?: Error;
64
+ retryCount: number;
65
+ connectedAt?: Date;
66
+ lastHealthCheck?: Date;
67
+ }
68
+
69
+ /**
70
+ * 连接事件
71
+ */
72
+ export interface ConnectionEvents {
73
+ connectionStateChanged: (serverName: string, state: ConnectionState) => void;
74
+ connectionError: (serverName: string, error: Error) => void;
75
+ reconnectAttempt: (serverName: string, attempt: number, maxRetries: number) => void;
76
+ healthCheckCompleted: (serverName: string, latency: number, healthy: boolean) => void;
77
+ }
78
+
79
+ /**
80
+ * 重试策略配置
81
+ */
82
+ export interface RetryConfig {
83
+ maxRetries: number;
84
+ initialDelay: number;
85
+ maxDelay: number;
86
+ jitterFactor: number;
87
+ }
88
+
89
+ /**
90
+ * 错误类型
91
+ */
92
+ export enum ErrorType {
93
+ RETRYABLE = "RETRYABLE",
94
+ NON_RETRYABLE = "NON_RETRYABLE",
95
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * MCP 模块共享常量
3
+ */
4
+ export const MCP_CONSTANTS = {
5
+ DEFAULT_CLIENT_NAME: "hataraku",
6
+ DEFAULT_CLIENT_VERSION: "1.0.0",
7
+ DEFAULT_STARTUP_TIMEOUT_SEC: 30,
8
+ DEFAULT_TOOL_TIMEOUT_SEC: 60,
9
+ DEFAULT_MAX_RETRIES: 3,
10
+ DEFAULT_INITIAL_RETRY_DELAY_MS: 1000,
11
+ DEFAULT_MAX_RETRY_DELAY_MS: 30000,
12
+ DEFAULT_JITTER_FACTOR: 0.1,
13
+ DEFAULT_TOOL_CACHE_TTL_MINUTES: 5,
14
+ DEFAULT_HEALTH_CHECK_INTERVAL_MS: 60000,
15
+ CACHE_CLEANUP_INTERVAL_MS: 60000,
16
+ } as const;
17
+
18
+ /**
19
+ * 将未知错误规范化为 Error 对象
20
+ * @param error 未知错误
21
+ * @param context 可选的上下文信息
22
+ */
23
+ export function normalizeError(error: unknown, context?: string): Error {
24
+ if (error instanceof Error) {
25
+ return error;
26
+ }
27
+
28
+ const message = context
29
+ ? `${context}: ${String(error)}`
30
+ : `Unknown error: ${String(error)}`;
31
+
32
+ return new Error(message);
33
+ }
34
+
35
+ /**
36
+ * 创建超时 Promise
37
+ * @param ms 超时时间(毫秒)
38
+ * @param message 超时错误消息
39
+ */
40
+ export function createTimeoutPromise(ms: number, message: string): Promise<never> {
41
+ return new Promise<never>((_, reject) =>
42
+ setTimeout(() => reject(new Error(message)), ms)
43
+ );
44
+ }
@@ -0,0 +1,38 @@
1
+ import type { ICache } from "./interface";
2
+ import type { SkillLoadOutcome } from "../core/types";
3
+ import { MemoryCache } from "./memory-cache";
4
+
5
+ export type { ICache };
6
+ export { MemoryCache };
7
+
8
+ export class CacheManager {
9
+ private cache: ICache;
10
+
11
+ constructor(ttl: number = 300000, enabled: boolean = true) {
12
+ this.cache = new MemoryCache(ttl, enabled);
13
+ }
14
+
15
+ public get(key: string): SkillLoadOutcome | null {
16
+ return this.cache.get(key);
17
+ }
18
+
19
+ public set(key: string, value: SkillLoadOutcome): void {
20
+ this.cache.set(key, value);
21
+ }
22
+
23
+ public has(key: string): boolean {
24
+ return this.cache.has(key);
25
+ }
26
+
27
+ public delete(key: string): void {
28
+ this.cache.delete(key);
29
+ }
30
+
31
+ public clear(): void {
32
+ this.cache.clear();
33
+ }
34
+
35
+ public setCache(cache: ICache): void {
36
+ this.cache = cache;
37
+ }
38
+ }
@@ -0,0 +1,9 @@
1
+ import type { SkillLoadOutcome } from "../core/types";
2
+
3
+ export interface ICache {
4
+ get(key: string): SkillLoadOutcome | null;
5
+ set(key: string, value: SkillLoadOutcome): void;
6
+ has(key: string): boolean;
7
+ delete(key: string): void;
8
+ clear(): void;
9
+ }