aiexecode 1.0.157
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/LICENSE +68 -0
- package/README.md +347 -0
- package/config_template/mcp_config.json +3 -0
- package/config_template/package_name_store.json +5 -0
- package/config_template/settings.json +5 -0
- package/index.js +879 -0
- package/mcp-agent-lib/example/01-basic-usage.js +82 -0
- package/mcp-agent-lib/example/02-quick-start.js +52 -0
- package/mcp-agent-lib/example/03-http-server.js +76 -0
- package/mcp-agent-lib/example/04-multiple-servers.js +117 -0
- package/mcp-agent-lib/example/05-error-handling.js +116 -0
- package/mcp-agent-lib/example/06-resources-and-prompts.js +174 -0
- package/mcp-agent-lib/example/07-advanced-configuration.js +191 -0
- package/mcp-agent-lib/example/08-real-world-chatbot.js +331 -0
- package/mcp-agent-lib/example/README.md +346 -0
- package/mcp-agent-lib/index.js +19 -0
- package/mcp-agent-lib/init.sh +3 -0
- package/mcp-agent-lib/package-lock.json +1216 -0
- package/mcp-agent-lib/package.json +53 -0
- package/mcp-agent-lib/sampleFastMCPClient/client.py +25 -0
- package/mcp-agent-lib/sampleFastMCPClient/run.sh +3 -0
- package/mcp-agent-lib/sampleFastMCPServer/run.sh +3 -0
- package/mcp-agent-lib/sampleFastMCPServer/server.py +12 -0
- package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/run.sh +3 -0
- package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/server.py +43 -0
- package/mcp-agent-lib/sampleFastMCPServerRootsRequest/server.py +63 -0
- package/mcp-agent-lib/sampleMCPHost/index.js +386 -0
- package/mcp-agent-lib/sampleMCPHost/mcp_config.json +24 -0
- package/mcp-agent-lib/sampleMCPHostFeatures/elicitation.js +151 -0
- package/mcp-agent-lib/sampleMCPHostFeatures/index.js +166 -0
- package/mcp-agent-lib/sampleMCPHostFeatures/roots.js +197 -0
- package/mcp-agent-lib/src/mcp_client.js +1860 -0
- package/mcp-agent-lib/src/mcp_message_logger.js +517 -0
- package/package.json +72 -0
- package/payload_viewer/out/404/index.html +1 -0
- package/payload_viewer/out/404.html +1 -0
- package/payload_viewer/out/_next/static/chunks/060f9a97930f3d04.js +1 -0
- package/payload_viewer/out/_next/static/chunks/103c802c8f4a5ea1.js +1 -0
- package/payload_viewer/out/_next/static/chunks/16474fd6c6910c45.js +1 -0
- package/payload_viewer/out/_next/static/chunks/17722e3ac4e00587.js +1 -0
- package/payload_viewer/out/_next/static/chunks/305b077a9873cf54.js +1 -0
- package/payload_viewer/out/_next/static/chunks/4c1d05c6741c2bdd.js +5 -0
- package/payload_viewer/out/_next/static/chunks/538cc02e54714b23.js +1 -0
- package/payload_viewer/out/_next/static/chunks/6251fa5907d2b226.js +5 -0
- package/payload_viewer/out/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- package/payload_viewer/out/_next/static/chunks/b6c0459f3789d25c.js +1 -0
- package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
- package/payload_viewer/out/_next/static/chunks/bd2dcf98c9b362f6.js +1 -0
- package/payload_viewer/out/_next/static/chunks/c8a542ae21335479.js +1 -0
- package/payload_viewer/out/_next/static/chunks/cdd12d5c1a5a6064.js +1 -0
- package/payload_viewer/out/_next/static/chunks/e411019f55d87c42.js +1 -0
- package/payload_viewer/out/_next/static/chunks/e60ef129113f6e24.js +1 -0
- package/payload_viewer/out/_next/static/chunks/f1ac9047ac4a3fde.js +1 -0
- package/payload_viewer/out/_next/static/chunks/turbopack-0ac29803ce3c3c7a.js +3 -0
- package/payload_viewer/out/_next/static/chunks/turbopack-89db4c64206a73e4.js +3 -0
- package/payload_viewer/out/_next/static/chunks/turbopack-a5b8235fa59d7119.js +3 -0
- package/payload_viewer/out/_next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/favicon.0b3bf435.ico +0 -0
- package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_buildManifest.js +14 -0
- package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_clientMiddlewareManifest.json +1 -0
- package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_ssgManifest.js +1 -0
- package/payload_viewer/out/favicon.ico +0 -0
- package/payload_viewer/out/file.svg +1 -0
- package/payload_viewer/out/globe.svg +1 -0
- package/payload_viewer/out/index.html +1 -0
- package/payload_viewer/out/index.txt +23 -0
- package/payload_viewer/out/next.svg +1 -0
- package/payload_viewer/out/vercel.svg +1 -0
- package/payload_viewer/out/window.svg +1 -0
- package/payload_viewer/web_server.js +861 -0
- package/prompts/completion_judge.txt +128 -0
- package/prompts/orchestrator.txt +1213 -0
- package/src/LLMClient/client.js +1375 -0
- package/src/LLMClient/converters/input-normalizer.js +238 -0
- package/src/LLMClient/converters/responses-to-claude.js +503 -0
- package/src/LLMClient/converters/responses-to-gemini.js +648 -0
- package/src/LLMClient/converters/responses-to-ollama.js +348 -0
- package/src/LLMClient/converters/responses-to-zai.js +667 -0
- package/src/LLMClient/errors.js +398 -0
- package/src/LLMClient/index.js +36 -0
- package/src/ai_based/completion_judge.js +421 -0
- package/src/ai_based/orchestrator.js +527 -0
- package/src/ai_based/pip_package_installer.js +173 -0
- package/src/ai_based/pip_package_lookup.js +197 -0
- package/src/cli/mcp_cli.js +70 -0
- package/src/cli/mcp_commands.js +255 -0
- package/src/commands/agents.js +18 -0
- package/src/commands/apikey.js +55 -0
- package/src/commands/bg.js +140 -0
- package/src/commands/commands.js +56 -0
- package/src/commands/debug.js +54 -0
- package/src/commands/exit.js +19 -0
- package/src/commands/help.js +35 -0
- package/src/commands/mcp.js +128 -0
- package/src/commands/model.js +176 -0
- package/src/commands/setup.js +13 -0
- package/src/commands/skills.js +51 -0
- package/src/commands/tools.js +165 -0
- package/src/commands/viewer.js +147 -0
- package/src/config/ai_models.js +312 -0
- package/src/config/config.js +10 -0
- package/src/config/constants.js +71 -0
- package/src/config/feature_flags.js +15 -0
- package/src/frontend/App.js +1263 -0
- package/src/frontend/README.md +81 -0
- package/src/frontend/components/AutocompleteMenu.js +47 -0
- package/src/frontend/components/BackgroundProcessList.js +175 -0
- package/src/frontend/components/BlankLine.js +62 -0
- package/src/frontend/components/ConversationItem.js +893 -0
- package/src/frontend/components/CurrentModelView.js +43 -0
- package/src/frontend/components/FileDiffViewer.js +616 -0
- package/src/frontend/components/Footer.js +25 -0
- package/src/frontend/components/Header.js +42 -0
- package/src/frontend/components/HelpView.js +154 -0
- package/src/frontend/components/Input.js +344 -0
- package/src/frontend/components/LoadingIndicator.js +31 -0
- package/src/frontend/components/ModelListView.js +49 -0
- package/src/frontend/components/ModelUpdatedView.js +22 -0
- package/src/frontend/components/SessionSpinner.js +66 -0
- package/src/frontend/components/SetupWizard.js +242 -0
- package/src/frontend/components/StreamOutput.js +34 -0
- package/src/frontend/components/TodoList.js +56 -0
- package/src/frontend/components/ToolApprovalPrompt.js +452 -0
- package/src/frontend/design/themeColors.js +42 -0
- package/src/frontend/hooks/useCompletion.js +84 -0
- package/src/frontend/hooks/useFileCompletion.js +467 -0
- package/src/frontend/hooks/useKeypress.js +145 -0
- package/src/frontend/index.js +65 -0
- package/src/frontend/utils/GridRenderer.js +140 -0
- package/src/frontend/utils/InlineFormatter.js +156 -0
- package/src/frontend/utils/diffUtils.js +235 -0
- package/src/frontend/utils/inputBuffer.js +441 -0
- package/src/frontend/utils/markdownParser.js +377 -0
- package/src/frontend/utils/outputRedirector.js +47 -0
- package/src/frontend/utils/renderInkComponent.js +42 -0
- package/src/frontend/utils/syntaxHighlighter.js +149 -0
- package/src/frontend/utils/toolUIFormatter.js +261 -0
- package/src/system/agents_loader.js +170 -0
- package/src/system/ai_request.js +737 -0
- package/src/system/background_process.js +317 -0
- package/src/system/code_executer.js +1233 -0
- package/src/system/command_loader.js +40 -0
- package/src/system/command_parser.js +133 -0
- package/src/system/conversation_state.js +265 -0
- package/src/system/conversation_trimmer.js +265 -0
- package/src/system/custom_command_loader.js +395 -0
- package/src/system/file_integrity.js +466 -0
- package/src/system/import_analyzer.py +174 -0
- package/src/system/log.js +82 -0
- package/src/system/mcp_integration.js +304 -0
- package/src/system/output_helper.js +89 -0
- package/src/system/session.js +1393 -0
- package/src/system/session_memory.js +481 -0
- package/src/system/skill_loader.js +324 -0
- package/src/system/system_info.js +483 -0
- package/src/system/tool_approval.js +160 -0
- package/src/system/tool_registry.js +184 -0
- package/src/system/ui_events.js +279 -0
- package/src/tools/code_editor.js +792 -0
- package/src/tools/file_reader.js +385 -0
- package/src/tools/glob.js +263 -0
- package/src/tools/response_message.js +30 -0
- package/src/tools/ripgrep.js +554 -0
- package/src/tools/skill_tool.js +122 -0
- package/src/tools/todo_write.js +182 -0
- package/src/tools/web_download.py +74 -0
- package/src/tools/web_downloader.js +83 -0
- package/src/util/clone.js +174 -0
- package/src/util/config.js +203 -0
- package/src/util/config_migration.js +174 -0
- package/src/util/debug_log.js +49 -0
- package/src/util/exit_handler.js +53 -0
- package/src/util/file_reference_parser.js +132 -0
- package/src/util/mcp_config_manager.js +159 -0
- package/src/util/output_formatter.js +50 -0
- package/src/util/path_helper.js +27 -0
- package/src/util/path_validator.js +178 -0
- package/src/util/prompt_loader.js +184 -0
- package/src/util/rag_helper.js +101 -0
- package/src/util/safe_fs.js +645 -0
- package/src/util/setup_wizard.js +62 -0
- package/src/util/text_formatter.js +33 -0
- package/src/util/version_check.js +116 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { uiEvents } from '../system/ui_events.js';
|
|
2
|
+
import { createDebugLogger } from '../util/debug_log.js';
|
|
3
|
+
import { theme } from '../frontend/design/themeColors.js';
|
|
4
|
+
import { updateCurrentTodos } from '../system/session_memory.js';
|
|
5
|
+
|
|
6
|
+
const debugLog = createDebugLogger('todo_write.log', 'todo_write');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Todo 관리 도구
|
|
10
|
+
* 현재 코딩 세션의 작업 목록을 생성하고 관리합니다.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Todo 리스트를 업데이트합니다
|
|
15
|
+
* @param {Object} params - 매개변수 객체
|
|
16
|
+
* @param {Array} params.todos - Todo 항목 배열
|
|
17
|
+
* @returns {Promise<Object>} 결과 객체
|
|
18
|
+
*/
|
|
19
|
+
export async function todo_write({ todos }) {
|
|
20
|
+
debugLog('========== todo_write START ==========');
|
|
21
|
+
debugLog(`Input todos count: ${todos?.length || 0}`);
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// todos 배열 유효성 검증
|
|
25
|
+
if (!Array.isArray(todos)) {
|
|
26
|
+
debugLog('ERROR: todos is not an array');
|
|
27
|
+
debugLog('========== todo_write ERROR END ==========');
|
|
28
|
+
return {
|
|
29
|
+
operation_successful: false,
|
|
30
|
+
error_message: 'todos must be an array'
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 각 todo 항목 검증
|
|
35
|
+
for (let i = 0; i < todos.length; i++) {
|
|
36
|
+
const todo = todos[i];
|
|
37
|
+
debugLog(`Validating todo ${i + 1}/${todos.length}:`);
|
|
38
|
+
debugLog(` content: "${todo.content}"`);
|
|
39
|
+
debugLog(` status: "${todo.status}"`);
|
|
40
|
+
debugLog(` activeForm: "${todo.activeForm}"`);
|
|
41
|
+
|
|
42
|
+
if (!todo.content || typeof todo.content !== 'string') {
|
|
43
|
+
debugLog(`ERROR: todo ${i + 1} has invalid content`);
|
|
44
|
+
return {
|
|
45
|
+
operation_successful: false,
|
|
46
|
+
error_message: `Todo ${i + 1} has invalid content (must be a non-empty string)`
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!todo.status || !['pending', 'in_progress', 'completed'].includes(todo.status)) {
|
|
51
|
+
debugLog(`ERROR: todo ${i + 1} has invalid status`);
|
|
52
|
+
return {
|
|
53
|
+
operation_successful: false,
|
|
54
|
+
error_message: `Todo ${i + 1} has invalid status (must be 'pending', 'in_progress', or 'completed')`
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!todo.activeForm || typeof todo.activeForm !== 'string') {
|
|
59
|
+
debugLog(`ERROR: todo ${i + 1} has invalid activeForm`);
|
|
60
|
+
return {
|
|
61
|
+
operation_successful: false,
|
|
62
|
+
error_message: `Todo ${i + 1} has invalid activeForm (must be a non-empty string)`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// in_progress 상태가 정확히 하나인지 검증
|
|
68
|
+
const inProgressCount = todos.filter(t => t.status === 'in_progress').length;
|
|
69
|
+
debugLog(`in_progress count: ${inProgressCount}`);
|
|
70
|
+
|
|
71
|
+
if (inProgressCount !== 1) {
|
|
72
|
+
debugLog(`WARNING: Expected exactly 1 in_progress todo, found ${inProgressCount}`);
|
|
73
|
+
// 경고만 하고 계속 진행 (유연성을 위해)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 세션 메모리에 todos 저장
|
|
77
|
+
debugLog('Saving todos to session memory...');
|
|
78
|
+
updateCurrentTodos(todos);
|
|
79
|
+
debugLog('Todos saved to session memory');
|
|
80
|
+
|
|
81
|
+
// UI 이벤트로 todo 업데이트 전달
|
|
82
|
+
debugLog('Emitting todo update event...');
|
|
83
|
+
uiEvents.updateTodos(todos);
|
|
84
|
+
debugLog('Todo update event emitted');
|
|
85
|
+
|
|
86
|
+
debugLog('========== todo_write SUCCESS END ==========');
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
operation_successful: true,
|
|
90
|
+
todos_count: todos.length,
|
|
91
|
+
todos: todos,
|
|
92
|
+
in_progress_count: inProgressCount,
|
|
93
|
+
pending_count: todos.filter(t => t.status === 'pending').length,
|
|
94
|
+
completed_count: todos.filter(t => t.status === 'completed').length
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
} catch (error) {
|
|
98
|
+
debugLog(`========== todo_write EXCEPTION ==========`);
|
|
99
|
+
debugLog(`Exception caught: ${error.message}`);
|
|
100
|
+
debugLog(`Stack trace: ${error.stack}`);
|
|
101
|
+
debugLog('========== todo_write EXCEPTION END ==========');
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
operation_successful: false,
|
|
105
|
+
error_message: error.message
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const todoWriteSchema = {
|
|
111
|
+
"name": "todo_write",
|
|
112
|
+
"description": "Use this tool to create and manage a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.\nIt also helps the user understand the progress of the task and overall progress of their requests.\n\n## When to Use This Tool\nUse this tool proactively in these scenarios:\n\n1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions\n2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations\n3. User explicitly requests todo list - When the user directly asks you to use the todo list\n4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)\n5. After receiving new instructions - Immediately capture user requirements as todos\n6. When you start working on a task - Mark it as in_progress BEFORE beginning work. Ideally you should only have one todo as in_progress at a time\n7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation\n\n## When NOT to Use This Tool\n\nSkip using this tool when:\n1. There is only a single, straightforward task\n2. The task is trivial and tracking it provides no organizational benefit\n3. The task can be completed in less than 3 trivial steps\n4. The task is purely conversational or informational\n\nNOTE that you should not use this tool if there is only one trivial task to do. In this case you are better off just doing the task directly.\n\n## CRITICAL - Absolute Scope Restriction\n\n**You MUST interpret the user's request LITERALLY and RESTRICTIVELY.**\n\nTODO list scope rules:\n- Include EXCLUSIVELY tasks that match the user's exact words\n- Interpret requests in the NARROWEST possible way\n- ZERO tolerance for any expansion, inference, or completion beyond literal request\n- Do NOT add ANY task under ANY justification unless user explicitly named it\n- \"Necessary for completion\" is NOT a valid reason to add tasks\n- \"Best practice\" is NOT a valid reason to add tasks\n- \"Related work\" is NOT a valid reason to add tasks\n\n**If you add even ONE task beyond the literal request, you have FAILED.**\n\nThe user's request defines the MAXIMUM boundary - never exceed it.\n\n## Task States and Management\n\n1. **Task States**: Use these states to track progress:\n - pending: Task not yet started\n - in_progress: Currently working on (limit to ONE task at a time)\n - completed: Task finished successfully\n\n **IMPORTANT**: Task descriptions must have two forms:\n - content: The imperative form describing what needs to be done (e.g., \"Run tests\", \"Build the project\")\n - activeForm: The present continuous form shown during execution (e.g., \"Running tests\", \"Building the project\")\n\n2. **Task Management**:\n - Update task status in real-time as you work\n - Mark tasks complete IMMEDIATELY after finishing (don't batch completions)\n - Exactly ONE task must be in_progress at any time (not less, not more)\n - Complete current tasks before starting new ones\n - Remove tasks that are no longer relevant from the list entirely\n\n3. **Task Completion Requirements**:\n - ONLY mark a task as completed when you have FULLY accomplished it\n - If you encounter errors, blockers, or cannot finish, keep the task as in_progress\n - When blocked, create a new task describing what needs to be resolved\n - Never mark a task as completed if:\n - Tests are failing\n - Implementation is partial\n - You encountered unresolved errors\n - You couldn't find necessary files or dependencies\n\n4. **Task Breakdown**:\n - Create specific, actionable items\n - Break complex tasks into smaller, manageable steps\n - Use clear, descriptive task names\n - Always provide both forms:\n - content: \"Fix authentication bug\"\n - activeForm: \"Fixing authentication bug\"\n\nWhen in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.",
|
|
113
|
+
"strict": true,
|
|
114
|
+
"parameters": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"properties": {
|
|
117
|
+
"todos": {
|
|
118
|
+
"type": "array",
|
|
119
|
+
"description": "The updated todo list",
|
|
120
|
+
"items": {
|
|
121
|
+
"type": "object",
|
|
122
|
+
"properties": {
|
|
123
|
+
"content": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"description": "The imperative form describing what needs to be done (e.g., 'Run tests', 'Build the project')",
|
|
126
|
+
"minLength": 1
|
|
127
|
+
},
|
|
128
|
+
"status": {
|
|
129
|
+
"type": "string",
|
|
130
|
+
"description": "Task status: 'pending' (not yet started), 'in_progress' (currently working on), or 'completed' (finished successfully)",
|
|
131
|
+
"enum": ["pending", "in_progress", "completed"]
|
|
132
|
+
},
|
|
133
|
+
"activeForm": {
|
|
134
|
+
"type": "string",
|
|
135
|
+
"description": "The present continuous form shown during execution (e.g., 'Running tests', 'Building the project')",
|
|
136
|
+
"minLength": 1
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"required": ["content", "status", "activeForm"],
|
|
140
|
+
"additionalProperties": false
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"required": ["todos"],
|
|
145
|
+
"additionalProperties": false
|
|
146
|
+
},
|
|
147
|
+
"ui_display": {
|
|
148
|
+
"show_tool_call": true,
|
|
149
|
+
"show_tool_result": true,
|
|
150
|
+
"display_name": "Todo",
|
|
151
|
+
"format_tool_call": (args) => {
|
|
152
|
+
const todos = args.todos || [];
|
|
153
|
+
const inProgress = todos.filter(t => t.status === 'in_progress').length;
|
|
154
|
+
const completed = todos.filter(t => t.status === 'completed').length;
|
|
155
|
+
const pending = todos.filter(t => t.status === 'pending').length;
|
|
156
|
+
return `(${todos.length} tasks: ${completed} done, ${inProgress} active, ${pending} pending)`;
|
|
157
|
+
},
|
|
158
|
+
"format_tool_result": (result) => {
|
|
159
|
+
if (result.operation_successful) {
|
|
160
|
+
const total = result.todos_count || 0;
|
|
161
|
+
const completed = result.completed_count || 0;
|
|
162
|
+
const inProgress = result.in_progress_count || 0;
|
|
163
|
+
const pending = result.pending_count || 0;
|
|
164
|
+
return {
|
|
165
|
+
type: 'formatted',
|
|
166
|
+
parts: [
|
|
167
|
+
{ text: 'Updated ', style: {} },
|
|
168
|
+
{ text: String(total), style: { color: theme.brand.light, bold: true } },
|
|
169
|
+
{ text: ` task${total !== 1 ? 's' : ''} `, style: {} },
|
|
170
|
+
{ text: `(${completed} done, ${inProgress} active, ${pending} pending)`, style: { color: theme.text.dim } }
|
|
171
|
+
]
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return result.error_message || 'Error updating todos';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// 함수 맵 - 문자열로 함수 호출 가능
|
|
180
|
+
export const TODO_WRITE_FUNCTIONS = {
|
|
181
|
+
'todo_write': todo_write
|
|
182
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import requests
|
|
3
|
+
import os
|
|
4
|
+
from markitdown import MarkItDown
|
|
5
|
+
from urllib.parse import urljoin, urlparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
def fetch_and_convert(url, timeout=30, user_agent='Mozilla/5.0 (compatible; WebFetcher/1.0)', encoding='utf-8'):
|
|
10
|
+
try:
|
|
11
|
+
# 요청 헤더 설정
|
|
12
|
+
headers = {
|
|
13
|
+
'User-Agent': user_agent,
|
|
14
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
15
|
+
'Accept-Language': 'en-US,en;q=0.5',
|
|
16
|
+
'Accept-Encoding': 'gzip, deflate',
|
|
17
|
+
'Connection': 'keep-alive',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# 웹 페이지 다운로드
|
|
21
|
+
print(f"Fetching: {url}", file=sys.stderr)
|
|
22
|
+
response = requests.get(url, headers=headers, timeout=timeout)
|
|
23
|
+
response.raise_for_status()
|
|
24
|
+
|
|
25
|
+
# 콘텐츠 타입 확인
|
|
26
|
+
content_type = response.headers.get('content-type', '').lower()
|
|
27
|
+
print(f"Content-Type: {content_type}", file=sys.stderr)
|
|
28
|
+
|
|
29
|
+
# HTML 콘텐츠 임시 저장
|
|
30
|
+
import tempfile
|
|
31
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.html', encoding=encoding, delete=False) as temp_file:
|
|
32
|
+
temp_html_path = temp_file.name
|
|
33
|
+
temp_file.write(response.text)
|
|
34
|
+
|
|
35
|
+
# MarkItDown으로 변환
|
|
36
|
+
md = MarkItDown()
|
|
37
|
+
print("Converting to markdown...", file=sys.stderr)
|
|
38
|
+
result = md.convert(temp_html_path)
|
|
39
|
+
|
|
40
|
+
# 임시 파일 삭제
|
|
41
|
+
os.remove(temp_html_path)
|
|
42
|
+
|
|
43
|
+
# 마크다운 내용 생성
|
|
44
|
+
markdown_content = f"# {result.title or 'Web Page'}\n\n"
|
|
45
|
+
markdown_content += f"**Original URL**: {url}\n"
|
|
46
|
+
markdown_content += f"**Fetched**: {response.headers.get('date', 'Unknown')}\n"
|
|
47
|
+
markdown_content += f"**Content-Type**: {content_type}\n\n"
|
|
48
|
+
markdown_content += "---\n\n"
|
|
49
|
+
markdown_content += result.text_content
|
|
50
|
+
|
|
51
|
+
# stdout으로 출력
|
|
52
|
+
print(markdown_content)
|
|
53
|
+
print(f"Successfully fetched and converted: {url}", file=sys.stderr)
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
except requests.RequestException as e:
|
|
57
|
+
print(f"Network error: {e}", file=sys.stderr)
|
|
58
|
+
return False
|
|
59
|
+
except Exception as e:
|
|
60
|
+
print(f"Conversion error: {e}", file=sys.stderr)
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
if len(sys.argv) < 2:
|
|
65
|
+
print("Usage: python web_download.py <url> [timeout] [user_agent] [encoding]", file=sys.stderr)
|
|
66
|
+
sys.exit(1)
|
|
67
|
+
|
|
68
|
+
url = sys.argv[1]
|
|
69
|
+
timeout = int(sys.argv[2]) if len(sys.argv) > 2 else 30
|
|
70
|
+
user_agent = sys.argv[3] if len(sys.argv) > 3 else 'Mozilla/5.0 (compatible; WebFetcher/1.0)'
|
|
71
|
+
encoding = sys.argv[4] if len(sys.argv) > 4 else 'utf-8'
|
|
72
|
+
|
|
73
|
+
success = fetch_and_convert(url, timeout, user_agent, encoding)
|
|
74
|
+
sys.exit(0 if success else 1)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { execPythonCode } from "../system/code_executer.js";
|
|
2
|
+
import { safeReadFile } from '../util/safe_fs.js';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { theme } from '../frontend/design/themeColors.js';
|
|
5
|
+
|
|
6
|
+
// 이 파일은 웹 페이지를 가져와서 텍스트 형식으로 변환하는 도구를 제공합니다.
|
|
7
|
+
// 외부 참고 자료가 필요한 미션일 때 Orchestrator가 지식을 확보하고 Verifier가 근거를 남길 수 있게 보조합니다.
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 웹 페이지 가져오기 도구
|
|
11
|
+
* 웹 페이지를 가져와서 읽기 쉬운 텍스트 형식으로 변환합니다.
|
|
12
|
+
* MarkItDown 라이브러리를 활용하여 고품질의 변환을 제공합니다.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 웹 페이지를 가져와서 텍스트 형식으로 변환합니다
|
|
17
|
+
* @param {Object} params - 매개변수 객체
|
|
18
|
+
* @param {string} params.url - 가져올 웹 페이지 URL
|
|
19
|
+
* @returns {Promise<{operation_successful: boolean, url: string, content: string, error_message: string|null}>} 결과 객체
|
|
20
|
+
*/
|
|
21
|
+
export async function fetch_web_page({ url }) {
|
|
22
|
+
const timeout = 30;
|
|
23
|
+
const user_agent = 'Mozilla/5.0 (compatible; WebFetcher/1.0)';
|
|
24
|
+
const encoding = 'utf-8';
|
|
25
|
+
const pythonCode = await safeReadFile(join(process.app_custom?.__dirname || process.cwd(), 'src', 'tools', 'web_download.py'), 'utf8');
|
|
26
|
+
const result = await execPythonCode(pythonCode, [url, timeout, user_agent, encoding]);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
operation_successful: result.code === 0,
|
|
30
|
+
url: url,
|
|
31
|
+
content: result.code === 0 ? result.stdout : '',
|
|
32
|
+
error_message: result.code === 0 ? null : result.stderr || 'Fetch operation failed'
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 함수 스키마
|
|
37
|
+
export const fetchWebPageSchema = {
|
|
38
|
+
"name": "fetch_web_page",
|
|
39
|
+
"description": "Fetches a web page and converts it to readable text format for reference purposes. Returns the content directly.",
|
|
40
|
+
"strict": true,
|
|
41
|
+
"parameters": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"required": ["url"],
|
|
44
|
+
"properties": {
|
|
45
|
+
"url": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "URL of the web page to fetch"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"additionalProperties": false
|
|
51
|
+
},
|
|
52
|
+
"ui_display": {
|
|
53
|
+
"show_tool_call": true,
|
|
54
|
+
"show_tool_result": true,
|
|
55
|
+
"display_name": "Fetch",
|
|
56
|
+
"format_tool_call": (args) => {
|
|
57
|
+
const url = args.url || '';
|
|
58
|
+
const shortened = url.length > 50 ? url.substring(0, 47) + '...' : url;
|
|
59
|
+
return `(${shortened})`;
|
|
60
|
+
},
|
|
61
|
+
"format_tool_result": (result) => {
|
|
62
|
+
if (result.operation_successful) {
|
|
63
|
+
const contentLength = result.content?.length || 0;
|
|
64
|
+
return {
|
|
65
|
+
type: 'formatted',
|
|
66
|
+
parts: [
|
|
67
|
+
{ text: 'Fetched ', style: {} },
|
|
68
|
+
{ text: String(contentLength), style: { color: theme.brand.light, bold: true } },
|
|
69
|
+
{ text: ' characters', style: {} }
|
|
70
|
+
]
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return result.error_message || 'Fetch failed';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// 함수 맵 - 문자열로 함수 호출 가능
|
|
79
|
+
export const WEB_DOWNLOADER_FUNCTIONS = {
|
|
80
|
+
'fetch_web_page': fetch_web_page
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// 도구 메타데이터
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 깊은 복사 유틸리티
|
|
3
|
+
* JSON.parse(JSON.stringify()) 패턴을 대체하는 안전한 깊은 복사 함수들을 제공합니다.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 값을 깊은 복사합니다.
|
|
8
|
+
* - 순환 참조를 감지하고 처리합니다.
|
|
9
|
+
* - Date, RegExp 등의 특수 객체를 올바르게 복사합니다.
|
|
10
|
+
* - undefined, function, Symbol은 복사되지 않습니다 (JSON.stringify와 동일).
|
|
11
|
+
*
|
|
12
|
+
* @param {*} value - 복사할 값
|
|
13
|
+
* @param {WeakMap} [visited] - 순환 참조 감지용 (내부 사용)
|
|
14
|
+
* @returns {*} 깊은 복사된 값
|
|
15
|
+
*/
|
|
16
|
+
export function deepClone(value, visited = new WeakMap()) {
|
|
17
|
+
// 기본 타입은 그대로 반환
|
|
18
|
+
if (value === null || typeof value !== 'object') {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 순환 참조 감지
|
|
23
|
+
if (visited.has(value)) {
|
|
24
|
+
return visited.get(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Date 객체 처리
|
|
28
|
+
if (value instanceof Date) {
|
|
29
|
+
return new Date(value.getTime());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// RegExp 객체 처리
|
|
33
|
+
if (value instanceof RegExp) {
|
|
34
|
+
return new RegExp(value.source, value.flags);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Map 객체 처리
|
|
38
|
+
if (value instanceof Map) {
|
|
39
|
+
const clonedMap = new Map();
|
|
40
|
+
visited.set(value, clonedMap);
|
|
41
|
+
for (const [key, val] of value) {
|
|
42
|
+
clonedMap.set(deepClone(key, visited), deepClone(val, visited));
|
|
43
|
+
}
|
|
44
|
+
return clonedMap;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Set 객체 처리
|
|
48
|
+
if (value instanceof Set) {
|
|
49
|
+
const clonedSet = new Set();
|
|
50
|
+
visited.set(value, clonedSet);
|
|
51
|
+
for (const val of value) {
|
|
52
|
+
clonedSet.add(deepClone(val, visited));
|
|
53
|
+
}
|
|
54
|
+
return clonedSet;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 배열 처리
|
|
58
|
+
if (Array.isArray(value)) {
|
|
59
|
+
const clonedArray = [];
|
|
60
|
+
visited.set(value, clonedArray);
|
|
61
|
+
for (let i = 0; i < value.length; i++) {
|
|
62
|
+
clonedArray[i] = deepClone(value[i], visited);
|
|
63
|
+
}
|
|
64
|
+
return clonedArray;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 일반 객체 처리
|
|
68
|
+
const clonedObj = {};
|
|
69
|
+
visited.set(value, clonedObj);
|
|
70
|
+
|
|
71
|
+
for (const key of Object.keys(value)) {
|
|
72
|
+
const val = value[key];
|
|
73
|
+
// undefined와 function은 스킵 (JSON.stringify 동작과 일치)
|
|
74
|
+
if (val !== undefined && typeof val !== 'function') {
|
|
75
|
+
clonedObj[key] = deepClone(val, visited);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return clonedObj;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* JSON 직렬화 가능한 값만 깊은 복사합니다.
|
|
84
|
+
* JSON.parse(JSON.stringify())와 동일한 동작이지만 에러 처리가 추가되었습니다.
|
|
85
|
+
*
|
|
86
|
+
* @param {*} value - 복사할 값
|
|
87
|
+
* @param {*} [fallback] - 복사 실패 시 반환할 기본값 (기본: null)
|
|
88
|
+
* @returns {*} 깊은 복사된 값 또는 fallback
|
|
89
|
+
*/
|
|
90
|
+
export function jsonClone(value, fallback = null) {
|
|
91
|
+
try {
|
|
92
|
+
return JSON.parse(JSON.stringify(value));
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return fallback;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 얕은 복사를 수행합니다.
|
|
100
|
+
* 첫 번째 레벨만 복사하고 중첩된 객체는 참조를 유지합니다.
|
|
101
|
+
*
|
|
102
|
+
* @param {*} value - 복사할 값
|
|
103
|
+
* @returns {*} 얕은 복사된 값
|
|
104
|
+
*/
|
|
105
|
+
export function shallowClone(value) {
|
|
106
|
+
if (value === null || typeof value !== 'object') {
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (Array.isArray(value)) {
|
|
111
|
+
return [...value];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { ...value };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 두 객체를 깊은 병합합니다.
|
|
119
|
+
* target에 source의 값을 병합합니다 (source가 우선).
|
|
120
|
+
*
|
|
121
|
+
* @param {Object} target - 대상 객체
|
|
122
|
+
* @param {Object} source - 소스 객체
|
|
123
|
+
* @returns {Object} 병합된 새 객체
|
|
124
|
+
*/
|
|
125
|
+
export function deepMerge(target, source) {
|
|
126
|
+
if (source === null || typeof source !== 'object') {
|
|
127
|
+
return source;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (target === null || typeof target !== 'object') {
|
|
131
|
+
return deepClone(source);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (Array.isArray(source)) {
|
|
135
|
+
return deepClone(source);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const result = { ...target };
|
|
139
|
+
|
|
140
|
+
for (const key of Object.keys(source)) {
|
|
141
|
+
const sourceValue = source[key];
|
|
142
|
+
const targetValue = target[key];
|
|
143
|
+
|
|
144
|
+
if (
|
|
145
|
+
sourceValue !== null &&
|
|
146
|
+
typeof sourceValue === 'object' &&
|
|
147
|
+
!Array.isArray(sourceValue) &&
|
|
148
|
+
targetValue !== null &&
|
|
149
|
+
typeof targetValue === 'object' &&
|
|
150
|
+
!Array.isArray(targetValue)
|
|
151
|
+
) {
|
|
152
|
+
result[key] = deepMerge(targetValue, sourceValue);
|
|
153
|
+
} else {
|
|
154
|
+
result[key] = deepClone(sourceValue);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 객체가 깊은 복사 가능한지 (JSON 직렬화 가능한지) 검사합니다.
|
|
163
|
+
*
|
|
164
|
+
* @param {*} value - 검사할 값
|
|
165
|
+
* @returns {boolean} JSON 직렬화 가능하면 true
|
|
166
|
+
*/
|
|
167
|
+
export function isCloneable(value) {
|
|
168
|
+
try {
|
|
169
|
+
JSON.stringify(value);
|
|
170
|
+
return true;
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|