foliko 1.0.75 → 1.0.76

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 (88) hide show
  1. package/.claude/settings.local.json +159 -157
  2. package/cli/bin/foliko.js +12 -12
  3. package/cli/src/commands/chat.js +143 -143
  4. package/cli/src/commands/list.js +93 -93
  5. package/cli/src/index.js +75 -75
  6. package/cli/src/ui/chat-ui.js +201 -201
  7. package/cli/src/utils/ansi.js +40 -40
  8. package/cli/src/utils/markdown.js +292 -292
  9. package/examples/ambient-example.js +194 -194
  10. package/examples/basic.js +115 -115
  11. package/examples/bootstrap.js +121 -121
  12. package/examples/mcp-example.js +56 -56
  13. package/examples/skill-example.js +49 -49
  14. package/examples/test-chat.js +137 -137
  15. package/examples/test-mcp.js +85 -85
  16. package/examples/test-reload.js +59 -59
  17. package/examples/test-telegram.js +50 -50
  18. package/examples/test-tg-bot.js +45 -45
  19. package/examples/test-tg-simple.js +47 -47
  20. package/examples/test-tg.js +62 -62
  21. package/examples/test-think.js +43 -43
  22. package/examples/test-web-plugin.js +103 -103
  23. package/examples/test-weixin-feishu.js +103 -103
  24. package/examples/workflow.js +158 -158
  25. package/package.json +1 -1
  26. package/plugins/ai-plugin.js +102 -102
  27. package/plugins/ambient-agent/EventWatcher.js +113 -113
  28. package/plugins/ambient-agent/ExplorerLoop.js +640 -640
  29. package/plugins/ambient-agent/GoalManager.js +197 -197
  30. package/plugins/ambient-agent/Reflector.js +95 -95
  31. package/plugins/ambient-agent/StateStore.js +90 -90
  32. package/plugins/ambient-agent/constants.js +101 -101
  33. package/plugins/ambient-agent/index.js +579 -579
  34. package/plugins/audit-plugin.js +187 -187
  35. package/plugins/default-plugins.js +662 -662
  36. package/plugins/email/constants.js +64 -64
  37. package/plugins/email/handlers.js +461 -461
  38. package/plugins/email/index.js +278 -278
  39. package/plugins/email/monitor.js +269 -269
  40. package/plugins/email/parser.js +138 -138
  41. package/plugins/email/reply.js +151 -151
  42. package/plugins/email/utils.js +124 -124
  43. package/plugins/feishu-plugin.js +481 -481
  44. package/plugins/file-system-plugin.js +826 -826
  45. package/plugins/install-plugin.js +199 -199
  46. package/plugins/python-executor-plugin.js +367 -367
  47. package/plugins/python-plugin-loader.js +481 -481
  48. package/plugins/rules-plugin.js +294 -294
  49. package/plugins/scheduler-plugin.js +691 -691
  50. package/plugins/session-plugin.js +369 -369
  51. package/plugins/shell-executor-plugin.js +197 -197
  52. package/plugins/storage-plugin.js +240 -240
  53. package/plugins/subagent-plugin.js +845 -845
  54. package/plugins/telegram-plugin.js +482 -482
  55. package/plugins/think-plugin.js +345 -345
  56. package/plugins/tools-plugin.js +196 -196
  57. package/plugins/web-plugin.js +606 -606
  58. package/plugins/weixin-plugin.js +545 -545
  59. package/src/capabilities/index.js +11 -11
  60. package/src/capabilities/skill-manager.js +609 -609
  61. package/src/capabilities/workflow-engine.js +1109 -1109
  62. package/src/core/agent-chat.js +882 -882
  63. package/src/core/agent.js +892 -892
  64. package/src/core/framework.js +465 -465
  65. package/src/core/index.js +19 -19
  66. package/src/core/plugin-base.js +219 -219
  67. package/src/core/plugin-manager.js +863 -863
  68. package/src/core/provider.js +114 -114
  69. package/src/core/sub-agent-config.js +264 -264
  70. package/src/core/system-prompt-builder.js +120 -120
  71. package/src/core/tool-registry.js +517 -517
  72. package/src/core/tool-router.js +297 -297
  73. package/src/executors/executor-base.js +58 -58
  74. package/src/executors/mcp-executor.js +741 -741
  75. package/src/index.js +25 -25
  76. package/src/utils/circuit-breaker.js +301 -301
  77. package/src/utils/error-boundary.js +363 -363
  78. package/src/utils/error.js +374 -374
  79. package/src/utils/event-emitter.js +97 -97
  80. package/src/utils/id.js +133 -133
  81. package/src/utils/index.js +217 -217
  82. package/src/utils/logger.js +181 -181
  83. package/src/utils/plugin-helpers.js +90 -90
  84. package/src/utils/retry.js +122 -122
  85. package/src/utils/sandbox.js +292 -292
  86. package/test/tool-registry-validation.test.js +218 -218
  87. package/website/script.js +136 -136
  88. package/foliko-1.0.75.tgz +0 -0
@@ -1,292 +1,292 @@
1
- /**
2
- * 简单的流式 Markdown 渲染器
3
- * 支持: **粗体**, *斜体*, `代码`, ```代码块```, # 标题, 列表, 引用, <think>思考
4
- */
5
-
6
- const { BOLD, DIM, CLEAR, RED, GREEN, YELLOW, CYAN, BLUE, MAGENTA, WHITE } = require('./ansi');
7
-
8
- // Markdown 标记对应的 ANSI 样式
9
- const STYLES = {
10
- bold: { prefix: BOLD, suffix: CLEAR },
11
- italic: { prefix: DIM, suffix: CLEAR },
12
- code: { prefix: CYAN, suffix: CLEAR }, // dim = 暗色
13
- codeBlock: { prefix: YELLOW, suffix: CLEAR }, // yellow = 黄色
14
- h1: { prefix: BOLD, suffix: CLEAR }, // bold white
15
- h2: { prefix: BOLD, suffix: CLEAR }, // bold white
16
- h3: { prefix: BOLD, suffix: CLEAR },
17
- link: { prefix: '\x1B[34m\x1B[4m', suffix: CLEAR }, // blue underline
18
- list: { prefix: DIM, suffix: CLEAR }, // dim = 暗色
19
- blockquote: { prefix: DIM, suffix: CLEAR }, // dim = 暗色
20
- think: { prefix: `\x1B[38;5;240m`, suffix: CLEAR }, // yellow = 黄色
21
- };
22
-
23
- /**
24
- * 验证并修复UTF-16代理对
25
- */
26
- function fixSurrogates(text) {
27
- if (!text) return '';
28
- const result = [];
29
- let i = 0;
30
- while (i < text.length) {
31
- const char = text[i];
32
- const code = text.charCodeAt(i);
33
- // 高代理
34
- if (code >= 0xd800 && code <= 0xdbff) {
35
- const nextCode = text.charCodeAt(i + 1);
36
- // 如果有低代理,配对
37
- if (nextCode >= 0xdc00 && nextCode <= 0xdfff) {
38
- result.push(char + text[i + 1]);
39
- i += 2;
40
- continue;
41
- }
42
- // 孤立的高代理,替换为方框
43
- result.push('\uFFFD');
44
- i += 1;
45
- continue;
46
- }
47
- // 低代理(不应该单独出现)
48
- if (code >= 0xdc00 && code <= 0xdfff) {
49
- result.push('\uFFFD');
50
- i += 1;
51
- continue;
52
- }
53
- result.push(char);
54
- i++;
55
- }
56
- return result.join('');
57
- }
58
-
59
- /**
60
- * 渲染单行 markdown(流式友好,简单处理)
61
- */
62
- function renderInline(text) {
63
- if (!text) return '';
64
-
65
- // 修复不完整的代理对
66
- const fixed = fixSurrogates(text);
67
- let result = fixed;
68
-
69
- // 处理行内代码 `code`
70
- result = result.replace(/`([^`]+)`/g, (match, code) => {
71
- return `${STYLES.code.prefix}${code}${STYLES.code.suffix}`;
72
- });
73
-
74
- // 处理 **粗体**
75
- result = result.replace(/\*\*([^*]+)\*\*/g, (match, bold) => {
76
- return `${STYLES.bold.prefix}${bold}${STYLES.bold.suffix}`;
77
- });
78
-
79
- // 处理 *斜体*(不匹配已处理的粗体内部)
80
- result = result.replace(/(?<!\*)\*([^*\n]+)\*(?!\*)/g, (match, italic) => {
81
- return `${STYLES.italic.prefix}${italic}${STYLES.italic.suffix}`;
82
- });
83
-
84
- // 处理 ~~删除线~~ (简单处理)
85
- result = result.replace(/~~([^~]+)~~/g, (match, strikethrough) => {
86
- return `${DIM}${strikethrough}${CLEAR}`;
87
- });
88
-
89
- // 处理行内链接 [text](url) -> text
90
- result = result.replace(/\[([^\]]+)\]\([^)]+\)/g, (match, text) => {
91
- return `${STYLES.link.prefix}${text}${STYLES.link.suffix}`;
92
- });
93
-
94
- return result;
95
- }
96
-
97
- /**
98
- * 渲染包含思考标签的行
99
- */
100
- function renderThink(line, state = { inThink: false }) {
101
- // <think>思考内容</think> -> 思考内容用暗色显示
102
- let result = '';
103
-
104
- let i = 0;
105
- let lastEnd = 0;
106
-
107
- // 如果在思考块中但这行没有开始标签,在行首添加黄色前缀
108
- if (state.inThink && !line.includes('<think>')) {
109
- result += STYLES.think.prefix;
110
- }
111
-
112
- while (i < line.length) {
113
- // 检查 <think> (7个字符)
114
- if (line.substring(i, i + 7) === '<think>') {
115
- if (i > lastEnd) {
116
- result += renderInline(line.substring(lastEnd, i));
117
- }
118
- result += STYLES.think.prefix + '<think>';
119
- i += 7;
120
- lastEnd = i;
121
- state.inThink = true;
122
- // 如果这行只有<think>标签,不要在这里添加CLEAR
123
- // 后续行会继续使用黄色
124
- if (i >= line.length) {
125
- // 没有更多内容,return时不添加CLEAR,保持黄色状态
126
- return result;
127
- }
128
- continue;
129
- }
130
-
131
- // 检查</think> (8个字符)
132
- if (line.substring(i, i + 8) === '</think>') {
133
- if (i > lastEnd) {
134
- result += line.substring(lastEnd, i) + STYLES.think.suffix;
135
- }
136
- result += '</think>' + CLEAR;
137
- i += 8;
138
- lastEnd = i;
139
- state.inThink = false;
140
- continue;
141
- }
142
-
143
- i++;
144
- }
145
-
146
- if (lastEnd < line.length) {
147
- result += renderInline(line.substring(lastEnd));
148
- }
149
-
150
- return result;
151
- }
152
-
153
- /**
154
- * 渲染一行 markdown(处理标题、列表等)
155
- * state: { inThink: boolean, inCodeBlock: boolean } - 状态,会被修改
156
- */
157
- function renderLine(line, state = { inThink: false, inCodeBlock: false }) {
158
- // 思考标签 - 保留首尾换行符
159
- const leadingNL = line.match(/^\n*/)[0];
160
- const trailingNL = line.match(/\n*$/)[0];
161
- const trimmed = line.trim();
162
-
163
- // 代码块处理
164
- if (trimmed.startsWith('```')) {
165
- if (state.inCodeBlock) {
166
- // 结束代码块
167
- state.inCodeBlock = false;
168
- return `\`\`\`${trimmed.slice(3) ? ' ' + trimmed.slice(3) : ''}${STYLES.codeBlock.suffix}`;
169
- } else {
170
- // 开始代码块
171
- state.inCodeBlock = true;
172
- const lang = trimmed.slice(3).trim();
173
- return `${STYLES.codeBlock.prefix}\`\`\`${lang ? ' ' + lang : ''}`;
174
- }
175
- }
176
-
177
- // 如果在代码块中,整行用代码样式
178
- if (state.inCodeBlock) {
179
- return `${STYLES.codeBlock.prefix}${line}${STYLES.codeBlock.suffix}`;
180
- }
181
-
182
- // 如果在思考块中或者有思考标签
183
- if (state.inThink || trimmed.includes('<think>') || trimmed.includes('</think>')) {
184
- const result = leadingNL + renderThink(trimmed, state) + trailingNL;
185
- return result;
186
- }
187
-
188
- // 标题
189
- if (line.startsWith('### ')) {
190
- return `${STYLES.h3.prefix}${renderInline(line.substring(4))}${STYLES.h3.suffix}`;
191
- }
192
- if (line.startsWith('## ')) {
193
- return `${STYLES.h2.prefix}${renderInline(line.substring(3))}${STYLES.h2.suffix}`;
194
- }
195
- if (line.startsWith('# ')) {
196
- return `${STYLES.h1.prefix}${renderInline(line.substring(2))}${STYLES.h1.suffix}`;
197
- }
198
-
199
- // 无序列表
200
- if (line.match(/^[-*+] /)) {
201
- return line.replace(/^([-*+] )(.*)/, (match, bullet, content) => {
202
- return `${STYLES.list.prefix}${bullet}${CLEAR}${renderInline(content)}`;
203
- });
204
- }
205
-
206
- // 有序列表
207
- if (line.match(/^\d+\. /)) {
208
- return line.replace(/^(\d+\. )(.*)/, (match, bullet, content) => {
209
- return `${STYLES.list.prefix}${bullet}${CLEAR}${renderInline(content)}`;
210
- });
211
- }
212
-
213
- // 引用
214
- if (line.startsWith('> ')) {
215
- return line.replace(/^> (.*)/, (match, content) => {
216
- return `${STYLES.blockquote.prefix}| ${renderInline(content)}${STYLES.blockquote.suffix}`;
217
- });
218
- }
219
-
220
- return renderInline(line);
221
- }
222
-
223
- /**
224
- * 渲染完整的 markdown 文本
225
- */
226
- function render(text) {
227
- if (!text) return '';
228
-
229
- const lines = text.split('\n');
230
- const rendered = [];
231
-
232
- let inCodeBlock = false;
233
-
234
- for (const line of lines) {
235
- // 代码块
236
- if (line.startsWith('```')) {
237
- if (inCodeBlock) {
238
- rendered.push(`\`\`\`${STYLES.codeBlock.suffix}`);
239
- inCodeBlock = false;
240
- } else {
241
- const lang = line.substring(3).trim() || '';
242
- rendered.push(`${STYLES.codeBlock.prefix}\`\`\`${lang ? ' ' + lang : ''}`);
243
- inCodeBlock = true;
244
- }
245
- continue;
246
- }
247
-
248
- if (inCodeBlock) {
249
- rendered.push(`${STYLES.codeBlock.prefix}${line}${STYLES.codeBlock.suffix}`);
250
- } else {
251
- rendered.push(renderLine(line));
252
- }
253
- }
254
-
255
- return rendered.join('\n');
256
- }
257
-
258
- /**
259
- * 流式渲染 - 每次返回一个完整的行
260
- * 返回 { done: boolean, line: string }
261
- */
262
- function* streamRender(text) {
263
- if (!text) return;
264
-
265
- const lines = text.split('\n');
266
- let inCodeBlock = false;
267
-
268
- for (const line of lines) {
269
- // 代码块
270
- if (line.startsWith('```')) {
271
- if (inCodeBlock) {
272
- yield { done: false, line: `${STYLES.codeBlock.suffix}` };
273
- inCodeBlock = false;
274
- } else {
275
- const lang = line.substring(3).trim() || '';
276
- yield { done: false, line: `${STYLES.codeBlock.prefix}\`\`\`${lang ? ' ' + lang : ''}` };
277
- inCodeBlock = true;
278
- }
279
- continue;
280
- }
281
-
282
- if (inCodeBlock) {
283
- yield { done: false, line: `${STYLES.codeBlock.prefix}${line}${STYLES.codeBlock.suffix}` };
284
- } else {
285
- yield { done: false, line: renderLine(line) };
286
- }
287
- }
288
-
289
- yield { done: true };
290
- }
291
-
292
- module.exports = { render, renderInline, renderLine, streamRender, STYLES };
1
+ /**
2
+ * 简单的流式 Markdown 渲染器
3
+ * 支持: **粗体**, *斜体*, `代码`, ```代码块```, # 标题, 列表, 引用, <think>思考
4
+ */
5
+
6
+ const { BOLD, DIM, CLEAR, RED, GREEN, YELLOW, CYAN, BLUE, MAGENTA, WHITE } = require('./ansi');
7
+
8
+ // Markdown 标记对应的 ANSI 样式
9
+ const STYLES = {
10
+ bold: { prefix: BOLD, suffix: CLEAR },
11
+ italic: { prefix: DIM, suffix: CLEAR },
12
+ code: { prefix: CYAN, suffix: CLEAR }, // dim = 暗色
13
+ codeBlock: { prefix: YELLOW, suffix: CLEAR }, // yellow = 黄色
14
+ h1: { prefix: BOLD, suffix: CLEAR }, // bold white
15
+ h2: { prefix: BOLD, suffix: CLEAR }, // bold white
16
+ h3: { prefix: BOLD, suffix: CLEAR },
17
+ link: { prefix: '\x1B[34m\x1B[4m', suffix: CLEAR }, // blue underline
18
+ list: { prefix: DIM, suffix: CLEAR }, // dim = 暗色
19
+ blockquote: { prefix: DIM, suffix: CLEAR }, // dim = 暗色
20
+ think: { prefix: `\x1B[38;5;240m`, suffix: CLEAR }, // yellow = 黄色
21
+ };
22
+
23
+ /**
24
+ * 验证并修复UTF-16代理对
25
+ */
26
+ function fixSurrogates(text) {
27
+ if (!text) return '';
28
+ const result = [];
29
+ let i = 0;
30
+ while (i < text.length) {
31
+ const char = text[i];
32
+ const code = text.charCodeAt(i);
33
+ // 高代理
34
+ if (code >= 0xd800 && code <= 0xdbff) {
35
+ const nextCode = text.charCodeAt(i + 1);
36
+ // 如果有低代理,配对
37
+ if (nextCode >= 0xdc00 && nextCode <= 0xdfff) {
38
+ result.push(char + text[i + 1]);
39
+ i += 2;
40
+ continue;
41
+ }
42
+ // 孤立的高代理,替换为方框
43
+ result.push('\uFFFD');
44
+ i += 1;
45
+ continue;
46
+ }
47
+ // 低代理(不应该单独出现)
48
+ if (code >= 0xdc00 && code <= 0xdfff) {
49
+ result.push('\uFFFD');
50
+ i += 1;
51
+ continue;
52
+ }
53
+ result.push(char);
54
+ i++;
55
+ }
56
+ return result.join('');
57
+ }
58
+
59
+ /**
60
+ * 渲染单行 markdown(流式友好,简单处理)
61
+ */
62
+ function renderInline(text) {
63
+ if (!text) return '';
64
+
65
+ // 修复不完整的代理对
66
+ const fixed = fixSurrogates(text);
67
+ let result = fixed;
68
+
69
+ // 处理行内代码 `code`
70
+ result = result.replace(/`([^`]+)`/g, (match, code) => {
71
+ return `${STYLES.code.prefix}${code}${STYLES.code.suffix}`;
72
+ });
73
+
74
+ // 处理 **粗体**
75
+ result = result.replace(/\*\*([^*]+)\*\*/g, (match, bold) => {
76
+ return `${STYLES.bold.prefix}${bold}${STYLES.bold.suffix}`;
77
+ });
78
+
79
+ // 处理 *斜体*(不匹配已处理的粗体内部)
80
+ result = result.replace(/(?<!\*)\*([^*\n]+)\*(?!\*)/g, (match, italic) => {
81
+ return `${STYLES.italic.prefix}${italic}${STYLES.italic.suffix}`;
82
+ });
83
+
84
+ // 处理 ~~删除线~~ (简单处理)
85
+ result = result.replace(/~~([^~]+)~~/g, (match, strikethrough) => {
86
+ return `${DIM}${strikethrough}${CLEAR}`;
87
+ });
88
+
89
+ // 处理行内链接 [text](url) -> text
90
+ result = result.replace(/\[([^\]]+)\]\([^)]+\)/g, (match, text) => {
91
+ return `${STYLES.link.prefix}${text}${STYLES.link.suffix}`;
92
+ });
93
+
94
+ return result;
95
+ }
96
+
97
+ /**
98
+ * 渲染包含思考标签的行
99
+ */
100
+ function renderThink(line, state = { inThink: false }) {
101
+ // <think>思考内容</think> -> 思考内容用暗色显示
102
+ let result = '';
103
+
104
+ let i = 0;
105
+ let lastEnd = 0;
106
+
107
+ // 如果在思考块中但这行没有开始标签,在行首添加黄色前缀
108
+ if (state.inThink && !line.includes('<think>')) {
109
+ result += STYLES.think.prefix;
110
+ }
111
+
112
+ while (i < line.length) {
113
+ // 检查 <think> (7个字符)
114
+ if (line.substring(i, i + 7) === '<think>') {
115
+ if (i > lastEnd) {
116
+ result += renderInline(line.substring(lastEnd, i));
117
+ }
118
+ result += STYLES.think.prefix + '<think>';
119
+ i += 7;
120
+ lastEnd = i;
121
+ state.inThink = true;
122
+ // 如果这行只有<think>标签,不要在这里添加CLEAR
123
+ // 后续行会继续使用黄色
124
+ if (i >= line.length) {
125
+ // 没有更多内容,return时不添加CLEAR,保持黄色状态
126
+ return result;
127
+ }
128
+ continue;
129
+ }
130
+
131
+ // 检查</think> (8个字符)
132
+ if (line.substring(i, i + 8) === '</think>') {
133
+ if (i > lastEnd) {
134
+ result += line.substring(lastEnd, i) + STYLES.think.suffix;
135
+ }
136
+ result += '</think>' + CLEAR;
137
+ i += 8;
138
+ lastEnd = i;
139
+ state.inThink = false;
140
+ continue;
141
+ }
142
+
143
+ i++;
144
+ }
145
+
146
+ if (lastEnd < line.length) {
147
+ result += renderInline(line.substring(lastEnd));
148
+ }
149
+
150
+ return result;
151
+ }
152
+
153
+ /**
154
+ * 渲染一行 markdown(处理标题、列表等)
155
+ * state: { inThink: boolean, inCodeBlock: boolean } - 状态,会被修改
156
+ */
157
+ function renderLine(line, state = { inThink: false, inCodeBlock: false }) {
158
+ // 思考标签 - 保留首尾换行符
159
+ const leadingNL = line.match(/^\n*/)[0];
160
+ const trailingNL = line.match(/\n*$/)[0];
161
+ const trimmed = line.trim();
162
+
163
+ // 代码块处理
164
+ if (trimmed.startsWith('```')) {
165
+ if (state.inCodeBlock) {
166
+ // 结束代码块
167
+ state.inCodeBlock = false;
168
+ return `\`\`\`${trimmed.slice(3) ? ' ' + trimmed.slice(3) : ''}${STYLES.codeBlock.suffix}`;
169
+ } else {
170
+ // 开始代码块
171
+ state.inCodeBlock = true;
172
+ const lang = trimmed.slice(3).trim();
173
+ return `${STYLES.codeBlock.prefix}\`\`\`${lang ? ' ' + lang : ''}`;
174
+ }
175
+ }
176
+
177
+ // 如果在代码块中,整行用代码样式
178
+ if (state.inCodeBlock) {
179
+ return `${STYLES.codeBlock.prefix}${line}${STYLES.codeBlock.suffix}`;
180
+ }
181
+
182
+ // 如果在思考块中或者有思考标签
183
+ if (state.inThink || trimmed.includes('<think>') || trimmed.includes('</think>')) {
184
+ const result = leadingNL + renderThink(trimmed, state) + trailingNL;
185
+ return result;
186
+ }
187
+
188
+ // 标题
189
+ if (line.startsWith('### ')) {
190
+ return `${STYLES.h3.prefix}${renderInline(line.substring(4))}${STYLES.h3.suffix}`;
191
+ }
192
+ if (line.startsWith('## ')) {
193
+ return `${STYLES.h2.prefix}${renderInline(line.substring(3))}${STYLES.h2.suffix}`;
194
+ }
195
+ if (line.startsWith('# ')) {
196
+ return `${STYLES.h1.prefix}${renderInline(line.substring(2))}${STYLES.h1.suffix}`;
197
+ }
198
+
199
+ // 无序列表
200
+ if (line.match(/^[-*+] /)) {
201
+ return line.replace(/^([-*+] )(.*)/, (match, bullet, content) => {
202
+ return `${STYLES.list.prefix}${bullet}${CLEAR}${renderInline(content)}`;
203
+ });
204
+ }
205
+
206
+ // 有序列表
207
+ if (line.match(/^\d+\. /)) {
208
+ return line.replace(/^(\d+\. )(.*)/, (match, bullet, content) => {
209
+ return `${STYLES.list.prefix}${bullet}${CLEAR}${renderInline(content)}`;
210
+ });
211
+ }
212
+
213
+ // 引用
214
+ if (line.startsWith('> ')) {
215
+ return line.replace(/^> (.*)/, (match, content) => {
216
+ return `${STYLES.blockquote.prefix}| ${renderInline(content)}${STYLES.blockquote.suffix}`;
217
+ });
218
+ }
219
+
220
+ return renderInline(line);
221
+ }
222
+
223
+ /**
224
+ * 渲染完整的 markdown 文本
225
+ */
226
+ function render(text) {
227
+ if (!text) return '';
228
+
229
+ const lines = text.split('\n');
230
+ const rendered = [];
231
+
232
+ let inCodeBlock = false;
233
+
234
+ for (const line of lines) {
235
+ // 代码块
236
+ if (line.startsWith('```')) {
237
+ if (inCodeBlock) {
238
+ rendered.push(`\`\`\`${STYLES.codeBlock.suffix}`);
239
+ inCodeBlock = false;
240
+ } else {
241
+ const lang = line.substring(3).trim() || '';
242
+ rendered.push(`${STYLES.codeBlock.prefix}\`\`\`${lang ? ' ' + lang : ''}`);
243
+ inCodeBlock = true;
244
+ }
245
+ continue;
246
+ }
247
+
248
+ if (inCodeBlock) {
249
+ rendered.push(`${STYLES.codeBlock.prefix}${line}${STYLES.codeBlock.suffix}`);
250
+ } else {
251
+ rendered.push(renderLine(line));
252
+ }
253
+ }
254
+
255
+ return rendered.join('\n');
256
+ }
257
+
258
+ /**
259
+ * 流式渲染 - 每次返回一个完整的行
260
+ * 返回 { done: boolean, line: string }
261
+ */
262
+ function* streamRender(text) {
263
+ if (!text) return;
264
+
265
+ const lines = text.split('\n');
266
+ let inCodeBlock = false;
267
+
268
+ for (const line of lines) {
269
+ // 代码块
270
+ if (line.startsWith('```')) {
271
+ if (inCodeBlock) {
272
+ yield { done: false, line: `${STYLES.codeBlock.suffix}` };
273
+ inCodeBlock = false;
274
+ } else {
275
+ const lang = line.substring(3).trim() || '';
276
+ yield { done: false, line: `${STYLES.codeBlock.prefix}\`\`\`${lang ? ' ' + lang : ''}` };
277
+ inCodeBlock = true;
278
+ }
279
+ continue;
280
+ }
281
+
282
+ if (inCodeBlock) {
283
+ yield { done: false, line: `${STYLES.codeBlock.prefix}${line}${STYLES.codeBlock.suffix}` };
284
+ } else {
285
+ yield { done: false, line: renderLine(line) };
286
+ }
287
+ }
288
+
289
+ yield { done: true };
290
+ }
291
+
292
+ module.exports = { render, renderInline, renderLine, streamRender, STYLES };