@emqo/claudebridge 0.3.0 → 0.3.1

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.
@@ -8,5 +8,5 @@ export interface Adapter {
8
8
  start(): Promise<void>;
9
9
  stop(): void;
10
10
  }
11
- /** Split long text into chunks respecting newlines */
11
+ /** Split long text into chunks respecting newlines, with code-block-aware balancing */
12
12
  export declare function chunkText(text: string, maxLen: number): string[];
@@ -1,7 +1,8 @@
1
- /** Split long text into chunks respecting newlines */
1
+ /** Split long text into chunks respecting newlines, with code-block-aware balancing */
2
2
  export function chunkText(text, maxLen) {
3
3
  if (text.length <= maxLen)
4
4
  return [text];
5
+ // Phase 1: split by newlines (existing logic)
5
6
  const chunks = [];
6
7
  let remaining = text;
7
8
  while (remaining.length > 0) {
@@ -15,5 +16,35 @@ export function chunkText(text, maxLen) {
15
16
  chunks.push(remaining.slice(0, cut));
16
17
  remaining = remaining.slice(cut).replace(/^\n/, "");
17
18
  }
19
+ // Phase 2: balance code fences across chunks
20
+ // Ensures each chunk is self-contained valid MarkdownV2
21
+ let inCode = false;
22
+ let lang = "";
23
+ for (let i = 0; i < chunks.length; i++) {
24
+ const chunk = chunks[i];
25
+ const startsInCode = inCode;
26
+ // Scan fences in original chunk to determine end state and track language
27
+ const fenceRegex = /```(\w*)/g;
28
+ let toggleState = startsInCode;
29
+ let m;
30
+ while ((m = fenceRegex.exec(chunk)) !== null) {
31
+ // Opening fence (not in code) → track language
32
+ if (!toggleState && m[1]) {
33
+ lang = m[1];
34
+ }
35
+ toggleState = !toggleState;
36
+ }
37
+ inCode = toggleState;
38
+ // Apply fixes: reopen/close code blocks as needed
39
+ let fixed = chunk;
40
+ if (startsInCode) {
41
+ fixed = "```" + lang + "\n" + fixed;
42
+ }
43
+ if (inCode) {
44
+ fixed = fixed + "\n```";
45
+ // inCode stays true — next chunk's content is still logically inside code
46
+ }
47
+ chunks[i] = fixed;
48
+ }
18
49
  return chunks;
19
50
  }
@@ -2,6 +2,7 @@
2
2
  export declare function escapeMarkdownV2(text: string): string;
3
3
  /**
4
4
  * Convert standard markdown to Telegram MarkdownV2.
5
- * Handles code blocks (preserve as-is), inline code, bold, italic, links.
5
+ * Uses iterative scanning for code blocks instead of regex split,
6
+ * properly handling unclosed blocks and multiple consecutive blocks.
6
7
  */
7
8
  export declare function toTelegramMarkdown(text: string): string;
@@ -3,49 +3,84 @@ export function escapeMarkdownV2(text) {
3
3
  // Characters that must be escaped outside code blocks
4
4
  return text.replace(/([_*\[\]()~`>#+\-=|{}.!\\])/g, "\\$1");
5
5
  }
6
+ /**
7
+ * Process inline markdown: inline code, links, bold, italic, strikethrough.
8
+ * Order: extract protected elements → escape → restore formatted elements → restore placeholders.
9
+ */
10
+ function processInline(text) {
11
+ let s = text;
12
+ // 1. Extract inline code → placeholders (before escaping)
13
+ const inlineCodes = [];
14
+ s = s.replace(/`([^`]+)`/g, (_, code) => {
15
+ const escaped = code.replace(/([\\`])/g, "\\$1");
16
+ inlineCodes.push(`\`${escaped}\``);
17
+ return `\x00IC${inlineCodes.length - 1}\x00`;
18
+ });
19
+ // 2. Extract markdown links [text](url) → placeholders (before escaping)
20
+ // Handles one level of nested parens in URLs, e.g. wiki/Rust_(programming_language)
21
+ const links = [];
22
+ s = s.replace(/\[([^\]]+)\]\(([^()]*(?:\([^()]*\))*[^()]*)\)/g, (_, linkText, url) => {
23
+ const escapedText = escapeMarkdownV2(linkText);
24
+ // Inside MarkdownV2 link URL, only \ and ) need escaping
25
+ const escapedUrl = url.replace(/\\/g, "\\\\").replace(/\)/g, "\\)");
26
+ links.push(`[${escapedText}](${escapedUrl})`);
27
+ return `\x00LK${links.length - 1}\x00`;
28
+ });
29
+ // 3. Escape all remaining special chars
30
+ s = escapeMarkdownV2(s);
31
+ // 4. Restore bold **text** → *text* (MarkdownV2 single asterisk)
32
+ s = s.replace(/\\\*\\\*([\s\S]+?)\\\*\\\*/g, "*$1*");
33
+ // 5. Restore italic _text_ → _text_
34
+ s = s.replace(/\\_(.+?)\\_/g, "_$1_");
35
+ // 6. Restore strikethrough ~~text~~ → ~text~
36
+ s = s.replace(/\\~\\~(.+?)\\~\\~/g, "~$1~");
37
+ // 7. Restore link placeholders
38
+ s = s.replace(/\x00LK(\d+)\x00/g, (_, i) => links[Number(i)]);
39
+ // 8. Restore inline code placeholders
40
+ s = s.replace(/\x00IC(\d+)\x00/g, (_, i) => inlineCodes[Number(i)]);
41
+ return s;
42
+ }
6
43
  /**
7
44
  * Convert standard markdown to Telegram MarkdownV2.
8
- * Handles code blocks (preserve as-is), inline code, bold, italic, links.
45
+ * Uses iterative scanning for code blocks instead of regex split,
46
+ * properly handling unclosed blocks and multiple consecutive blocks.
9
47
  */
10
48
  export function toTelegramMarkdown(text) {
11
- const parts = [];
12
- // Split by code blocks first (``` ... ```)
13
- const segments = text.split(/(```[\s\S]*?```)/g);
14
- for (const seg of segments) {
15
- if (seg.startsWith("```")) {
16
- // Code block: extract lang and content, only escape backslash and backtick inside
17
- const match = seg.match(/^```(\w*)\n?([\s\S]*?)```$/);
18
- if (match) {
19
- const lang = match[1];
20
- const code = match[2].replace(/([\\`])/g, "\\$1");
21
- parts.push(`\`\`\`${lang}\n${code}\`\`\``);
22
- }
23
- else {
24
- parts.push(seg);
25
- }
49
+ const result = [];
50
+ let pos = 0;
51
+ while (pos < text.length) {
52
+ // Find next code fence ```
53
+ const fenceStart = text.indexOf("```", pos);
54
+ if (fenceStart === -1) {
55
+ // No more code blocks — process rest as inline
56
+ result.push(processInline(text.slice(pos)));
57
+ break;
58
+ }
59
+ // Process text before code block as inline
60
+ if (fenceStart > pos) {
61
+ result.push(processInline(text.slice(pos, fenceStart)));
26
62
  }
27
- else {
28
- // Process inline elements
29
- let s = seg;
30
- // Protect inline code first
31
- const inlineCodes = [];
32
- s = s.replace(/`([^`]+)`/g, (_, code) => {
33
- const escaped = code.replace(/([\\`])/g, "\\$1");
34
- inlineCodes.push(`\`${escaped}\``);
35
- return `\x00IC${inlineCodes.length - 1}\x00`;
36
- });
37
- // Escape special chars in normal text
38
- s = escapeMarkdownV2(s);
39
- // Restore bold **text** → *text*
40
- s = s.replace(/\\\*\\\*(.+?)\\\*\\\*/g, "*$1*");
41
- // Restore italic _text_ (single underscore)
42
- s = s.replace(/\\_(.+?)\\_/g, "_$1_");
43
- // Restore links [text](url)
44
- s = s.replace(/\\\[(.+?)\\\]\\\((.+?)\\\)/g, "[$1]($2)");
45
- // Restore inline codes
46
- s = s.replace(/\x00IC(\d+)\x00/g, (_, i) => inlineCodes[Number(i)]);
47
- parts.push(s);
63
+ // Find closing ```
64
+ const afterOpen = fenceStart + 3;
65
+ const fenceEnd = text.indexOf("```", afterOpen);
66
+ if (fenceEnd === -1) {
67
+ // Unclosed code block — auto-close it
68
+ const raw = text.slice(afterOpen);
69
+ const langMatch = raw.match(/^(\w*)\n?/);
70
+ const lang = langMatch ? langMatch[1] : "";
71
+ const contentStart = langMatch ? langMatch[0].length : 0;
72
+ const code = raw.slice(contentStart).replace(/([\\`])/g, "\\$1");
73
+ result.push(`\`\`\`${lang}\n${code}\n\`\`\``);
74
+ break;
48
75
  }
76
+ // Complete code block — extract lang and content, escape only \ and `
77
+ const raw = text.slice(afterOpen, fenceEnd);
78
+ const langMatch = raw.match(/^(\w*)\n?/);
79
+ const lang = langMatch ? langMatch[1] : "";
80
+ const contentStart = langMatch ? langMatch[0].length : 0;
81
+ const code = raw.slice(contentStart).replace(/([\\`])/g, "\\$1");
82
+ result.push(`\`\`\`${lang}\n${code}\`\`\``);
83
+ pos = fenceEnd + 3;
49
84
  }
50
- return parts.join("");
85
+ return result.join("");
51
86
  }
@@ -40,6 +40,18 @@ export function generateSkillDoc(ctx) {
40
40
  `- 用户要添加任务/待办 → 使用 task add`,
41
41
  `- 命令输出 JSON,请用自然语言向用户回复结果,不要直接展示 JSON`,
42
42
  `- 提醒会由 Bridge 定时器自动推送,你只需创建即可`,
43
+ ``,
44
+ `### 高级用法:任务自动分解`,
45
+ `- 遇到复杂/大型任务时,必须拆分为多个 auto-task 分步执行`,
46
+ `- 每个 auto-task 应该是独立可完成的小任务(预算内可完成)`,
47
+ `- auto-task 按创建顺序排队,每60秒执行一个`,
48
+ `- 在 auto-task 执行中可以创建新的 auto-task(链式执行)`,
49
+ `- 每个 auto-task 的描述要足够详细,因为它会在全新会话中执行`,
50
+ `- 示例:用户说"优化整个项目" → 创建多个 auto-task:`,
51
+ ` 1. "分析项目结构,列出所有需要优化的模块和具体改进点"`,
52
+ ` 2. "优化模块A:[具体描述,包含文件路径和修改内容]"`,
53
+ ` 3. "优化模块B:[具体描述]"`,
54
+ ` 4. "运行测试验证所有修改,提交代码,生成优化报告"`,
43
55
  ].join("\n");
44
56
  }
45
57
  return [
@@ -76,5 +88,17 @@ export function generateSkillDoc(ctx) {
76
88
  `- User wants to add a task/todo → use task add`,
77
89
  `- Commands output JSON. Respond to the user in natural language, do not dump raw JSON.`,
78
90
  `- Reminders are automatically pushed by Bridge timers — you only need to create them.`,
91
+ ``,
92
+ `### Advanced: Auto-Task Decomposition`,
93
+ `- For complex/large tasks, decompose into multiple auto-tasks`,
94
+ `- Each auto-task should be independently completable within budget`,
95
+ `- Auto-tasks execute in FIFO order, one every 60 seconds`,
96
+ `- An auto-task can create new auto-tasks (chaining)`,
97
+ `- Each description must be detailed enough for a fresh session`,
98
+ `- Example: user says "optimize the project" → create:`,
99
+ ` 1. "Analyze project structure, list modules needing optimization"`,
100
+ ` 2. "Optimize module A: [specific file paths and changes]"`,
101
+ ` 3. "Optimize module B: [specific description]"`,
102
+ ` 4. "Run tests, commit changes, generate optimization report"`,
79
103
  ].join("\n");
80
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emqo/claudebridge",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Bridge Claude Code Agent SDK to chat platforms (Telegram, Discord, etc.)",
5
5
  "main": "dist/index.js",
6
6
  "bin": {