activo 0.4.3 → 0.5.0
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/README.md +203 -1
- package/data/2026-03-04_20-54.json +181 -0
- package/data/2026-03-04_20-56.json +181 -0
- package/data/apex-rulesets/egov.yaml +469 -0
- package/data/apex-rulesets/modernize.yaml +687 -0
- package/data/apex-rulesets/quality.yaml +1677 -0
- package/data/apex-rulesets/rule-schema.yaml +587 -0
- package/data/apex-rulesets/secure.yaml +1688 -0
- package/data/apex-rulesets/spring.yaml +455 -0
- package/data/apex-rulesets/sql-format.yaml +99 -0
- package/data/apex-rulesets/sql-oracle.yaml +281 -0
- package/data/apex-rulesets/sql.yaml +1660 -0
- package/dist/cli/headless.d.ts.map +1 -1
- package/dist/cli/headless.js +32 -10
- package/dist/cli/headless.js.map +1 -1
- package/dist/cli/index.js +31 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/core/agent.d.ts +3 -3
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +255 -17
- package/dist/core/agent.js.map +1 -1
- package/dist/core/commands.d.ts +2 -1
- package/dist/core/commands.d.ts.map +1 -1
- package/dist/core/commands.js +61 -9
- package/dist/core/commands.js.map +1 -1
- package/dist/core/config.d.ts +14 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +41 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/conversation.d.ts +2 -2
- package/dist/core/conversation.d.ts.map +1 -1
- package/dist/core/conversation.js.map +1 -1
- package/dist/core/intentRouter.d.ts +43 -0
- package/dist/core/intentRouter.d.ts.map +1 -0
- package/dist/core/intentRouter.js +804 -0
- package/dist/core/intentRouter.js.map +1 -0
- package/dist/core/llm/anthropic.d.ts +24 -0
- package/dist/core/llm/anthropic.d.ts.map +1 -0
- package/dist/core/llm/anthropic.js +226 -0
- package/dist/core/llm/anthropic.js.map +1 -0
- package/dist/core/llm/ollama.d.ts +5 -14
- package/dist/core/llm/ollama.d.ts.map +1 -1
- package/dist/core/llm/ollama.js +3 -0
- package/dist/core/llm/ollama.js.map +1 -1
- package/dist/core/llm/types.d.ts +22 -0
- package/dist/core/llm/types.d.ts.map +1 -0
- package/dist/core/llm/types.js +2 -0
- package/dist/core/llm/types.js.map +1 -0
- package/dist/core/mcp/client.d.ts +6 -0
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +16 -0
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/init.d.ts +12 -0
- package/dist/core/mcp/init.d.ts.map +1 -0
- package/dist/core/mcp/init.js +55 -0
- package/dist/core/mcp/init.js.map +1 -0
- package/dist/core/mcp/logger.d.ts +14 -0
- package/dist/core/mcp/logger.d.ts.map +1 -0
- package/dist/core/mcp/logger.js +50 -0
- package/dist/core/mcp/logger.js.map +1 -0
- package/dist/core/tools/analyzeAll.d.ts.map +1 -1
- package/dist/core/tools/analyzeAll.js +16 -28
- package/dist/core/tools/analyzeAll.js.map +1 -1
- package/dist/core/tools/analyzePatterns.d.ts +3 -0
- package/dist/core/tools/analyzePatterns.d.ts.map +1 -0
- package/dist/core/tools/analyzePatterns.js +293 -0
- package/dist/core/tools/analyzePatterns.js.map +1 -0
- package/dist/core/tools/apexPaths.d.ts +14 -0
- package/dist/core/tools/apexPaths.d.ts.map +1 -0
- package/dist/core/tools/apexPaths.js +54 -0
- package/dist/core/tools/apexPaths.js.map +1 -0
- package/dist/core/tools/apexUtils.d.ts +36 -0
- package/dist/core/tools/apexUtils.d.ts.map +1 -0
- package/dist/core/tools/apexUtils.js +83 -0
- package/dist/core/tools/apexUtils.js.map +1 -0
- package/dist/core/tools/explainIssue.d.ts +3 -0
- package/dist/core/tools/explainIssue.d.ts.map +1 -0
- package/dist/core/tools/explainIssue.js +181 -0
- package/dist/core/tools/explainIssue.js.map +1 -0
- package/dist/core/tools/fixGen.d.ts +3 -0
- package/dist/core/tools/fixGen.d.ts.map +1 -0
- package/dist/core/tools/fixGen.js +338 -0
- package/dist/core/tools/fixGen.js.map +1 -0
- package/dist/core/tools/generateImprovements.d.ts +21 -0
- package/dist/core/tools/generateImprovements.d.ts.map +1 -0
- package/dist/core/tools/generateImprovements.js +602 -0
- package/dist/core/tools/generateImprovements.js.map +1 -0
- package/dist/core/tools/generateReport.d.ts +3 -0
- package/dist/core/tools/generateReport.d.ts.map +1 -0
- package/dist/core/tools/generateReport.js +315 -0
- package/dist/core/tools/generateReport.js.map +1 -0
- package/dist/core/tools/index.d.ts +7 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +62 -23
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/javaAst.d.ts.map +1 -1
- package/dist/core/tools/javaAst.js +191 -0
- package/dist/core/tools/javaAst.js.map +1 -1
- package/dist/core/tools/recommendProfile.d.ts +3 -0
- package/dist/core/tools/recommendProfile.d.ts.map +1 -0
- package/dist/core/tools/recommendProfile.js +334 -0
- package/dist/core/tools/recommendProfile.js.map +1 -0
- package/dist/core/tools/ruleGen.d.ts +3 -0
- package/dist/core/tools/ruleGen.d.ts.map +1 -0
- package/dist/core/tools/ruleGen.js +1103 -0
- package/dist/core/tools/ruleGen.js.map +1 -0
- package/dist/core/tools/standards.d.ts.map +1 -1
- package/dist/core/tools/standards.js +7 -3
- package/dist/core/tools/standards.js.map +1 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +86 -35
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/InputBox.d.ts +1 -3
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +146 -5
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/MessageList.d.ts +3 -1
- package/dist/ui/components/MessageList.d.ts.map +1 -1
- package/dist/ui/components/MessageList.js +13 -7
- package/dist/ui/components/MessageList.js.map +1 -1
- package/dist/ui/components/StatusBar.d.ts +1 -1
- package/dist/ui/components/StatusBar.d.ts.map +1 -1
- package/dist/ui/components/StatusBar.js +3 -2
- package/dist/ui/components/StatusBar.js.map +1 -1
- package/dist/ui/components/ToolStatus.d.ts +3 -1
- package/dist/ui/components/ToolStatus.d.ts.map +1 -1
- package/dist/ui/components/ToolStatus.js +19 -4
- package/dist/ui/components/ToolStatus.js.map +1 -1
- package/package.json +7 -1
- package/demo.gif +0 -0
- package/demo.tape +0 -53
- package/screenshot.png +0 -0
- package/src/cli/banner.ts +0 -38
- package/src/cli/headless.ts +0 -63
- package/src/cli/index.ts +0 -57
- package/src/core/agent.ts +0 -237
- package/src/core/commands.ts +0 -118
- package/src/core/config.ts +0 -98
- package/src/core/conversation.ts +0 -235
- package/src/core/llm/ollama.ts +0 -351
- package/src/core/mcp/client.ts +0 -143
- package/src/core/tools/analyzeAll.ts +0 -494
- package/src/core/tools/ast.ts +0 -826
- package/src/core/tools/builtIn.ts +0 -221
- package/src/core/tools/cache.ts +0 -570
- package/src/core/tools/cssAnalysis.ts +0 -324
- package/src/core/tools/dependencyAnalysis.ts +0 -363
- package/src/core/tools/embeddings.ts +0 -746
- package/src/core/tools/frontendAst.ts +0 -802
- package/src/core/tools/htmlAnalysis.ts +0 -466
- package/src/core/tools/index.ts +0 -160
- package/src/core/tools/javaAst.ts +0 -812
- package/src/core/tools/memory.ts +0 -655
- package/src/core/tools/mybatisAnalysis.ts +0 -322
- package/src/core/tools/openapiAnalysis.ts +0 -431
- package/src/core/tools/pythonAnalysis.ts +0 -477
- package/src/core/tools/sqlAnalysis.ts +0 -298
- package/src/core/tools/standards.test.ts +0 -186
- package/src/core/tools/standards.ts +0 -889
- package/src/core/tools/types.ts +0 -38
- package/src/ui/App.tsx +0 -334
- package/src/ui/components/InputBox.tsx +0 -37
- package/src/ui/components/MessageList.tsx +0 -80
- package/src/ui/components/StatusBar.tsx +0 -36
- package/src/ui/components/ToolStatus.tsx +0 -38
- package/tsconfig.json +0 -21
package/src/core/agent.ts
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
import { OllamaClient, ChatMessage } from "./llm/ollama.js";
|
|
2
|
-
import { Config } from "./config.js";
|
|
3
|
-
import { getAllTools, selectTools, executeTool, ToolCall, ToolResult, Tool } from "./tools/index.js";
|
|
4
|
-
|
|
5
|
-
export interface AgentEvent {
|
|
6
|
-
type: "thinking" | "content" | "tool_use" | "tool_result" | "done" | "error";
|
|
7
|
-
content?: string;
|
|
8
|
-
tool?: string;
|
|
9
|
-
status?: "start" | "complete" | "error";
|
|
10
|
-
args?: Record<string, unknown>;
|
|
11
|
-
result?: ToolResult;
|
|
12
|
-
error?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface AgentResult {
|
|
16
|
-
content: string;
|
|
17
|
-
toolCalls: Array<{
|
|
18
|
-
tool: string;
|
|
19
|
-
args: Record<string, unknown>;
|
|
20
|
-
result: ToolResult;
|
|
21
|
-
}>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const BASE_SYSTEM_PROMPT = `You are ACTIVO, a code quality analyzer. You MUST call tools to perform tasks.
|
|
25
|
-
|
|
26
|
-
## RULES
|
|
27
|
-
1. Call tool IMMEDIATELY when user requests an action
|
|
28
|
-
2. NEVER fabricate results - only report actual tool output
|
|
29
|
-
3. After tool returns, summarize in user's language (Korean if user speaks Korean)
|
|
30
|
-
4. Use analyze_all for broad code analysis`;
|
|
31
|
-
|
|
32
|
-
// Build system prompt with optional context
|
|
33
|
-
function buildSystemPrompt(contextSummary?: string): string {
|
|
34
|
-
if (!contextSummary) {
|
|
35
|
-
return BASE_SYSTEM_PROMPT;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return `${BASE_SYSTEM_PROMPT}
|
|
39
|
-
|
|
40
|
-
## 이전 대화 컨텍스트
|
|
41
|
-
|
|
42
|
-
${contextSummary}
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
위 내용은 이전 세션에서의 대화 요약입니다. 필요시 참고하세요.`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export async function processMessage(
|
|
49
|
-
userMessage: string,
|
|
50
|
-
history: ChatMessage[],
|
|
51
|
-
client: OllamaClient,
|
|
52
|
-
config: Config,
|
|
53
|
-
onEvent?: (event: AgentEvent) => void,
|
|
54
|
-
contextSummary?: string
|
|
55
|
-
): Promise<AgentResult> {
|
|
56
|
-
const tools = selectTools(userMessage);
|
|
57
|
-
const systemPrompt = buildSystemPrompt(contextSummary);
|
|
58
|
-
|
|
59
|
-
const messages: ChatMessage[] = [
|
|
60
|
-
{ role: "system", content: systemPrompt },
|
|
61
|
-
...history,
|
|
62
|
-
{ role: "user", content: userMessage },
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
const toolCallResults: AgentResult["toolCalls"] = [];
|
|
66
|
-
let finalContent = "";
|
|
67
|
-
let iterations = 0;
|
|
68
|
-
const maxIterations = 10;
|
|
69
|
-
|
|
70
|
-
while (iterations < maxIterations) {
|
|
71
|
-
iterations++;
|
|
72
|
-
|
|
73
|
-
onEvent?.({ type: "thinking" });
|
|
74
|
-
|
|
75
|
-
const response = await client.chat(messages, tools as Tool[]);
|
|
76
|
-
messages.push(response);
|
|
77
|
-
|
|
78
|
-
// If no tool calls, we're done
|
|
79
|
-
if (!response.toolCalls?.length) {
|
|
80
|
-
finalContent = response.content;
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Process tool calls
|
|
85
|
-
for (const toolCall of response.toolCalls) {
|
|
86
|
-
onEvent?.({
|
|
87
|
-
type: "tool_use",
|
|
88
|
-
tool: toolCall.name,
|
|
89
|
-
status: "start",
|
|
90
|
-
args: toolCall.arguments,
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
const result = await executeTool(toolCall);
|
|
94
|
-
|
|
95
|
-
onEvent?.({
|
|
96
|
-
type: "tool_result",
|
|
97
|
-
tool: toolCall.name,
|
|
98
|
-
status: result.success ? "complete" : "error",
|
|
99
|
-
result,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
toolCallResults.push({
|
|
103
|
-
tool: toolCall.name,
|
|
104
|
-
args: toolCall.arguments,
|
|
105
|
-
result,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// Add tool result to messages
|
|
109
|
-
messages.push({
|
|
110
|
-
role: "tool",
|
|
111
|
-
content: result.success ? result.content : `Error: ${result.error}`,
|
|
112
|
-
toolCallId: toolCall.id,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Continue the conversation with tool results
|
|
117
|
-
onEvent?.({ type: "content", content: response.content });
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (iterations >= maxIterations) {
|
|
121
|
-
onEvent?.({ type: "error", error: "Maximum iterations reached" });
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
onEvent?.({ type: "done" });
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
content: finalContent,
|
|
128
|
-
toolCalls: toolCallResults,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export async function* streamProcessMessage(
|
|
133
|
-
userMessage: string,
|
|
134
|
-
history: ChatMessage[],
|
|
135
|
-
client: OllamaClient,
|
|
136
|
-
config: Config,
|
|
137
|
-
abortSignal?: AbortSignal,
|
|
138
|
-
contextSummary?: string
|
|
139
|
-
): AsyncGenerator<AgentEvent> {
|
|
140
|
-
const tools = selectTools(userMessage);
|
|
141
|
-
const systemPrompt = buildSystemPrompt(contextSummary);
|
|
142
|
-
|
|
143
|
-
const messages: ChatMessage[] = [
|
|
144
|
-
{ role: "system", content: systemPrompt },
|
|
145
|
-
...history,
|
|
146
|
-
{ role: "user", content: userMessage },
|
|
147
|
-
];
|
|
148
|
-
|
|
149
|
-
let iterations = 0;
|
|
150
|
-
const maxIterations = 10;
|
|
151
|
-
|
|
152
|
-
while (iterations < maxIterations) {
|
|
153
|
-
// Check if aborted
|
|
154
|
-
if (abortSignal?.aborted) {
|
|
155
|
-
yield { type: "error", error: "Operation cancelled" };
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
iterations++;
|
|
160
|
-
|
|
161
|
-
yield { type: "thinking" };
|
|
162
|
-
|
|
163
|
-
let fullContent = "";
|
|
164
|
-
const pendingToolCalls: ToolCall[] = [];
|
|
165
|
-
|
|
166
|
-
// Collect all events first (non-streaming mode for tools)
|
|
167
|
-
for await (const event of client.streamChat(messages, tools as Tool[], abortSignal)) {
|
|
168
|
-
if (abortSignal?.aborted) {
|
|
169
|
-
yield { type: "error", error: "Operation cancelled" };
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
if (event.type === "content" && event.content) {
|
|
173
|
-
fullContent += event.content;
|
|
174
|
-
// Don't yield content yet - wait to see if there are tool calls
|
|
175
|
-
} else if (event.type === "tool_call" && event.toolCall) {
|
|
176
|
-
pendingToolCalls.push(event.toolCall);
|
|
177
|
-
} else if (event.type === "error") {
|
|
178
|
-
yield { type: "error", error: event.error };
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Only yield content if NO tool calls (avoid hallucinated pre-tool text)
|
|
184
|
-
if (pendingToolCalls.length === 0 && fullContent) {
|
|
185
|
-
yield { type: "content", content: fullContent };
|
|
186
|
-
} else if (pendingToolCalls.length > 0) {
|
|
187
|
-
// Clear content when tool calls exist
|
|
188
|
-
fullContent = "";
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
messages.push({ role: "assistant", content: fullContent, toolCalls: pendingToolCalls.length > 0 ? pendingToolCalls : undefined });
|
|
192
|
-
|
|
193
|
-
// If no tool calls, we're done
|
|
194
|
-
if (pendingToolCalls.length === 0) {
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Process tool calls
|
|
199
|
-
for (const toolCall of pendingToolCalls) {
|
|
200
|
-
// Check if aborted before each tool call
|
|
201
|
-
if (abortSignal?.aborted) {
|
|
202
|
-
yield { type: "error", error: "Operation cancelled" };
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
yield {
|
|
207
|
-
type: "tool_use",
|
|
208
|
-
tool: toolCall.name,
|
|
209
|
-
status: "start",
|
|
210
|
-
args: toolCall.arguments,
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
const result = await executeTool(toolCall);
|
|
214
|
-
|
|
215
|
-
// Check if aborted after tool execution
|
|
216
|
-
if (abortSignal?.aborted) {
|
|
217
|
-
yield { type: "error", error: "Operation cancelled" };
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
yield {
|
|
222
|
-
type: "tool_result",
|
|
223
|
-
tool: toolCall.name,
|
|
224
|
-
status: result.success ? "complete" : "error",
|
|
225
|
-
result,
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
messages.push({
|
|
229
|
-
role: "tool",
|
|
230
|
-
content: result.success ? result.content : `Error: ${result.error}`,
|
|
231
|
-
toolCallId: toolCall.id,
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
yield { type: "done" };
|
|
237
|
-
}
|
package/src/core/commands.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { Config, loadConfig, saveConfig } from "./config.js";
|
|
2
|
-
|
|
3
|
-
export interface SlashCommandResult {
|
|
4
|
-
output?: string;
|
|
5
|
-
exit?: boolean;
|
|
6
|
-
clear?: boolean;
|
|
7
|
-
changeModel?: string;
|
|
8
|
-
showHelp?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
type CommandHandler = (args: string[], config: Config) => SlashCommandResult;
|
|
12
|
-
|
|
13
|
-
const HELP_MESSAGE = `
|
|
14
|
-
ACTIVO - AI 코드 품질 분석 도구
|
|
15
|
-
|
|
16
|
-
[슬래시 커맨드]
|
|
17
|
-
/help 이 도움말 표시
|
|
18
|
-
/exit, /quit 종료
|
|
19
|
-
/clear 채팅 기록 삭제
|
|
20
|
-
/model <name> 모델 변경 (예: /model qwen2.5:7b)
|
|
21
|
-
/info 현재 설정 정보 표시
|
|
22
|
-
|
|
23
|
-
[단축키]
|
|
24
|
-
Enter 메시지 전송
|
|
25
|
-
ESC 진행 중 작업 취소
|
|
26
|
-
Ctrl+C x2 종료
|
|
27
|
-
|
|
28
|
-
[사용 예시]
|
|
29
|
-
"src 폴더 구조 보여줘"
|
|
30
|
-
"package.json 분석해줘"
|
|
31
|
-
"코드 품질 검사해줘"
|
|
32
|
-
"PDF를 마크다운으로 변환해줘"
|
|
33
|
-
`.trim();
|
|
34
|
-
|
|
35
|
-
const commandHandlers: Record<string, CommandHandler> = {
|
|
36
|
-
help: () => ({
|
|
37
|
-
output: HELP_MESSAGE,
|
|
38
|
-
showHelp: true,
|
|
39
|
-
}),
|
|
40
|
-
|
|
41
|
-
exit: () => ({
|
|
42
|
-
exit: true,
|
|
43
|
-
output: "Goodbye!",
|
|
44
|
-
}),
|
|
45
|
-
|
|
46
|
-
quit: () => ({
|
|
47
|
-
exit: true,
|
|
48
|
-
output: "Goodbye!",
|
|
49
|
-
}),
|
|
50
|
-
|
|
51
|
-
clear: () => ({
|
|
52
|
-
clear: true,
|
|
53
|
-
output: "채팅 기록이 삭제되었습니다.",
|
|
54
|
-
}),
|
|
55
|
-
|
|
56
|
-
model: (args, config) => {
|
|
57
|
-
if (args.length === 0) {
|
|
58
|
-
return {
|
|
59
|
-
output: `현재 모델: ${config.ollama.model}\n\n사용법: /model <model_name>\n예시: /model qwen2.5:7b`,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const newModel = args[0];
|
|
64
|
-
config.ollama.model = newModel;
|
|
65
|
-
saveConfig(config);
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
changeModel: newModel,
|
|
69
|
-
output: `모델이 "${newModel}"로 변경되었습니다.`,
|
|
70
|
-
};
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
info: (args, config) => {
|
|
74
|
-
const info = `
|
|
75
|
-
[ACTIVO 정보]
|
|
76
|
-
버전: 0.2.1
|
|
77
|
-
|
|
78
|
-
[Ollama 설정]
|
|
79
|
-
URL: ${config.ollama.baseUrl}
|
|
80
|
-
모델: ${config.ollama.model}
|
|
81
|
-
컨텍스트: ${config.ollama.contextLength}
|
|
82
|
-
|
|
83
|
-
[표준 디렉토리]
|
|
84
|
-
${config.standards.directory}
|
|
85
|
-
`.trim();
|
|
86
|
-
|
|
87
|
-
return { output: info };
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
export function handleSlashCommand(
|
|
92
|
-
input: string,
|
|
93
|
-
config: Config
|
|
94
|
-
): SlashCommandResult | null {
|
|
95
|
-
// "/" 로 시작하지 않으면 null 반환
|
|
96
|
-
if (!input.startsWith("/")) {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 파싱: "/" 제거 후 공백으로 분할
|
|
101
|
-
const trimmed = input.slice(1).trim();
|
|
102
|
-
const [command, ...args] = trimmed.split(/\s+/);
|
|
103
|
-
|
|
104
|
-
if (!command) {
|
|
105
|
-
return { output: "명령어를 입력하세요. /help 로 도움말을 확인하세요." };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const handler = commandHandlers[command.toLowerCase()];
|
|
109
|
-
if (handler) {
|
|
110
|
-
return handler(args, config);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return { output: `알 수 없는 명령어: /${command}\n/help 로 사용 가능한 명령어를 확인하세요.` };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function getAvailableCommands(): string[] {
|
|
117
|
-
return Object.keys(commandHandlers);
|
|
118
|
-
}
|
package/src/core/config.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import os from "os";
|
|
4
|
-
|
|
5
|
-
export interface OllamaConfig {
|
|
6
|
-
baseUrl: string;
|
|
7
|
-
model: string;
|
|
8
|
-
contextLength: number;
|
|
9
|
-
keepAlive: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface Config {
|
|
13
|
-
ollama: OllamaConfig;
|
|
14
|
-
standards: {
|
|
15
|
-
directory: string;
|
|
16
|
-
};
|
|
17
|
-
mcp: {
|
|
18
|
-
servers: Record<string, MCPServerConfig>;
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface MCPServerConfig {
|
|
23
|
-
command: string;
|
|
24
|
-
args?: string[];
|
|
25
|
-
env?: Record<string, string>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const DEFAULT_CONFIG: Config = {
|
|
29
|
-
ollama: {
|
|
30
|
-
baseUrl: "http://localhost:11434",
|
|
31
|
-
model: "mistral:latest",
|
|
32
|
-
contextLength: 4096,
|
|
33
|
-
keepAlive: 1800, // 30 minutes
|
|
34
|
-
},
|
|
35
|
-
standards: {
|
|
36
|
-
directory: ".activo/standards",
|
|
37
|
-
},
|
|
38
|
-
mcp: {
|
|
39
|
-
servers: {},
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
function getConfigPath(): string {
|
|
44
|
-
return path.join(os.homedir(), ".activo", "config.json");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function loadConfig(): Config {
|
|
48
|
-
const configPath = getConfigPath();
|
|
49
|
-
|
|
50
|
-
if (fs.existsSync(configPath)) {
|
|
51
|
-
try {
|
|
52
|
-
const data = fs.readFileSync(configPath, "utf-8");
|
|
53
|
-
const loaded = JSON.parse(data);
|
|
54
|
-
return {
|
|
55
|
-
ollama: { ...DEFAULT_CONFIG.ollama, ...loaded.ollama },
|
|
56
|
-
standards: { ...DEFAULT_CONFIG.standards, ...loaded.standards },
|
|
57
|
-
mcp: { ...DEFAULT_CONFIG.mcp, ...loaded.mcp },
|
|
58
|
-
};
|
|
59
|
-
} catch {
|
|
60
|
-
return DEFAULT_CONFIG;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return DEFAULT_CONFIG;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function saveConfig(config: Config): void {
|
|
68
|
-
const configPath = getConfigPath();
|
|
69
|
-
const configDir = path.dirname(configPath);
|
|
70
|
-
|
|
71
|
-
if (!fs.existsSync(configDir)) {
|
|
72
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function getProjectConfig(): Config {
|
|
79
|
-
const projectConfigPath = path.join(process.cwd(), ".activo", "config.json");
|
|
80
|
-
|
|
81
|
-
if (fs.existsSync(projectConfigPath)) {
|
|
82
|
-
try {
|
|
83
|
-
const data = fs.readFileSync(projectConfigPath, "utf-8");
|
|
84
|
-
const projectConfig = JSON.parse(data);
|
|
85
|
-
const globalConfig = loadConfig();
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
ollama: { ...globalConfig.ollama, ...projectConfig.ollama },
|
|
89
|
-
standards: { ...globalConfig.standards, ...projectConfig.standards },
|
|
90
|
-
mcp: { ...globalConfig.mcp, ...projectConfig.mcp },
|
|
91
|
-
};
|
|
92
|
-
} catch {
|
|
93
|
-
return loadConfig();
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return loadConfig();
|
|
98
|
-
}
|
package/src/core/conversation.ts
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { ChatMessage, OllamaClient } from "./llm/ollama.js";
|
|
4
|
-
|
|
5
|
-
// Conversation storage directory
|
|
6
|
-
const CONVERSATION_DIR = ".activo/conversations";
|
|
7
|
-
|
|
8
|
-
// Session data interface
|
|
9
|
-
interface SessionData {
|
|
10
|
-
id: string;
|
|
11
|
-
startedAt: string;
|
|
12
|
-
updatedAt: string;
|
|
13
|
-
messages: ChatMessage[];
|
|
14
|
-
summary?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Get conversation directory path
|
|
18
|
-
function getConversationDir(): string {
|
|
19
|
-
return path.resolve(process.cwd(), CONVERSATION_DIR);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Ensure conversation directory exists
|
|
23
|
-
function ensureConversationDir(): void {
|
|
24
|
-
const dir = getConversationDir();
|
|
25
|
-
if (!fs.existsSync(dir)) {
|
|
26
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Generate session ID
|
|
31
|
-
export function generateSessionId(): string {
|
|
32
|
-
return `session_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Get session file path
|
|
36
|
-
function getSessionPath(sessionId: string): string {
|
|
37
|
-
return path.join(getConversationDir(), `${sessionId}.json`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Get latest session file
|
|
41
|
-
function getLatestSessionPath(): string | null {
|
|
42
|
-
const dir = getConversationDir();
|
|
43
|
-
if (!fs.existsSync(dir)) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const files = fs.readdirSync(dir)
|
|
48
|
-
.filter(f => f.startsWith("session_") && f.endsWith(".json"))
|
|
49
|
-
.sort()
|
|
50
|
-
.reverse();
|
|
51
|
-
|
|
52
|
-
if (files.length === 0) {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return path.join(dir, files[0]);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Load session data
|
|
60
|
-
export function loadSession(sessionId: string): SessionData | null {
|
|
61
|
-
const sessionPath = getSessionPath(sessionId);
|
|
62
|
-
if (!fs.existsSync(sessionPath)) {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
return JSON.parse(fs.readFileSync(sessionPath, "utf-8"));
|
|
68
|
-
} catch {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Load latest session
|
|
74
|
-
export function loadLatestSession(): SessionData | null {
|
|
75
|
-
const latestPath = getLatestSessionPath();
|
|
76
|
-
if (!latestPath) {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
return JSON.parse(fs.readFileSync(latestPath, "utf-8"));
|
|
82
|
-
} catch {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Save session data
|
|
88
|
-
export function saveSession(session: SessionData): void {
|
|
89
|
-
ensureConversationDir();
|
|
90
|
-
session.updatedAt = new Date().toISOString();
|
|
91
|
-
fs.writeFileSync(getSessionPath(session.id), JSON.stringify(session, null, 2));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Create new session
|
|
95
|
-
export function createSession(): SessionData {
|
|
96
|
-
return {
|
|
97
|
-
id: generateSessionId(),
|
|
98
|
-
startedAt: new Date().toISOString(),
|
|
99
|
-
updatedAt: new Date().toISOString(),
|
|
100
|
-
messages: [],
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Add message to session
|
|
105
|
-
export function addMessageToSession(session: SessionData, message: ChatMessage): void {
|
|
106
|
-
session.messages.push(message);
|
|
107
|
-
saveSession(session);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Summarize old messages using LLM
|
|
111
|
-
async function summarizeMessages(
|
|
112
|
-
messages: ChatMessage[],
|
|
113
|
-
client: OllamaClient
|
|
114
|
-
): Promise<string> {
|
|
115
|
-
if (messages.length === 0) {
|
|
116
|
-
return "";
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Format messages for summarization
|
|
120
|
-
const conversationText = messages
|
|
121
|
-
.filter(m => m.role !== "system" && m.role !== "tool")
|
|
122
|
-
.map(m => {
|
|
123
|
-
if (m.role === "user") {
|
|
124
|
-
return `사용자: ${m.content}`;
|
|
125
|
-
} else if (m.role === "assistant") {
|
|
126
|
-
const toolInfo = m.toolCalls?.length
|
|
127
|
-
? ` [도구 호출: ${m.toolCalls.map(t => t.name).join(", ")}]`
|
|
128
|
-
: "";
|
|
129
|
-
return `어시스턴트: ${m.content.slice(0, 200)}${m.content.length > 200 ? "..." : ""}${toolInfo}`;
|
|
130
|
-
}
|
|
131
|
-
return "";
|
|
132
|
-
})
|
|
133
|
-
.filter(s => s)
|
|
134
|
-
.join("\n");
|
|
135
|
-
|
|
136
|
-
const summaryPrompt = `다음 대화를 3-5개의 핵심 포인트로 요약해주세요. 한국어로 작성하고, 각 포인트는 한 줄로 작성하세요.
|
|
137
|
-
|
|
138
|
-
대화:
|
|
139
|
-
${conversationText}
|
|
140
|
-
|
|
141
|
-
요약 (핵심 포인트만):`;
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
const response = await client.chat([
|
|
145
|
-
{ role: "user", content: summaryPrompt }
|
|
146
|
-
]);
|
|
147
|
-
return response.content.trim();
|
|
148
|
-
} catch (error) {
|
|
149
|
-
// Fallback: simple extraction
|
|
150
|
-
const userMessages = messages
|
|
151
|
-
.filter(m => m.role === "user")
|
|
152
|
-
.map(m => m.content.slice(0, 50))
|
|
153
|
-
.slice(-3);
|
|
154
|
-
return `이전 요청: ${userMessages.join(", ")}`;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Get context for new session (hybrid approach)
|
|
159
|
-
export async function getSessionContext(
|
|
160
|
-
client: OllamaClient,
|
|
161
|
-
recentCount: number = 5
|
|
162
|
-
): Promise<{ summary: string; recentMessages: ChatMessage[] }> {
|
|
163
|
-
const latestSession = loadLatestSession();
|
|
164
|
-
|
|
165
|
-
if (!latestSession || latestSession.messages.length === 0) {
|
|
166
|
-
return { summary: "", recentMessages: [] };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const allMessages = latestSession.messages;
|
|
170
|
-
|
|
171
|
-
// Filter out system messages for context
|
|
172
|
-
const contextMessages = allMessages.filter(m => m.role !== "system");
|
|
173
|
-
|
|
174
|
-
if (contextMessages.length <= recentCount) {
|
|
175
|
-
// Not enough messages to summarize, return all as recent
|
|
176
|
-
return {
|
|
177
|
-
summary: latestSession.summary || "",
|
|
178
|
-
recentMessages: contextMessages
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Split: old messages for summary, recent messages to keep
|
|
183
|
-
const oldMessages = contextMessages.slice(0, -recentCount);
|
|
184
|
-
const recentMessages = contextMessages.slice(-recentCount);
|
|
185
|
-
|
|
186
|
-
// Generate or use existing summary
|
|
187
|
-
let summary = latestSession.summary || "";
|
|
188
|
-
|
|
189
|
-
if (oldMessages.length > 0 && !summary) {
|
|
190
|
-
summary = await summarizeMessages(oldMessages, client);
|
|
191
|
-
// Save summary back to session
|
|
192
|
-
latestSession.summary = summary;
|
|
193
|
-
saveSession(latestSession);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return { summary, recentMessages };
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Build context string for system prompt
|
|
200
|
-
export function buildContextPrompt(summary: string): string {
|
|
201
|
-
if (!summary) {
|
|
202
|
-
return "";
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return `
|
|
206
|
-
## 이전 대화 컨텍스트
|
|
207
|
-
|
|
208
|
-
${summary}
|
|
209
|
-
|
|
210
|
-
---
|
|
211
|
-
위 내용은 이전 세션에서의 대화 요약입니다. 필요시 참고하세요.
|
|
212
|
-
`;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Clean old sessions (keep only last N)
|
|
216
|
-
export function cleanOldSessions(keepCount: number = 10): void {
|
|
217
|
-
const dir = getConversationDir();
|
|
218
|
-
if (!fs.existsSync(dir)) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const files = fs.readdirSync(dir)
|
|
223
|
-
.filter(f => f.startsWith("session_") && f.endsWith(".json"))
|
|
224
|
-
.sort()
|
|
225
|
-
.reverse();
|
|
226
|
-
|
|
227
|
-
// Delete old sessions beyond keepCount
|
|
228
|
-
for (let i = keepCount; i < files.length; i++) {
|
|
229
|
-
try {
|
|
230
|
-
fs.unlinkSync(path.join(dir, files[i]));
|
|
231
|
-
} catch {
|
|
232
|
-
// Ignore deletion errors
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|