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.
Files changed (188) hide show
  1. package/LICENSE +68 -0
  2. package/README.md +347 -0
  3. package/config_template/mcp_config.json +3 -0
  4. package/config_template/package_name_store.json +5 -0
  5. package/config_template/settings.json +5 -0
  6. package/index.js +879 -0
  7. package/mcp-agent-lib/example/01-basic-usage.js +82 -0
  8. package/mcp-agent-lib/example/02-quick-start.js +52 -0
  9. package/mcp-agent-lib/example/03-http-server.js +76 -0
  10. package/mcp-agent-lib/example/04-multiple-servers.js +117 -0
  11. package/mcp-agent-lib/example/05-error-handling.js +116 -0
  12. package/mcp-agent-lib/example/06-resources-and-prompts.js +174 -0
  13. package/mcp-agent-lib/example/07-advanced-configuration.js +191 -0
  14. package/mcp-agent-lib/example/08-real-world-chatbot.js +331 -0
  15. package/mcp-agent-lib/example/README.md +346 -0
  16. package/mcp-agent-lib/index.js +19 -0
  17. package/mcp-agent-lib/init.sh +3 -0
  18. package/mcp-agent-lib/package-lock.json +1216 -0
  19. package/mcp-agent-lib/package.json +53 -0
  20. package/mcp-agent-lib/sampleFastMCPClient/client.py +25 -0
  21. package/mcp-agent-lib/sampleFastMCPClient/run.sh +3 -0
  22. package/mcp-agent-lib/sampleFastMCPServer/run.sh +3 -0
  23. package/mcp-agent-lib/sampleFastMCPServer/server.py +12 -0
  24. package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/run.sh +3 -0
  25. package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/server.py +43 -0
  26. package/mcp-agent-lib/sampleFastMCPServerRootsRequest/server.py +63 -0
  27. package/mcp-agent-lib/sampleMCPHost/index.js +386 -0
  28. package/mcp-agent-lib/sampleMCPHost/mcp_config.json +24 -0
  29. package/mcp-agent-lib/sampleMCPHostFeatures/elicitation.js +151 -0
  30. package/mcp-agent-lib/sampleMCPHostFeatures/index.js +166 -0
  31. package/mcp-agent-lib/sampleMCPHostFeatures/roots.js +197 -0
  32. package/mcp-agent-lib/src/mcp_client.js +1860 -0
  33. package/mcp-agent-lib/src/mcp_message_logger.js +517 -0
  34. package/package.json +72 -0
  35. package/payload_viewer/out/404/index.html +1 -0
  36. package/payload_viewer/out/404.html +1 -0
  37. package/payload_viewer/out/_next/static/chunks/060f9a97930f3d04.js +1 -0
  38. package/payload_viewer/out/_next/static/chunks/103c802c8f4a5ea1.js +1 -0
  39. package/payload_viewer/out/_next/static/chunks/16474fd6c6910c45.js +1 -0
  40. package/payload_viewer/out/_next/static/chunks/17722e3ac4e00587.js +1 -0
  41. package/payload_viewer/out/_next/static/chunks/305b077a9873cf54.js +1 -0
  42. package/payload_viewer/out/_next/static/chunks/4c1d05c6741c2bdd.js +5 -0
  43. package/payload_viewer/out/_next/static/chunks/538cc02e54714b23.js +1 -0
  44. package/payload_viewer/out/_next/static/chunks/6251fa5907d2b226.js +5 -0
  45. package/payload_viewer/out/_next/static/chunks/a6dad97d9634a72d.js +1 -0
  46. package/payload_viewer/out/_next/static/chunks/b6c0459f3789d25c.js +1 -0
  47. package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
  48. package/payload_viewer/out/_next/static/chunks/bd2dcf98c9b362f6.js +1 -0
  49. package/payload_viewer/out/_next/static/chunks/c8a542ae21335479.js +1 -0
  50. package/payload_viewer/out/_next/static/chunks/cdd12d5c1a5a6064.js +1 -0
  51. package/payload_viewer/out/_next/static/chunks/e411019f55d87c42.js +1 -0
  52. package/payload_viewer/out/_next/static/chunks/e60ef129113f6e24.js +1 -0
  53. package/payload_viewer/out/_next/static/chunks/f1ac9047ac4a3fde.js +1 -0
  54. package/payload_viewer/out/_next/static/chunks/turbopack-0ac29803ce3c3c7a.js +3 -0
  55. package/payload_viewer/out/_next/static/chunks/turbopack-89db4c64206a73e4.js +3 -0
  56. package/payload_viewer/out/_next/static/chunks/turbopack-a5b8235fa59d7119.js +3 -0
  57. package/payload_viewer/out/_next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
  58. package/payload_viewer/out/_next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
  59. package/payload_viewer/out/_next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
  60. package/payload_viewer/out/_next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
  61. package/payload_viewer/out/_next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
  62. package/payload_viewer/out/_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
  63. package/payload_viewer/out/_next/static/media/favicon.0b3bf435.ico +0 -0
  64. package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_buildManifest.js +14 -0
  65. package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_clientMiddlewareManifest.json +1 -0
  66. package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_ssgManifest.js +1 -0
  67. package/payload_viewer/out/favicon.ico +0 -0
  68. package/payload_viewer/out/file.svg +1 -0
  69. package/payload_viewer/out/globe.svg +1 -0
  70. package/payload_viewer/out/index.html +1 -0
  71. package/payload_viewer/out/index.txt +23 -0
  72. package/payload_viewer/out/next.svg +1 -0
  73. package/payload_viewer/out/vercel.svg +1 -0
  74. package/payload_viewer/out/window.svg +1 -0
  75. package/payload_viewer/web_server.js +861 -0
  76. package/prompts/completion_judge.txt +128 -0
  77. package/prompts/orchestrator.txt +1213 -0
  78. package/src/LLMClient/client.js +1375 -0
  79. package/src/LLMClient/converters/input-normalizer.js +238 -0
  80. package/src/LLMClient/converters/responses-to-claude.js +503 -0
  81. package/src/LLMClient/converters/responses-to-gemini.js +648 -0
  82. package/src/LLMClient/converters/responses-to-ollama.js +348 -0
  83. package/src/LLMClient/converters/responses-to-zai.js +667 -0
  84. package/src/LLMClient/errors.js +398 -0
  85. package/src/LLMClient/index.js +36 -0
  86. package/src/ai_based/completion_judge.js +421 -0
  87. package/src/ai_based/orchestrator.js +527 -0
  88. package/src/ai_based/pip_package_installer.js +173 -0
  89. package/src/ai_based/pip_package_lookup.js +197 -0
  90. package/src/cli/mcp_cli.js +70 -0
  91. package/src/cli/mcp_commands.js +255 -0
  92. package/src/commands/agents.js +18 -0
  93. package/src/commands/apikey.js +55 -0
  94. package/src/commands/bg.js +140 -0
  95. package/src/commands/commands.js +56 -0
  96. package/src/commands/debug.js +54 -0
  97. package/src/commands/exit.js +19 -0
  98. package/src/commands/help.js +35 -0
  99. package/src/commands/mcp.js +128 -0
  100. package/src/commands/model.js +176 -0
  101. package/src/commands/setup.js +13 -0
  102. package/src/commands/skills.js +51 -0
  103. package/src/commands/tools.js +165 -0
  104. package/src/commands/viewer.js +147 -0
  105. package/src/config/ai_models.js +312 -0
  106. package/src/config/config.js +10 -0
  107. package/src/config/constants.js +71 -0
  108. package/src/config/feature_flags.js +15 -0
  109. package/src/frontend/App.js +1263 -0
  110. package/src/frontend/README.md +81 -0
  111. package/src/frontend/components/AutocompleteMenu.js +47 -0
  112. package/src/frontend/components/BackgroundProcessList.js +175 -0
  113. package/src/frontend/components/BlankLine.js +62 -0
  114. package/src/frontend/components/ConversationItem.js +893 -0
  115. package/src/frontend/components/CurrentModelView.js +43 -0
  116. package/src/frontend/components/FileDiffViewer.js +616 -0
  117. package/src/frontend/components/Footer.js +25 -0
  118. package/src/frontend/components/Header.js +42 -0
  119. package/src/frontend/components/HelpView.js +154 -0
  120. package/src/frontend/components/Input.js +344 -0
  121. package/src/frontend/components/LoadingIndicator.js +31 -0
  122. package/src/frontend/components/ModelListView.js +49 -0
  123. package/src/frontend/components/ModelUpdatedView.js +22 -0
  124. package/src/frontend/components/SessionSpinner.js +66 -0
  125. package/src/frontend/components/SetupWizard.js +242 -0
  126. package/src/frontend/components/StreamOutput.js +34 -0
  127. package/src/frontend/components/TodoList.js +56 -0
  128. package/src/frontend/components/ToolApprovalPrompt.js +452 -0
  129. package/src/frontend/design/themeColors.js +42 -0
  130. package/src/frontend/hooks/useCompletion.js +84 -0
  131. package/src/frontend/hooks/useFileCompletion.js +467 -0
  132. package/src/frontend/hooks/useKeypress.js +145 -0
  133. package/src/frontend/index.js +65 -0
  134. package/src/frontend/utils/GridRenderer.js +140 -0
  135. package/src/frontend/utils/InlineFormatter.js +156 -0
  136. package/src/frontend/utils/diffUtils.js +235 -0
  137. package/src/frontend/utils/inputBuffer.js +441 -0
  138. package/src/frontend/utils/markdownParser.js +377 -0
  139. package/src/frontend/utils/outputRedirector.js +47 -0
  140. package/src/frontend/utils/renderInkComponent.js +42 -0
  141. package/src/frontend/utils/syntaxHighlighter.js +149 -0
  142. package/src/frontend/utils/toolUIFormatter.js +261 -0
  143. package/src/system/agents_loader.js +170 -0
  144. package/src/system/ai_request.js +737 -0
  145. package/src/system/background_process.js +317 -0
  146. package/src/system/code_executer.js +1233 -0
  147. package/src/system/command_loader.js +40 -0
  148. package/src/system/command_parser.js +133 -0
  149. package/src/system/conversation_state.js +265 -0
  150. package/src/system/conversation_trimmer.js +265 -0
  151. package/src/system/custom_command_loader.js +395 -0
  152. package/src/system/file_integrity.js +466 -0
  153. package/src/system/import_analyzer.py +174 -0
  154. package/src/system/log.js +82 -0
  155. package/src/system/mcp_integration.js +304 -0
  156. package/src/system/output_helper.js +89 -0
  157. package/src/system/session.js +1393 -0
  158. package/src/system/session_memory.js +481 -0
  159. package/src/system/skill_loader.js +324 -0
  160. package/src/system/system_info.js +483 -0
  161. package/src/system/tool_approval.js +160 -0
  162. package/src/system/tool_registry.js +184 -0
  163. package/src/system/ui_events.js +279 -0
  164. package/src/tools/code_editor.js +792 -0
  165. package/src/tools/file_reader.js +385 -0
  166. package/src/tools/glob.js +263 -0
  167. package/src/tools/response_message.js +30 -0
  168. package/src/tools/ripgrep.js +554 -0
  169. package/src/tools/skill_tool.js +122 -0
  170. package/src/tools/todo_write.js +182 -0
  171. package/src/tools/web_download.py +74 -0
  172. package/src/tools/web_downloader.js +83 -0
  173. package/src/util/clone.js +174 -0
  174. package/src/util/config.js +203 -0
  175. package/src/util/config_migration.js +174 -0
  176. package/src/util/debug_log.js +49 -0
  177. package/src/util/exit_handler.js +53 -0
  178. package/src/util/file_reference_parser.js +132 -0
  179. package/src/util/mcp_config_manager.js +159 -0
  180. package/src/util/output_formatter.js +50 -0
  181. package/src/util/path_helper.js +27 -0
  182. package/src/util/path_validator.js +178 -0
  183. package/src/util/prompt_loader.js +184 -0
  184. package/src/util/rag_helper.js +101 -0
  185. package/src/util/safe_fs.js +645 -0
  186. package/src/util/setup_wizard.js +62 -0
  187. package/src/util/text_formatter.js +33 -0
  188. 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
+ }