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.
- package/README.en.md +63 -13
- package/README.md +52 -14
- package/dist/adapters/cli/aiden.d.ts.map +1 -1
- package/dist/adapters/cli/aiden.js +0 -40
- package/dist/adapters/cli/aiden.js.map +1 -1
- package/dist/adapters/cli/claude-code.d.ts.map +1 -1
- package/dist/adapters/cli/claude-code.js +27 -63
- package/dist/adapters/cli/claude-code.js.map +1 -1
- package/dist/adapters/cli/coco.d.ts.map +1 -1
- package/dist/adapters/cli/coco.js +0 -33
- package/dist/adapters/cli/coco.js.map +1 -1
- package/dist/adapters/cli/codex.d.ts.map +1 -1
- package/dist/adapters/cli/codex.js +0 -27
- package/dist/adapters/cli/codex.js.map +1 -1
- package/dist/adapters/cli/gemini.d.ts.map +1 -1
- package/dist/adapters/cli/gemini.js +1 -29
- package/dist/adapters/cli/gemini.js.map +1 -1
- package/dist/adapters/cli/opencode.d.ts.map +1 -1
- package/dist/adapters/cli/opencode.js +1 -44
- package/dist/adapters/cli/opencode.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +15 -8
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/cli.js +737 -16
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +30 -0
- package/dist/config.js.map +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +8 -4
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/scheduler.d.ts +38 -16
- package/dist/core/scheduler.d.ts.map +1 -1
- package/dist/core/scheduler.js +335 -149
- package/dist/core/scheduler.js.map +1 -1
- package/dist/core/session-manager.d.ts +13 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +128 -25
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/types.d.ts +26 -4
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +6 -0
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-pool.d.ts +15 -3
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +233 -31
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +71 -18
- package/dist/daemon.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +29 -1
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +266 -56
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts +1 -0
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +200 -40
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +4 -3
- package/dist/im/lark/client.js.map +1 -1
- package/dist/im/lark/message-parser.d.ts.map +1 -1
- package/dist/im/lark/message-parser.js +44 -6
- package/dist/im/lark/message-parser.js.map +1 -1
- package/dist/services/schedule-store.d.ts +20 -3
- package/dist/services/schedule-store.d.ts.map +1 -1
- package/dist/services/schedule-store.js +140 -16
- package/dist/services/schedule-store.js.map +1 -1
- package/dist/skills/definitions.d.ts +17 -0
- package/dist/skills/definitions.d.ts.map +1 -0
- package/dist/skills/definitions.js +254 -0
- package/dist/skills/definitions.js.map +1 -0
- package/dist/skills/installer.d.ts +9 -0
- package/dist/skills/installer.d.ts.map +1 -0
- package/dist/skills/installer.js +42 -0
- package/dist/skills/installer.js.map +1 -0
- package/dist/types.d.ts +80 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -5
- package/dist/types.js.map +1 -1
- package/dist/utils/lark-upload.d.ts +2 -0
- package/dist/utils/lark-upload.d.ts.map +1 -0
- package/dist/utils/lark-upload.js +27 -0
- package/dist/utils/lark-upload.js.map +1 -0
- package/dist/utils/screen-analyzer.d.ts +67 -0
- package/dist/utils/screen-analyzer.d.ts.map +1 -0
- package/dist/utils/screen-analyzer.js +256 -0
- package/dist/utils/screen-analyzer.js.map +1 -0
- package/dist/utils/screenshot-renderer.d.ts +11 -0
- package/dist/utils/screenshot-renderer.d.ts.map +1 -0
- package/dist/utils/screenshot-renderer.js +225 -0
- package/dist/utils/screenshot-renderer.js.map +1 -0
- package/dist/utils/terminal-renderer.d.ts +33 -0
- package/dist/utils/terminal-renderer.d.ts.map +1 -1
- package/dist/utils/terminal-renderer.js +34 -1
- package/dist/utils/terminal-renderer.js.map +1 -1
- package/dist/utils/user-token.d.ts.map +1 -1
- package/dist/utils/user-token.js +7 -11
- package/dist/utils/user-token.js.map +1 -1
- package/dist/worker.js +286 -15
- package/dist/worker.js.map +1 -1
- package/package.json +2 -5
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -16
- package/dist/index.js.map +0 -1
- package/dist/server.d.ts +0 -3
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -115
- package/dist/server.js.map +0 -1
- package/dist/tools/get-thread-messages.d.ts +0 -26
- package/dist/tools/get-thread-messages.d.ts.map +0 -1
- package/dist/tools/get-thread-messages.js +0 -35
- package/dist/tools/get-thread-messages.js.map +0 -1
- package/dist/tools/index.d.ts +0 -9
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -10
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/list-bots.d.ts +0 -40
- package/dist/tools/list-bots.d.ts.map +0 -1
- package/dist/tools/list-bots.js +0 -74
- package/dist/tools/list-bots.js.map +0 -1
- package/dist/tools/send-to-thread.d.ts +0 -46
- package/dist/tools/send-to-thread.d.ts.map +0 -1
- package/dist/tools/send-to-thread.js +0 -242
- 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
|
package/dist/server.js.map
DELETED
|
@@ -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"}
|
package/dist/tools/index.d.ts
DELETED
|
@@ -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"}
|
package/dist/tools/index.js
DELETED
|
@@ -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
|
package/dist/tools/index.js.map
DELETED
|
@@ -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"}
|
package/dist/tools/list-bots.js
DELETED
|
@@ -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
|