foliko 1.0.75 → 1.0.77

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 (89) hide show
  1. package/.claude/settings.local.json +159 -157
  2. package/.env.example +3 -1
  3. package/cli/bin/foliko.js +12 -12
  4. package/cli/src/commands/chat.js +143 -143
  5. package/cli/src/commands/list.js +93 -93
  6. package/cli/src/index.js +75 -75
  7. package/cli/src/ui/chat-ui.js +201 -201
  8. package/cli/src/utils/ansi.js +40 -40
  9. package/cli/src/utils/markdown.js +292 -292
  10. package/examples/ambient-example.js +194 -194
  11. package/examples/basic.js +115 -115
  12. package/examples/bootstrap.js +121 -121
  13. package/examples/mcp-example.js +56 -56
  14. package/examples/skill-example.js +49 -49
  15. package/examples/test-chat.js +137 -137
  16. package/examples/test-mcp.js +85 -85
  17. package/examples/test-reload.js +59 -59
  18. package/examples/test-telegram.js +50 -50
  19. package/examples/test-tg-bot.js +45 -45
  20. package/examples/test-tg-simple.js +47 -47
  21. package/examples/test-tg.js +62 -62
  22. package/examples/test-think.js +43 -43
  23. package/examples/test-web-plugin.js +103 -103
  24. package/examples/test-weixin-feishu.js +103 -103
  25. package/examples/workflow.js +158 -158
  26. package/package.json +81 -81
  27. package/plugins/ai-plugin.js +102 -102
  28. package/plugins/ambient-agent/EventWatcher.js +113 -113
  29. package/plugins/ambient-agent/ExplorerLoop.js +640 -640
  30. package/plugins/ambient-agent/GoalManager.js +197 -197
  31. package/plugins/ambient-agent/Reflector.js +95 -95
  32. package/plugins/ambient-agent/StateStore.js +90 -90
  33. package/plugins/ambient-agent/constants.js +101 -101
  34. package/plugins/ambient-agent/index.js +579 -579
  35. package/plugins/audit-plugin.js +187 -187
  36. package/plugins/default-plugins.js +662 -662
  37. package/plugins/email/constants.js +64 -64
  38. package/plugins/email/handlers.js +461 -461
  39. package/plugins/email/index.js +278 -278
  40. package/plugins/email/monitor.js +269 -269
  41. package/plugins/email/parser.js +138 -138
  42. package/plugins/email/reply.js +151 -151
  43. package/plugins/email/utils.js +124 -124
  44. package/plugins/feishu-plugin.js +481 -481
  45. package/plugins/file-system-plugin.js +826 -826
  46. package/plugins/install-plugin.js +199 -199
  47. package/plugins/python-executor-plugin.js +367 -367
  48. package/plugins/python-plugin-loader.js +481 -481
  49. package/plugins/rules-plugin.js +294 -294
  50. package/plugins/scheduler-plugin.js +691 -691
  51. package/plugins/session-plugin.js +369 -369
  52. package/plugins/shell-executor-plugin.js +197 -197
  53. package/plugins/storage-plugin.js +240 -240
  54. package/plugins/subagent-plugin.js +845 -845
  55. package/plugins/telegram-plugin.js +482 -482
  56. package/plugins/think-plugin.js +345 -345
  57. package/plugins/tools-plugin.js +196 -196
  58. package/plugins/web-plugin.js +606 -606
  59. package/plugins/weixin-plugin.js +545 -545
  60. package/src/capabilities/index.js +11 -11
  61. package/src/capabilities/skill-manager.js +609 -609
  62. package/src/capabilities/workflow-engine.js +1109 -1109
  63. package/src/core/agent-chat.js +882 -882
  64. package/src/core/agent.js +892 -892
  65. package/src/core/framework.js +465 -465
  66. package/src/core/index.js +19 -19
  67. package/src/core/plugin-base.js +219 -219
  68. package/src/core/plugin-manager.js +863 -863
  69. package/src/core/provider.js +114 -114
  70. package/src/core/sub-agent-config.js +264 -264
  71. package/src/core/system-prompt-builder.js +120 -120
  72. package/src/core/tool-registry.js +517 -517
  73. package/src/core/tool-router.js +297 -297
  74. package/src/executors/executor-base.js +58 -58
  75. package/src/executors/mcp-executor.js +741 -741
  76. package/src/index.js +25 -25
  77. package/src/utils/circuit-breaker.js +301 -301
  78. package/src/utils/error-boundary.js +363 -363
  79. package/src/utils/error.js +374 -374
  80. package/src/utils/event-emitter.js +97 -97
  81. package/src/utils/id.js +133 -133
  82. package/src/utils/index.js +217 -217
  83. package/src/utils/logger.js +181 -181
  84. package/src/utils/plugin-helpers.js +90 -90
  85. package/src/utils/retry.js +122 -122
  86. package/src/utils/sandbox.js +292 -292
  87. package/test/tool-registry-validation.test.js +218 -218
  88. package/website/script.js +136 -136
  89. 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 };