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
package/src/core/skills.ts
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* S10: Skills System
|
|
3
|
-
* Load skills from CLAUDE.md files and expose them as available commands
|
|
4
|
-
*
|
|
5
|
-
* Skills are domain-specific knowledge packages that extend agent capabilities.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
9
|
-
import { join, dirname } from 'path';
|
|
10
|
-
|
|
11
|
-
export interface Skill {
|
|
12
|
-
name: string;
|
|
13
|
-
description: string;
|
|
14
|
-
prompt: string;
|
|
15
|
-
source: string; // File path or 'builtin'
|
|
16
|
-
tags: string[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface SkillsCatalog {
|
|
20
|
-
skills: Skill[];
|
|
21
|
-
loadedAt: Date;
|
|
22
|
-
sources: string[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* S10: Skills Loader — discovers and loads skills from filesystem
|
|
27
|
-
*/
|
|
28
|
-
export class SkillsLoader {
|
|
29
|
-
private skills: Map<string, Skill> = new Map();
|
|
30
|
-
private sources: Set<string> = new Set();
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* S10: Load skills from a directory
|
|
34
|
-
* Looks for .md files with # SkillName header
|
|
35
|
-
*/
|
|
36
|
-
loadFromDir(dirPath: string): number {
|
|
37
|
-
if (!existsSync(dirPath)) return 0;
|
|
38
|
-
|
|
39
|
-
let count = 0;
|
|
40
|
-
const files = readdirSync(dirPath);
|
|
41
|
-
|
|
42
|
-
for (const file of files) {
|
|
43
|
-
const fullPath = join(dirPath, file);
|
|
44
|
-
const stat = statSync(fullPath);
|
|
45
|
-
|
|
46
|
-
if (stat.isDirectory()) {
|
|
47
|
-
count += this.loadFromDir(fullPath);
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (file.endsWith('.md') || file.endsWith('.skill')) {
|
|
52
|
-
const loaded = this.loadFromFile(fullPath);
|
|
53
|
-
if (loaded) count++;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return count;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* S10: Load a single skill file
|
|
62
|
-
*/
|
|
63
|
-
loadFromFile(filePath: string): boolean {
|
|
64
|
-
try {
|
|
65
|
-
const content = readFileSync(filePath, 'utf-8');
|
|
66
|
-
const skill = this.parseSkillFile(content, filePath);
|
|
67
|
-
if (skill) {
|
|
68
|
-
this.skills.set(skill.name, skill);
|
|
69
|
-
this.sources.add(filePath);
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
} catch (error) {
|
|
73
|
-
// Ignore errors
|
|
74
|
-
}
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* S10: Parse a skill file — extract name, description, prompt from markdown
|
|
80
|
-
*/
|
|
81
|
-
private parseSkillFile(content: string, source: string): Skill | null {
|
|
82
|
-
const lines = content.split('\n');
|
|
83
|
-
|
|
84
|
-
// Find skill name from first heading
|
|
85
|
-
let name = '';
|
|
86
|
-
let description = '';
|
|
87
|
-
let promptLines: string[] = [];
|
|
88
|
-
let inPrompt = false;
|
|
89
|
-
let tags: string[] = [];
|
|
90
|
-
|
|
91
|
-
for (let i = 0; i < lines.length; i++) {
|
|
92
|
-
const line = lines[i];
|
|
93
|
-
|
|
94
|
-
// Match # SkillName or ## SkillName
|
|
95
|
-
if (/^#{1,2}\s+(\S+)/.test(line.trim())) {
|
|
96
|
-
if (!name) {
|
|
97
|
-
name = line.replace(/^#{1,2}\s+/, '').trim();
|
|
98
|
-
} else {
|
|
99
|
-
// Second heading — end of prompt
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Description line (after name, before first code block or empty line)
|
|
106
|
-
if (name && !description && line.trim() && !line.startsWith('```')) {
|
|
107
|
-
description = line.trim();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Code block — start of prompt template
|
|
111
|
-
if (line.startsWith('```')) {
|
|
112
|
-
if (!inPrompt) {
|
|
113
|
-
inPrompt = true;
|
|
114
|
-
continue;
|
|
115
|
-
} else {
|
|
116
|
-
// End of prompt
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (inPrompt) {
|
|
122
|
-
promptLines.push(line);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (!name) return null;
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
name,
|
|
130
|
-
description: description || name,
|
|
131
|
-
prompt: promptLines.join('\n').trim(),
|
|
132
|
-
source,
|
|
133
|
-
tags: this.extractTags(content)
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
private extractTags(content: string): string[] {
|
|
138
|
-
const tagMatch = content.match(/tags?:\s*(.+)/i);
|
|
139
|
-
if (tagMatch) {
|
|
140
|
-
return tagMatch[1].split(/[,\s]+/).filter(Boolean);
|
|
141
|
-
}
|
|
142
|
-
return [];
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* S10: Get all skills
|
|
147
|
-
*/
|
|
148
|
-
getAll(): Skill[] {
|
|
149
|
-
return [...this.skills.values()];
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* S10: Get skill by name
|
|
154
|
-
*/
|
|
155
|
-
get(name: string): Skill | undefined {
|
|
156
|
-
return this.skills.get(name);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* S10: Search skills by tag or name
|
|
161
|
-
*/
|
|
162
|
-
search(query: string): Skill[] {
|
|
163
|
-
const q = query.toLowerCase();
|
|
164
|
-
return this.getAll().filter(s =>
|
|
165
|
-
s.name.toLowerCase().includes(q) ||
|
|
166
|
-
s.description.toLowerCase().includes(q) ||
|
|
167
|
-
s.tags.some(t => t.toLowerCase().includes(q))
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* S10: Get catalog
|
|
173
|
-
*/
|
|
174
|
-
getCatalog(): SkillsCatalog {
|
|
175
|
-
return {
|
|
176
|
-
skills: this.getAll(),
|
|
177
|
-
loadedAt: new Date(),
|
|
178
|
-
sources: [...this.sources]
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* S10: Built-in skills
|
|
185
|
-
*/
|
|
186
|
-
export const BUILT_IN_SKILLS: Skill[] = [
|
|
187
|
-
{
|
|
188
|
-
name: 'explain-code',
|
|
189
|
-
description: 'Explain what a piece of code does in plain English',
|
|
190
|
-
prompt: 'Explain this code:\n\n```\n{{CODE}}\n```\n\nBe concise and clear.',
|
|
191
|
-
source: 'builtin',
|
|
192
|
-
tags: ['analysis', 'documentation']
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
name: 'review-code',
|
|
196
|
-
description: 'Review code for bugs, performance issues, and best practices',
|
|
197
|
-
prompt: 'Review this code:\n\n```\n{{CODE}}\n```\n\nFocus on: bugs, security, performance, readability.',
|
|
198
|
-
source: 'builtin',
|
|
199
|
-
tags: ['review', 'quality']
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
name: 'write-test',
|
|
203
|
-
description: 'Generate unit tests for the given code',
|
|
204
|
-
prompt: 'Write unit tests for:\n\n```\n{{CODE}}\n```\n\nUse a testing framework appropriate for the language.',
|
|
205
|
-
source: 'builtin',
|
|
206
|
-
tags: ['testing', 'quality']
|
|
207
|
-
}
|
|
208
|
-
];
|
package/src/core/state.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* S11: State & Session Management
|
|
3
|
-
*
|
|
4
|
-
* Three layers:
|
|
5
|
-
* 1. EphemeralState - in-memory, current session only
|
|
6
|
-
* 2. PersistentState - survives restarts (file-based)
|
|
7
|
-
* 3. SessionState - per-conversation state
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
11
|
-
import { join } from 'path';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* S11: Ephemeral State — in-memory key-value store
|
|
15
|
-
*/
|
|
16
|
-
export class EphemeralState<T = any> {
|
|
17
|
-
private data: Map<string, T> = new Map();
|
|
18
|
-
|
|
19
|
-
get(key: string): T | undefined { return this.data.get(key); }
|
|
20
|
-
set(key: string, value: T): void { this.data.set(key, value); }
|
|
21
|
-
has(key: string): boolean { return this.data.has(key); }
|
|
22
|
-
delete(key: string): void { this.data.delete(key); }
|
|
23
|
-
clear(): void { this.data.clear(); }
|
|
24
|
-
keys(): string[] { return [...this.data.keys()]; }
|
|
25
|
-
entries(): [string, T][] { return [...this.data.entries()]; }
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* S11: Persistent State — file-backed key-value store
|
|
30
|
-
*/
|
|
31
|
-
export class PersistentState<T = any> {
|
|
32
|
-
private filePath: string;
|
|
33
|
-
private data: Record<string, T> = {};
|
|
34
|
-
|
|
35
|
-
constructor(filePath: string) {
|
|
36
|
-
this.filePath = filePath;
|
|
37
|
-
this.load();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
private load(): void {
|
|
41
|
-
try {
|
|
42
|
-
if (existsSync(this.filePath)) {
|
|
43
|
-
this.data = JSON.parse(readFileSync(this.filePath, 'utf-8'));
|
|
44
|
-
}
|
|
45
|
-
} catch {
|
|
46
|
-
this.data = {};
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
private save(): void {
|
|
51
|
-
try {
|
|
52
|
-
writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), 'utf-8');
|
|
53
|
-
} catch (error: any) {
|
|
54
|
-
console.error('[State] Failed to save:', error.message);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
get(key: string): T | undefined { return this.data[key]; }
|
|
59
|
-
set(key: string, value: T): void { this.data[key] = value; this.save(); }
|
|
60
|
-
has(key: string): boolean { return key in this.data; }
|
|
61
|
-
delete(key: string): void { delete this.data[key]; this.save(); }
|
|
62
|
-
clear(): void { this.data = {}; this.save(); }
|
|
63
|
-
keys(): string[] { return Object.keys(this.data); }
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* S11: Session State — per-conversation state
|
|
68
|
-
*/
|
|
69
|
-
export class SessionState<T = any> {
|
|
70
|
-
private sessions: Map<string, EphemeralState<T>> = new Map();
|
|
71
|
-
private currentSessionId: string | null = null;
|
|
72
|
-
|
|
73
|
-
createSession(sessionId: string): void {
|
|
74
|
-
this.sessions.set(sessionId, new EphemeralState<T>());
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
deleteSession(sessionId: string): void {
|
|
78
|
-
this.sessions.delete(sessionId);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
setCurrentSession(sessionId: string): void {
|
|
82
|
-
if (!this.sessions.has(sessionId)) {
|
|
83
|
-
this.createSession(sessionId);
|
|
84
|
-
}
|
|
85
|
-
this.currentSessionId = sessionId;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
getCurrentSession(): EphemeralState<T> | null {
|
|
89
|
-
if (!this.currentSessionId) return null;
|
|
90
|
-
return this.sessions.get(this.currentSessionId) || null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
getSession(sessionId: string): EphemeralState<T> | null {
|
|
94
|
-
return this.sessions.get(sessionId) || null;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* S11: Global State Manager
|
|
100
|
-
*/
|
|
101
|
-
export class StateManager {
|
|
102
|
-
ephemeral: EphemeralState;
|
|
103
|
-
persistent: PersistentState;
|
|
104
|
-
session: SessionState;
|
|
105
|
-
|
|
106
|
-
constructor(dataDir: string = join(process.cwd(), '.thatgfsj', 'state')) {
|
|
107
|
-
this.ephemeral = new EphemeralState();
|
|
108
|
-
this.persistent = new PersistentState(join(dataDir, 'persistent.json'));
|
|
109
|
-
this.session = new SessionState();
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
let globalStateManager: StateManager | null = null;
|
|
114
|
-
|
|
115
|
-
export function getStateManager(): StateManager {
|
|
116
|
-
if (!globalStateManager) {
|
|
117
|
-
globalStateManager = new StateManager();
|
|
118
|
-
}
|
|
119
|
-
return globalStateManager;
|
|
120
|
-
}
|
package/src/core/subagent.ts
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* S06: Subagent Manager
|
|
3
|
-
* Spawn and manage subagents for parallel task execution
|
|
4
|
-
*
|
|
5
|
-
* Subagents enable "infinite parallelism" — complex tasks are broken into
|
|
6
|
-
* independent subtasks, each run by a separate agent instance.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { AIEngine, ChatMessage } from './ai-engine.js';
|
|
10
|
-
import { AIConfig } from './types.js';
|
|
11
|
-
|
|
12
|
-
export interface SubagentOptions {
|
|
13
|
-
/** Task prompt for the subagent */
|
|
14
|
-
prompt: string;
|
|
15
|
-
/** Custom system prompt (optional) */
|
|
16
|
-
systemPrompt?: string;
|
|
17
|
-
/** Tools available to this subagent */
|
|
18
|
-
tools?: any[];
|
|
19
|
-
/** Max iterations for this subagent */
|
|
20
|
-
maxIterations?: number;
|
|
21
|
-
/** Subagent name for logging */
|
|
22
|
-
name?: string;
|
|
23
|
-
/** Working directory */
|
|
24
|
-
cwd?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface SubagentResult {
|
|
28
|
-
success: boolean;
|
|
29
|
-
output: string;
|
|
30
|
-
error?: string;
|
|
31
|
-
iterations: number;
|
|
32
|
-
durationMs: number;
|
|
33
|
-
name: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* S06: Subagent Manager
|
|
38
|
-
*/
|
|
39
|
-
export class SubagentManager {
|
|
40
|
-
private config: AIConfig;
|
|
41
|
-
private activeSubagents: Map<string, {
|
|
42
|
-
task: Promise<SubagentResult>;
|
|
43
|
-
startedAt: Date;
|
|
44
|
-
name: string;
|
|
45
|
-
}> = new Map();
|
|
46
|
-
private maxConcurrent = 3;
|
|
47
|
-
|
|
48
|
-
constructor(config: AIConfig) {
|
|
49
|
-
this.config = config;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* S06: Spawn a subagent to run a task asynchronously
|
|
54
|
-
*/
|
|
55
|
-
async spawn(options: SubagentOptions): Promise<SubagentResult> {
|
|
56
|
-
const name = options.name || `subagent_${Date.now()}`;
|
|
57
|
-
const startedAt = new Date();
|
|
58
|
-
|
|
59
|
-
const task = this.runSubagent(name, options, startedAt);
|
|
60
|
-
|
|
61
|
-
// Track active subagent
|
|
62
|
-
this.activeSubagents.set(name, { task, startedAt, name });
|
|
63
|
-
|
|
64
|
-
// Auto-cleanup after completion
|
|
65
|
-
task.then(() => {
|
|
66
|
-
setTimeout(() => this.activeSubagents.delete(name), 5000);
|
|
67
|
-
}).catch(() => {
|
|
68
|
-
this.activeSubagents.delete(name);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// Return handle — caller can await or ignore
|
|
72
|
-
return task;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* S06: Spawn multiple subagents in parallel (bounded concurrency)
|
|
77
|
-
*/
|
|
78
|
-
async spawnAll(tasks: SubagentOptions[]): Promise<SubagentResult[]> {
|
|
79
|
-
const results: SubagentResult[] = [];
|
|
80
|
-
const queue = [...tasks];
|
|
81
|
-
const running: Promise<void>[] = [];
|
|
82
|
-
|
|
83
|
-
while (queue.length > 0 || running.length > 0) {
|
|
84
|
-
while (running.length < this.maxConcurrent && queue.length > 0) {
|
|
85
|
-
const task = queue.shift()!;
|
|
86
|
-
const p: Promise<void> = this.spawn(task).then(r => { results.push(r); }).then(() => {});
|
|
87
|
-
running.push(p);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (running.length > 0) {
|
|
91
|
-
await Promise.race(running).catch(() => {});
|
|
92
|
-
await Promise.allSettled(running);
|
|
93
|
-
running.length = 0;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return results;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* S06: Run a single subagent task
|
|
102
|
-
*/
|
|
103
|
-
private async runSubagent(
|
|
104
|
-
name: string,
|
|
105
|
-
options: SubagentOptions,
|
|
106
|
-
startedAt: Date
|
|
107
|
-
): Promise<SubagentResult> {
|
|
108
|
-
const startMs = Date.now();
|
|
109
|
-
let iterations = 0;
|
|
110
|
-
const maxIterations = options.maxIterations || 5;
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
// Create isolated AI engine for subagent
|
|
114
|
-
const subAI = new AIEngine({
|
|
115
|
-
...this.config,
|
|
116
|
-
model: options.systemPrompt ? (this.config.model || 'unknown') : this.config.model
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Register tools if provided
|
|
120
|
-
if (options.tools) {
|
|
121
|
-
for (const tool of options.tools) {
|
|
122
|
-
subAI.registerTool(tool);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Build messages
|
|
127
|
-
const messages: ChatMessage[] = [];
|
|
128
|
-
if (options.systemPrompt) {
|
|
129
|
-
messages.push({ role: 'system', content: options.systemPrompt });
|
|
130
|
-
} else {
|
|
131
|
-
messages.push({
|
|
132
|
-
role: 'system',
|
|
133
|
-
content: 'You are ' + (options.name || 'a subagent') + '. Complete the task assigned by the parent agent. Be concise and focused.'
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
messages.push({ role: 'user', content: options.prompt });
|
|
137
|
-
|
|
138
|
-
// Run agent loop
|
|
139
|
-
let output = '';
|
|
140
|
-
for await (const chunk of subAI.chatStream(messages, maxIterations)) {
|
|
141
|
-
output += chunk;
|
|
142
|
-
iterations++;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return {
|
|
146
|
-
success: true,
|
|
147
|
-
output,
|
|
148
|
-
iterations,
|
|
149
|
-
durationMs: Date.now() - startMs,
|
|
150
|
-
name
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
} catch (error: any) {
|
|
154
|
-
return {
|
|
155
|
-
success: false,
|
|
156
|
-
output: '',
|
|
157
|
-
error: error.message,
|
|
158
|
-
iterations,
|
|
159
|
-
durationMs: Date.now() - startMs,
|
|
160
|
-
name
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* S06: Get number of active subagents
|
|
167
|
-
*/
|
|
168
|
-
getActiveCount(): number {
|
|
169
|
-
return this.activeSubagents.size;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* S06: Get active subagent names
|
|
174
|
-
*/
|
|
175
|
-
getActiveNames(): string[] {
|
|
176
|
-
return [...this.activeSubagents.values()].map(s => s.name);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* S06: Abort all active subagents
|
|
181
|
-
*/
|
|
182
|
-
abortAll(): void {
|
|
183
|
-
for (const [name] of this.activeSubagents) {
|
|
184
|
-
// Subagents don't have abort support yet, just remove from tracking
|
|
185
|
-
this.activeSubagents.delete(name);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* S06: Set max concurrent subagents
|
|
191
|
-
*/
|
|
192
|
-
setMaxConcurrent(max: number) {
|
|
193
|
-
this.maxConcurrent = max;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* S04: System Prompt Builder
|
|
3
|
-
* Dynamic system prompt construction - not a static string, but a program
|
|
4
|
-
* that assembles context from multiple sources each turn
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { readFileSync, existsSync } from 'fs';
|
|
8
|
-
import { join } from 'path';
|
|
9
|
-
import { Tool } from './types.js';
|
|
10
|
-
|
|
11
|
-
export interface SystemPromptConfig {
|
|
12
|
-
cwd?: string;
|
|
13
|
-
tools?: Tool[];
|
|
14
|
-
includeClaudeMd?: boolean;
|
|
15
|
-
projectInstructions?: string;
|
|
16
|
-
permissionMode?: 'accept' | 'deny' | 'ask';
|
|
17
|
-
date?: Date;
|
|
18
|
-
model?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* S04: System Prompt Builder - assembles prompt from multiple fragments
|
|
23
|
-
*/
|
|
24
|
-
export class SystemPromptBuilder {
|
|
25
|
-
private config: SystemPromptConfig;
|
|
26
|
-
|
|
27
|
-
constructor(config: SystemPromptConfig = {}) {
|
|
28
|
-
this.config = {
|
|
29
|
-
cwd: process.cwd(),
|
|
30
|
-
tools: [],
|
|
31
|
-
includeClaudeMd: true,
|
|
32
|
-
permissionMode: 'ask',
|
|
33
|
-
date: new Date(),
|
|
34
|
-
model: 'unknown',
|
|
35
|
-
...config
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
build(): string {
|
|
40
|
-
const fragments: string[] = [];
|
|
41
|
-
fragments.push(this.buildIdentity());
|
|
42
|
-
fragments.push(this.buildToolInstructions());
|
|
43
|
-
fragments.push(this.buildEnvironment());
|
|
44
|
-
fragments.push(this.buildPermissionMode());
|
|
45
|
-
fragments.push(this.buildProjectInstructions());
|
|
46
|
-
fragments.push(this.buildDateInfo());
|
|
47
|
-
fragments.push(this.buildOutputFormat());
|
|
48
|
-
return fragments.filter(Boolean).join('\n\n');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
private buildIdentity(): string {
|
|
52
|
-
return [
|
|
53
|
-
'You are Thatgfsj Code, an AI coding assistant.',
|
|
54
|
-
'',
|
|
55
|
-
'You have access to tools (file operations, shell commands, git, search) that let you',
|
|
56
|
-
'read, write, and modify files and run commands.',
|
|
57
|
-
'',
|
|
58
|
-
'Be concise, helpful, and show your reasoning when working through problems.',
|
|
59
|
-
'',
|
|
60
|
-
'When using tools:',
|
|
61
|
-
'- Prefer small, targeted operations over large batch changes',
|
|
62
|
-
'- Confirm destructive operations (rm, git push --force, etc.)',
|
|
63
|
-
'- Explain what you are going to do before doing it',
|
|
64
|
-
'- If something is not clear, ask clarifying questions'
|
|
65
|
-
].join('\n');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
private buildToolInstructions(): string {
|
|
69
|
-
const tools = this.config.tools || [];
|
|
70
|
-
if (tools.length === 0) {
|
|
71
|
-
return '## Tools\n\nNo tools are currently registered.';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const toolDescs = tools.map(t => {
|
|
75
|
-
const params = t.parameters
|
|
76
|
-
.map(p => ' - ' + p.name + ' (' + p.type + (p.required ? ', required' : '') + '): ' + p.description)
|
|
77
|
-
.join('\n');
|
|
78
|
-
return '### ' + t.name + '\n' + t.description + '\n\nParameters:\n' + params;
|
|
79
|
-
}).join('\n\n');
|
|
80
|
-
|
|
81
|
-
return '## Tools\n\nYou have access to the following tools:\n\n' + toolDescs + '\n\nTo use a tool, respond with a tool call. Each tool call must include:\n- The tool name\n- Parameters as a JSON object';
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
private buildEnvironment(): string {
|
|
85
|
-
const cwd = this.config.cwd || process.cwd();
|
|
86
|
-
return '## Environment\n\nWorking directory: ' + cwd;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
private buildPermissionMode(): string {
|
|
90
|
-
const mode = this.config.permissionMode || 'ask';
|
|
91
|
-
const explanations: Record<string, string> = {
|
|
92
|
-
accept: 'All tool calls are automatically allowed without confirmation.',
|
|
93
|
-
deny: 'All tool calls are blocked. You may only read and discuss.',
|
|
94
|
-
ask: 'Dangerous or destructive commands require your confirmation before execution.'
|
|
95
|
-
};
|
|
96
|
-
return '## Permission Mode\n\nCurrent mode: ' + mode + '\n\n' + (explanations[mode] || '');
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
private buildProjectInstructions(): string {
|
|
100
|
-
const cwd = this.config.cwd || process.cwd();
|
|
101
|
-
if (this.config.includeClaudeMd !== false) {
|
|
102
|
-
const paths = [join(cwd, 'CLAUDE.md'), join(cwd, '.claude.md')];
|
|
103
|
-
for (const path of paths) {
|
|
104
|
-
if (existsSync(path)) {
|
|
105
|
-
try {
|
|
106
|
-
const content = readFileSync(path, 'utf-8');
|
|
107
|
-
return '## Project Instructions (from ' + path.split(/[/\\]/).pop() + ')\n\n' + content;
|
|
108
|
-
} catch {
|
|
109
|
-
// Ignore
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
if (this.config.projectInstructions) {
|
|
115
|
-
return '## Project Instructions\n\n' + this.config.projectInstructions;
|
|
116
|
-
}
|
|
117
|
-
return '';
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
private buildDateInfo(): string {
|
|
121
|
-
const now = this.config.date || new Date();
|
|
122
|
-
const iso = now.toISOString().replace('T', ' ').split('.')[0];
|
|
123
|
-
return '## Current Time\n\n' + iso;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
private buildOutputFormat(): string {
|
|
127
|
-
return [
|
|
128
|
-
'## Output Format',
|
|
129
|
-
'',
|
|
130
|
-
'- Use clear section headers for multi-part responses',
|
|
131
|
-
'- Use bullet points for lists',
|
|
132
|
-
'- Use code blocks for code',
|
|
133
|
-
'- Be concise, avoid unnecessary repetition',
|
|
134
|
-
'- If you need multiple steps, explain the plan first'
|
|
135
|
-
].join('\n');
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
setTools(tools: Tool[]) { this.config.tools = tools; return this; }
|
|
139
|
-
setCwd(cwd: string) { this.config.cwd = cwd; return this; }
|
|
140
|
-
setPermissionMode(mode: 'accept' | 'deny' | 'ask') { this.config.permissionMode = mode; return this; }
|
|
141
|
-
setProjectInstructions(instructions: string) { this.config.projectInstructions = instructions; return this; }
|
|
142
|
-
setModel(model: string) { this.config.model = model; return this; }
|
|
143
|
-
|
|
144
|
-
buildToolsFragment(): string { return this.buildToolInstructions(); }
|
|
145
|
-
|
|
146
|
-
injectReminder(reminder: string): string {
|
|
147
|
-
return '\n## Reminder\n\n' + reminder + '\n';
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
let globalBuilder: SystemPromptBuilder | null = null;
|
|
152
|
-
|
|
153
|
-
export function getSystemPromptBuilder(config?: SystemPromptConfig): SystemPromptBuilder {
|
|
154
|
-
if (!globalBuilder) globalBuilder = new SystemPromptBuilder(config);
|
|
155
|
-
return globalBuilder;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export function updateSystemPrompt(tools?: Tool[], permissionMode?: 'accept' | 'deny' | 'ask') {
|
|
159
|
-
if (globalBuilder) {
|
|
160
|
-
if (tools) globalBuilder.setTools(tools);
|
|
161
|
-
if (permissionMode) globalBuilder.setPermissionMode(permissionMode);
|
|
162
|
-
}
|
|
163
|
-
}
|