thatgfsj-code 0.2.1 → 0.2.2

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 (64) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/DEVELOPMENT.md +286 -0
  3. package/README.md +131 -30
  4. package/dist/core/ai-engine.d.ts +17 -0
  5. package/dist/core/ai-engine.d.ts.map +1 -1
  6. package/dist/core/ai-engine.js +23 -7
  7. package/dist/core/ai-engine.js.map +1 -1
  8. package/dist/index.js +18 -27
  9. package/dist/index.js.map +1 -1
  10. package/dist/repl/input.d.ts +8 -0
  11. package/dist/repl/input.d.ts.map +1 -1
  12. package/dist/repl/input.js +12 -0
  13. package/dist/repl/input.js.map +1 -1
  14. package/dist/repl/loop.d.ts +56 -0
  15. package/dist/repl/loop.d.ts.map +1 -1
  16. package/dist/repl/loop.js +333 -4
  17. package/dist/repl/loop.js.map +1 -1
  18. package/dist/repl/output.d.ts.map +1 -1
  19. package/dist/repl/output.js +3 -1
  20. package/dist/repl/output.js.map +1 -1
  21. package/dist/repl/welcome.js +1 -1
  22. package/dist/repl/welcome.js.map +1 -1
  23. package/docs/API_KEY_GUIDE.md +6 -0
  24. package/docs/FAQ.md +25 -3
  25. package/package.json +35 -3
  26. package/.patches/0001-fix-repl-replace-readline-with-inquirer-input-for-ke.patch +0 -279
  27. package/.patches/0002-fix-repl-stream-AI-output-directly-to-stdout-so-term.patch +0 -564
  28. package/.patches/0003-fix-session-break-the-hallucination-loop-in-assistan.patch +0 -194
  29. package/.patches/0004-chore-release-bump-version-to-0.2.1.patch +0 -24
  30. package/ROADMAP.md +0 -107
  31. package/src/agent/core.ts +0 -179
  32. package/src/agent/index.ts +0 -8
  33. package/src/agent/intent.ts +0 -181
  34. package/src/agent/streaming.ts +0 -132
  35. package/src/core/ai-engine.ts +0 -437
  36. package/src/core/cli.ts +0 -171
  37. package/src/core/config.ts +0 -147
  38. package/src/core/context-compactor.ts +0 -245
  39. package/src/core/hooks.ts +0 -196
  40. package/src/core/permissions.ts +0 -308
  41. package/src/core/session.ts +0 -165
  42. package/src/core/skills.ts +0 -208
  43. package/src/core/state.ts +0 -120
  44. package/src/core/subagent.ts +0 -195
  45. package/src/core/system-prompt.ts +0 -163
  46. package/src/core/tool-registry.ts +0 -157
  47. package/src/core/types.ts +0 -280
  48. package/src/index.ts +0 -544
  49. package/src/mcp/client.ts +0 -330
  50. package/src/repl/index.ts +0 -8
  51. package/src/repl/input.ts +0 -139
  52. package/src/repl/loop.ts +0 -280
  53. package/src/repl/output.ts +0 -222
  54. package/src/repl/welcome.ts +0 -296
  55. package/src/tools/file.ts +0 -117
  56. package/src/tools/git.ts +0 -132
  57. package/src/tools/index.ts +0 -48
  58. package/src/tools/search.ts +0 -263
  59. package/src/tools/shell.ts +0 -181
  60. package/src/utils/diff-preview.ts +0 -202
  61. package/src/utils/index.ts +0 -8
  62. package/src/utils/memory.ts +0 -223
  63. package/src/utils/project-context.ts +0 -207
  64. package/tsconfig.json +0 -19
package/src/mcp/client.ts DELETED
@@ -1,330 +0,0 @@
1
- /**
2
- * S07: MCP Client (Model Context Protocol)
3
- * Connect to MCP servers and expose their tools as agent tools
4
- *
5
- * MCP turns the agent from a closed system into an open platform.
6
- * Any server can expose tools via the MCP JSON-RPC 2.0 protocol.
7
- */
8
-
9
- import { spawn, ChildProcess } from 'child_process';
10
- import { Tool, ToolResult } from '../core/types.js';
11
-
12
- // ==================== MCP Types ====================
13
-
14
- interface MCPJsonRPCRequest {
15
- jsonrpc: '2.0';
16
- id: number | string;
17
- method: string;
18
- params?: any;
19
- }
20
-
21
- interface MCPJsonRPCResponse {
22
- jsonrpc: '2.0';
23
- id: number | string;
24
- result?: any;
25
- error?: { code: number; message: string; data?: any };
26
- }
27
-
28
- interface MCPTool {
29
- name: string;
30
- description: string;
31
- inputSchema: {
32
- type: 'object';
33
- properties: Record<string, any>;
34
- required?: string[];
35
- };
36
- }
37
-
38
- // ==================== MCP Client ====================
39
-
40
- export class MCPClient {
41
- private process: ChildProcess | null = null;
42
- private tools: Map<string, MCPTool> = new Map();
43
- private requestId = 0;
44
- private pendingRequests: Map<number | string, {
45
- resolve: (value: any) => void;
46
- reject: (reason: any) => void;
47
- }> = new Map();
48
- private name: string;
49
- private connected = false;
50
-
51
- constructor(name: string) {
52
- this.name = name;
53
- }
54
-
55
- /**
56
- * S07: Connect to an MCP server
57
- * @param command Command to run (e.g., 'npx', 'node')
58
- * @param args Arguments (e.g., ['-y', '@modelcontextprotocol/server-filesystem', '/path/to/dir'])
59
- */
60
- async connect(command: string, args: string[]): Promise<void> {
61
- return new Promise((resolve, reject) => {
62
- this.process = spawn(command, args, {
63
- stdio: ['pipe', 'pipe', 'pipe']
64
- });
65
-
66
- let buffer = '';
67
-
68
- this.process.stdout?.on('data', (data: Buffer) => {
69
- buffer += data.toString();
70
- const lines = buffer.split('\n');
71
- buffer = lines.pop() || '';
72
-
73
- for (const line of lines) {
74
- if (line.trim()) {
75
- this.handleMessage(line);
76
- }
77
- }
78
- });
79
-
80
- this.process.stderr?.on('data', (data: Buffer) => {
81
- console.error(`[MCP ${this.name}] stderr:`, data.toString().trim());
82
- });
83
-
84
- this.process.on('error', (err) => {
85
- reject(err);
86
- });
87
-
88
- this.process.on('close', (code) => {
89
- this.connected = false;
90
- console.log(`[MCP ${this.name}] exited with code ${code}`);
91
- });
92
-
93
- // Initialize MCP connection
94
- this.sendRequest('initialize', {
95
- protocolVersion: '2024-11-05',
96
- capabilities: { tools: {} },
97
- clientInfo: { name: 'thatgfsj-code', version: '1.0.0' }
98
- }).then(() => {
99
- this.connected = true;
100
- // Send initialized notification
101
- this.sendNotification('initialized', {});
102
- // List available tools
103
- return this.listTools();
104
- }).then(() => {
105
- resolve();
106
- }).catch(reject);
107
-
108
- // Timeout
109
- setTimeout(() => reject(new Error('MCP connection timeout')), 30000);
110
- });
111
- }
112
-
113
- /**
114
- * S07: Handle incoming JSON-RPC message
115
- */
116
- private handleMessage(raw: string): void {
117
- try {
118
- const msg: MCPJsonRPCResponse = JSON.parse(raw);
119
-
120
- // Handle response
121
- const pending = this.pendingRequests.get(msg.id);
122
- if (pending) {
123
- this.pendingRequests.delete(msg.id);
124
- if (msg.error) {
125
- pending.reject(new Error(msg.error.message));
126
- } else {
127
- pending.resolve(msg.result);
128
- }
129
- return;
130
- }
131
- } catch {
132
- // Not JSON, ignore
133
- }
134
- }
135
-
136
- /**
137
- * S07: Send a JSON-RPC request
138
- */
139
- private sendRequest(method: string, params: any): Promise<any> {
140
- return new Promise((resolve, reject) => {
141
- if (!this.process?.connected) {
142
- reject(new Error('MCP process not connected'));
143
- return;
144
- }
145
-
146
- const id = ++this.requestId;
147
- this.pendingRequests.set(id, { resolve, reject });
148
-
149
- const request: MCPJsonRPCRequest = {
150
- jsonrpc: '2.0',
151
- id,
152
- method,
153
- params
154
- };
155
-
156
- this.process.stdin?.write(JSON.stringify(request) + '\n');
157
-
158
- // Timeout
159
- setTimeout(() => {
160
- if (this.pendingRequests.has(id)) {
161
- this.pendingRequests.delete(id);
162
- reject(new Error('MCP request timeout: ' + method));
163
- }
164
- }, 30000);
165
- });
166
- }
167
-
168
- /**
169
- * S07: Send a notification (no response expected)
170
- */
171
- private sendNotification(method: string, params: any): void {
172
- if (!this.process?.connected) return;
173
-
174
- const notification = {
175
- jsonrpc: '2.0',
176
- method,
177
- params
178
- };
179
-
180
- this.process.stdin?.write(JSON.stringify(notification) + '\n');
181
- }
182
-
183
- /**
184
- * S07: Call an MCP tool
185
- */
186
- async callTool(name: string, arguments_: Record<string, any>): Promise<ToolResult> {
187
- try {
188
- const result = await this.sendRequest('tools/call', {
189
- name,
190
- arguments: arguments_
191
- });
192
-
193
- return {
194
- success: true,
195
- output: typeof result === 'string' ? result : JSON.stringify(result)
196
- };
197
- } catch (error: any) {
198
- return {
199
- success: false,
200
- error: error.message
201
- };
202
- }
203
- }
204
-
205
- /**
206
- * S07: List tools from MCP server
207
- */
208
- private async listTools(): Promise<void> {
209
- try {
210
- const result = await this.sendRequest('tools/list', {});
211
- const tools: MCPTool[] = result.tools || [];
212
-
213
- this.tools.clear();
214
- for (const tool of tools) {
215
- this.tools.set(tool.name, tool);
216
- }
217
-
218
- console.log(`[MCP ${this.name}] Loaded ${tools.length} tools:`, [...this.tools.keys()].join(', '));
219
- } catch (error: any) {
220
- console.error(`[MCP ${this.name}] Failed to list tools:`, error.message);
221
- }
222
- }
223
-
224
- /**
225
- * S07: Get registered tools as agent Tool[]
226
- */
227
- getTools(): Tool[] {
228
- return [...this.tools.values()].map(mcpTool => ({
229
- name: this.name + ':' + mcpTool.name,
230
- description: mcpTool.description,
231
- parameters: Object.entries(mcpTool.inputSchema.properties || {}).map(([name, schema]: [string, any]) => ({
232
- name,
233
- type: schema.type || 'string',
234
- description: schema.description || '',
235
- required: (mcpTool.inputSchema.required || []).includes(name)
236
- })),
237
- execute: async (params: Record<string, any>): Promise<ToolResult> => {
238
- return this.callTool(mcpTool.name, params);
239
- }
240
- }));
241
- }
242
-
243
- /**
244
- * S07: Get tool names
245
- */
246
- getToolNames(): string[] {
247
- return [...this.tools.keys()];
248
- }
249
-
250
- /**
251
- * S07: Check if connected
252
- */
253
- isConnected(): boolean {
254
- return this.connected;
255
- }
256
-
257
- /**
258
- * S07: Disconnect from MCP server
259
- */
260
- disconnect(): void {
261
- if (this.process) {
262
- this.process.kill();
263
- this.process = null;
264
- }
265
- this.connected = false;
266
- this.tools.clear();
267
- this.pendingRequests.clear();
268
- }
269
- }
270
-
271
- // ==================== MCP Server Manager ====================
272
-
273
- export class MCPServerManager {
274
- private clients: Map<string, MCPClient> = new Map();
275
-
276
- /**
277
- * S07: Add and connect an MCP server
278
- */
279
- async addServer(name: string, command: string, args: string[]): Promise<void> {
280
- const client = new MCPClient(name);
281
- await client.connect(command, args);
282
- this.clients.set(name, client);
283
- }
284
-
285
- /**
286
- * S07: Get all tools from all servers
287
- */
288
- getAllTools(): Tool[] {
289
- const allTools: Tool[] = [];
290
- for (const client of this.clients.values()) {
291
- allTools.push(...client.getTools());
292
- }
293
- return allTools;
294
- }
295
-
296
- /**
297
- * S07: Get tool names from all servers
298
- */
299
- getAllToolNames(): string[] {
300
- const names: string[] = [];
301
- for (const [serverName, client] of this.clients) {
302
- for (const toolName of client.getToolNames()) {
303
- names.push(serverName + ':' + toolName);
304
- }
305
- }
306
- return names;
307
- }
308
-
309
- /**
310
- * S07: Disconnect a server
311
- */
312
- removeServer(name: string): void {
313
- const client = this.clients.get(name);
314
- if (client) {
315
- client.disconnect();
316
- this.clients.delete(name);
317
- }
318
- }
319
-
320
- /**
321
- * S07: Get server status
322
- */
323
- getStatus(): Record<string, boolean> {
324
- const status: Record<string, boolean> = {};
325
- for (const [name, client] of this.clients) {
326
- status[name] = client.isConnected();
327
- }
328
- return status;
329
- }
330
- }
package/src/repl/index.ts DELETED
@@ -1,8 +0,0 @@
1
- /**
2
- * REPL Module
3
- * Interactive input/output handling
4
- */
5
-
6
- export { REPLInput } from './input.js';
7
- export { REPLOutput } from './output.js';
8
- export { REPLLoop } from './loop.js';
package/src/repl/input.ts DELETED
@@ -1,139 +0,0 @@
1
- /**
2
- * REPL Input Handler
3
- *
4
- * 基于 @inquirer/input 实现,支持:
5
- * - 方向键(含数字小键盘方向键)行内移动 / 跳转词首尾
6
- * - Home / End / Backspace / Delete 行内编辑
7
- * - Ctrl+A / Ctrl+E 跳到行首/行尾(emacs 风格)
8
- * - Ctrl+C 中断当前输入(不清空整个会话),连续按两次空输入退出
9
- * - 命令历史:本地维护 history 数组,通过 default 预填上一条
10
- *
11
- * 不再使用 Node 内置 readline 的 rl.question()——它在 Windows 终端下
12
- * 对小键盘方向键的 ANSI 转义序列不友好,会导致光标无法移动 (Bug #1)。
13
- */
14
-
15
- import input from '@inquirer/input';
16
- import chalk from 'chalk';
17
-
18
- const MAX_HISTORY = 200;
19
-
20
- export type PromptResult =
21
- | { kind: 'value'; value: string } // 用户正常输入并提交(Enter)
22
- | { kind: 'cancelled' }; // Ctrl+C 中断当前输入
23
-
24
- export class REPLInput {
25
- private history: string[] = [];
26
- private historyIndex: number = -1; // -1 表示"未在历史中浏览"
27
- private currentDraft: string = ''; // 离开历史时保留用户原始草稿
28
- private defaultPrefix: string = chalk.green('\n> ');
29
- private consecutiveCancels: number = 0; // 连续空输入 + Ctrl+C 计数,达到阈值退出
30
-
31
- /**
32
- * Ask the user for input. Returns either a value or 'cancelled'.
33
- * Loop decides what to do with cancellation (usually: continue session,
34
- * unless user has cancelled an already-empty input twice in a row).
35
- */
36
- async prompt(prefix?: string, abortSignal?: AbortSignal): Promise<PromptResult> {
37
- const prefill = this.computePrefill();
38
- const previousDraft = prefill.kind === 'history' ? prefill.value : '';
39
-
40
- // 用 abortSignal 绑到 inquirer:外面 Ctrl+C / 主进程事件可以取消
41
- const value = await input({
42
- message: prefix ?? this.defaultPrefix,
43
- prefill: 'editable', // 关键:启用方向键 + 行内编辑,包括小键盘
44
- default: previousDraft, // 历史预填
45
- // Ctrl+C 由 @inquirer/input 抛出一个 symbol-like 错误,我们捕获为 cancelled
46
- validate: () => true,
47
- }, abortSignal ? { signal: abortSignal } : undefined).catch((err: any) => {
48
- // 区分真实错误与用户取消;inquirer 用 ExitPromptError (name: 'ExitPromptError')
49
- if (err && (err.name === 'ExitPromptError' || err.message?.includes('User force closed'))) {
50
- return null;
51
- }
52
- throw err;
53
- });
54
-
55
- if (value === null || value === undefined) {
56
- this.consecutiveCancels++;
57
- return { kind: 'cancelled' };
58
- }
59
-
60
- const trimmed = value.trim();
61
- if (trimmed.length > 0) {
62
- // 避免把连续重复的内容都压入历史
63
- if (this.history[this.history.length - 1] !== trimmed) {
64
- this.history.push(trimmed);
65
- if (this.history.length > MAX_HISTORY) this.history.shift();
66
- }
67
- }
68
-
69
- this.consecutiveCancels = 0;
70
- return { kind: 'value', value: trimmed };
71
- }
72
-
73
- /**
74
- * Should we ask the loop to exit? Two consecutive Ctrl+C on empty input => exit.
75
- */
76
- shouldExitOnCancel(): boolean {
77
- return this.consecutiveCancels >= 2;
78
- }
79
-
80
- /**
81
- * Reset consecutive-cancel counter when the loop has decided to keep running.
82
- */
83
- resetCancelCounter(): void {
84
- this.consecutiveCancels = 0;
85
- }
86
-
87
- /**
88
- * Decide what to prefill. Two modes:
89
- * - 'history': when user is navigating up/down with arrows
90
- * - 'draft': when user has cleared the input — preserve their draft
91
- */
92
- private computePrefill(): { kind: 'history' | 'draft' | 'none'; value: string } {
93
- if (this.historyIndex >= 0 && this.historyIndex < this.history.length) {
94
- return { kind: 'history', value: this.history[this.historyIndex] };
95
- }
96
- if (this.currentDraft) return { kind: 'draft', value: this.currentDraft };
97
- return { kind: 'none', value: '' };
98
- }
99
-
100
- /**
101
- * Save the user's in-progress draft so ↑↓ preserves their unsent typing.
102
- */
103
- saveDraft(text: string): void {
104
- this.currentDraft = text;
105
- }
106
-
107
- /**
108
- * Navigate up in history (called from a global key listener, if needed).
109
- */
110
- historyUp(): string {
111
- if (this.history.length === 0) return this.currentDraft;
112
- if (this.historyIndex < 0) this.historyIndex = this.history.length;
113
- if (this.historyIndex > 0) this.historyIndex--;
114
- return this.history[this.historyIndex] ?? '';
115
- }
116
-
117
- /**
118
- * Navigate down in history.
119
- */
120
- historyDown(): string {
121
- if (this.history.length === 0) return this.currentDraft;
122
- if (this.historyIndex >= this.history.length) return this.currentDraft;
123
- this.historyIndex++;
124
- if (this.historyIndex >= this.history.length) {
125
- this.historyIndex = this.history.length;
126
- return this.currentDraft;
127
- }
128
- return this.history[this.historyIndex] ?? '';
129
- }
130
-
131
- getHistory(): readonly string[] {
132
- return this.history;
133
- }
134
-
135
- clearHistory(): void {
136
- this.history = [];
137
- this.historyIndex = -1;
138
- }
139
- }