agent-muster 0.1.1 → 0.1.3

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.md CHANGED
@@ -1,124 +1,169 @@
1
1
  # Agent Muster
2
2
 
3
- 在本地浏览器中管理多个 AI 编码 Agent、项目和对话,也可以通过钉钉机器人向 Agent 发送任务并接收回复。
3
+ English | [简体中文](./README.zh-CN.md)
4
4
 
5
- ## 功能
5
+ Manage local AI coding agents, projects, and conversations in your browser. Send tasks and receive replies through DingTalk or Feishu bots.
6
6
 
7
- - 自动检测本机已安装的 Codex、Claude Code、Pi 和 Kimi CLI。
8
- - 按项目管理对话,在多个窗口中查看和继续不同任务。
9
- - 选择 Agent 和模型,实时查看回复、工具调用、执行状态及可用的 Token 用量。
10
- - 保存项目和对话记录,支持停止正在运行的任务。
11
- - 扫码创建钉钉应用,绑定项目和 Agent,将钉钉消息接入本地对话。
7
+ ## Features
12
8
 
13
- ## 环境要求
9
+ - Detect installed Codex, Claude Code, Pi, and Kimi CLI agents.
10
+ - Organize conversations by project and work across one, two, or four conversation panes.
11
+ - Select agents and models, and view live replies, tool calls, execution status, and available token usage.
12
+ - Save projects and conversation history, resume conversations, and stop running tasks.
13
+ - Connect DingTalk and Feishu applications to local agents through the dedicated IM integration page.
14
14
 
15
- - Node.js **22.19.0 或更高版本**。
16
- - 要执行任务,需要先安装并配置至少一个支持的 Agent CLI,并确保其命令在 `PATH` 中可用。
17
- - Agent 的登录、模型权限和费用由对应服务管理。
15
+ ## Requirements
18
16
 
19
- | Agent | 检测的命令 |
17
+ - Node.js **22.19.0 or later**.
18
+ - Install and configure at least one supported agent CLI, with its command available in `PATH`.
19
+ - Authentication, model access, and charges are managed by each agent's provider.
20
+
21
+ | Agent | Detected command |
20
22
  | --- | --- |
21
23
  | Codex | `codex` |
22
24
  | Claude Code | `claude` |
23
25
  | Pi | `pi` |
24
26
  | Kimi CLI | `kimi` |
25
27
 
26
- ## 快速开始
28
+ ## Quick Start
27
29
 
28
- 无需全局安装,直接启动:
30
+ Run without a global installation:
29
31
 
30
32
  ```bash
31
33
  npx agent-muster
32
34
  ```
33
35
 
34
- 启动后会自动打开浏览器,默认地址为:
36
+ The browser opens automatically at the default address:
35
37
 
36
38
  ```text
37
39
  http://127.0.0.1:17321
38
40
  ```
39
41
 
40
- 也可以全局安装:
42
+ Or install globally:
41
43
 
42
44
  ```bash
43
45
  npm install -g agent-muster
44
46
  agent-muster
45
47
  ```
46
48
 
47
- 保持启动终端运行,按 `Ctrl+C` 停止服务。
49
+ Keep the terminal running. Press `Ctrl+C` to stop the service. The current web interface uses Chinese labels; their names are included below so you can find the relevant controls.
48
50
 
49
- ## 命令
51
+ ## Commands
50
52
 
51
- | 命令 | 说明 |
53
+ | Command | Description |
52
54
  | --- | --- |
53
- | `agent-muster` | 启动 Web 界面并打开浏览器 |
54
- | `agent-muster web` | 显式启动 Web 界面 |
55
- | `agent-muster web --no-open` | 启动服务,不自动打开浏览器 |
56
- | `agent-muster web --port 18000` | 指定监听端口 |
57
- | `agent-muster agents` | 列出 Agent 及安装状态 |
58
- | `agent-muster sessions` | 列出已保存的对话 |
55
+ | `agent-muster` | Start the web interface and open the browser |
56
+ | `agent-muster web` | Explicitly start the web interface |
57
+ | `agent-muster web --no-open` | Start without opening the browser |
58
+ | `agent-muster web --port 18000` | Use a custom listening port |
59
+ | `agent-muster agents` | List agents and their installation status |
60
+ | `agent-muster sessions` | List saved conversations |
61
+ | `agent-muster update` | Check for updates and update the global installation |
59
62
 
60
- 端口和浏览器选项可以组合使用:
63
+ Combine port and browser options:
61
64
 
62
65
  ```bash
63
66
  npx agent-muster web --port 18000 --no-open
64
67
  ```
65
68
 
66
- 服务仅监听本机地址 `127.0.0.1`,使用自定义端口时,请访问 `http://127.0.0.1:18000`。
69
+ The service listens only on `127.0.0.1`. With the custom port above, visit `http://127.0.0.1:18000`.
70
+
71
+ ## Create Your First Task
72
+
73
+ 1. Start Agent Muster and add a local project directory using **添加项目** (Add project).
74
+ 2. Create a conversation in that project using **创建对话** (Create conversation), then select an installed agent and a model.
75
+ 3. Enter a task, such as: `Inspect this project's startup flow and explain how to run it.`
76
+ 4. Follow replies, tool calls, and execution status in the conversation pane. Send further messages to continue the task.
77
+
78
+ The agent runs in the selected project directory. Model availability depends on the corresponding CLI configuration and access permissions. Continuing a conversation requires a resumable session created by the agent.
79
+
80
+ ## IM Integration
81
+
82
+ Open **IM 集成** (IM integration) in the sidebar and choose **钉钉** (DingTalk) or **飞书** (Feishu). Each application has its own project and agent binding.
83
+
84
+ | Capability | DingTalk | Feishu |
85
+ | --- | --- | --- |
86
+ | Application creation | Scan a QR code with DingTalk | Scan a QR code with Feishu |
87
+ | Message listener | DingTalk Stream SDK | Feishu SDK WebSocket connection |
88
+ | Incoming messages | Text, images, and rich text with images | Text |
89
+ | Conversations | Direct messages and group mentions of the bot | Direct messages and group mentions of the bot |
90
+ | Replies | Automatic agent replies and messages sent from the web interface | Automatic agent replies and messages sent from the web interface |
91
+ | Application management | Project/agent binding, name/icon sync, and local removal | Project/agent binding, name/icon sync, connection status, and local removal |
92
+
93
+ ### Create and Connect an Application
94
+
95
+ 1. Add a project in Agent Muster and confirm the target agent is installed and configured.
96
+ 2. Open **IM 集成**, choose a platform, and click **扫码创建应用** (Create application by scanning).
97
+ 3. Scan the QR code with the selected platform's mobile app. Follow the authorization page to create and authorize the application. If the QR code expires, request a new one.
98
+ 4. Select a project and an agent for the new application. Click **一键同步** (Sync) to refresh its name and icon from the platform.
99
+ 5. Send the bot a direct message, or add it to a group and mention it with a task. For Feishu, check that the application shows **消息监听已连接** (Message listener connected).
100
+
101
+ Keep Agent Muster running and connected to the selected platform. Messages are received through SDK connections established by the local service, so a public webhook address is not required.
67
102
 
68
- ## 创建第一个任务
103
+ ### Receive Tasks and Reply
69
104
 
70
- 1. 启动 Agent Muster,在 Web 界面添加一个本地项目目录。
71
- 2. 在该项目下新建对话,选择已安装的 Agent 和模型。
72
- 3. 输入任务并发送,例如:`检查这个项目的启动流程,说明如何运行。`
73
- 4. 在对话窗口查看回复、工具调用和执行状态,继续发送消息完成任务。
105
+ An incoming message starts a task using the bound agent in the bound project. When execution finishes, Agent Muster sends the result back to the original IM conversation.
74
106
 
75
- Agent 在选中的项目目录中执行任务。模型列表取决于对应 CLI 的配置与可用权限。
107
+ Messages in the same IM conversation reuse its local agent session and are processed in order. Changing an application's project or agent binding clears its IM conversation mappings; the next incoming message starts a new local conversation.
76
108
 
77
- ## 钉钉接入
109
+ DingTalk image input is supported by the Codex, Claude Code, and Pi integrations. The Kimi CLI integration does not support image input. The Feishu integration currently processes text messages only.
78
110
 
79
- 1. 先在 Agent Muster 中添加项目,确认目标 Agent 可用。
80
- 2. 打开「钉钉接入」,点击「扫码创建应用」。
81
- 3. 使用钉钉扫描二维码,按授权页面提示完成应用创建和授权。
82
- 4. 为创建的应用选择项目和 Agent,可点击「一键同步」更新名称和图标。
83
- 5. 在钉钉中向该机器人发送消息,群聊中可通过提及机器人发送任务。
111
+ ### Send Messages from the Web Interface
84
112
 
85
- 收到消息后,Agent Muster 会在绑定项目中调用选定的 Agent,任务结束后将回复发送回钉钉。同一钉钉会话会继续使用已有的本地对话;同一会话中的任务按顺序处理。
113
+ Open a conversation created through IM, then use the send-mode selector beside the message input:
86
114
 
87
- 接入期间需要保持 Agent Muster 运行,并能连接钉钉服务。支持文本、图片和图文消息;当前 Kimi CLI 接入不支持图片输入。
115
+ - **发送到钉钉** / **发送到飞书** (Send to DingTalk / Feishu): send the entered text directly to the linked IM conversation as the application, without running the agent. The sent message is recorded in the local conversation.
116
+ - **交给 Agent** (Send to agent): continue the local agent task. This mode does not automatically send the resulting reply to IM.
88
117
 
89
- 删除 Web 界面中的 IM 接入会停止接收该应用的消息,已有对话记录会保留,钉钉中的应用不会被删除。
118
+ Direct sending requires an existing IM conversation mapping. The Feishu sender splits long replies into multiple text messages.
90
119
 
91
- ## 本地数据
120
+ ### Remove an Integration
92
121
 
93
- 项目、对话和钉钉接入配置保存在用户主目录下的 `.agent-muster` 中,重新启动后会读取已有记录。钉钉接入配置包含应用凭证。
122
+ Click **删除** (Delete) on the application entry to remove its local integration and stop receiving its messages. Existing conversation history is retained. The application on DingTalk or Feishu is not deleted.
94
123
 
95
- ## 常见问题
124
+ ## Local Data
96
125
 
97
- ### Agent 显示未安装
126
+ Projects, conversations, and IM configuration are stored in `.agent-muster` under your home directory and loaded again on startup. DingTalk and Feishu application credentials are stored in `dingtalk-apps.json` and `feishu-apps.json`, respectively. Keep these files private.
98
127
 
99
- 在启动 Agent Muster 的终端中运行对应命令,确认 CLI 已安装且可以正常执行。安装或调整 `PATH` 后重新启动 Agent Muster。
128
+ ## FAQ
100
129
 
101
- ### 默认端口被占用
130
+ ### An Agent Appears Uninstalled
102
131
 
103
- 通过 `--port` 指定其他端口:
132
+ Run its command in the terminal used to start Agent Muster. Confirm that the CLI is installed and works. Restart Agent Muster after installation or changes to `PATH`.
133
+
134
+ ### The Default Port Is in Use
135
+
136
+ Choose another port:
104
137
 
105
138
  ```bash
106
139
  npx agent-muster web --port 18000
107
140
  ```
108
141
 
109
- ### 钉钉机器人没有回复
142
+ ### An IM Bot Does Not Reply
143
+
144
+ Confirm that Agent Muster is running, platform authorization is complete, and the application is bound to a project and an available agent. In groups, mention the bot. For Feishu, check its connection status and use a text message. Inspect the web conversation log and startup terminal for task status and errors.
145
+
146
+ ### A Message Cannot Be Sent from the Web Interface
147
+
148
+ Send a message to the bot from IM first, then open the resulting local conversation. Confirm that the integration still exists and its project/agent binding has not changed. Select **发送到钉钉** or **发送到飞书** before sending.
110
149
 
111
- 确认本地服务仍在运行,应用已绑定项目和可用的 Agent,并已完成钉钉授权。可以在 Web 对话记录和启动终端中查看任务状态与错误信息。
150
+ ## Updates
112
151
 
113
- ## 更新
152
+ On web startup, Agent Muster checks for a newer version in the background and prints an update notice in the terminal.
153
+
154
+ For a global installation:
155
+
156
+ ```bash
157
+ agent-muster update
158
+ ```
114
159
 
115
- 全局安装的用户可以运行:
160
+ This command checks the latest version and uses npm to update the global installation when an update is available. Restart the service afterward. You can also update manually:
116
161
 
117
162
  ```bash
118
163
  npm install -g agent-muster@latest
119
164
  ```
120
165
 
121
- 使用 `npx` 时,可以明确指定最新版本:
166
+ When using `npx`, explicitly select the latest published version:
122
167
 
123
168
  ```bash
124
169
  npx agent-muster@latest
@@ -0,0 +1,170 @@
1
+ # Agent Muster
2
+
3
+ [English](./README.md) | 简体中文
4
+
5
+ 在本地浏览器中管理多个 AI 编码 Agent、项目和对话,也可以通过钉钉或飞书机器人向 Agent 发送任务并接收回复。
6
+
7
+ ## 功能
8
+
9
+ - 自动检测本机已安装的 Codex、Claude Code、Pi 和 Kimi CLI。
10
+ - 按项目管理对话,支持单窗、双窗和四窗布局,同时查看和继续不同任务。
11
+ - 选择 Agent 和模型,实时查看回复、工具调用、执行状态及可用的 Token 用量。
12
+ - 保存项目和对话记录,支持继续对话和停止正在运行的任务。
13
+ - 通过独立的 IM 集成页面,将钉钉和飞书应用接入本地 Agent。
14
+
15
+ ## 环境要求
16
+
17
+ - Node.js **22.19.0 或更高版本**。
18
+ - 要执行任务,需要先安装并配置至少一个支持的 Agent CLI,并确保其命令在 `PATH` 中可用。
19
+ - Agent 的登录、模型权限和费用由对应服务管理。
20
+
21
+ | Agent | 检测的命令 |
22
+ | --- | --- |
23
+ | Codex | `codex` |
24
+ | Claude Code | `claude` |
25
+ | Pi | `pi` |
26
+ | Kimi CLI | `kimi` |
27
+
28
+ ## 快速开始
29
+
30
+ 无需全局安装,直接启动:
31
+
32
+ ```bash
33
+ npx agent-muster
34
+ ```
35
+
36
+ 启动后会自动打开浏览器,默认地址为:
37
+
38
+ ```text
39
+ http://127.0.0.1:17321
40
+ ```
41
+
42
+ 也可以全局安装:
43
+
44
+ ```bash
45
+ npm install -g agent-muster
46
+ agent-muster
47
+ ```
48
+
49
+ 保持启动终端运行,按 `Ctrl+C` 停止服务。当前 Web 界面使用中文标签。
50
+
51
+ ## 命令
52
+
53
+ | 命令 | 说明 |
54
+ | --- | --- |
55
+ | `agent-muster` | 启动 Web 界面并打开浏览器 |
56
+ | `agent-muster web` | 显式启动 Web 界面 |
57
+ | `agent-muster web --no-open` | 启动服务,不自动打开浏览器 |
58
+ | `agent-muster web --port 18000` | 指定监听端口 |
59
+ | `agent-muster agents` | 列出 Agent 及安装状态 |
60
+ | `agent-muster sessions` | 列出已保存的对话 |
61
+ | `agent-muster update` | 检查并更新全局安装到最新版本 |
62
+
63
+ 端口和浏览器选项可以组合使用:
64
+
65
+ ```bash
66
+ npx agent-muster web --port 18000 --no-open
67
+ ```
68
+
69
+ 服务仅监听本机地址 `127.0.0.1`,使用上述自定义端口时,请访问 `http://127.0.0.1:18000`。
70
+
71
+ ## 创建第一个任务
72
+
73
+ 1. 启动 Agent Muster,在 Web 界面点击「添加项目」,选择一个本地项目目录。
74
+ 2. 在该项目下点击「创建对话」,选择已安装的 Agent 和模型。
75
+ 3. 输入任务并发送,例如:`检查这个项目的启动流程,说明如何运行。`
76
+ 4. 在对话窗口查看回复、工具调用和执行状态,继续发送消息完成任务。
77
+
78
+ Agent 在选中的项目目录中执行任务。模型列表取决于对应 CLI 的配置与可用权限。继续对话需要 Agent 已创建可恢复的会话。
79
+
80
+ ## IM 集成
81
+
82
+ 在侧边栏打开「IM 集成」,选择「钉钉」或「飞书」。每个应用分别绑定项目和 Agent。
83
+
84
+ | 能力 | 钉钉 | 飞书 |
85
+ | --- | --- | --- |
86
+ | 创建应用 | 使用钉钉扫码创建 | 使用飞书扫码创建 |
87
+ | 消息监听 | 钉钉 Stream SDK | 飞书 SDK WebSocket 长连接 |
88
+ | 接收消息 | 文本、图片和图文消息 | 文本消息 |
89
+ | 对话场景 | 单聊、群聊中提及机器人 | 单聊、群聊中提及机器人 |
90
+ | 消息回复 | Agent 自动回复、Web 界面主动发送 | Agent 自动回复、Web 界面主动发送 |
91
+ | 应用管理 | 绑定项目和 Agent、同步名称和图标、删除本地接入 | 绑定项目和 Agent、同步名称和图标、查看连接状态、删除本地接入 |
92
+
93
+ ### 扫码创建与绑定应用
94
+
95
+ 1. 先在 Agent Muster 中添加项目,确认目标 Agent 已安装并配置完成。
96
+ 2. 打开「IM 集成」,选择平台,点击「扫码创建应用」。
97
+ 3. 使用对应平台的手机应用扫描二维码,按授权页面提示完成应用创建和授权。二维码过期后可重新获取。
98
+ 4. 为创建的应用选择项目和 Agent,点击「一键同步」可更新平台上的名称和图标。
99
+ 5. 向机器人发送单聊消息,或将机器人加入群聊并提及它发送任务。使用飞书时,确认应用状态显示「消息监听已连接」。
100
+
101
+ 接入期间需要保持 Agent Muster 运行,并能连接对应平台。本地服务通过 SDK 建立连接接收消息,无需提供公网 Webhook 地址。
102
+
103
+ ### 接收任务与自动回复
104
+
105
+ 收到消息后,Agent Muster 会在绑定项目中调用选定的 Agent,任务结束后将结果发送回原 IM 对话。
106
+
107
+ 同一 IM 对话会继续使用已有的本地 Agent 会话,消息按顺序处理。修改应用绑定的项目或 Agent 会清空 IM 对话映射,下一条消息将创建新的本地对话。
108
+
109
+ 钉钉图片输入支持 Codex、Claude Code 和 Pi,当前 Kimi CLI 接入不支持图片输入。飞书接入目前只处理文本消息。
110
+
111
+ ### 在 Web 界面主动发送消息
112
+
113
+ 打开由 IM 创建的本地对话,在输入框旁选择发送方式:
114
+
115
+ - 「发送到钉钉」或「发送到飞书」:以应用身份将输入的文本直接发送到关联的 IM 对话,不运行 Agent;发送记录保存在本地对话中。
116
+ - 「交给 Agent」:继续本地 Agent 任务,此模式不会将执行结果自动发送到 IM。
117
+
118
+ 主动发送需要已有的 IM 对话映射。飞书发送较长的回复时会分成多条文本消息。
119
+
120
+ ### 删除接入
121
+
122
+ 点击应用条目中的「删除」,可移除本地接入并停止接收该应用的消息。已有对话记录会保留,钉钉或飞书平台中的应用不会被删除。
123
+
124
+ ## 本地数据
125
+
126
+ 项目、对话和 IM 接入配置保存在用户主目录下的 `.agent-muster` 中,重新启动后会读取已有记录。钉钉和飞书应用凭证分别保存在 `dingtalk-apps.json` 和 `feishu-apps.json` 中,请妥善保管这些文件。
127
+
128
+ ## 常见问题
129
+
130
+ ### Agent 显示未安装
131
+
132
+ 在启动 Agent Muster 的终端中运行对应命令,确认 CLI 已安装且可以正常执行。安装或调整 `PATH` 后重新启动 Agent Muster。
133
+
134
+ ### 默认端口被占用
135
+
136
+ 通过 `--port` 指定其他端口:
137
+
138
+ ```bash
139
+ npx agent-muster web --port 18000
140
+ ```
141
+
142
+ ### IM 机器人没有回复
143
+
144
+ 确认本地服务仍在运行,已完成平台授权,应用已绑定项目和可用的 Agent。群聊中需要提及机器人;使用飞书时,检查连接状态并发送文本消息。可以在 Web 对话日志和启动终端中查看任务状态与错误信息。
145
+
146
+ ### Web 界面无法主动发送消息
147
+
148
+ 先从 IM 向机器人发送一条消息,再打开由此创建的本地对话。确认应用接入仍存在,项目和 Agent 绑定未被修改,并选择「发送到钉钉」或「发送到飞书」后发送。
149
+
150
+ ## 更新
151
+
152
+ 启动 Web 服务时会在后台检查新版本,并在终端显示更新提示。
153
+
154
+ 全局安装的用户可以运行:
155
+
156
+ ```bash
157
+ agent-muster update
158
+ ```
159
+
160
+ 命令会检查最新版本,已有新版本时通过 npm 更新全局安装。更新完成后重新启动服务即可生效,也可以手动更新:
161
+
162
+ ```bash
163
+ npm install -g agent-muster@latest
164
+ ```
165
+
166
+ 使用 `npx` 时,可以明确指定最新发布版本:
167
+
168
+ ```bash
169
+ npx agent-muster@latest
170
+ ```
File without changes
package/dist/cli.js CHANGED
@@ -2,9 +2,12 @@ import { spawn } from 'node:child_process';
2
2
  import { AgentRegistry } from './agents.js';
3
3
  import { DingTalkBridge } from './dingtalk-bridge.js';
4
4
  import { DingTalkRegistry } from './dingtalk.js';
5
+ import { FeishuRegistry } from './feishu.js';
6
+ import { FeishuBridge } from './feishu-bridge.js';
5
7
  import { ProjectRegistry } from './projects.js';
6
8
  import { SessionManager } from './sessions.js';
7
9
  import { startServer } from './server.js';
10
+ import { checkForUpdates, updatePackage } from './updates.js';
8
11
  function openBrowser(url) {
9
12
  const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
10
13
  const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
@@ -12,6 +15,10 @@ function openBrowser(url) {
12
15
  }
13
16
  async function main() {
14
17
  const [command = 'web', ...options] = process.argv.slice(2);
18
+ if (command === 'update') {
19
+ await updatePackage();
20
+ return;
21
+ }
15
22
  const registry = new AgentRegistry();
16
23
  await registry.scan();
17
24
  const sessions = new SessionManager();
@@ -20,6 +27,8 @@ async function main() {
20
27
  await projects.load();
21
28
  const dingtalk = new DingTalkRegistry();
22
29
  await dingtalk.load();
30
+ const feishu = new FeishuRegistry();
31
+ await feishu.load();
23
32
  if (command === 'agents') {
24
33
  console.log('Installed Agents\n');
25
34
  for (const agent of registry.list())
@@ -38,12 +47,16 @@ async function main() {
38
47
  for (const { sessionId, appId } of dingtalk.sessionBindings())
39
48
  sessions.linkDingTalkSession(sessionId, appId);
40
49
  new DingTalkBridge(dingtalk, registry, sessions);
50
+ for (const { sessionId, appId } of feishu.sessionBindings())
51
+ sessions.linkFeishuSession(sessionId, appId);
52
+ new FeishuBridge(feishu, registry, sessions);
41
53
  const portIndex = options.indexOf('--port');
42
54
  const port = portIndex >= 0 ? Number(options[portIndex + 1]) : 17321;
43
55
  if (!Number.isInteger(port) || port < 1 || port > 65535)
44
56
  throw new Error('Port must be 1–65535');
45
- const server = await startServer(port, registry, projects, sessions, dingtalk);
57
+ const server = await startServer(port, registry, projects, sessions, dingtalk, feishu, options.includes('--dev'));
46
58
  dingtalk.startSubscriptions();
59
+ feishu.startSubscriptions();
47
60
  const url = `http://127.0.0.1:${port}`;
48
61
  console.log(`Agent Muster started\n\nWeb: ${url}\n\nDetected Agents:`);
49
62
  for (const agent of registry.list())
@@ -51,8 +64,9 @@ async function main() {
51
64
  console.log('\nPress Ctrl+C to stop');
52
65
  if (!options.includes('--no-open'))
53
66
  openBrowser(url);
54
- const shutdown = () => { dingtalk.shutdown(); sessions.shutdown(); server.close(); server.closeAllConnections(); };
67
+ const shutdown = () => { dingtalk.shutdown(); feishu.shutdown(); sessions.shutdown(); server.close(); server.closeAllConnections(); };
55
68
  process.once('SIGINT', shutdown);
56
69
  process.once('SIGTERM', shutdown);
70
+ void checkForUpdates().catch(() => console.warn('无法检查更新,请稍后运行 agent-muster update 重试。'));
57
71
  }
58
72
  main().catch(error => { console.error(error.message); process.exitCode = 1; });
@@ -0,0 +1,96 @@
1
+ const terminalStatuses = new Set(['completed', 'failed', 'stopped']);
2
+ function waitForCompletion(sessions, session) {
3
+ if (terminalStatuses.has(session.status) && !sessions.isRunning(session.id))
4
+ return Promise.resolve();
5
+ return new Promise(resolve => {
6
+ const check = () => {
7
+ if (!terminalStatuses.has(session.status) || sessions.isRunning(session.id))
8
+ return;
9
+ sessions.off(session.id, onEvent);
10
+ sessions.off(`${session.id}:idle`, check);
11
+ resolve();
12
+ };
13
+ const onEvent = (event) => { if (event.type === 'status')
14
+ check(); };
15
+ sessions.on(session.id, onEvent);
16
+ sessions.on(`${session.id}:idle`, check);
17
+ check();
18
+ });
19
+ }
20
+ export class FeishuBridge {
21
+ feishu;
22
+ agents;
23
+ sessions;
24
+ queues = new Map();
25
+ seen = new Set();
26
+ constructor(feishu, agents, sessions) {
27
+ this.feishu = feishu;
28
+ this.agents = agents;
29
+ this.sessions = sessions;
30
+ feishu.on('message', event => this.enqueue(event));
31
+ }
32
+ enqueue({ appId, message: event }) {
33
+ const { message, sender } = event;
34
+ if (sender.sender_type !== 'user' || message.message_type !== 'text')
35
+ return;
36
+ const key = `${appId}:${message.chat_id}`;
37
+ const messageKey = `${appId}:${message.message_id}`;
38
+ if (this.seen.has(messageKey))
39
+ return;
40
+ this.seen.add(messageKey);
41
+ if (this.seen.size > 1000)
42
+ this.seen.delete(this.seen.values().next().value);
43
+ const task = (this.queues.get(key) || Promise.resolve()).then(() => this.process(appId, event)).catch(async (error) => {
44
+ console.error(`飞书消息 ${message.message_id} 处理失败:`, error);
45
+ try {
46
+ await this.feishu.send(appId, message.chat_id, 'Agent 执行失败,请在 Agent Muster 中查看会话日志。');
47
+ }
48
+ catch (replyError) {
49
+ console.error('飞书错误消息发送失败:', replyError);
50
+ }
51
+ });
52
+ this.queues.set(key, task);
53
+ void task.finally(() => { if (this.queues.get(key) === task)
54
+ this.queues.delete(key); });
55
+ }
56
+ async process(appId, { message, sender }) {
57
+ const body = JSON.parse(message.content);
58
+ let content = body.text;
59
+ for (const mention of message.mentions || [])
60
+ content = content.replaceAll(mention.key, mention.mentioned_type === 'app' ? '' : `@${mention.name}`);
61
+ content = content.trim();
62
+ if (!content)
63
+ return;
64
+ const binding = this.feishu.getBinding(appId);
65
+ if (!binding?.project || !binding.agent) {
66
+ await this.feishu.send(appId, message.chat_id, '请先在 Agent Muster 中为此应用绑定项目和 Agent。');
67
+ return;
68
+ }
69
+ if (!this.agents.get(binding.agent)?.installed) {
70
+ await this.feishu.send(appId, message.chat_id, '绑定的 Agent 当前不可用。');
71
+ return;
72
+ }
73
+ const senderId = sender.sender_id?.open_id;
74
+ if (!senderId)
75
+ throw new Error('飞书消息缺少发送人标识');
76
+ const conversation = { type: message.chat_type === 'group' ? 'group' : 'single', senderId };
77
+ const prompt = message.chat_type === 'group' ? `${senderId} 在群聊中说:\n${content}` : content;
78
+ const previousId = this.feishu.getSession(appId, message.chat_id);
79
+ let session = previousId ? this.sessions.get(previousId) : undefined;
80
+ if (session && (this.sessions.isRunning(session.id) || !terminalStatuses.has(session.status)))
81
+ await waitForCompletion(this.sessions, session);
82
+ const lastEventId = session?.events.at(-1)?.id || 0;
83
+ if (session) {
84
+ this.sessions.linkFeishuSession(session.id, appId);
85
+ this.sessions.send(session.id, prompt, undefined, undefined, undefined, conversation);
86
+ }
87
+ else {
88
+ session = await this.sessions.create(binding.agent, binding.project, prompt, undefined, undefined, undefined, undefined, { appId, conversation });
89
+ await this.feishu.setSession(appId, message.chat_id, session.id);
90
+ }
91
+ await waitForCompletion(this.sessions, session);
92
+ const output = session.events.filter(event => event.id > lastEventId && event.type === 'output').at(-1)?.text.trim();
93
+ const reply = session.status === 'completed' ? output || 'Agent 已执行完成。' : 'Agent 执行失败,请在 Agent Muster 中查看会话日志。';
94
+ await this.feishu.send(appId, message.chat_id, reply);
95
+ }
96
+ }