botmux 2.2.6 → 2.3.0

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 (126) hide show
  1. package/README.en.md +63 -13
  2. package/README.md +52 -14
  3. package/dist/adapters/cli/aiden.d.ts.map +1 -1
  4. package/dist/adapters/cli/aiden.js +0 -40
  5. package/dist/adapters/cli/aiden.js.map +1 -1
  6. package/dist/adapters/cli/claude-code.d.ts.map +1 -1
  7. package/dist/adapters/cli/claude-code.js +27 -63
  8. package/dist/adapters/cli/claude-code.js.map +1 -1
  9. package/dist/adapters/cli/coco.d.ts.map +1 -1
  10. package/dist/adapters/cli/coco.js +0 -33
  11. package/dist/adapters/cli/coco.js.map +1 -1
  12. package/dist/adapters/cli/codex.d.ts.map +1 -1
  13. package/dist/adapters/cli/codex.js +0 -27
  14. package/dist/adapters/cli/codex.js.map +1 -1
  15. package/dist/adapters/cli/gemini.d.ts.map +1 -1
  16. package/dist/adapters/cli/gemini.js +1 -29
  17. package/dist/adapters/cli/gemini.js.map +1 -1
  18. package/dist/adapters/cli/opencode.d.ts.map +1 -1
  19. package/dist/adapters/cli/opencode.js +1 -44
  20. package/dist/adapters/cli/opencode.js.map +1 -1
  21. package/dist/adapters/cli/types.d.ts +15 -8
  22. package/dist/adapters/cli/types.d.ts.map +1 -1
  23. package/dist/cli.js +737 -16
  24. package/dist/cli.js.map +1 -1
  25. package/dist/config.d.ts +16 -0
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +30 -0
  28. package/dist/config.js.map +1 -1
  29. package/dist/core/command-handler.d.ts.map +1 -1
  30. package/dist/core/command-handler.js +8 -4
  31. package/dist/core/command-handler.js.map +1 -1
  32. package/dist/core/scheduler.d.ts +38 -16
  33. package/dist/core/scheduler.d.ts.map +1 -1
  34. package/dist/core/scheduler.js +335 -149
  35. package/dist/core/scheduler.js.map +1 -1
  36. package/dist/core/session-manager.d.ts +13 -2
  37. package/dist/core/session-manager.d.ts.map +1 -1
  38. package/dist/core/session-manager.js +128 -25
  39. package/dist/core/session-manager.js.map +1 -1
  40. package/dist/core/types.d.ts +26 -4
  41. package/dist/core/types.d.ts.map +1 -1
  42. package/dist/core/types.js +6 -0
  43. package/dist/core/types.js.map +1 -1
  44. package/dist/core/worker-pool.d.ts +15 -3
  45. package/dist/core/worker-pool.d.ts.map +1 -1
  46. package/dist/core/worker-pool.js +233 -31
  47. package/dist/core/worker-pool.js.map +1 -1
  48. package/dist/daemon.d.ts.map +1 -1
  49. package/dist/daemon.js +71 -18
  50. package/dist/daemon.js.map +1 -1
  51. package/dist/im/lark/card-builder.d.ts +29 -1
  52. package/dist/im/lark/card-builder.d.ts.map +1 -1
  53. package/dist/im/lark/card-builder.js +266 -56
  54. package/dist/im/lark/card-builder.js.map +1 -1
  55. package/dist/im/lark/card-handler.d.ts +1 -0
  56. package/dist/im/lark/card-handler.d.ts.map +1 -1
  57. package/dist/im/lark/card-handler.js +200 -40
  58. package/dist/im/lark/card-handler.js.map +1 -1
  59. package/dist/im/lark/client.d.ts.map +1 -1
  60. package/dist/im/lark/client.js +4 -3
  61. package/dist/im/lark/client.js.map +1 -1
  62. package/dist/im/lark/message-parser.d.ts.map +1 -1
  63. package/dist/im/lark/message-parser.js +44 -6
  64. package/dist/im/lark/message-parser.js.map +1 -1
  65. package/dist/services/schedule-store.d.ts +20 -3
  66. package/dist/services/schedule-store.d.ts.map +1 -1
  67. package/dist/services/schedule-store.js +140 -16
  68. package/dist/services/schedule-store.js.map +1 -1
  69. package/dist/skills/definitions.d.ts +17 -0
  70. package/dist/skills/definitions.d.ts.map +1 -0
  71. package/dist/skills/definitions.js +254 -0
  72. package/dist/skills/definitions.js.map +1 -0
  73. package/dist/skills/installer.d.ts +9 -0
  74. package/dist/skills/installer.d.ts.map +1 -0
  75. package/dist/skills/installer.js +42 -0
  76. package/dist/skills/installer.js.map +1 -0
  77. package/dist/types.d.ts +80 -7
  78. package/dist/types.d.ts.map +1 -1
  79. package/dist/types.js +1 -5
  80. package/dist/types.js.map +1 -1
  81. package/dist/utils/lark-upload.d.ts +2 -0
  82. package/dist/utils/lark-upload.d.ts.map +1 -0
  83. package/dist/utils/lark-upload.js +27 -0
  84. package/dist/utils/lark-upload.js.map +1 -0
  85. package/dist/utils/screen-analyzer.d.ts +67 -0
  86. package/dist/utils/screen-analyzer.d.ts.map +1 -0
  87. package/dist/utils/screen-analyzer.js +256 -0
  88. package/dist/utils/screen-analyzer.js.map +1 -0
  89. package/dist/utils/screenshot-renderer.d.ts +11 -0
  90. package/dist/utils/screenshot-renderer.d.ts.map +1 -0
  91. package/dist/utils/screenshot-renderer.js +225 -0
  92. package/dist/utils/screenshot-renderer.js.map +1 -0
  93. package/dist/utils/terminal-renderer.d.ts +33 -0
  94. package/dist/utils/terminal-renderer.d.ts.map +1 -1
  95. package/dist/utils/terminal-renderer.js +34 -1
  96. package/dist/utils/terminal-renderer.js.map +1 -1
  97. package/dist/utils/user-token.d.ts.map +1 -1
  98. package/dist/utils/user-token.js +7 -11
  99. package/dist/utils/user-token.js.map +1 -1
  100. package/dist/worker.js +286 -15
  101. package/dist/worker.js.map +1 -1
  102. package/package.json +2 -5
  103. package/dist/index.d.ts +0 -3
  104. package/dist/index.d.ts.map +0 -1
  105. package/dist/index.js +0 -16
  106. package/dist/index.js.map +0 -1
  107. package/dist/server.d.ts +0 -3
  108. package/dist/server.d.ts.map +0 -1
  109. package/dist/server.js +0 -115
  110. package/dist/server.js.map +0 -1
  111. package/dist/tools/get-thread-messages.d.ts +0 -26
  112. package/dist/tools/get-thread-messages.d.ts.map +0 -1
  113. package/dist/tools/get-thread-messages.js +0 -35
  114. package/dist/tools/get-thread-messages.js.map +0 -1
  115. package/dist/tools/index.d.ts +0 -9
  116. package/dist/tools/index.d.ts.map +0 -1
  117. package/dist/tools/index.js +0 -10
  118. package/dist/tools/index.js.map +0 -1
  119. package/dist/tools/list-bots.d.ts +0 -40
  120. package/dist/tools/list-bots.d.ts.map +0 -1
  121. package/dist/tools/list-bots.js +0 -74
  122. package/dist/tools/list-bots.js.map +0 -1
  123. package/dist/tools/send-to-thread.d.ts +0 -46
  124. package/dist/tools/send-to-thread.d.ts.map +0 -1
  125. package/dist/tools/send-to-thread.js +0 -242
  126. package/dist/tools/send-to-thread.js.map +0 -1
package/dist/server.js DELETED
@@ -1,115 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
3
- import { execSync } from 'node:child_process';
4
- import { existsSync } from 'node:fs';
5
- import { join } from 'node:path';
6
- import { registerBot, loadBotConfigs } from './bot-registry.js';
7
- import * as sessionStore from './services/session-store.js';
8
- import { tools } from './tools/index.js';
9
- import { logger } from './utils/logger.js';
10
- /**
11
- * Walk up the process tree and check whether any ancestor has a CLI PID
12
- * marker written by the botmux worker. Walking (not just checking ppid)
13
- * handles CLIs that fork internal subprocesses before spawning MCP servers.
14
- *
15
- * Cross-platform: uses `ps -o ppid=` (works on macOS + Linux).
16
- */
17
- function hasAncestorCliMarker() {
18
- const dataDir = process.env.SESSION_DATA_DIR;
19
- if (!dataDir)
20
- return false;
21
- const markersDir = join(dataDir, '.botmux-cli-pids');
22
- let pid = process.ppid;
23
- for (let depth = 0; depth < 8 && pid > 1; depth++) {
24
- if (existsSync(join(markersDir, String(pid))))
25
- return true;
26
- try {
27
- const output = execSync(`ps -o ppid= -p ${pid}`, {
28
- encoding: 'utf-8',
29
- timeout: 2000,
30
- stdio: ['ignore', 'pipe', 'ignore'],
31
- }).trim();
32
- pid = parseInt(output, 10);
33
- if (isNaN(pid))
34
- break;
35
- }
36
- catch {
37
- break;
38
- }
39
- }
40
- return false;
41
- }
42
- export function createServer() {
43
- // Register all bots so MCP tools can send messages as any bot.
44
- // loadBotConfigs() reads from bots.json / env vars — works regardless
45
- // of whether the CLI passes LARK_APP_ID through to the MCP subprocess.
46
- try {
47
- const configs = loadBotConfigs();
48
- for (const cfg of configs) {
49
- registerBot(cfg);
50
- }
51
- logger.info(`MCP server registered ${configs.length} bot(s)`);
52
- }
53
- catch (err) {
54
- logger.warn(`MCP server: no bot configs found (${err.message}). Tools will fail at runtime.`);
55
- }
56
- // Scope session store to the owning bot's per-bot file (sessions-{appId}.json).
57
- // LARK_APP_ID is inherited from the worker process env.
58
- const appId = process.env.LARK_APP_ID;
59
- if (appId) {
60
- sessionStore.init(appId);
61
- }
62
- // Two-gate session detection:
63
- //
64
- // 1. BOTMUX=1 in env — set in the static MCP config so it reaches all
65
- // CLI MCP servers (the MCP SDK only passes config env + a 6-var
66
- // whitelist to the server subprocess, NOT the full parent env).
67
- //
68
- // 2. hasAncestorCliMarker() — walks the process tree (via `ps -o ppid=`)
69
- // and checks if any ancestor PID has a marker file written by the
70
- // botmux worker. Handles CLIs that fork internal subprocesses.
71
- // Cross-platform: `ps -o ppid=` works on both macOS and Linux.
72
- const isBotmuxSession = process.env.BOTMUX === '1' && hasAncestorCliMarker();
73
- const instructions = isBotmuxSession
74
- ? [
75
- 'You are connected to a Lark (Feishu) topic group. The user reads Lark, not your terminal.',
76
- 'Anything you want the user to see MUST go through the send_to_thread tool — your terminal output never reaches the chat.',
77
- '',
78
- 'Guidelines:',
79
- '- Use send_to_thread for: key conclusions, proposed plans (wait for confirmation before executing), final results, and progress updates.',
80
- '- The message includes a session_id — pass it back when calling send_to_thread.',
81
- '- Send plain text — formatting is handled automatically. You can also attach images and files.',
82
- '- To send images: pass local file paths in the `images` array (e.g. screenshots, charts, diagrams). Images are embedded inline in the message.',
83
- '- To send files: pass local file paths in the `files` array (e.g. PDFs, documents). Each file is sent as a separate message.',
84
- '- Use get_thread_messages to read earlier conversation context if needed.',
85
- ].join('\n')
86
- : undefined;
87
- const server = new McpServer({
88
- name: 'botmux',
89
- version: '1.0.0',
90
- }, {
91
- ...(instructions && { instructions }),
92
- });
93
- // Only register tools inside botmux sessions. Outside botmux, tools would
94
- // fail anyway and just waste tool-description context tokens.
95
- if (isBotmuxSession) {
96
- for (const [name, tool] of Object.entries(tools)) {
97
- server.tool(name, tool.description, tool.schema.shape, async (args) => {
98
- logger.info(`Tool called: ${name}`, args);
99
- const result = await tool.execute(args);
100
- return {
101
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
102
- };
103
- });
104
- }
105
- }
106
- else {
107
- // Declare empty tools capability so CLI clients (e.g. Codex) that call
108
- // tools/list during startup don't fail with "Method not found" (-32601).
109
- server.server.registerCapabilities({ tools: {} });
110
- server.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [] }));
111
- logger.info('MCP server: not a botmux session — running as empty shell (no tools, no instructions)');
112
- }
113
- return server;
114
- }
115
- //# sourceMappingURL=server.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -1,26 +0,0 @@
1
- import { z } from 'zod';
2
- export declare const schema: z.ZodObject<{
3
- session_id: z.ZodString;
4
- limit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
5
- }, "strip", z.ZodTypeAny, {
6
- session_id: string;
7
- limit: number;
8
- }, {
9
- session_id: string;
10
- limit?: number | undefined;
11
- }>;
12
- export declare const description = "Get message history from the Lark thread associated with a session.";
13
- export declare function execute(args: z.infer<typeof schema>): Promise<{
14
- error: string;
15
- sessionId?: undefined;
16
- threadId?: undefined;
17
- messages?: undefined;
18
- total?: undefined;
19
- } | {
20
- sessionId: string;
21
- threadId: string;
22
- messages: import("../types.js").LarkMessage[];
23
- total: number;
24
- error?: undefined;
25
- }>;
26
- //# sourceMappingURL=get-thread-messages.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"get-thread-messages.d.ts","sourceRoot":"","sources":["../../src/tools/get-thread-messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,eAAO,MAAM,MAAM;;;;;;;;;EAGjB,CAAC;AAEH,eAAO,MAAM,WAAW,wEAAwE,CAAC;AAEjG,wBAAsB,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC;;;;;;;;;;;;GAuBzD"}
@@ -1,35 +0,0 @@
1
- import { z } from 'zod';
2
- import * as sessionStore from '../services/session-store.js';
3
- import { listThreadMessages } from '../im/lark/client.js';
4
- import { parseApiMessage } from '../im/lark/message-parser.js';
5
- import { config } from '../config.js';
6
- import { logger } from '../utils/logger.js';
7
- export const schema = z.object({
8
- session_id: z.string().describe('Session ID for the active session'),
9
- limit: z.number().optional().default(50).describe('Max number of messages to return (default 50)'),
10
- });
11
- export const description = 'Get message history from the Lark thread associated with a session.';
12
- export async function execute(args) {
13
- const session = sessionStore.getSession(args.session_id);
14
- if (!session) {
15
- return { error: `Session ${args.session_id} not found` };
16
- }
17
- try {
18
- // List chat messages and filter by root_id to get thread messages
19
- const appId = session.larkAppId || config.lark.appId;
20
- const rawMessages = await listThreadMessages(appId, session.chatId, session.rootMessageId, args.limit);
21
- const messages = rawMessages.map(parseApiMessage);
22
- logger.info(`Retrieved ${messages.length} messages for session ${args.session_id}`);
23
- return {
24
- sessionId: args.session_id,
25
- threadId: session.rootMessageId,
26
- messages,
27
- total: messages.length,
28
- };
29
- }
30
- catch (err) {
31
- logger.error(`Failed to get thread messages: ${err.message}`);
32
- return { error: `Failed to get messages: ${err.message}` };
33
- }
34
- }
35
- //# sourceMappingURL=get-thread-messages.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"get-thread-messages.js","sourceRoot":"","sources":["../../src/tools/get-thread-messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,YAAY,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;IACpE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,+CAA+C,CAAC;CACnG,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,qEAAqE,CAAC;AAEjG,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAA4B;IACxD,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACzD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,KAAK,EAAE,WAAW,IAAI,CAAC,UAAU,YAAY,EAAE,CAAC;IAC3D,CAAC;IAED,IAAI,CAAC;QACH,kEAAkE;QAClE,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;QACrD,MAAM,WAAW,GAAG,MAAM,kBAAkB,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACvG,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QAElD,MAAM,CAAC,IAAI,CAAC,aAAa,QAAQ,CAAC,MAAM,yBAAyB,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QACpF,OAAO;YACL,SAAS,EAAE,IAAI,CAAC,UAAU;YAC1B,QAAQ,EAAE,OAAO,CAAC,aAAa;YAC/B,QAAQ;YACR,KAAK,EAAE,QAAQ,CAAC,MAAM;SACvB,CAAC;IACJ,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,KAAK,CAAC,kCAAkC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9D,OAAO,EAAE,KAAK,EAAE,2BAA2B,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;IAC7D,CAAC;AACH,CAAC"}
@@ -1,9 +0,0 @@
1
- import * as sendToThread from './send-to-thread.js';
2
- import * as getThreadMessages from './get-thread-messages.js';
3
- import * as listBots from './list-bots.js';
4
- export declare const tools: {
5
- readonly send_to_thread: typeof sendToThread;
6
- readonly get_thread_messages: typeof getThreadMessages;
7
- readonly list_bots: typeof listBots;
8
- };
9
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,YAAY,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,iBAAiB,MAAM,0BAA0B,CAAC;AAE9D,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,eAAO,MAAM,KAAK;;;;CAKR,CAAC"}
@@ -1,10 +0,0 @@
1
- import { TOOL_NAMES } from '../types.js';
2
- import * as sendToThread from './send-to-thread.js';
3
- import * as getThreadMessages from './get-thread-messages.js';
4
- import * as listBots from './list-bots.js';
5
- export const tools = {
6
- [TOOL_NAMES.SEND_TO_THREAD]: sendToThread,
7
- [TOOL_NAMES.GET_THREAD_MESSAGES]: getThreadMessages,
8
- [TOOL_NAMES.LIST_BOTS]: listBots,
9
- };
10
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,YAAY,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,iBAAiB,MAAM,0BAA0B,CAAC;AAE9D,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,YAAY;IACzC,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,iBAAiB;IAEnD,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,QAAQ;CACxB,CAAC"}
@@ -1,40 +0,0 @@
1
- import { z } from 'zod';
2
- export declare const schema: z.ZodObject<{
3
- session_id: z.ZodString;
4
- }, "strip", z.ZodTypeAny, {
5
- session_id: string;
6
- }, {
7
- session_id: string;
8
- }>;
9
- export declare const description = "List bots available in the current group chat. Returns bot names, open_ids, and CLI types for use with send_to_thread mentions.";
10
- export declare function execute(args: z.infer<typeof schema>): Promise<{
11
- error: string;
12
- sessionId?: undefined;
13
- chatId?: undefined;
14
- bots?: undefined;
15
- total?: undefined;
16
- hint?: undefined;
17
- } | {
18
- sessionId: string;
19
- chatId: string;
20
- bots: {
21
- name: string;
22
- openId: string;
23
- isSelf: boolean;
24
- }[];
25
- total: number;
26
- hint: string;
27
- error?: undefined;
28
- } | {
29
- sessionId: string;
30
- bots: {
31
- name: string;
32
- openId: string;
33
- isSelf: boolean;
34
- }[];
35
- total: number;
36
- hint: string;
37
- error?: undefined;
38
- chatId?: undefined;
39
- }>;
40
- //# sourceMappingURL=list-bots.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"list-bots.d.ts","sourceRoot":"","sources":["../../src/tools/list-bots.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,eAAO,MAAM,MAAM;;;;;;EAEjB,CAAC;AAEH,eAAO,MAAM,WAAW,oIAAoI,CAAC;AAoB7J,wBAAsB,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDzD"}
@@ -1,74 +0,0 @@
1
- import { z } from 'zod';
2
- import { readFileSync, existsSync } from 'node:fs';
3
- import { join } from 'node:path';
4
- import * as sessionStore from '../services/session-store.js';
5
- import { listChatBotMembers } from '../im/lark/client.js';
6
- import { config } from '../config.js';
7
- import { logger } from '../utils/logger.js';
8
- export const schema = z.object({
9
- session_id: z.string().describe('Session ID — used to determine which group chat to query for bot members'),
10
- });
11
- export const description = 'List bots available in the current group chat. Returns bot names, open_ids, and CLI types for use with send_to_thread mentions.';
12
- /** Read bots-info.json written by the daemon. */
13
- function readBotInfo() {
14
- const filePath = join(config.session.dataDir, 'bots-info.json');
15
- if (!existsSync(filePath))
16
- return [];
17
- try {
18
- return JSON.parse(readFileSync(filePath, 'utf-8'));
19
- }
20
- catch {
21
- return [];
22
- }
23
- }
24
- export async function execute(args) {
25
- const session = sessionStore.getSession(args.session_id);
26
- if (!session) {
27
- return { error: `Session ${args.session_id} not found` };
28
- }
29
- const appId = session.larkAppId || config.lark.appId;
30
- const botInfo = readBotInfo();
31
- // Build a map of cliId → bot info for lookup (open_id matching is unreliable
32
- // because Lark open_ids are per-app scoped)
33
- const botByCli = new Map();
34
- for (const b of botInfo) {
35
- botByCli.set(b.cliId, b);
36
- }
37
- try {
38
- // Query group chat members to find bots in this chat
39
- const chatBots = await listChatBotMembers(appId, session.chatId);
40
- const result = chatBots.map(cb => {
41
- const info = botByCli.get(cb.name); // cb.name is cliId
42
- return {
43
- name: cb.displayName,
44
- openId: cb.openId,
45
- isSelf: info?.larkAppId === appId,
46
- };
47
- });
48
- return {
49
- sessionId: args.session_id,
50
- chatId: session.chatId,
51
- bots: result,
52
- total: result.length,
53
- hint: 'Use send_to_thread with mentions parameter to @mention a bot. Pass open_id and name from this list.',
54
- };
55
- }
56
- catch (err) {
57
- logger.warn(`listChatBotMembers failed, falling back to bots-info.json: ${err.message}`);
58
- // Fallback: return all known bots from the registry file
59
- const result = botInfo
60
- .filter(b => b.botOpenId)
61
- .map(b => ({
62
- name: b.botName ?? b.cliId,
63
- openId: b.botOpenId,
64
- isSelf: b.larkAppId === appId,
65
- }));
66
- return {
67
- sessionId: args.session_id,
68
- bots: result,
69
- total: result.length,
70
- hint: 'Use send_to_thread with mentions parameter to @mention a bot. Note: chat member query failed, showing all registered bots.',
71
- };
72
- }
73
- }
74
- //# sourceMappingURL=list-bots.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"list-bots.js","sourceRoot":"","sources":["../../src/tools/list-bots.ts"],"names":[],"mappings":"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"}
@@ -1,46 +0,0 @@
1
- import { z } from 'zod';
2
- export declare const schema: z.ZodObject<{
3
- session_id: z.ZodString;
4
- content: z.ZodString;
5
- images: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
6
- files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
7
- mentions: z.ZodOptional<z.ZodArray<z.ZodObject<{
8
- open_id: z.ZodString;
9
- name: z.ZodString;
10
- }, "strip", z.ZodTypeAny, {
11
- open_id: string;
12
- name: string;
13
- }, {
14
- open_id: string;
15
- name: string;
16
- }>, "many">>;
17
- }, "strip", z.ZodTypeAny, {
18
- content: string;
19
- session_id: string;
20
- mentions?: {
21
- open_id: string;
22
- name: string;
23
- }[] | undefined;
24
- images?: string[] | undefined;
25
- files?: string[] | undefined;
26
- }, {
27
- content: string;
28
- session_id: string;
29
- mentions?: {
30
- open_id: string;
31
- name: string;
32
- }[] | undefined;
33
- images?: string[] | undefined;
34
- files?: string[] | undefined;
35
- }>;
36
- export declare const description = "Send a message to the Lark thread associated with a session. Supports plain text, images (embedded inline), and file attachments. Just send plain text \u2014 formatting is handled automatically. Use `images` to attach local image files (png/jpg/gif etc.) and `files` to attach documents.";
37
- export declare function execute(args: z.infer<typeof schema>): Promise<{
38
- error: string;
39
- } | {
40
- sessionId: string;
41
- fileMessageIds?: string[] | undefined;
42
- success: boolean;
43
- messageId: string;
44
- error?: undefined;
45
- }>;
46
- //# sourceMappingURL=send-to-thread.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"send-to-thread.d.ts","sourceRoot":"","sources":["../../src/tools/send-to-thread.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASjB,CAAC;AAEH,eAAO,MAAM,WAAW,oSAA+R,CAAC;AAuExT,wBAAsB,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC;;;;;;;;GAuKzD"}
@@ -1,242 +0,0 @@
1
- import { z } from 'zod';
2
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
3
- import { join } from 'node:path';
4
- import { replyMessage, uploadImage, uploadFile } from '../im/lark/client.js';
5
- import { config } from '../config.js';
6
- import * as sessionStore from '../services/session-store.js';
7
- import { logger } from '../utils/logger.js';
8
- export const schema = z.object({
9
- session_id: z.string().describe('Session ID for the active session'),
10
- content: z.string().describe('Message text to send (plain text). Can be empty string when sending only images/files.'),
11
- images: z.array(z.string()).optional().describe('Optional local file paths of images to attach (e.g. ["/tmp/chart.png"]). Images are embedded inline in the message.'),
12
- files: z.array(z.string()).optional().describe('Optional local file paths of files to attach (e.g. ["/tmp/report.pdf"]). Each file is sent as a separate message.'),
13
- mentions: z.array(z.object({
14
- open_id: z.string().describe('Open ID of the user/bot to @mention'),
15
- name: z.string().describe('Display name for the @mention'),
16
- })).optional().describe('Optional list of users/bots to @mention in the message. Get open_ids from list_bots tool.'),
17
- });
18
- export const description = 'Send a message to the Lark thread associated with a session. Supports plain text, images (embedded inline), and file attachments. Just send plain text — formatting is handled automatically. Use `images` to attach local image files (png/jpg/gif etc.) and `files` to attach documents.';
19
- /** Build a post content block from plain text, splitting by newlines into paragraphs.
20
- * When mentions are provided, @Name patterns in the text are replaced with inline `at` tags. */
21
- function textToPostContent(text, mentions) {
22
- // Build a regex that matches any @Name from the mentions list
23
- let mentionPattern = null;
24
- const mentionMap = new Map(); // lowercase name -> open_id
25
- if (mentions && mentions.length > 0) {
26
- const patterns = [];
27
- for (const m of mentions) {
28
- const escaped = m.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
29
- patterns.push(escaped);
30
- mentionMap.set(m.name.toLowerCase(), m.open_id);
31
- }
32
- mentionPattern = new RegExp(`@(${patterns.join('|')})\\b`, 'gi');
33
- }
34
- return text.split('\n').map(line => {
35
- if (!mentionPattern)
36
- return [{ tag: 'text', text: line }];
37
- const nodes = [];
38
- let lastIndex = 0;
39
- for (const match of line.matchAll(mentionPattern)) {
40
- const matchedName = match[1];
41
- const openId = mentionMap.get(matchedName.toLowerCase());
42
- if (!openId)
43
- continue;
44
- // Add text before the match
45
- if (match.index > lastIndex) {
46
- nodes.push({ tag: 'text', text: line.slice(lastIndex, match.index) });
47
- }
48
- nodes.push({ tag: 'at', user_id: openId });
49
- lastIndex = match.index + match[0].length;
50
- }
51
- // Add remaining text
52
- if (lastIndex < line.length) {
53
- nodes.push({ tag: 'text', text: line.slice(lastIndex) });
54
- }
55
- // If no matches were found in this line, return as plain text
56
- if (nodes.length === 0) {
57
- nodes.push({ tag: 'text', text: line });
58
- }
59
- return nodes;
60
- });
61
- }
62
- /** Try to extract plain text from post JSON that Claude sometimes generates */
63
- function extractTextFromPostJson(raw) {
64
- try {
65
- const parsed = JSON.parse(raw);
66
- const inner = parsed.zh_cn ?? parsed.en_us ?? parsed;
67
- if (!Array.isArray(inner.content))
68
- return null;
69
- // Flatten post blocks back to plain text
70
- const lines = [];
71
- for (const paragraph of inner.content) {
72
- if (!Array.isArray(paragraph))
73
- continue;
74
- const parts = [];
75
- for (const node of paragraph) {
76
- if (node.tag === 'text' && typeof node.text === 'string') {
77
- parts.push(node.text);
78
- }
79
- }
80
- lines.push(parts.join(''));
81
- }
82
- return lines.join('\n').trim();
83
- }
84
- catch {
85
- return null;
86
- }
87
- }
88
- export async function execute(args) {
89
- const session = sessionStore.getSession(args.session_id);
90
- if (!session) {
91
- return { error: `Session ${args.session_id} not found` };
92
- }
93
- if (session.status === 'closed') {
94
- return { error: `Session ${args.session_id} is closed` };
95
- }
96
- try {
97
- // Read the session owner's open_id from the persisted session data.
98
- // The MCP server runs in a separate process (spawned by the CLI) and
99
- // does NOT inherit env vars from the worker, so we can't rely on __OWNER_OPEN_ID.
100
- const mentionUser = session.ownerOpenId;
101
- const replyInThread = true; // Always reply in thread to create topics in all chat types
102
- const appId = session.larkAppId || config.lark.appId;
103
- // Validate that image/file paths exist before doing anything
104
- for (const p of [...(args.images ?? []), ...(args.files ?? [])]) {
105
- if (!existsSync(p)) {
106
- return { error: `File not found: ${p}` };
107
- }
108
- }
109
- // Upload images in parallel
110
- const imageKeys = [];
111
- if (args.images && args.images.length > 0) {
112
- const results = await Promise.all(args.images.map(p => uploadImage(appId, p)));
113
- imageKeys.push(...results);
114
- }
115
- // If Claude sent post JSON as content, extract the plain text from it
116
- let text = args.content;
117
- const extracted = extractTextFromPostJson(text);
118
- if (extracted) {
119
- text = extracted;
120
- }
121
- // Build post content: text paragraphs + inline images.
122
- // Pass mentions so @Name in text gets replaced with proper `at` tags inline.
123
- const postContent = text ? textToPostContent(text, args.mentions) : [];
124
- for (const key of imageKeys) {
125
- postContent.push([{ tag: 'img', image_key: key }]);
126
- }
127
- // If there are mentions that weren't found in the text (e.g. no @Name in content),
128
- // append them at the end as fallback
129
- if (args.mentions && args.mentions.length > 0) {
130
- const usedOpenIds = new Set();
131
- for (const para of postContent) {
132
- for (const node of para) {
133
- if (node.tag === 'at' && node.user_id)
134
- usedOpenIds.add(node.user_id);
135
- }
136
- }
137
- const unusedMentions = args.mentions.filter(m => !usedOpenIds.has(m.open_id));
138
- if (unusedMentions.length > 0) {
139
- if (postContent.length === 0)
140
- postContent.push([]);
141
- const lastLine = postContent[postContent.length - 1];
142
- for (const m of unusedMentions) {
143
- lastLine.push({ tag: 'at', user_id: m.open_id });
144
- }
145
- }
146
- }
147
- // Append @mention to session owner (human user)
148
- if (mentionUser) {
149
- if (postContent.length === 0)
150
- postContent.push([]);
151
- postContent[postContent.length - 1].push({ tag: 'at', user_id: mentionUser });
152
- }
153
- const content = JSON.stringify({
154
- zh_cn: { title: '', content: postContent },
155
- });
156
- const messageId = await replyMessage(appId, session.rootMessageId, content, 'post', replyInThread);
157
- // Send file attachments as separate messages (Lark post doesn't support inline files)
158
- const fileMessageIds = [];
159
- if (args.files && args.files.length > 0) {
160
- for (const filePath of args.files) {
161
- const fileKey = await uploadFile(appId, filePath);
162
- const fileContent = JSON.stringify({ file_key: fileKey });
163
- const fid = await replyMessage(appId, session.rootMessageId, fileContent, 'file', replyInThread);
164
- fileMessageIds.push(fid);
165
- }
166
- }
167
- // Write signal files for bot-to-bot mentions.
168
- // Lark WSClient does not deliver im.message.receive_v1 events for bot-sent messages,
169
- // so the daemon uses these signal files to route messages to target bots internally.
170
- //
171
- // Resolve targets from two sources:
172
- // 1. Explicit args.mentions (CLI passed open_ids directly)
173
- // 2. Auto-detect @BotName in text content (CLIs often forget the mentions param)
174
- const botInfoPath = join(config.session.dataDir, 'bots-info.json');
175
- let botEntries = [];
176
- try {
177
- if (existsSync(botInfoPath)) {
178
- botEntries = JSON.parse(readFileSync(botInfoPath, 'utf-8'));
179
- }
180
- }
181
- catch { /* ignore */ }
182
- // Collect target open_ids: explicit mentions + auto-detected from text
183
- const targetOpenIds = new Set();
184
- const botOpenIds = new Set(botEntries.filter(e => e.botOpenId).map(e => e.botOpenId));
185
- // Find self open_id to exclude from targets (don't signal yourself)
186
- const selfOpenId = botEntries.find(e => e.larkAppId === appId)?.botOpenId;
187
- // 1. Explicit mentions (excluding self)
188
- if (args.mentions) {
189
- for (const m of args.mentions) {
190
- if (m.open_id !== selfOpenId && botOpenIds.has(m.open_id))
191
- targetOpenIds.add(m.open_id);
192
- }
193
- }
194
- // 2. Auto-detect @BotName / @cliId in text (case-insensitive)
195
- if (text && botEntries.length > 0) {
196
- for (const entry of botEntries) {
197
- if (!entry.botOpenId || entry.larkAppId === appId)
198
- continue; // skip self
199
- const names = [entry.botName, entry.cliId].filter(Boolean);
200
- for (const name of names) {
201
- // Match @Name with word boundary (handles "@Aiden", "@Claude Code", "@claude-code")
202
- const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
203
- if (new RegExp(`@${escaped}\\b`, 'i').test(text)) {
204
- targetOpenIds.add(entry.botOpenId);
205
- break;
206
- }
207
- }
208
- }
209
- }
210
- if (targetOpenIds.size > 0) {
211
- const signalDir = join(config.session.dataDir, 'bot-mentions');
212
- if (!existsSync(signalDir))
213
- mkdirSync(signalDir, { recursive: true });
214
- for (const openId of targetOpenIds) {
215
- const signal = {
216
- rootMessageId: session.rootMessageId,
217
- chatId: session.chatId,
218
- chatType: session.chatType,
219
- senderAppId: appId,
220
- targetBotOpenId: openId,
221
- content: text,
222
- messageId,
223
- timestamp: Date.now(),
224
- };
225
- const filename = `${Date.now()}-${openId.slice(-8)}.json`;
226
- writeFileSync(join(signalDir, filename), JSON.stringify(signal));
227
- logger.info(`Wrote bot-mention signal for ${openId} in thread ${session.rootMessageId}`);
228
- }
229
- }
230
- return {
231
- success: true,
232
- messageId,
233
- ...(fileMessageIds.length > 0 && { fileMessageIds }),
234
- sessionId: args.session_id,
235
- };
236
- }
237
- catch (err) {
238
- logger.error(`Failed to send to thread: ${err.message}`);
239
- return { error: `Failed to send message: ${err.message}` };
240
- }
241
- }
242
- //# sourceMappingURL=send-to-thread.js.map