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.
Files changed (166) hide show
  1. package/README.md +203 -1
  2. package/data/2026-03-04_20-54.json +181 -0
  3. package/data/2026-03-04_20-56.json +181 -0
  4. package/data/apex-rulesets/egov.yaml +469 -0
  5. package/data/apex-rulesets/modernize.yaml +687 -0
  6. package/data/apex-rulesets/quality.yaml +1677 -0
  7. package/data/apex-rulesets/rule-schema.yaml +587 -0
  8. package/data/apex-rulesets/secure.yaml +1688 -0
  9. package/data/apex-rulesets/spring.yaml +455 -0
  10. package/data/apex-rulesets/sql-format.yaml +99 -0
  11. package/data/apex-rulesets/sql-oracle.yaml +281 -0
  12. package/data/apex-rulesets/sql.yaml +1660 -0
  13. package/dist/cli/headless.d.ts.map +1 -1
  14. package/dist/cli/headless.js +32 -10
  15. package/dist/cli/headless.js.map +1 -1
  16. package/dist/cli/index.js +31 -3
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/core/agent.d.ts +3 -3
  19. package/dist/core/agent.d.ts.map +1 -1
  20. package/dist/core/agent.js +255 -17
  21. package/dist/core/agent.js.map +1 -1
  22. package/dist/core/commands.d.ts +2 -1
  23. package/dist/core/commands.d.ts.map +1 -1
  24. package/dist/core/commands.js +61 -9
  25. package/dist/core/commands.js.map +1 -1
  26. package/dist/core/config.d.ts +14 -0
  27. package/dist/core/config.d.ts.map +1 -1
  28. package/dist/core/config.js +41 -4
  29. package/dist/core/config.js.map +1 -1
  30. package/dist/core/conversation.d.ts +2 -2
  31. package/dist/core/conversation.d.ts.map +1 -1
  32. package/dist/core/conversation.js.map +1 -1
  33. package/dist/core/intentRouter.d.ts +43 -0
  34. package/dist/core/intentRouter.d.ts.map +1 -0
  35. package/dist/core/intentRouter.js +804 -0
  36. package/dist/core/intentRouter.js.map +1 -0
  37. package/dist/core/llm/anthropic.d.ts +24 -0
  38. package/dist/core/llm/anthropic.d.ts.map +1 -0
  39. package/dist/core/llm/anthropic.js +226 -0
  40. package/dist/core/llm/anthropic.js.map +1 -0
  41. package/dist/core/llm/ollama.d.ts +5 -14
  42. package/dist/core/llm/ollama.d.ts.map +1 -1
  43. package/dist/core/llm/ollama.js +3 -0
  44. package/dist/core/llm/ollama.js.map +1 -1
  45. package/dist/core/llm/types.d.ts +22 -0
  46. package/dist/core/llm/types.d.ts.map +1 -0
  47. package/dist/core/llm/types.js +2 -0
  48. package/dist/core/llm/types.js.map +1 -0
  49. package/dist/core/mcp/client.d.ts +6 -0
  50. package/dist/core/mcp/client.d.ts.map +1 -1
  51. package/dist/core/mcp/client.js +16 -0
  52. package/dist/core/mcp/client.js.map +1 -1
  53. package/dist/core/mcp/init.d.ts +12 -0
  54. package/dist/core/mcp/init.d.ts.map +1 -0
  55. package/dist/core/mcp/init.js +55 -0
  56. package/dist/core/mcp/init.js.map +1 -0
  57. package/dist/core/mcp/logger.d.ts +14 -0
  58. package/dist/core/mcp/logger.d.ts.map +1 -0
  59. package/dist/core/mcp/logger.js +50 -0
  60. package/dist/core/mcp/logger.js.map +1 -0
  61. package/dist/core/tools/analyzeAll.d.ts.map +1 -1
  62. package/dist/core/tools/analyzeAll.js +16 -28
  63. package/dist/core/tools/analyzeAll.js.map +1 -1
  64. package/dist/core/tools/analyzePatterns.d.ts +3 -0
  65. package/dist/core/tools/analyzePatterns.d.ts.map +1 -0
  66. package/dist/core/tools/analyzePatterns.js +293 -0
  67. package/dist/core/tools/analyzePatterns.js.map +1 -0
  68. package/dist/core/tools/apexPaths.d.ts +14 -0
  69. package/dist/core/tools/apexPaths.d.ts.map +1 -0
  70. package/dist/core/tools/apexPaths.js +54 -0
  71. package/dist/core/tools/apexPaths.js.map +1 -0
  72. package/dist/core/tools/apexUtils.d.ts +36 -0
  73. package/dist/core/tools/apexUtils.d.ts.map +1 -0
  74. package/dist/core/tools/apexUtils.js +83 -0
  75. package/dist/core/tools/apexUtils.js.map +1 -0
  76. package/dist/core/tools/explainIssue.d.ts +3 -0
  77. package/dist/core/tools/explainIssue.d.ts.map +1 -0
  78. package/dist/core/tools/explainIssue.js +181 -0
  79. package/dist/core/tools/explainIssue.js.map +1 -0
  80. package/dist/core/tools/fixGen.d.ts +3 -0
  81. package/dist/core/tools/fixGen.d.ts.map +1 -0
  82. package/dist/core/tools/fixGen.js +338 -0
  83. package/dist/core/tools/fixGen.js.map +1 -0
  84. package/dist/core/tools/generateImprovements.d.ts +21 -0
  85. package/dist/core/tools/generateImprovements.d.ts.map +1 -0
  86. package/dist/core/tools/generateImprovements.js +602 -0
  87. package/dist/core/tools/generateImprovements.js.map +1 -0
  88. package/dist/core/tools/generateReport.d.ts +3 -0
  89. package/dist/core/tools/generateReport.d.ts.map +1 -0
  90. package/dist/core/tools/generateReport.js +315 -0
  91. package/dist/core/tools/generateReport.js.map +1 -0
  92. package/dist/core/tools/index.d.ts +7 -0
  93. package/dist/core/tools/index.d.ts.map +1 -1
  94. package/dist/core/tools/index.js +62 -23
  95. package/dist/core/tools/index.js.map +1 -1
  96. package/dist/core/tools/javaAst.d.ts.map +1 -1
  97. package/dist/core/tools/javaAst.js +191 -0
  98. package/dist/core/tools/javaAst.js.map +1 -1
  99. package/dist/core/tools/recommendProfile.d.ts +3 -0
  100. package/dist/core/tools/recommendProfile.d.ts.map +1 -0
  101. package/dist/core/tools/recommendProfile.js +334 -0
  102. package/dist/core/tools/recommendProfile.js.map +1 -0
  103. package/dist/core/tools/ruleGen.d.ts +3 -0
  104. package/dist/core/tools/ruleGen.d.ts.map +1 -0
  105. package/dist/core/tools/ruleGen.js +1103 -0
  106. package/dist/core/tools/ruleGen.js.map +1 -0
  107. package/dist/core/tools/standards.d.ts.map +1 -1
  108. package/dist/core/tools/standards.js +7 -3
  109. package/dist/core/tools/standards.js.map +1 -1
  110. package/dist/ui/App.d.ts.map +1 -1
  111. package/dist/ui/App.js +86 -35
  112. package/dist/ui/App.js.map +1 -1
  113. package/dist/ui/components/InputBox.d.ts +1 -3
  114. package/dist/ui/components/InputBox.d.ts.map +1 -1
  115. package/dist/ui/components/InputBox.js +146 -5
  116. package/dist/ui/components/InputBox.js.map +1 -1
  117. package/dist/ui/components/MessageList.d.ts +3 -1
  118. package/dist/ui/components/MessageList.d.ts.map +1 -1
  119. package/dist/ui/components/MessageList.js +13 -7
  120. package/dist/ui/components/MessageList.js.map +1 -1
  121. package/dist/ui/components/StatusBar.d.ts +1 -1
  122. package/dist/ui/components/StatusBar.d.ts.map +1 -1
  123. package/dist/ui/components/StatusBar.js +3 -2
  124. package/dist/ui/components/StatusBar.js.map +1 -1
  125. package/dist/ui/components/ToolStatus.d.ts +3 -1
  126. package/dist/ui/components/ToolStatus.d.ts.map +1 -1
  127. package/dist/ui/components/ToolStatus.js +19 -4
  128. package/dist/ui/components/ToolStatus.js.map +1 -1
  129. package/package.json +7 -1
  130. package/demo.gif +0 -0
  131. package/demo.tape +0 -53
  132. package/screenshot.png +0 -0
  133. package/src/cli/banner.ts +0 -38
  134. package/src/cli/headless.ts +0 -63
  135. package/src/cli/index.ts +0 -57
  136. package/src/core/agent.ts +0 -237
  137. package/src/core/commands.ts +0 -118
  138. package/src/core/config.ts +0 -98
  139. package/src/core/conversation.ts +0 -235
  140. package/src/core/llm/ollama.ts +0 -351
  141. package/src/core/mcp/client.ts +0 -143
  142. package/src/core/tools/analyzeAll.ts +0 -494
  143. package/src/core/tools/ast.ts +0 -826
  144. package/src/core/tools/builtIn.ts +0 -221
  145. package/src/core/tools/cache.ts +0 -570
  146. package/src/core/tools/cssAnalysis.ts +0 -324
  147. package/src/core/tools/dependencyAnalysis.ts +0 -363
  148. package/src/core/tools/embeddings.ts +0 -746
  149. package/src/core/tools/frontendAst.ts +0 -802
  150. package/src/core/tools/htmlAnalysis.ts +0 -466
  151. package/src/core/tools/index.ts +0 -160
  152. package/src/core/tools/javaAst.ts +0 -812
  153. package/src/core/tools/memory.ts +0 -655
  154. package/src/core/tools/mybatisAnalysis.ts +0 -322
  155. package/src/core/tools/openapiAnalysis.ts +0 -431
  156. package/src/core/tools/pythonAnalysis.ts +0 -477
  157. package/src/core/tools/sqlAnalysis.ts +0 -298
  158. package/src/core/tools/standards.test.ts +0 -186
  159. package/src/core/tools/standards.ts +0 -889
  160. package/src/core/tools/types.ts +0 -38
  161. package/src/ui/App.tsx +0 -334
  162. package/src/ui/components/InputBox.tsx +0 -37
  163. package/src/ui/components/MessageList.tsx +0 -80
  164. package/src/ui/components/StatusBar.tsx +0 -36
  165. package/src/ui/components/ToolStatus.tsx +0 -38
  166. 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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }