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.
- package/CHANGELOG.md +131 -0
- package/DEVELOPMENT.md +286 -0
- package/README.md +131 -30
- package/dist/core/ai-engine.d.ts +17 -0
- package/dist/core/ai-engine.d.ts.map +1 -1
- package/dist/core/ai-engine.js +23 -7
- package/dist/core/ai-engine.js.map +1 -1
- package/dist/index.js +18 -27
- package/dist/index.js.map +1 -1
- package/dist/repl/input.d.ts +8 -0
- package/dist/repl/input.d.ts.map +1 -1
- package/dist/repl/input.js +12 -0
- package/dist/repl/input.js.map +1 -1
- package/dist/repl/loop.d.ts +56 -0
- package/dist/repl/loop.d.ts.map +1 -1
- package/dist/repl/loop.js +333 -4
- package/dist/repl/loop.js.map +1 -1
- package/dist/repl/output.d.ts.map +1 -1
- package/dist/repl/output.js +3 -1
- package/dist/repl/output.js.map +1 -1
- package/dist/repl/welcome.js +1 -1
- package/dist/repl/welcome.js.map +1 -1
- package/docs/API_KEY_GUIDE.md +6 -0
- package/docs/FAQ.md +25 -3
- package/package.json +35 -3
- package/.patches/0001-fix-repl-replace-readline-with-inquirer-input-for-ke.patch +0 -279
- package/.patches/0002-fix-repl-stream-AI-output-directly-to-stdout-so-term.patch +0 -564
- package/.patches/0003-fix-session-break-the-hallucination-loop-in-assistan.patch +0 -194
- package/.patches/0004-chore-release-bump-version-to-0.2.1.patch +0 -24
- package/ROADMAP.md +0 -107
- package/src/agent/core.ts +0 -179
- package/src/agent/index.ts +0 -8
- package/src/agent/intent.ts +0 -181
- package/src/agent/streaming.ts +0 -132
- package/src/core/ai-engine.ts +0 -437
- package/src/core/cli.ts +0 -171
- package/src/core/config.ts +0 -147
- package/src/core/context-compactor.ts +0 -245
- package/src/core/hooks.ts +0 -196
- package/src/core/permissions.ts +0 -308
- package/src/core/session.ts +0 -165
- package/src/core/skills.ts +0 -208
- package/src/core/state.ts +0 -120
- package/src/core/subagent.ts +0 -195
- package/src/core/system-prompt.ts +0 -163
- package/src/core/tool-registry.ts +0 -157
- package/src/core/types.ts +0 -280
- package/src/index.ts +0 -544
- package/src/mcp/client.ts +0 -330
- package/src/repl/index.ts +0 -8
- package/src/repl/input.ts +0 -139
- package/src/repl/loop.ts +0 -280
- package/src/repl/output.ts +0 -222
- package/src/repl/welcome.ts +0 -296
- package/src/tools/file.ts +0 -117
- package/src/tools/git.ts +0 -132
- package/src/tools/index.ts +0 -48
- package/src/tools/search.ts +0 -263
- package/src/tools/shell.ts +0 -181
- package/src/utils/diff-preview.ts +0 -202
- package/src/utils/index.ts +0 -8
- package/src/utils/memory.ts +0 -223
- package/src/utils/project-context.ts +0 -207
- package/tsconfig.json +0 -19
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
From 85a3542565751fa08120fa6189454788ee643ad7 Mon Sep 17 00:00:00 2001
|
|
2
|
-
From: Thatgfsj <thatgfsj@users.noreply.github.com>
|
|
3
|
-
Date: Mon, 6 Jul 2026 02:33:48 +0800
|
|
4
|
-
Subject: [PATCH 3/4] =?UTF-8?q?fix(session):=20break=20the=20'[=E5=B7=B2?=
|
|
5
|
-
=?UTF-8?q?=E4=B8=AD=E6=96=AD]'=20hallucination=20loop=20in=20assistant=20?=
|
|
6
|
-
=?UTF-8?q?history?=
|
|
7
|
-
MIME-Version: 1.0
|
|
8
|
-
Content-Type: text/plain; charset=UTF-8
|
|
9
|
-
Content-Transfer-Encoding: 8bit
|
|
10
|
-
|
|
11
|
-
When a tool/shell call retry produced literal text '[已中断' in the
|
|
12
|
-
assistant output, that string was added back into the session history
|
|
13
|
-
and re-fed to the LLM next turn. The LLM then interpreted the literal
|
|
14
|
-
as 'I was interrupted', rewrote the same paragraph, and the cycle
|
|
15
|
-
repeated indefinitely.
|
|
16
|
-
|
|
17
|
-
SessionManager.addMessage now drops messages whose content matches the
|
|
18
|
-
pollution patterns (literal [已中断/已中断 marker, isolated think blocks,
|
|
19
|
-
etc.), and getMessages() collapses adjacent identical assistant
|
|
20
|
-
messages. REPLLoop surfaces a warning when a message was dropped so the
|
|
21
|
-
user knows to rephrase.
|
|
22
|
-
|
|
23
|
-
Closes the 'AI keeps saying interrupted' bug reported by the user.
|
|
24
|
-
---
|
|
25
|
-
src/core/session.ts | 102 +++++++++++++++++++++++++++++++++++++++-----
|
|
26
|
-
1 file changed, 92 insertions(+), 10 deletions(-)
|
|
27
|
-
|
|
28
|
-
diff --git a/src/core/session.ts b/src/core/session.ts
|
|
29
|
-
index cfd53d9..fd5804b 100644
|
|
30
|
-
--- a/src/core/session.ts
|
|
31
|
-
+++ b/src/core/session.ts
|
|
32
|
-
@@ -1,13 +1,51 @@
|
|
33
|
-
/**
|
|
34
|
-
* Session Manager - Manages conversation history
|
|
35
|
-
+ *
|
|
36
|
-
+ * F4 (anti-[已中断]):
|
|
37
|
-
+ * - addMessage 拦截含"已中断" / 链形 think 块等污染字符串
|
|
38
|
-
+ * (之前 AI 在失败重试时会把这些字面写进 history 造成病毒循环)
|
|
39
|
-
+ * - sanitize() 在 lastMessages 输出到 LLM 前再做一次去噪
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
import { ChatMessage, Session } from './types.js';
|
|
43
|
-
|
|
44
|
-
+/**
|
|
45
|
-
+ * Patterns that indicate the message is polluted by a previous broken
|
|
46
|
-
+ * tool/stream retry loop. These should never be re-sent to the LLM.
|
|
47
|
-
+ */
|
|
48
|
-
+const POLLUTION_PATTERNS: RegExp[] = [
|
|
49
|
-
+ /\[已中断/, // literally "[已中断" - sentinel from bad run
|
|
50
|
-
+ /\u5df2\u4e2d\u65ad/, // 已中断 (escaped form)
|
|
51
|
-
+ /^\s*\[已中断\s*$/m, // line that is exactly [已中断
|
|
52
|
-
+ /^<think>[\s\S]*?<\/think>\s*$/m, // entire message that is just a think block
|
|
53
|
-
+ /\u{1F6AB}/u, // ⛔ emoji sometimes used as interrupt marker
|
|
54
|
-
+];
|
|
55
|
-
+
|
|
56
|
-
+function looksPolluted(content: string): boolean {
|
|
57
|
-
+ if (!content) return false;
|
|
58
|
-
+ // Strip obvious thinking fences and check the *remaining* core content.
|
|
59
|
-
+ const stripped = content
|
|
60
|
-
+ .replace(/<think>[\s\S]*?<\/think>/g, '')
|
|
61
|
-
+ .replace(/<\/?think>/g, '')
|
|
62
|
-
+ .trim();
|
|
63
|
-
+
|
|
64
|
-
+ for (const p of POLLUTION_PATTERNS) {
|
|
65
|
-
+ if (p.test(content)) return true;
|
|
66
|
-
+ }
|
|
67
|
-
+
|
|
68
|
-
+ // A message whose entire stripped form is shorter than 6 chars AND
|
|
69
|
-
+ // contains an interruption-related token is also suspect.
|
|
70
|
-
+ if (stripped.length < 6 && /(中断|interrupted|cancelled|已取消)/i.test(stripped)) {
|
|
71
|
-
+ return true;
|
|
72
|
-
+ }
|
|
73
|
-
+ return false;
|
|
74
|
-
+}
|
|
75
|
-
+
|
|
76
|
-
export class SessionManager {
|
|
77
|
-
private messages: ChatMessage[] = [];
|
|
78
|
-
private sessionId: string;
|
|
79
|
-
private createdAt: Date;
|
|
80
|
-
+ private droppedCount = 0;
|
|
81
|
-
|
|
82
|
-
constructor() {
|
|
83
|
-
this.sessionId = this.generateId();
|
|
84
|
-
@@ -15,25 +53,72 @@ export class SessionManager {
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
- * Add a message to the session
|
|
89
|
-
+ * Add a message to the session.
|
|
90
|
-
+ * Returns true if accepted, false if dropped because it looked polluted.
|
|
91
|
-
*/
|
|
92
|
-
- addMessage(role: ChatMessage['role'], content: string): void {
|
|
93
|
-
+ addMessage(role: ChatMessage['role'], content: string): boolean {
|
|
94
|
-
+ if (role !== 'system' && looksPolluted(content)) {
|
|
95
|
-
+ this.droppedCount++;
|
|
96
|
-
+ // Keep a 1-line internal note so the user can see *something* happened,
|
|
97
|
-
+ // but the original polluted text does NOT go to the LLM context.
|
|
98
|
-
+ this.messages.push({
|
|
99
|
-
+ role: 'user',
|
|
100
|
-
+ content: '[system: dropped a polluted prior message containing "已中断" markers. Continue with the original task.]',
|
|
101
|
-
+ name: 'system'
|
|
102
|
-
+ });
|
|
103
|
-
+ return false;
|
|
104
|
-
+ }
|
|
105
|
-
+
|
|
106
|
-
this.messages.push({
|
|
107
|
-
role,
|
|
108
|
-
content,
|
|
109
|
-
name: role === 'user' ? 'user' : undefined
|
|
110
|
-
});
|
|
111
|
-
+ return true;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
- * Get all messages
|
|
116
|
-
+ * Add a message WITHOUT any sanitization (for trusted callers, e.g. system prompt)
|
|
117
|
-
+ */
|
|
118
|
-
+ addMessageRaw(role: ChatMessage['role'], content: string): void {
|
|
119
|
-
+ this.messages.push({ role, content });
|
|
120
|
-
+ }
|
|
121
|
-
+
|
|
122
|
-
+ /**
|
|
123
|
-
+ * How many polluted messages have we filtered out? Useful for debugging
|
|
124
|
-
+ * the "AI keeps saying interrupted" loop.
|
|
125
|
-
+ */
|
|
126
|
-
+ getDroppedCount(): number {
|
|
127
|
-
+ return this.droppedCount;
|
|
128
|
-
+ }
|
|
129
|
-
+
|
|
130
|
-
+ /**
|
|
131
|
-
+ * Return messages ready to send to the LLM. We additionally dedupe
|
|
132
|
-
+ * adjacent identical assistant messages (a recovery hack against the
|
|
133
|
-
+ * "AI repeats the same paragraph each turn" loop).
|
|
134
|
-
*/
|
|
135
|
-
getMessages(): ChatMessage[] {
|
|
136
|
-
- return [...this.messages];
|
|
137
|
-
+ const out: ChatMessage[] = [];
|
|
138
|
-
+ for (let i = 0; i < this.messages.length; i++) {
|
|
139
|
-
+ const m = this.messages[i];
|
|
140
|
-
+ const prev = out[out.length - 1];
|
|
141
|
-
+ if (
|
|
142
|
-
+ prev &&
|
|
143
|
-
+ m.role === 'assistant' &&
|
|
144
|
-
+ prev.role === 'assistant' &&
|
|
145
|
-
+ m.content.length > 0 &&
|
|
146
|
-
+ m.content === prev.content
|
|
147
|
-
+ ) {
|
|
148
|
-
+ // collapse identical neighbour
|
|
149
|
-
+ continue;
|
|
150
|
-
+ }
|
|
151
|
-
+ out.push(m);
|
|
152
|
-
+ }
|
|
153
|
-
+ return out;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
- * Get message count
|
|
158
|
-
+ * Get message count (raw, includes internal notes)
|
|
159
|
-
*/
|
|
160
|
-
getMessageCount(): number {
|
|
161
|
-
return this.messages.length;
|
|
162
|
-
@@ -44,6 +129,7 @@ export class SessionManager {
|
|
163
|
-
*/
|
|
164
|
-
clear(): void {
|
|
165
|
-
this.messages = [];
|
|
166
|
-
+ this.droppedCount = 0;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
@@ -63,10 +149,9 @@ export class SessionManager {
|
|
171
|
-
*/
|
|
172
|
-
truncate(maxMessages: number = 20): void {
|
|
173
|
-
if (this.messages.length > maxMessages) {
|
|
174
|
-
- // Keep system message if exists
|
|
175
|
-
const systemMsg = this.messages.find(m => m.role === 'system');
|
|
176
|
-
const otherMsgs = this.messages.filter(m => m.role !== 'system');
|
|
177
|
-
-
|
|
178
|
-
+
|
|
179
|
-
this.messages = [
|
|
180
|
-
...(systemMsg ? [systemMsg] : []),
|
|
181
|
-
...otherMsgs.slice(-(maxMessages - 1))
|
|
182
|
-
@@ -74,9 +159,6 @@ export class SessionManager {
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
- /**
|
|
187
|
-
- * Generate unique session ID
|
|
188
|
-
- */
|
|
189
|
-
private generateId(): string {
|
|
190
|
-
return `session_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
191
|
-
}
|
|
192
|
-
--
|
|
193
|
-
2.45.2.windows.1
|
|
194
|
-
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
From 84b72cd73990da198d3fd7a72b5f53ec0dee40f6 Mon Sep 17 00:00:00 2001
|
|
2
|
-
From: Thatgfsj <thatgfsj@users.noreply.github.com>
|
|
3
|
-
Date: Mon, 6 Jul 2026 02:33:52 +0800
|
|
4
|
-
Subject: [PATCH 4/4] chore(release): bump version to 0.2.1
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
package.json | 2 +-
|
|
8
|
-
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
9
|
-
|
|
10
|
-
diff --git a/package.json b/package.json
|
|
11
|
-
index be6bc8d..b09a74a 100644
|
|
12
|
-
--- a/package.json
|
|
13
|
-
+++ b/package.json
|
|
14
|
-
@@ -1,6 +1,6 @@
|
|
15
|
-
{
|
|
16
|
-
"name": "thatgfsj-code",
|
|
17
|
-
- "version": "0.2.0",
|
|
18
|
-
+ "version": "0.2.1",
|
|
19
|
-
"description": "Thatgfsj Code - Your AI Coding Assistant",
|
|
20
|
-
"main": "dist/index.js",
|
|
21
|
-
"type": "module",
|
|
22
|
-
--
|
|
23
|
-
2.45.2.windows.1
|
|
24
|
-
|
package/ROADMAP.md
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# Thatgfsj Code - Development Roadmap
|
|
2
|
-
|
|
3
|
-
## ✅ 全部阶段已完成
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 阶段总结
|
|
8
|
-
|
|
9
|
-
### ✅ 第一阶段: REPL 优化
|
|
10
|
-
- [x] 彩色 UI (Banner, 分隔线)
|
|
11
|
-
- [x] 命令提示符
|
|
12
|
-
- [x] Ctrl+C 中断
|
|
13
|
-
- [x] 内置命令 (exit, clear, context, help, tools)
|
|
14
|
-
- [x] 项目信息显示
|
|
15
|
-
- [x] 流式 Spinner
|
|
16
|
-
- [x] 多行输入检测
|
|
17
|
-
- [x] 历史记录
|
|
18
|
-
|
|
19
|
-
### ✅ 第二阶段: 工具框架
|
|
20
|
-
- [x] Git 工具 (status, log, diff, commit, branch, checkout, pull, push)
|
|
21
|
-
- [x] 代码搜索工具 (grep, find, tree, files)
|
|
22
|
-
|
|
23
|
-
### ✅ 第三阶段: LLM 集成
|
|
24
|
-
- [x] 意图识别 (chat/code/command/query/complex)
|
|
25
|
-
- [x] 任务拆解
|
|
26
|
-
- [x] Streaming 输出
|
|
27
|
-
- [x] Ollama 本地模型支持
|
|
28
|
-
|
|
29
|
-
### ✅ 第四阶段: 安全 + UX
|
|
30
|
-
- [x] Diff 预览
|
|
31
|
-
- [x] 项目感知 (自动读取 package.json, .gitignore)
|
|
32
|
-
- [x] 记忆机制 (会话修改历史)
|
|
33
|
-
- [x] 危险命令确认
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## 最终文件结构
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
src/
|
|
41
|
-
├── index.ts # CLI 入口
|
|
42
|
-
├── repl/ # 交互式 REPL
|
|
43
|
-
│ ├── input.ts
|
|
44
|
-
│ ├── output.ts
|
|
45
|
-
│ └── loop.ts
|
|
46
|
-
├── agent/ # Agent 核心
|
|
47
|
-
│ ├── core.ts
|
|
48
|
-
│ ├── intent.ts
|
|
49
|
-
│ └── streaming.ts
|
|
50
|
-
├── tools/ # 工具
|
|
51
|
-
│ ├── file.ts
|
|
52
|
-
│ ├── shell.ts
|
|
53
|
-
│ ├── git.ts
|
|
54
|
-
│ ├── search.ts
|
|
55
|
-
│ └── index.ts
|
|
56
|
-
├── utils/ # 工具函数
|
|
57
|
-
│ ├── diff-preview.ts
|
|
58
|
-
│ ├── project-context.ts
|
|
59
|
-
│ └── memory.ts
|
|
60
|
-
└── core/ # 核心模块
|
|
61
|
-
├── ai-engine.ts
|
|
62
|
-
├── config.ts
|
|
63
|
-
├── session.ts
|
|
64
|
-
├── tool-registry.ts
|
|
65
|
-
└── types.ts
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## 使用方法
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
# 交互模式
|
|
74
|
-
gfcode
|
|
75
|
-
|
|
76
|
-
# 单命令
|
|
77
|
-
gfcode "创建文件 hello.js"
|
|
78
|
-
|
|
79
|
-
# 指定模型
|
|
80
|
-
gfcode -m Pro/moonshotai/Kimi-K2.5 "你的任务"
|
|
81
|
-
|
|
82
|
-
# 使用 Ollama
|
|
83
|
-
$env:PROVIDER="ollama"
|
|
84
|
-
$env:MODEL="llama2"
|
|
85
|
-
gfcode "你好"
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## 可用工具
|
|
91
|
-
|
|
92
|
-
| 工具 | 功能 |
|
|
93
|
-
|------|------|
|
|
94
|
-
| `file` | 读取/写入/删除文件 |
|
|
95
|
-
| `shell` | 执行 Shell 命令 |
|
|
96
|
-
| `git` | Git 操作 |
|
|
97
|
-
| `search` | 代码搜索 |
|
|
98
|
-
|
|
99
|
-
## 可用提供商
|
|
100
|
-
|
|
101
|
-
| Provider | 说明 |
|
|
102
|
-
|----------|------|
|
|
103
|
-
| siliconflow | 硅基流动 (默认) |
|
|
104
|
-
| minimax | MiniMax |
|
|
105
|
-
| openai | OpenAI |
|
|
106
|
-
| anthropic | Anthropic |
|
|
107
|
-
| ollama | 本地模型 |
|
package/src/agent/core.ts
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent Module
|
|
3
|
-
* Core agent logic with intent recognition, task planning, and streaming
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { AIEngine } from '../core/ai-engine.js';
|
|
7
|
-
import { SessionManager } from '../core/session.js';
|
|
8
|
-
import { IntentRecognizer, Intent, SubTask } from './intent.js';
|
|
9
|
-
import { StreamingOutput } from './streaming.js';
|
|
10
|
-
import chalk from 'chalk';
|
|
11
|
-
|
|
12
|
-
export interface AgentConfig {
|
|
13
|
-
maxIterations?: number;
|
|
14
|
-
streaming?: boolean;
|
|
15
|
-
verbose?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class Agent {
|
|
19
|
-
private ai: AIEngine;
|
|
20
|
-
private session: SessionManager;
|
|
21
|
-
private intentRecognizer: IntentRecognizer;
|
|
22
|
-
private config: AgentConfig;
|
|
23
|
-
private streaming: StreamingOutput;
|
|
24
|
-
|
|
25
|
-
constructor(ai: AIEngine, session: SessionManager, config?: AgentConfig) {
|
|
26
|
-
this.ai = ai;
|
|
27
|
-
this.session = session;
|
|
28
|
-
this.intentRecognizer = new IntentRecognizer(ai);
|
|
29
|
-
this.config = {
|
|
30
|
-
maxIterations: 5,
|
|
31
|
-
streaming: false,
|
|
32
|
-
verbose: false,
|
|
33
|
-
...config
|
|
34
|
-
};
|
|
35
|
-
this.streaming = new StreamingOutput();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Process user input through the agent
|
|
40
|
-
*/
|
|
41
|
-
async process(input: string): Promise<string> {
|
|
42
|
-
// Step 1: Recognize intent
|
|
43
|
-
const intent = await this.intentRecognizer.recognize(input);
|
|
44
|
-
|
|
45
|
-
if (this.config.verbose) {
|
|
46
|
-
console.log(chalk.gray(`\n📍 Intent: ${intent.type} (${intent.confidence}) - ${intent.reason}`));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Step 2: Handle complex tasks
|
|
50
|
-
if (intent.type === 'complex') {
|
|
51
|
-
return await this.handleComplexTask(input, intent);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Step 3: Route to appropriate handler
|
|
55
|
-
switch (intent.type) {
|
|
56
|
-
case 'command':
|
|
57
|
-
return await this.handleCommand(input);
|
|
58
|
-
case 'query':
|
|
59
|
-
return await this.handleQuery(input);
|
|
60
|
-
case 'code':
|
|
61
|
-
case 'chat':
|
|
62
|
-
default:
|
|
63
|
-
return await this.handleGeneral(input);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Handle complex multi-step tasks
|
|
69
|
-
*/
|
|
70
|
-
private async handleComplexTask(input: string, intent: Intent): Promise<string> {
|
|
71
|
-
const subtasks = await this.intentRecognizer.breakIntoSubtasks(input);
|
|
72
|
-
|
|
73
|
-
if (this.config.verbose) {
|
|
74
|
-
console.log(chalk.cyan(`\n📋 Breaking into ${subtasks.length} subtasks:`));
|
|
75
|
-
subtasks.forEach((task, i) => {
|
|
76
|
-
console.log(chalk.gray(` ${i + 1}. ${task.description} [${task.action}]`));
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
let results: string[] = [];
|
|
81
|
-
|
|
82
|
-
for (let i = 0; i < subtasks.length; i++) {
|
|
83
|
-
const task = subtasks[i];
|
|
84
|
-
console.log(chalk.cyan(`\n[${i + 1}/${subtasks.length}] ${task.description}...`));
|
|
85
|
-
|
|
86
|
-
const result = await this.process(task.description);
|
|
87
|
-
results.push(result);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return results.join('\n\n');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Handle command intent
|
|
95
|
-
*/
|
|
96
|
-
private async handleCommand(input: string): Promise<string> {
|
|
97
|
-
// Extract command from input
|
|
98
|
-
const command = input
|
|
99
|
-
.replace(/^(run|execute|exec)\s+/i, '')
|
|
100
|
-
.trim();
|
|
101
|
-
|
|
102
|
-
// Add to session and get response
|
|
103
|
-
this.session.addMessage('user', input);
|
|
104
|
-
|
|
105
|
-
const response = await this.ai.chat(this.session.getMessages(), this.config.maxIterations);
|
|
106
|
-
|
|
107
|
-
this.session.addMessage('assistant', response.content);
|
|
108
|
-
|
|
109
|
-
return response.content;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Handle query intent
|
|
114
|
-
*/
|
|
115
|
-
private async handleQuery(input: string): Promise<string> {
|
|
116
|
-
this.session.addMessage('user', input);
|
|
117
|
-
|
|
118
|
-
const response = await this.ai.chat(this.session.getMessages(), this.config.maxIterations);
|
|
119
|
-
|
|
120
|
-
this.session.addMessage('assistant', response.content);
|
|
121
|
-
|
|
122
|
-
return response.content;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Handle general/code/chat intents
|
|
127
|
-
*/
|
|
128
|
-
private async handleGeneral(input: string): Promise<string> {
|
|
129
|
-
this.session.addMessage('user', input);
|
|
130
|
-
|
|
131
|
-
const response = await this.ai.chat(this.session.getMessages(), this.config.maxIterations);
|
|
132
|
-
|
|
133
|
-
this.session.addMessage('assistant', response.content);
|
|
134
|
-
|
|
135
|
-
return response.content;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Process with streaming output
|
|
140
|
-
*/
|
|
141
|
-
async processStream(input: string, onChunk: (chunk: string) => void): Promise<string> {
|
|
142
|
-
const wasStreaming = this.config.streaming;
|
|
143
|
-
this.config.streaming = true;
|
|
144
|
-
|
|
145
|
-
this.streaming.start({ onChunk });
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
const result = await this.process(input);
|
|
149
|
-
this.streaming.end();
|
|
150
|
-
return result;
|
|
151
|
-
} catch (error) {
|
|
152
|
-
this.streaming.stop();
|
|
153
|
-
throw error;
|
|
154
|
-
} finally {
|
|
155
|
-
this.config.streaming = wasStreaming;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Get session
|
|
161
|
-
*/
|
|
162
|
-
getSession(): SessionManager {
|
|
163
|
-
return this.session;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Clear session
|
|
168
|
-
*/
|
|
169
|
-
clearSession(): void {
|
|
170
|
-
this.session.clear();
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Interrupt ongoing operation
|
|
175
|
-
*/
|
|
176
|
-
interrupt(): void {
|
|
177
|
-
this.streaming.stop();
|
|
178
|
-
}
|
|
179
|
-
}
|
package/src/agent/index.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent Module
|
|
3
|
-
* Core agent logic with intent recognition and task planning
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export { Agent, type AgentConfig } from './core.js';
|
|
7
|
-
export { IntentRecognizer, type Intent, type SubTask } from './intent.js';
|
|
8
|
-
export { StreamingOutput } from './streaming.js';
|
package/src/agent/intent.ts
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Intent Recognition
|
|
3
|
-
* Identifies user intent: chat, code, command, or query
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface Intent {
|
|
7
|
-
type: 'chat' | 'code' | 'command' | 'query' | 'complex';
|
|
8
|
-
confidence: number;
|
|
9
|
-
reason?: string;
|
|
10
|
-
subTasks?: SubTask[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface SubTask {
|
|
14
|
-
description: string;
|
|
15
|
-
action: string;
|
|
16
|
-
target?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Intent Recognition Prompt
|
|
21
|
-
*/
|
|
22
|
-
const INTENT_PROMPT = `Analyze the user message and identify their intent.
|
|
23
|
-
|
|
24
|
-
Intent types:
|
|
25
|
-
- "chat": General conversation, questions, small talk
|
|
26
|
-
- "code": Write, edit, refactor code
|
|
27
|
-
- "command": Execute shell commands, git operations
|
|
28
|
-
- "query": Search files, find information, read code
|
|
29
|
-
- "complex": Multiple actions needed - break into subtasks
|
|
30
|
-
|
|
31
|
-
Respond with JSON:
|
|
32
|
-
{
|
|
33
|
-
"type": "chat|code|command|query|complex",
|
|
34
|
-
"confidence": 0.0-1.0,
|
|
35
|
-
"reason": "brief explanation",
|
|
36
|
-
"subTasks": [{"description": "...", "action": "...", "target": "..."}] // only for complex
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
User message:`;
|
|
40
|
-
|
|
41
|
-
export class IntentRecognizer {
|
|
42
|
-
private ai: any;
|
|
43
|
-
|
|
44
|
-
constructor(aiEngine: any) {
|
|
45
|
-
this.ai = aiEngine;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Recognize intent from user message
|
|
50
|
-
*/
|
|
51
|
-
async recognize(message: string): Promise<Intent> {
|
|
52
|
-
// Fast path: simple keyword matching for common patterns
|
|
53
|
-
const fastIntent = this.fastRecognize(message);
|
|
54
|
-
if (fastIntent.confidence > 0.8) {
|
|
55
|
-
return fastIntent;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Use AI for complex cases
|
|
59
|
-
return await this.aiRecognize(message);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Fast keyword-based recognition
|
|
64
|
-
*/
|
|
65
|
-
private fastRecognize(message: string): Intent {
|
|
66
|
-
const lower = message.toLowerCase().trim();
|
|
67
|
-
const words = lower.split(/\s+/);
|
|
68
|
-
|
|
69
|
-
// Command patterns
|
|
70
|
-
const commandPatterns = [
|
|
71
|
-
/^run\s+/, /^execute\s+/, /^exec\s+/,
|
|
72
|
-
/^npm\s+/, /^node\s+/, /^git\s+/,
|
|
73
|
-
/^pip\s+/, /^python\s+/, /^cargo\s+/,
|
|
74
|
-
/^make\s+/, /^docker\s+/, /^curl\s+/,
|
|
75
|
-
];
|
|
76
|
-
|
|
77
|
-
for (const pattern of commandPatterns) {
|
|
78
|
-
if (pattern.test(lower)) {
|
|
79
|
-
return { type: 'command', confidence: 0.9, reason: 'Command pattern detected' };
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Code patterns
|
|
84
|
-
const codePatterns = [
|
|
85
|
-
/^write\s+/, /^create\s+.*file/i,
|
|
86
|
-
/^edit\s+/, /^modify\s+/, /^change\s+/,
|
|
87
|
-
/^refactor/i, /^implement/i,
|
|
88
|
-
/^add\s+.*function/i, /^add\s+.*class/i,
|
|
89
|
-
/\{[\s\S]*\}/, // Contains code block
|
|
90
|
-
/function\s+\w+\s*\(/,
|
|
91
|
-
/const\s+\w+\s*=/,
|
|
92
|
-
/let\s+\w+\s*=/,
|
|
93
|
-
/class\s+\w+/,
|
|
94
|
-
];
|
|
95
|
-
|
|
96
|
-
for (const pattern of codePatterns) {
|
|
97
|
-
if (pattern.test(message)) {
|
|
98
|
-
return { type: 'code', confidence: 0.85, reason: 'Code pattern detected' };
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Query patterns
|
|
103
|
-
const queryPatterns = [
|
|
104
|
-
/^find\s+/, /^search\s+/, /^grep\s+/,
|
|
105
|
-
/^show\s+.*files/i, /^list\s+.*files/i,
|
|
106
|
-
/^what\s+is\s+/, /^how\s+does\s+/,
|
|
107
|
-
/^explain\s+/, /^what's\s+in\s+/,
|
|
108
|
-
/^read\s+/, /^look\s+at\s+/,
|
|
109
|
-
];
|
|
110
|
-
|
|
111
|
-
for (const pattern of queryPatterns) {
|
|
112
|
-
if (pattern.test(lower)) {
|
|
113
|
-
return { type: 'query', confidence: 0.85, reason: 'Query pattern detected' };
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Complex task patterns (multiple actions)
|
|
118
|
-
const complexPatterns = [
|
|
119
|
-
/refactor.*and.*test/i,
|
|
120
|
-
/create.*and.*setup/i,
|
|
121
|
-
/build.*and.*deploy/i,
|
|
122
|
-
/fix.*and.*verify/i,
|
|
123
|
-
/migrate.*to/i,
|
|
124
|
-
/rewrite.*from/i,
|
|
125
|
-
];
|
|
126
|
-
|
|
127
|
-
for (const pattern of complexPatterns) {
|
|
128
|
-
if (pattern.test(lower)) {
|
|
129
|
-
return { type: 'complex', confidence: 0.8, reason: 'Multiple actions detected' };
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Default to chat
|
|
134
|
-
return { type: 'chat', confidence: 0.6, reason: 'Default intent' };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* AI-powered recognition for ambiguous cases
|
|
139
|
-
*/
|
|
140
|
-
private async aiRecognize(message: string): Promise<Intent> {
|
|
141
|
-
// For now, use fast recognition as fallback
|
|
142
|
-
// TODO: Implement AI-based recognition for complex cases
|
|
143
|
-
return this.fastRecognize(message);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Break complex task into subtasks
|
|
148
|
-
*/
|
|
149
|
-
async breakIntoSubtasks(message: string): Promise<SubTask[]> {
|
|
150
|
-
const lower = message.toLowerCase();
|
|
151
|
-
|
|
152
|
-
// Simple rule-based decomposition
|
|
153
|
-
const tasks: SubTask[] = [];
|
|
154
|
-
|
|
155
|
-
// "refactor X and add tests"
|
|
156
|
-
if (lower.includes('refactor') && lower.includes('test')) {
|
|
157
|
-
tasks.push({ description: 'Refactor code', action: 'code', target: 'refactor' });
|
|
158
|
-
tasks.push({ description: 'Add tests', action: 'code', target: 'test' });
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// "create X and setup Y"
|
|
162
|
-
if (lower.includes('create') && lower.includes('setup')) {
|
|
163
|
-
tasks.push({ description: 'Create component', action: 'code', target: 'create' });
|
|
164
|
-
tasks.push({ description: 'Setup configuration', action: 'command', target: 'setup' });
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// "fix X and verify"
|
|
168
|
-
if (lower.includes('fix') && (lower.includes('verify') || lower.includes('test'))) {
|
|
169
|
-
tasks.push({ description: 'Fix issue', action: 'code', target: 'fix' });
|
|
170
|
-
tasks.push({ description: 'Verify fix', action: 'command', target: 'test' });
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// If no decomposition rules matched, treat as single task
|
|
174
|
-
if (tasks.length === 0) {
|
|
175
|
-
const intent = this.fastRecognize(message);
|
|
176
|
-
tasks.push({ description: message, action: intent.type, target: message });
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return tasks;
|
|
180
|
-
}
|
|
181
|
-
}
|