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,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid Renderer
|
|
3
|
+
* 마크다운 테이블을 렌더링하는 컴포넌트
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { Text, Box } from 'ink';
|
|
8
|
+
import { theme } from '../design/themeColors.js';
|
|
9
|
+
import { ProcessInlineText, calculateTextWidth } from './InlineFormatter.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 마크다운 테이블을 렌더링하는 컴포넌트
|
|
13
|
+
* @param {Object} props
|
|
14
|
+
* @param {string[]} props.columnHeaders - 테이블 헤더
|
|
15
|
+
* @param {string[][]} props.dataRows - 테이블 데이터 행
|
|
16
|
+
* @param {number} props.maxWidth - 터미널 최대 너비
|
|
17
|
+
*/
|
|
18
|
+
export const GridRenderer = ({ columnHeaders, dataRows, maxWidth }) => {
|
|
19
|
+
// 컬럼 너비 계산 (마크다운 처리 후 실제 표시 너비 기준)
|
|
20
|
+
const widthPerColumn = columnHeaders.map((headerText, columnIndex) => {
|
|
21
|
+
const headerDisplayWidth = calculateTextWidth(headerText);
|
|
22
|
+
const maxDataWidth = Math.max(
|
|
23
|
+
...dataRows.map((rowData) => calculateTextWidth(rowData[columnIndex] || ''))
|
|
24
|
+
);
|
|
25
|
+
return Math.max(headerDisplayWidth, maxDataWidth) + 2; // 패딩 추가
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// 테이블이 터미널 너비를 초과하지 않도록 조정
|
|
29
|
+
const totalRequiredWidth = widthPerColumn.reduce((sum, w) => sum + w + 1, 1);
|
|
30
|
+
const shrinkRatio = totalRequiredWidth > maxWidth ? maxWidth / totalRequiredWidth : 1;
|
|
31
|
+
const finalWidths = widthPerColumn.map((w) => Math.floor(w * shrinkRatio));
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 셀 내용을 렌더링 (너비에 맞게 잘라내거나 패딩 추가)
|
|
35
|
+
*/
|
|
36
|
+
const buildCell = (cellText, cellWidth, isHeaderCell = false) => {
|
|
37
|
+
const availableWidth = Math.max(0, cellWidth - 2);
|
|
38
|
+
const actualWidth = calculateTextWidth(cellText);
|
|
39
|
+
|
|
40
|
+
let displayText = cellText;
|
|
41
|
+
if (actualWidth > availableWidth) {
|
|
42
|
+
if (availableWidth <= 3) {
|
|
43
|
+
// 너비가 매우 작으면 단순 잘라내기
|
|
44
|
+
displayText = cellText.substring(0, Math.min(cellText.length, availableWidth));
|
|
45
|
+
} else {
|
|
46
|
+
// 이진 탐색으로 마크다운을 보존하면서 최적 절단점 찾기
|
|
47
|
+
let leftBound = 0;
|
|
48
|
+
let rightBound = cellText.length;
|
|
49
|
+
let bestFit = cellText;
|
|
50
|
+
|
|
51
|
+
while (leftBound <= rightBound) {
|
|
52
|
+
const midPoint = Math.floor((leftBound + rightBound) / 2);
|
|
53
|
+
const testText = cellText.substring(0, midPoint);
|
|
54
|
+
const testWidth = calculateTextWidth(testText);
|
|
55
|
+
|
|
56
|
+
if (testWidth <= availableWidth - 3) {
|
|
57
|
+
bestFit = testText;
|
|
58
|
+
leftBound = midPoint + 1;
|
|
59
|
+
} else {
|
|
60
|
+
rightBound = midPoint - 1;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
displayText = bestFit + '...';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 정확한 패딩 계산
|
|
69
|
+
const finalDisplayWidth = calculateTextWidth(displayText);
|
|
70
|
+
const paddingRequired = Math.max(0, availableWidth - finalDisplayWidth);
|
|
71
|
+
|
|
72
|
+
return React.createElement(Text, null,
|
|
73
|
+
isHeaderCell
|
|
74
|
+
? React.createElement(Text, { bold: true, color: theme.text.link },
|
|
75
|
+
React.createElement(ProcessInlineText, { content: displayText })
|
|
76
|
+
)
|
|
77
|
+
: React.createElement(ProcessInlineText, { content: displayText }),
|
|
78
|
+
' '.repeat(paddingRequired)
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 테두리 렌더링
|
|
84
|
+
*/
|
|
85
|
+
const buildBorderLine = (position) => {
|
|
86
|
+
const borderStyles = {
|
|
87
|
+
top: { leftCorner: '┌', junction: '┬', rightCorner: '┐', line: '─' },
|
|
88
|
+
mid: { leftCorner: '├', junction: '┼', rightCorner: '┤', line: '─' },
|
|
89
|
+
bottom: { leftCorner: '└', junction: '┴', rightCorner: '┘', line: '─' }
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const style = borderStyles[position];
|
|
93
|
+
const segments = finalWidths.map((width) => style.line.repeat(width));
|
|
94
|
+
const borderText = style.leftCorner + segments.join(style.junction) + style.rightCorner;
|
|
95
|
+
|
|
96
|
+
return React.createElement(Text, { color: theme.text.secondary }, borderText);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 테이블 행 렌더링
|
|
101
|
+
*/
|
|
102
|
+
const buildTableRow = (rowCells, isHeaderRow = false) => {
|
|
103
|
+
const renderedCells = rowCells.map((cellContent, idx) => {
|
|
104
|
+
const width = finalWidths[idx] || 0;
|
|
105
|
+
return buildCell(cellContent || '', width, isHeaderRow);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return React.createElement(Text, { color: theme.text.primary },
|
|
109
|
+
'│ ',
|
|
110
|
+
...renderedCells.map((cell, idx) =>
|
|
111
|
+
React.createElement(React.Fragment, { key: idx },
|
|
112
|
+
cell,
|
|
113
|
+
idx < renderedCells.length - 1 ? ' │ ' : ''
|
|
114
|
+
)
|
|
115
|
+
),
|
|
116
|
+
' │'
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return React.createElement(Box, { flexDirection: 'column', marginY: 0 },
|
|
121
|
+
// 상단 테두리
|
|
122
|
+
buildBorderLine('top'),
|
|
123
|
+
|
|
124
|
+
// 헤더 행
|
|
125
|
+
buildTableRow(columnHeaders, true),
|
|
126
|
+
|
|
127
|
+
// 중간 테두리
|
|
128
|
+
buildBorderLine('mid'),
|
|
129
|
+
|
|
130
|
+
// 데이터 행
|
|
131
|
+
...dataRows.map((row, idx) =>
|
|
132
|
+
React.createElement(React.Fragment, { key: idx },
|
|
133
|
+
buildTableRow(row)
|
|
134
|
+
)
|
|
135
|
+
),
|
|
136
|
+
|
|
137
|
+
// 하단 테두리
|
|
138
|
+
buildBorderLine('bottom')
|
|
139
|
+
);
|
|
140
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline Formatter
|
|
3
|
+
* 인라인 마크다운 요소를 파싱하여 Ink 컴포넌트로 변환
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { Text } from 'ink';
|
|
8
|
+
import { theme } from '../design/themeColors.js';
|
|
9
|
+
import stringWidth from 'string-width';
|
|
10
|
+
|
|
11
|
+
// 마크다운 마커 길이 상수
|
|
12
|
+
const MARKER_LENGTHS = {
|
|
13
|
+
BOLD: 2, // "**"
|
|
14
|
+
ITALIC: 1, // "*" or "_"
|
|
15
|
+
STRIKE: 2, // "~~"
|
|
16
|
+
CODE: 1, // "`"
|
|
17
|
+
UNDERLINE_START: 3, // "<u>"
|
|
18
|
+
UNDERLINE_END: 4 // "</u>"
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 인라인 마크다운을 렌더링하는 컴포넌트
|
|
23
|
+
*/
|
|
24
|
+
const ProcessInlineTextInternal = ({ content }) => {
|
|
25
|
+
// 마크다운이나 URL이 없으면 바로 반환
|
|
26
|
+
if (!/[*_~`<[https?:]/.test(content)) {
|
|
27
|
+
return React.createElement(Text, { color: theme.text.primary }, content);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const elements = [];
|
|
31
|
+
let currentPosition = 0;
|
|
32
|
+
const patternRegex = /(\*\*.*?\*\*|\*.*?\*|_.*?_|~~.*?~~|\[.*?\]\(.*?\)|`+.+?`+|<u>.*?<\/u>|https?:\/\/\S+)/g;
|
|
33
|
+
let matchResult;
|
|
34
|
+
|
|
35
|
+
while ((matchResult = patternRegex.exec(content)) !== null) {
|
|
36
|
+
if (matchResult.index > currentPosition) {
|
|
37
|
+
elements.push(
|
|
38
|
+
React.createElement(Text, { key: `plain-${currentPosition}` },
|
|
39
|
+
content.slice(currentPosition, matchResult.index)
|
|
40
|
+
)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const matchedText = matchResult[0];
|
|
45
|
+
let formattedElement = null;
|
|
46
|
+
const elementKey = `fmt-${matchResult.index}`;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// 볼드 처리
|
|
50
|
+
if (matchedText.startsWith('**') &&
|
|
51
|
+
matchedText.endsWith('**') &&
|
|
52
|
+
matchedText.length > MARKER_LENGTHS.BOLD * 2) {
|
|
53
|
+
formattedElement = React.createElement(Text, { key: elementKey, bold: true },
|
|
54
|
+
matchedText.slice(MARKER_LENGTHS.BOLD, -MARKER_LENGTHS.BOLD)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
// 이탤릭 처리
|
|
58
|
+
else if (matchedText.length > MARKER_LENGTHS.ITALIC * 2 &&
|
|
59
|
+
((matchedText.startsWith('*') && matchedText.endsWith('*')) ||
|
|
60
|
+
(matchedText.startsWith('_') && matchedText.endsWith('_'))) &&
|
|
61
|
+
!/\w/.test(content.substring(matchResult.index - 1, matchResult.index)) &&
|
|
62
|
+
!/\w/.test(content.substring(patternRegex.lastIndex, patternRegex.lastIndex + 1)) &&
|
|
63
|
+
!/\S[./\\]/.test(content.substring(matchResult.index - 2, matchResult.index)) &&
|
|
64
|
+
!/[./\\]\S/.test(content.substring(patternRegex.lastIndex, patternRegex.lastIndex + 2))) {
|
|
65
|
+
formattedElement = React.createElement(Text, { key: elementKey, italic: true },
|
|
66
|
+
matchedText.slice(MARKER_LENGTHS.ITALIC, -MARKER_LENGTHS.ITALIC)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
// 취소선 처리
|
|
70
|
+
else if (matchedText.startsWith('~~') &&
|
|
71
|
+
matchedText.endsWith('~~') &&
|
|
72
|
+
matchedText.length > MARKER_LENGTHS.STRIKE * 2) {
|
|
73
|
+
formattedElement = React.createElement(Text, { key: elementKey, strikethrough: true },
|
|
74
|
+
matchedText.slice(MARKER_LENGTHS.STRIKE, -MARKER_LENGTHS.STRIKE)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
// 인라인 코드 처리
|
|
78
|
+
else if (matchedText.startsWith('`') &&
|
|
79
|
+
matchedText.endsWith('`') &&
|
|
80
|
+
matchedText.length > MARKER_LENGTHS.CODE) {
|
|
81
|
+
const codePattern = matchedText.match(/^(`+)(.+?)\1$/s);
|
|
82
|
+
if (codePattern && codePattern[2]) {
|
|
83
|
+
formattedElement = React.createElement(Text, {
|
|
84
|
+
key: elementKey,
|
|
85
|
+
color: theme.status.warning
|
|
86
|
+
}, codePattern[2]);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 링크 처리
|
|
90
|
+
else if (matchedText.startsWith('[') &&
|
|
91
|
+
matchedText.includes('](') &&
|
|
92
|
+
matchedText.endsWith(')')) {
|
|
93
|
+
const linkPattern = matchedText.match(/\[(.*?)\]\((.*?)\)/);
|
|
94
|
+
if (linkPattern) {
|
|
95
|
+
const linkLabel = linkPattern[1];
|
|
96
|
+
const linkUrl = linkPattern[2];
|
|
97
|
+
formattedElement = React.createElement(Text, { key: elementKey },
|
|
98
|
+
linkLabel,
|
|
99
|
+
React.createElement(Text, { color: theme.text.link }, ` (${linkUrl})`)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// 밑줄 처리
|
|
104
|
+
else if (matchedText.startsWith('<u>') &&
|
|
105
|
+
matchedText.endsWith('</u>') &&
|
|
106
|
+
matchedText.length > MARKER_LENGTHS.UNDERLINE_START + MARKER_LENGTHS.UNDERLINE_END - 1) {
|
|
107
|
+
formattedElement = React.createElement(Text, { key: elementKey, underline: true },
|
|
108
|
+
matchedText.slice(MARKER_LENGTHS.UNDERLINE_START, -MARKER_LENGTHS.UNDERLINE_END)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
// URL 처리
|
|
112
|
+
else if (matchedText.match(/^https?:\/\//)) {
|
|
113
|
+
formattedElement = React.createElement(Text, {
|
|
114
|
+
key: elementKey,
|
|
115
|
+
color: theme.text.link
|
|
116
|
+
}, matchedText);
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
// 파싱 오류 발생 시 무시
|
|
120
|
+
formattedElement = null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
elements.push(
|
|
124
|
+
formattedElement ?? React.createElement(Text, { key: elementKey }, matchedText)
|
|
125
|
+
);
|
|
126
|
+
currentPosition = patternRegex.lastIndex;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (currentPosition < content.length) {
|
|
130
|
+
elements.push(
|
|
131
|
+
React.createElement(Text, { key: `plain-${currentPosition}` },
|
|
132
|
+
content.slice(currentPosition)
|
|
133
|
+
)
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return React.createElement(React.Fragment, null, elements.filter(el => el !== null));
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const ProcessInlineText = React.memo(ProcessInlineTextInternal);
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 마크다운 포맷팅을 제거하고 실제 텍스트 길이를 계산
|
|
144
|
+
* 테이블의 컬럼 너비 계산 등에 사용
|
|
145
|
+
*/
|
|
146
|
+
export function calculateTextWidth(content) {
|
|
147
|
+
const plainText = content
|
|
148
|
+
.replace(/\*\*(.*?)\*\*/g, '$1')
|
|
149
|
+
.replace(/\*(.*?)\*/g, '$1')
|
|
150
|
+
.replace(/_(.*?)_/g, '$1')
|
|
151
|
+
.replace(/~~(.*?)~~/g, '$1')
|
|
152
|
+
.replace(/`(.*?)`/g, '$1')
|
|
153
|
+
.replace(/<u>(.*?)<\/u>/g, '$1')
|
|
154
|
+
.replace(/.*\[(.*?)\]\(.*\)/g, '$1');
|
|
155
|
+
return stringWidth(plainText);
|
|
156
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* diffUtils - Shared utilities for preparing diff data
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createDebugLogger } from '../../util/debug_log.js';
|
|
6
|
+
|
|
7
|
+
const debugLog = createDebugLogger('ui_utils.log', 'diffUtils');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Prepare diff data for edit_file_replace operation
|
|
11
|
+
* This function extracts the logic from ToolApprovalPrompt to ensure
|
|
12
|
+
* consistent diff display in both approval prompt and history view
|
|
13
|
+
*
|
|
14
|
+
* @param {string} oldString - The string to be replaced
|
|
15
|
+
* @param {string} newString - The replacement string
|
|
16
|
+
* @param {string} fileContent - The full file content
|
|
17
|
+
* @param {boolean} replaceAll - If true, replace all occurrences
|
|
18
|
+
* @returns {Object} Diff data object with oldContent, newContent, context, line numbers, or error
|
|
19
|
+
*/
|
|
20
|
+
export function prepareEditReplaceDiff(oldString, newString, fileContent, replaceAll = false) {
|
|
21
|
+
debugLog('========== prepareEditReplaceDiff START ==========');
|
|
22
|
+
debugLog(`oldString length: ${oldString?.length || 0}`);
|
|
23
|
+
debugLog(`oldString preview: ${JSON.stringify(oldString?.substring(0, 100))}`);
|
|
24
|
+
debugLog(`newString length: ${newString?.length || 0}`);
|
|
25
|
+
debugLog(`newString preview: ${JSON.stringify(newString?.substring(0, 100))}`);
|
|
26
|
+
debugLog(`fileContent length: ${fileContent?.length || 0}`);
|
|
27
|
+
debugLog(`replaceAll: ${replaceAll}`);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
if (!fileContent) {
|
|
31
|
+
debugLog('ERROR: fileContent is empty or null');
|
|
32
|
+
debugLog('========== prepareEditReplaceDiff END (ERROR) ==========');
|
|
33
|
+
return {
|
|
34
|
+
error: 'File not read yet or empty'
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Find old_string in file content
|
|
39
|
+
debugLog('Searching for old_string in file content...');
|
|
40
|
+
const index = fileContent.indexOf(oldString);
|
|
41
|
+
debugLog(`old_string found at index: ${index}`);
|
|
42
|
+
|
|
43
|
+
if (index === -1) {
|
|
44
|
+
debugLog('ERROR: old_string not found in file');
|
|
45
|
+
debugLog('========== prepareEditReplaceDiff END (ERROR) ==========');
|
|
46
|
+
return {
|
|
47
|
+
error: 'old_string not found in file'
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Split file into lines
|
|
52
|
+
debugLog('Splitting file content into lines...');
|
|
53
|
+
const lines = fileContent.split('\n');
|
|
54
|
+
const actualLines = fileContent === '' ? [] :
|
|
55
|
+
(fileContent.endsWith('\n') ? lines.slice(0, -1) : lines);
|
|
56
|
+
debugLog(`Total lines: ${lines.length}, Actual lines: ${actualLines.length}`);
|
|
57
|
+
|
|
58
|
+
// If replaceAll is true, find all occurrences and show full file diff
|
|
59
|
+
if (replaceAll) {
|
|
60
|
+
debugLog('========== REPLACE_ALL MODE ==========');
|
|
61
|
+
// Find all occurrences in the file content
|
|
62
|
+
debugLog('Finding all occurrences of old_string...');
|
|
63
|
+
const occurrences = [];
|
|
64
|
+
let searchIndex = 0;
|
|
65
|
+
while (true) {
|
|
66
|
+
const foundIndex = fileContent.indexOf(oldString, searchIndex);
|
|
67
|
+
if (foundIndex === -1) break;
|
|
68
|
+
occurrences.push(foundIndex);
|
|
69
|
+
debugLog(` Found occurrence #${occurrences.length} at index ${foundIndex}`);
|
|
70
|
+
searchIndex = foundIndex + 1;
|
|
71
|
+
}
|
|
72
|
+
debugLog(`Total occurrences found: ${occurrences.length}`);
|
|
73
|
+
|
|
74
|
+
if (occurrences.length === 0) {
|
|
75
|
+
debugLog('ERROR: No occurrences found in replaceAll mode');
|
|
76
|
+
debugLog('========== prepareEditReplaceDiff END (ERROR) ==========');
|
|
77
|
+
return {
|
|
78
|
+
error: 'old_string not found in file'
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Calculate affected line ranges for all occurrences
|
|
83
|
+
debugLog('Calculating affected line ranges for all occurrences...');
|
|
84
|
+
const affectedLineRanges = occurrences.map((idx, i) => {
|
|
85
|
+
const beforeMatch = fileContent.substring(0, idx);
|
|
86
|
+
const startLine = beforeMatch.split('\n').length;
|
|
87
|
+
|
|
88
|
+
const oldStringSplit = oldString.split('\n');
|
|
89
|
+
const oldStringLines = oldString.endsWith('\n')
|
|
90
|
+
? oldStringSplit.length - 1
|
|
91
|
+
: oldStringSplit.length;
|
|
92
|
+
const endLine = startLine + oldStringLines - 1;
|
|
93
|
+
|
|
94
|
+
debugLog(` Occurrence #${i + 1}: lines ${startLine}-${endLine}`);
|
|
95
|
+
return { startLine, endLine };
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Find the overall range
|
|
99
|
+
const firstAffectedLine = Math.min(...affectedLineRanges.map(r => r.startLine));
|
|
100
|
+
const lastAffectedLine = Math.max(...affectedLineRanges.map(r => r.endLine));
|
|
101
|
+
debugLog(`Overall affected range: lines ${firstAffectedLine}-${lastAffectedLine}`);
|
|
102
|
+
|
|
103
|
+
// Create old and new content for the affected range
|
|
104
|
+
const startLine = firstAffectedLine;
|
|
105
|
+
const endLine = lastAffectedLine;
|
|
106
|
+
|
|
107
|
+
debugLog(`Extracting affected lines from actualLines[${startLine - 1}:${endLine}]...`);
|
|
108
|
+
const affectedLines = actualLines.slice(startLine - 1, endLine);
|
|
109
|
+
const fullOldContent = affectedLines.join('\n');
|
|
110
|
+
debugLog(`fullOldContent length: ${fullOldContent.length} bytes, lines: ${affectedLines.length}`);
|
|
111
|
+
|
|
112
|
+
// Replace ALL occurrences in the entire file content
|
|
113
|
+
debugLog('Replacing ALL occurrences in file content...');
|
|
114
|
+
const replacedContent = fileContent.split(oldString).join(newString);
|
|
115
|
+
const replacedLines = replacedContent.split('\n');
|
|
116
|
+
const replacedActualLines = replacedContent === '' ? [] :
|
|
117
|
+
(replacedContent.endsWith('\n') ? replacedLines.slice(0, -1) : replacedLines);
|
|
118
|
+
debugLog(`Replaced content total lines: ${replacedActualLines.length}`);
|
|
119
|
+
|
|
120
|
+
// Calculate the new end line based on how many lines the replacement spans
|
|
121
|
+
debugLog('Calculating new end line...');
|
|
122
|
+
const oldLineCount = endLine - startLine + 1;
|
|
123
|
+
const newLineCount = affectedLines.join('\n').split(oldString).join(newString).split('\n').length;
|
|
124
|
+
const newEndLine = startLine + newLineCount - 1;
|
|
125
|
+
debugLog(` oldLineCount: ${oldLineCount}`);
|
|
126
|
+
debugLog(` newLineCount: ${newLineCount}`);
|
|
127
|
+
debugLog(` newEndLine: ${newEndLine}`);
|
|
128
|
+
|
|
129
|
+
debugLog(`Extracting fullNewContent from replacedActualLines[${startLine - 1}:${newEndLine}]...`);
|
|
130
|
+
const fullNewContent = replacedActualLines.slice(startLine - 1, newEndLine).join('\n');
|
|
131
|
+
debugLog(`fullNewContent length: ${fullNewContent.length} bytes`);
|
|
132
|
+
|
|
133
|
+
// Extract context (2 lines before and after)
|
|
134
|
+
const contextLines = 2;
|
|
135
|
+
const contextStartIdx = Math.max(0, startLine - 1 - contextLines);
|
|
136
|
+
const contextEndIdx = Math.min(actualLines.length, endLine + contextLines);
|
|
137
|
+
debugLog(`Context: before[${contextStartIdx}:${startLine - 1}], after[${endLine}:${contextEndIdx}]`);
|
|
138
|
+
|
|
139
|
+
const ctxBefore = actualLines.slice(contextStartIdx, startLine - 1);
|
|
140
|
+
const ctxAfter = actualLines.slice(endLine, contextEndIdx);
|
|
141
|
+
debugLog(`Context before: ${ctxBefore.length} lines, after: ${ctxAfter.length} lines`);
|
|
142
|
+
|
|
143
|
+
const result = {
|
|
144
|
+
startLine: startLine,
|
|
145
|
+
endLine: endLine,
|
|
146
|
+
oldContent: fullOldContent,
|
|
147
|
+
newContent: fullNewContent,
|
|
148
|
+
contextBefore: ctxBefore,
|
|
149
|
+
contextAfter: ctxAfter,
|
|
150
|
+
contextStartLine: contextStartIdx + 1
|
|
151
|
+
};
|
|
152
|
+
debugLog('========== prepareEditReplaceDiff END (REPLACE_ALL SUCCESS) ==========');
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Single replacement mode (original logic)
|
|
157
|
+
debugLog('========== SINGLE REPLACEMENT MODE ==========');
|
|
158
|
+
// Calculate line numbers
|
|
159
|
+
debugLog('Calculating line numbers...');
|
|
160
|
+
const beforeMatch = fileContent.substring(0, index);
|
|
161
|
+
const startLine = beforeMatch.split('\n').length;
|
|
162
|
+
debugLog(`startLine: ${startLine}`);
|
|
163
|
+
|
|
164
|
+
// Calculate actual line count (handle trailing newline)
|
|
165
|
+
const oldStringSplit = oldString.split('\n');
|
|
166
|
+
const oldStringLines = oldString.endsWith('\n')
|
|
167
|
+
? oldStringSplit.length - 1
|
|
168
|
+
: oldStringSplit.length;
|
|
169
|
+
const endLine = startLine + oldStringLines - 1;
|
|
170
|
+
debugLog(`oldStringLines: ${oldStringLines}, endLine: ${endLine}`);
|
|
171
|
+
|
|
172
|
+
// For partial line replacements, show the full line context
|
|
173
|
+
// For multi-line replacements, check if it's partial or full line
|
|
174
|
+
let fullOldContent, fullNewContent;
|
|
175
|
+
|
|
176
|
+
// Extract affected lines
|
|
177
|
+
debugLog(`Extracting affected lines from actualLines[${startLine - 1}:${endLine}]...`);
|
|
178
|
+
const affectedLines = actualLines.slice(startLine - 1, endLine);
|
|
179
|
+
fullOldContent = affectedLines.join('\n');
|
|
180
|
+
debugLog(`Affected lines count: ${affectedLines.length}`);
|
|
181
|
+
debugLog(`fullOldContent (from affected lines): ${JSON.stringify(fullOldContent.substring(0, 100))}`);
|
|
182
|
+
|
|
183
|
+
// Check if oldString matches the full affected lines (full line replacement)
|
|
184
|
+
// or if it's a partial replacement within those lines
|
|
185
|
+
const isFullLineMatch = (fullOldContent === oldString) ||
|
|
186
|
+
(fullOldContent === oldString.replace(/\n$/, '')) ||
|
|
187
|
+
(fullOldContent + '\n' === oldString);
|
|
188
|
+
debugLog(`isFullLineMatch: ${isFullLineMatch}`);
|
|
189
|
+
|
|
190
|
+
if (isFullLineMatch) {
|
|
191
|
+
// Full line replacement - use the matched content directly
|
|
192
|
+
debugLog('Full line replacement mode');
|
|
193
|
+
fullOldContent = oldString.replace(/\n$/, '');
|
|
194
|
+
fullNewContent = newString.replace(/\n$/, '');
|
|
195
|
+
debugLog(`fullOldContent length: ${fullOldContent.length}`);
|
|
196
|
+
debugLog(`fullNewContent length: ${fullNewContent.length}`);
|
|
197
|
+
} else {
|
|
198
|
+
// Partial line replacement (single or multi-line) - replace within full lines
|
|
199
|
+
debugLog('Partial line replacement mode - replacing within full lines');
|
|
200
|
+
fullNewContent = fullOldContent.split(oldString).join(newString);
|
|
201
|
+
debugLog(`fullOldContent length: ${fullOldContent.length}`);
|
|
202
|
+
debugLog(`fullNewContent length: ${fullNewContent.length}`);
|
|
203
|
+
debugLog(`fullNewContent preview: ${JSON.stringify(fullNewContent.substring(0, 100))}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Extract context (2 lines before and after)
|
|
207
|
+
const contextLines = 2;
|
|
208
|
+
const contextStartIdx = Math.max(0, startLine - 1 - contextLines);
|
|
209
|
+
const contextEndIdx = Math.min(actualLines.length, endLine + contextLines);
|
|
210
|
+
debugLog(`Context: before[${contextStartIdx}:${startLine - 1}], after[${endLine}:${contextEndIdx}]`);
|
|
211
|
+
|
|
212
|
+
const ctxBefore = actualLines.slice(contextStartIdx, startLine - 1);
|
|
213
|
+
const ctxAfter = actualLines.slice(endLine, contextEndIdx);
|
|
214
|
+
debugLog(`Context before: ${ctxBefore.length} lines, after: ${ctxAfter.length} lines`);
|
|
215
|
+
|
|
216
|
+
const result = {
|
|
217
|
+
startLine: startLine,
|
|
218
|
+
endLine: endLine,
|
|
219
|
+
oldContent: fullOldContent,
|
|
220
|
+
newContent: fullNewContent,
|
|
221
|
+
contextBefore: ctxBefore,
|
|
222
|
+
contextAfter: ctxAfter,
|
|
223
|
+
contextStartLine: contextStartIdx + 1
|
|
224
|
+
};
|
|
225
|
+
debugLog('========== prepareEditReplaceDiff END (SINGLE REPLACEMENT SUCCESS) ==========');
|
|
226
|
+
return result;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
debugLog(`EXCEPTION caught: ${error.message}`);
|
|
229
|
+
debugLog(`Stack trace: ${error.stack}`);
|
|
230
|
+
debugLog('========== prepareEditReplaceDiff END (EXCEPTION) ==========');
|
|
231
|
+
return {
|
|
232
|
+
error: error.message
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|