thatgfsj-code 0.7.1 → 0.7.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.
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +5 -0
- package/dist/app/index.js.map +1 -1
- package/dist/cmd/index.d.ts +0 -5
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/index.js +36 -56
- package/dist/cmd/index.js.map +1 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +13 -1
- package/dist/prompts/index.js.map +1 -1
- package/dist/session/index.d.ts +5 -21
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +26 -22
- package/dist/session/index.js.map +1 -1
- package/dist/tools/nwt.d.ts +5 -0
- package/dist/tools/nwt.d.ts.map +1 -1
- package/dist/tools/nwt.js +68 -0
- package/dist/tools/nwt.js.map +1 -1
- package/dist/tui/app.js +1 -1
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/components/ChatList.js +1 -1
- package/dist/tui/components/ChatList.js.map +1 -1
- package/dist/tui/components/Header.js +1 -1
- package/dist/tui/components/Thinking.d.ts +2 -1
- package/dist/tui/components/Thinking.d.ts.map +1 -1
- package/dist/tui/components/Thinking.js +5 -1
- package/dist/tui/components/Thinking.js.map +1 -1
- package/dist/tui/hooks/useChat.d.ts +1 -1
- package/dist/tui/hooks/useChat.d.ts.map +1 -1
- package/dist/tui/hooks/useChat.js +86 -97
- package/dist/tui/hooks/useChat.js.map +1 -1
- package/package.json +1 -1
- package/src/app/index.ts +6 -0
- package/src/cmd/index.tsx +38 -51
- package/src/prompts/index.ts +13 -1
- package/src/session/index.ts +28 -22
- package/src/tools/nwt.ts +68 -0
- package/src/tui/app.tsx +1 -1
- package/src/tui/components/ChatList.tsx +1 -1
- package/src/tui/components/Header.tsx +1 -1
- package/src/tui/components/Thinking.tsx +7 -1
- package/src/tui/hooks/useChat.ts +85 -97
- package/dist/app/agent.d.ts +0 -31
- package/dist/app/agent.d.ts.map +0 -1
- package/dist/app/agent.js +0 -106
- package/dist/app/agent.js.map +0 -1
- package/dist/tui/input.d.ts +0 -13
- package/dist/tui/input.d.ts.map +0 -1
- package/dist/tui/input.js +0 -47
- package/dist/tui/input.js.map +0 -1
- package/dist/tui/output.d.ts +0 -44
- package/dist/tui/output.d.ts.map +0 -1
- package/dist/tui/output.js +0 -202
- package/dist/tui/output.js.map +0 -1
- package/dist/tui/repl.d.ts +0 -15
- package/dist/tui/repl.d.ts.map +0 -1
- package/dist/tui/repl.js +0 -162
- package/dist/tui/repl.js.map +0 -1
- package/install.bat +0 -63
- package/install.ps1 +0 -217
- package/install.sh +0 -113
- package/src/tui/input.ts +0 -53
- package/src/tui/output.ts +0 -235
- package/src/tui/repl.ts +0 -181
package/src/session/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Session Manager - Manages conversation history
|
|
3
|
-
* Migrated from old src/core/session.ts
|
|
2
|
+
* Session Manager - Manages conversation history with auto-compaction
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
import type { ChatMessage } from '../types.js';
|
|
@@ -11,62 +10,69 @@ export class SessionManager {
|
|
|
11
10
|
private sessionId: string;
|
|
12
11
|
private createdAt: Date;
|
|
13
12
|
private compactor: ContextCompactor;
|
|
13
|
+
private maxMessages: number;
|
|
14
14
|
|
|
15
15
|
constructor(maxMessages = 50) {
|
|
16
|
+
this.maxMessages = maxMessages;
|
|
16
17
|
this.sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
17
18
|
this.createdAt = new Date();
|
|
18
19
|
this.compactor = new ContextCompactor({ maxMessages });
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
/**
|
|
22
|
-
* Add a message to the session
|
|
23
|
-
*/
|
|
24
22
|
addMessage(role: ChatMessage['role'], content: string, extras?: Partial<ChatMessage>): void {
|
|
25
23
|
this.messages.push({ role, content, ...extras });
|
|
24
|
+
this.autoCompact();
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
/**
|
|
29
|
-
* Get all messages (copy)
|
|
30
|
-
*/
|
|
31
27
|
getMessages(): ChatMessage[] {
|
|
32
28
|
return [...this.messages];
|
|
33
29
|
}
|
|
34
30
|
|
|
35
|
-
/**
|
|
36
|
-
* Get message count
|
|
37
|
-
*/
|
|
38
31
|
getMessageCount(): number {
|
|
39
32
|
return this.messages.length;
|
|
40
33
|
}
|
|
41
34
|
|
|
42
|
-
/**
|
|
43
|
-
* Clear session history
|
|
44
|
-
*/
|
|
45
35
|
clear(): void {
|
|
46
36
|
this.messages = [];
|
|
47
37
|
}
|
|
48
38
|
|
|
49
39
|
/**
|
|
50
|
-
*
|
|
40
|
+
* Auto-compact when messages exceed max.
|
|
41
|
+
* Keeps system messages + recent half. One summary replaces the rest.
|
|
51
42
|
*/
|
|
43
|
+
private autoCompact(): void {
|
|
44
|
+
if (this.messages.length <= this.maxMessages) return;
|
|
45
|
+
|
|
46
|
+
const systemMsgs = this.messages.filter(m => m.role === 'system');
|
|
47
|
+
const others = this.messages.filter(m => m.role !== 'system');
|
|
48
|
+
const keepCount = Math.floor(this.maxMessages * 0.5);
|
|
49
|
+
const recent = others.slice(-keepCount);
|
|
50
|
+
const removed = others.length - keepCount;
|
|
51
|
+
|
|
52
|
+
if (removed <= 0) return;
|
|
53
|
+
|
|
54
|
+
// Remove old summaries before adding new one
|
|
55
|
+
const cleanSystem = systemMsgs.filter(m => !m.content.startsWith('[Earlier conversation'));
|
|
56
|
+
const summary: ChatMessage = {
|
|
57
|
+
role: 'system',
|
|
58
|
+
content: `[Earlier conversation: ${removed} messages compacted to save context]`,
|
|
59
|
+
};
|
|
60
|
+
this.messages = [...cleanSystem, summary, ...recent];
|
|
61
|
+
}
|
|
62
|
+
|
|
52
63
|
truncate(maxMessages?: number): void {
|
|
53
64
|
if (maxMessages) {
|
|
65
|
+
this.maxMessages = maxMessages;
|
|
54
66
|
this.compactor = new ContextCompactor({ maxMessages });
|
|
55
67
|
}
|
|
56
|
-
const { compacted } = this.compactor.
|
|
68
|
+
const { compacted } = this.compactor.compact(this.messages);
|
|
57
69
|
this.messages = compacted;
|
|
58
70
|
}
|
|
59
71
|
|
|
60
|
-
/**
|
|
61
|
-
* Get session ID
|
|
62
|
-
*/
|
|
63
72
|
getId(): string {
|
|
64
73
|
return this.sessionId;
|
|
65
74
|
}
|
|
66
75
|
|
|
67
|
-
/**
|
|
68
|
-
* Get session info
|
|
69
|
-
*/
|
|
70
76
|
getInfo(): { id: string; messageCount: number; createdAt: Date } {
|
|
71
77
|
return {
|
|
72
78
|
id: this.sessionId,
|
package/src/tools/nwt.ts
CHANGED
|
@@ -107,6 +107,9 @@ export class NwtTool implements Tool {
|
|
|
107
107
|
if (!existsSync(eventsDir)) mkdirSync(eventsDir, { recursive: true });
|
|
108
108
|
if (!existsSync(archiveDir)) mkdirSync(archiveDir, { recursive: true });
|
|
109
109
|
|
|
110
|
+
// Auto-archive events older than 30 days on every init
|
|
111
|
+
this.autoArchiveIfNeeded(cwd);
|
|
112
|
+
|
|
110
113
|
// Write metadata
|
|
111
114
|
const meta = {
|
|
112
115
|
version: '1.0.0',
|
|
@@ -340,6 +343,71 @@ export class NwtTool implements Tool {
|
|
|
340
343
|
};
|
|
341
344
|
}
|
|
342
345
|
|
|
346
|
+
// ── Auto Archive ──────────────────────────────────────
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Auto-archive events older than 30 days.
|
|
350
|
+
* Runs silently on init - no output unless something was archived.
|
|
351
|
+
*/
|
|
352
|
+
private autoArchiveIfNeeded(cwd: string): void {
|
|
353
|
+
try {
|
|
354
|
+
const nwtDir = join(cwd, NWT_DIR);
|
|
355
|
+
const eventsDir = join(nwtDir, EVENTS_DIR);
|
|
356
|
+
const archiveDir = join(nwtDir, ARCHIVE_DIR);
|
|
357
|
+
|
|
358
|
+
if (!existsSync(eventsDir)) return;
|
|
359
|
+
|
|
360
|
+
const files = readdirSync(eventsDir).filter(f => f.endsWith('.json'));
|
|
361
|
+
if (files.length === 0) return;
|
|
362
|
+
|
|
363
|
+
const cutoff = new Date();
|
|
364
|
+
cutoff.setDate(cutoff.getDate() - ARCHIVE_AFTER_DAYS);
|
|
365
|
+
|
|
366
|
+
const toArchive: string[] = [];
|
|
367
|
+
const toKeep: string[] = [];
|
|
368
|
+
|
|
369
|
+
for (const file of files) {
|
|
370
|
+
try {
|
|
371
|
+
const event = JSON.parse(readFileSync(join(eventsDir, file), 'utf-8'));
|
|
372
|
+
const eventDate = new Date(event.timestamp);
|
|
373
|
+
if (eventDate < cutoff) {
|
|
374
|
+
toArchive.push(file);
|
|
375
|
+
} else {
|
|
376
|
+
toKeep.push(file);
|
|
377
|
+
}
|
|
378
|
+
} catch {
|
|
379
|
+
toKeep.push(file);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (toArchive.length === 0) return;
|
|
384
|
+
|
|
385
|
+
// Create monthly archive file
|
|
386
|
+
const archiveName = `archive-${new Date().toISOString().split('T')[0]}.json`;
|
|
387
|
+
const archiveEvents = toArchive.map(f => {
|
|
388
|
+
return JSON.parse(readFileSync(join(eventsDir, f), 'utf-8'));
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
if (!existsSync(archiveDir)) mkdirSync(archiveDir, { recursive: true });
|
|
392
|
+
|
|
393
|
+
// Append to existing archive or create new
|
|
394
|
+
const archivePath = join(archiveDir, archiveName);
|
|
395
|
+
let existing: any[] = [];
|
|
396
|
+
if (existsSync(archivePath)) {
|
|
397
|
+
try { existing = JSON.parse(readFileSync(archivePath, 'utf-8')); } catch {}
|
|
398
|
+
}
|
|
399
|
+
existing.push(...archiveEvents);
|
|
400
|
+
writeFileSync(archivePath, JSON.stringify(existing, null, 2));
|
|
401
|
+
|
|
402
|
+
// Remove archived files from events dir
|
|
403
|
+
for (const file of toArchive) {
|
|
404
|
+
try { renameSync(join(eventsDir, file), join(archiveDir, file)); } catch {}
|
|
405
|
+
}
|
|
406
|
+
} catch {
|
|
407
|
+
// Silent fail - don't break init
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
343
411
|
// ── Helpers ───────────────────────────────────────────
|
|
344
412
|
|
|
345
413
|
private loadEvents(cwd: string): TimelineEvent[] {
|
package/src/tui/app.tsx
CHANGED
|
@@ -53,7 +53,7 @@ export function TuiApp({ app }: Props) {
|
|
|
53
53
|
streamingToolCalls={streamingToolCalls}
|
|
54
54
|
width={terminalWidth - 4}
|
|
55
55
|
/>
|
|
56
|
-
{isThinking
|
|
56
|
+
<Thinking active={isThinking} />
|
|
57
57
|
<UserInput onSubmit={onSubmit} disabled={isThinking} />
|
|
58
58
|
<StatusBar messageCount={allMessages.length} skills={activeSkills} />
|
|
59
59
|
</Box>
|
|
@@ -13,7 +13,7 @@ interface Props {
|
|
|
13
13
|
|
|
14
14
|
export const ChatList = memo(function ChatList({ messages, streaming, streamingToolCalls, width }: Props) {
|
|
15
15
|
return (
|
|
16
|
-
<Box flexDirection="column"
|
|
16
|
+
<Box flexDirection="column">
|
|
17
17
|
{messages.map((msg, i) => (
|
|
18
18
|
<ChatMessage key={i} message={msg} width={width} />
|
|
19
19
|
))}
|
|
@@ -14,7 +14,7 @@ export const Header = React.memo(function Header({ provider, model }: Props) {
|
|
|
14
14
|
<Box>
|
|
15
15
|
<Text color="#06B6D4" bold> ⚡ </Text>
|
|
16
16
|
<Text color="#22D3EE" bold>THATGFSJ CODE</Text>
|
|
17
|
-
<Text dimColor> v0.7.
|
|
17
|
+
<Text dimColor> v0.7.3</Text>
|
|
18
18
|
</Box>
|
|
19
19
|
<Box>
|
|
20
20
|
<Text color="#06B6D4" bold> {provider} </Text>
|
|
@@ -4,10 +4,16 @@ import { Box, Text } from 'ink';
|
|
|
4
4
|
import Spinner from 'ink-spinner';
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
|
+
active?: boolean;
|
|
7
8
|
message?: string;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
export function Thinking({ message = 'Thinking' }: Props) {
|
|
11
|
+
export function Thinking({ active = true, message = 'Thinking' }: Props) {
|
|
12
|
+
if (!active) {
|
|
13
|
+
// Keep layout stable - render empty space
|
|
14
|
+
return <Box paddingY={0}><Text> </Text></Box>;
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
return (
|
|
12
18
|
<Box paddingLeft={1}>
|
|
13
19
|
<Text color="#06B6D4">
|
package/src/tui/hooks/useChat.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useCallback, useRef } from 'react';
|
|
1
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
2
2
|
import type { MessageData } from '../components/ChatMessage.js';
|
|
3
3
|
import type { ToolCallData } from '../components/ToolCall.js';
|
|
4
4
|
import type { App } from '../../app/index.js';
|
|
@@ -18,10 +18,12 @@ export function useChat(app: App) {
|
|
|
18
18
|
streamingToolCalls: [],
|
|
19
19
|
});
|
|
20
20
|
const processingRef = useRef(false);
|
|
21
|
+
const abortRef = useRef(false);
|
|
21
22
|
|
|
22
|
-
const sendMessage = useCallback(
|
|
23
|
+
const sendMessage = useCallback((input: string) => {
|
|
23
24
|
if (processingRef.current) return;
|
|
24
25
|
processingRef.current = true;
|
|
26
|
+
abortRef.current = false;
|
|
25
27
|
|
|
26
28
|
// Add user message
|
|
27
29
|
setState(prev => ({
|
|
@@ -34,117 +36,103 @@ export function useChat(app: App) {
|
|
|
34
36
|
|
|
35
37
|
app.session.addMessage('user', input);
|
|
36
38
|
|
|
39
|
+
// Process stream in background (non-blocking)
|
|
40
|
+
const stream = app.streamResponse();
|
|
37
41
|
let fullContent = '';
|
|
38
42
|
let currentToolCalls: ToolCallData[] = [];
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
const processStream = async () => {
|
|
45
|
+
try {
|
|
46
|
+
for await (const chunk of stream) {
|
|
47
|
+
if (abortRef.current) break;
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
...currentToolCalls
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
};
|
|
49
|
+
if (chunk.includes('@@TOOL@@')) {
|
|
50
|
+
const parts = chunk.split('\n');
|
|
51
|
+
for (const part of parts) {
|
|
52
|
+
if (part.startsWith('@@TOOL@@')) {
|
|
53
|
+
try {
|
|
54
|
+
const data = JSON.parse(part.slice(8));
|
|
55
|
+
if (data.action === 'call') {
|
|
56
|
+
currentToolCalls.push({ name: data.name, args: data.args || '' });
|
|
57
|
+
setState(prev => ({
|
|
58
|
+
...prev,
|
|
59
|
+
isThinking: false,
|
|
60
|
+
streamingToolCalls: [...currentToolCalls],
|
|
61
|
+
}));
|
|
62
|
+
} else if (data.action === 'result') {
|
|
63
|
+
const lastIdx = currentToolCalls.length - 1;
|
|
64
|
+
if (lastIdx >= 0) {
|
|
65
|
+
currentToolCalls[lastIdx] = {
|
|
66
|
+
...currentToolCalls[lastIdx],
|
|
67
|
+
result: data.output || data.error || '',
|
|
68
|
+
isError: !!data.error,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
setState(prev => ({
|
|
72
|
+
...prev,
|
|
73
|
+
streamingToolCalls: [...currentToolCalls],
|
|
74
|
+
isThinking: true,
|
|
75
|
+
}));
|
|
71
76
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
isThinking: true, // Continue thinking after tool
|
|
76
|
-
}));
|
|
77
|
+
} catch {
|
|
78
|
+
fullContent += part;
|
|
79
|
+
setState(prev => ({ ...prev, isThinking: false, streaming: fullContent }));
|
|
77
80
|
}
|
|
78
|
-
}
|
|
79
|
-
// Not a tool message, treat as text
|
|
81
|
+
} else if (part) {
|
|
80
82
|
fullContent += part;
|
|
81
|
-
setState(prev => ({
|
|
82
|
-
...prev,
|
|
83
|
-
isThinking: false,
|
|
84
|
-
streaming: fullContent,
|
|
85
|
-
}));
|
|
83
|
+
setState(prev => ({ ...prev, isThinking: false, streaming: fullContent }));
|
|
86
84
|
}
|
|
87
|
-
} else if (part) {
|
|
88
|
-
fullContent += part;
|
|
89
|
-
setState(prev => ({
|
|
90
|
-
...prev,
|
|
91
|
-
isThinking: false,
|
|
92
|
-
streaming: fullContent,
|
|
93
|
-
}));
|
|
94
85
|
}
|
|
86
|
+
} else {
|
|
87
|
+
fullContent += chunk;
|
|
88
|
+
setState(prev => ({ ...prev, isThinking: false, streaming: fullContent }));
|
|
95
89
|
}
|
|
96
|
-
} else {
|
|
97
|
-
// Regular text chunk
|
|
98
|
-
fullContent += chunk;
|
|
99
|
-
setState(prev => ({
|
|
100
|
-
...prev,
|
|
101
|
-
isThinking: false,
|
|
102
|
-
streaming: fullContent,
|
|
103
|
-
}));
|
|
104
90
|
}
|
|
105
|
-
}
|
|
106
91
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
92
|
+
// Finalize
|
|
93
|
+
if (fullContent.trim() || currentToolCalls.length > 0) {
|
|
94
|
+
app.session.addMessage('assistant', fullContent);
|
|
95
|
+
}
|
|
111
96
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
97
|
+
setState(prev => ({
|
|
98
|
+
...prev,
|
|
99
|
+
messages: [
|
|
100
|
+
...prev.messages,
|
|
101
|
+
...(fullContent.trim() || currentToolCalls.length > 0
|
|
102
|
+
? [{
|
|
103
|
+
role: 'assistant' as const,
|
|
104
|
+
content: fullContent,
|
|
105
|
+
toolCalls: currentToolCalls.length > 0 ? currentToolCalls : undefined,
|
|
106
|
+
}]
|
|
107
|
+
: []),
|
|
108
|
+
],
|
|
109
|
+
streaming: '',
|
|
110
|
+
streamingToolCalls: [],
|
|
111
|
+
isThinking: false,
|
|
112
|
+
}));
|
|
128
113
|
|
|
129
|
-
|
|
114
|
+
app.session.truncate();
|
|
115
|
+
} catch (error: any) {
|
|
116
|
+
setState(prev => ({
|
|
117
|
+
...prev,
|
|
118
|
+
messages: [
|
|
119
|
+
...prev.messages,
|
|
120
|
+
...(fullContent.trim()
|
|
121
|
+
? [{ role: 'assistant' as const, content: fullContent }]
|
|
122
|
+
: []),
|
|
123
|
+
{ role: 'assistant', content: `Error: ${error.message}` },
|
|
124
|
+
],
|
|
125
|
+
streaming: '',
|
|
126
|
+
streamingToolCalls: [],
|
|
127
|
+
isThinking: false,
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
...prev,
|
|
134
|
-
messages: [
|
|
135
|
-
...prev.messages,
|
|
136
|
-
...(fullContent.trim()
|
|
137
|
-
? [{ role: 'assistant' as const, content: fullContent }]
|
|
138
|
-
: []),
|
|
139
|
-
{ role: 'assistant', content: `Error: ${error.message}` },
|
|
140
|
-
],
|
|
141
|
-
streaming: '',
|
|
142
|
-
streamingToolCalls: [],
|
|
143
|
-
isThinking: false,
|
|
144
|
-
}));
|
|
145
|
-
}
|
|
131
|
+
processingRef.current = false;
|
|
132
|
+
};
|
|
146
133
|
|
|
147
|
-
|
|
134
|
+
// Start processing without blocking
|
|
135
|
+
processStream();
|
|
148
136
|
}, [app]);
|
|
149
137
|
|
|
150
138
|
return {
|
package/dist/app/agent.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent Loop - Core agent logic with streaming and tool execution
|
|
3
|
-
* Extracted from old src/core/ai-engine.ts
|
|
4
|
-
*
|
|
5
|
-
* The agent loop: while(true) { stream response → check tool calls → execute → repeat }
|
|
6
|
-
*/
|
|
7
|
-
import type { ChatMessage, ChatResponse } from '../types.js';
|
|
8
|
-
import type { HookManager } from '../hooks/index.js';
|
|
9
|
-
import { LLMService } from '../llm/index.js';
|
|
10
|
-
import { ToolRegistry } from '../tools/index.js';
|
|
11
|
-
export interface AgentConfig {
|
|
12
|
-
maxIterations?: number;
|
|
13
|
-
hooks?: HookManager;
|
|
14
|
-
}
|
|
15
|
-
export declare class Agent {
|
|
16
|
-
private llm;
|
|
17
|
-
private registry;
|
|
18
|
-
private hooks?;
|
|
19
|
-
private maxIterations;
|
|
20
|
-
constructor(llm: LLMService, registry: ToolRegistry, config?: AgentConfig);
|
|
21
|
-
/**
|
|
22
|
-
* Run agent loop with streaming output
|
|
23
|
-
* Returns the final response
|
|
24
|
-
*/
|
|
25
|
-
run(messages: ChatMessage[]): AsyncGenerator<string, ChatResponse>;
|
|
26
|
-
/**
|
|
27
|
-
* Non-streaming chat (convenience method)
|
|
28
|
-
*/
|
|
29
|
-
chat(messages: ChatMessage[]): Promise<ChatResponse>;
|
|
30
|
-
}
|
|
31
|
-
//# sourceMappingURL=agent.d.ts.map
|
package/dist/app/agent.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/app/agent.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,WAAW,WAAW;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,qBAAa,KAAK;IAChB,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,KAAK,CAAC,CAAc;IAC5B,OAAO,CAAC,aAAa,CAAS;gBAElB,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,GAAE,WAAgB;IAU7E;;;OAGG;IACI,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE,YAAY,CAAC;IA0FzE;;OAEG;IACG,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC;CAO3D"}
|
package/dist/app/agent.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent Loop - Core agent logic with streaming and tool execution
|
|
3
|
-
* Extracted from old src/core/ai-engine.ts
|
|
4
|
-
*
|
|
5
|
-
* The agent loop: while(true) { stream response → check tool calls → execute → repeat }
|
|
6
|
-
*/
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
export class Agent {
|
|
9
|
-
llm;
|
|
10
|
-
registry;
|
|
11
|
-
hooks;
|
|
12
|
-
maxIterations;
|
|
13
|
-
constructor(llm, registry, config = {}) {
|
|
14
|
-
this.llm = llm;
|
|
15
|
-
this.registry = registry;
|
|
16
|
-
this.hooks = config.hooks;
|
|
17
|
-
this.maxIterations = config.maxIterations ?? 10;
|
|
18
|
-
// Register tools with LLM service
|
|
19
|
-
this.llm.registerTools(registry.list());
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Run agent loop with streaming output
|
|
23
|
-
* Returns the final response
|
|
24
|
-
*/
|
|
25
|
-
async *run(messages) {
|
|
26
|
-
let currentMessages = [...messages];
|
|
27
|
-
let iterations = 0;
|
|
28
|
-
while (iterations < this.maxIterations) {
|
|
29
|
-
iterations++;
|
|
30
|
-
// Hook: beforeAgentLoop
|
|
31
|
-
if (this.hooks) {
|
|
32
|
-
await this.hooks.emit('beforeAgentLoop', { messages: currentMessages, iteration: iterations });
|
|
33
|
-
}
|
|
34
|
-
// Stream the response
|
|
35
|
-
let fullContent = '';
|
|
36
|
-
for await (const chunk of this.llm.chatStream(currentMessages, { maxIterations: 1 })) {
|
|
37
|
-
fullContent += chunk;
|
|
38
|
-
yield chunk;
|
|
39
|
-
}
|
|
40
|
-
// Get the full response to check for tool calls
|
|
41
|
-
const response = {
|
|
42
|
-
content: fullContent,
|
|
43
|
-
role: 'assistant',
|
|
44
|
-
};
|
|
45
|
-
// Check if the response contains tool calls
|
|
46
|
-
// For now, we rely on the LLM service to handle tool calls internally
|
|
47
|
-
// If the LLM service returned tool_calls, we need to process them
|
|
48
|
-
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
49
|
-
currentMessages.push({
|
|
50
|
-
role: 'assistant',
|
|
51
|
-
content: response.content,
|
|
52
|
-
tool_calls: response.tool_calls,
|
|
53
|
-
});
|
|
54
|
-
for (const toolCall of response.tool_calls) {
|
|
55
|
-
// Hook: beforeToolCall
|
|
56
|
-
if (this.hooks) {
|
|
57
|
-
await this.hooks.emit('beforeToolCall', {
|
|
58
|
-
toolName: toolCall.function.name,
|
|
59
|
-
toolParams: JSON.parse(toolCall.function.arguments || '{}'),
|
|
60
|
-
toolCallId: toolCall.id,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
const result = await this.registry.execute(toolCall.function.name, JSON.parse(toolCall.function.arguments || '{}'));
|
|
64
|
-
// Hook: afterToolCall
|
|
65
|
-
if (this.hooks) {
|
|
66
|
-
await this.hooks.emit('afterToolCall', {
|
|
67
|
-
toolName: toolCall.function.name,
|
|
68
|
-
toolParams: JSON.parse(toolCall.function.arguments || '{}'),
|
|
69
|
-
toolResult: result,
|
|
70
|
-
toolCallId: toolCall.id,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
currentMessages.push({
|
|
74
|
-
role: 'tool',
|
|
75
|
-
content: result.success ? (result.output || JSON.stringify(result.data)) : (result.error || 'Tool failed'),
|
|
76
|
-
tool_call_id: toolCall.id,
|
|
77
|
-
name: toolCall.function.name,
|
|
78
|
-
});
|
|
79
|
-
yield `\n${chalk.gray(`[tool: ${toolCall.function.name}]`)}`;
|
|
80
|
-
}
|
|
81
|
-
continue; // Next iteration
|
|
82
|
-
}
|
|
83
|
-
// No tool calls - done
|
|
84
|
-
if (response.content) {
|
|
85
|
-
currentMessages.push({ role: 'assistant', content: response.content });
|
|
86
|
-
}
|
|
87
|
-
// Hook: afterAgentLoop
|
|
88
|
-
if (this.hooks) {
|
|
89
|
-
await this.hooks.emit('afterAgentLoop', { messages: currentMessages, iteration: iterations });
|
|
90
|
-
}
|
|
91
|
-
return response;
|
|
92
|
-
}
|
|
93
|
-
return { content: '[Agent loop exceeded maximum iterations]', role: 'assistant' };
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Non-streaming chat (convenience method)
|
|
97
|
-
*/
|
|
98
|
-
async chat(messages) {
|
|
99
|
-
let fullContent = '';
|
|
100
|
-
for await (const chunk of this.run(messages)) {
|
|
101
|
-
fullContent += chunk;
|
|
102
|
-
}
|
|
103
|
-
return { content: fullContent, role: 'assistant' };
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
//# sourceMappingURL=agent.js.map
|
package/dist/app/agent.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/app/agent.ts"],"names":[],"mappings":"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"}
|
package/dist/tui/input.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* REPL Input Handler
|
|
3
|
-
*/
|
|
4
|
-
export declare class REPLInput {
|
|
5
|
-
private rl;
|
|
6
|
-
private history;
|
|
7
|
-
private historyIndex;
|
|
8
|
-
constructor();
|
|
9
|
-
prompt(prefix?: string): Promise<string>;
|
|
10
|
-
close(): void;
|
|
11
|
-
private completer;
|
|
12
|
-
}
|
|
13
|
-
//# sourceMappingURL=input.d.ts.map
|
package/dist/tui/input.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/tui/input.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,qBAAa,SAAS;IACpB,OAAO,CAAC,EAAE,CAAqB;IAC/B,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,YAAY,CAAc;;IAyB5B,MAAM,CAAC,MAAM,GAAE,MAAyB,GAAG,OAAO,CAAC,MAAM,CAAC;IAQhE,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,SAAS;CAKlB"}
|
package/dist/tui/input.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* REPL Input Handler
|
|
3
|
-
*/
|
|
4
|
-
import readline from 'readline';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
export class REPLInput {
|
|
7
|
-
rl;
|
|
8
|
-
history = [];
|
|
9
|
-
historyIndex = -1;
|
|
10
|
-
constructor() {
|
|
11
|
-
this.rl = readline.createInterface({
|
|
12
|
-
input: process.stdin,
|
|
13
|
-
output: process.stdout,
|
|
14
|
-
completer: this.completer.bind(this),
|
|
15
|
-
historySize: 100,
|
|
16
|
-
tabSize: 4,
|
|
17
|
-
});
|
|
18
|
-
this.rl.on('SIGINT', () => {
|
|
19
|
-
process.stdout.write(chalk.gray('\n Goodbye! 👋\n'));
|
|
20
|
-
process.exit(0);
|
|
21
|
-
});
|
|
22
|
-
this.rl.on('line', (line) => {
|
|
23
|
-
if (line.trim()) {
|
|
24
|
-
this.history.push(line);
|
|
25
|
-
if (this.history.length > 100)
|
|
26
|
-
this.history.shift();
|
|
27
|
-
}
|
|
28
|
-
this.historyIndex = this.history.length;
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
async prompt(prefix = chalk.cyan('❯ ')) {
|
|
32
|
-
return new Promise((resolve) => {
|
|
33
|
-
this.rl.question(prefix, (answer) => {
|
|
34
|
-
resolve(answer);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
close() {
|
|
39
|
-
this.rl.close();
|
|
40
|
-
}
|
|
41
|
-
completer(line) {
|
|
42
|
-
const commands = ['exit', 'quit', 'clear', 'help', 'tools', 'model'];
|
|
43
|
-
const hits = commands.filter(c => c.startsWith(line.toLowerCase()));
|
|
44
|
-
return [hits.length ? hits : [], line];
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
//# sourceMappingURL=input.js.map
|
package/dist/tui/input.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"input.js","sourceRoot":"","sources":["../../src/tui/input.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,QAAQ,MAAM,UAAU,CAAC;AAChC,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,OAAO,SAAS;IACZ,EAAE,CAAqB;IACvB,OAAO,GAAa,EAAE,CAAC;IACvB,YAAY,GAAW,CAAC,CAAC,CAAC;IAElC;QACE,IAAI,CAAC,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;YACjC,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;YACpC,WAAW,EAAE,GAAG;YAChB,OAAO,EAAE,CAAC;SACX,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;YACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC;YACtD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAC1B,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gBAChB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACxB,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,GAAG;oBAAE,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACtD,CAAC;YACD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QAC1C,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAAiB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE;gBAClC,OAAO,CAAC,MAAM,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK;QACH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;IAEO,SAAS,CAAC,IAAY;QAC5B,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QACrE,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QACpE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACzC,CAAC;CACF"}
|