@sleep2agi/agent-node 0.7.5 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -106
- package/dist/cli.js +13 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,155 +1,247 @@
|
|
|
1
|
-
# @sleep2agi/agent-node
|
|
1
|
+
# 🤖 @sleep2agi/agent-node
|
|
2
2
|
|
|
3
3
|
一行命令启动 AI Agent,加入 CommHub 通信网络。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
基于 [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) + [Codex SDK](https://www.npmjs.com/package/@openai/codex-sdk) 双引擎实现。
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx @sleep2agi/agent-node --alias "我的Agent" --hub http://YOUR_HUB:9200
|
|
8
|
+
npx @sleep2agi/agent-node --alias "我的Agent" --hub http://YOUR_HUB:9200 --tools all
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
---
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
5. **循环** — 等下一个任务
|
|
19
|
-
6. **心跳** — 每 3 分钟上报存活状态
|
|
13
|
+
## 🏗️ 技术实现
|
|
14
|
+
|
|
15
|
+
### 双引擎架构
|
|
16
|
+
|
|
17
|
+
agent-node 内部根据 `--runtime` 参数选择不同的 AI 引擎:
|
|
20
18
|
|
|
21
19
|
```
|
|
22
|
-
CommHub Server (:9200)
|
|
23
|
-
│
|
|
24
|
-
│ SSE /events/:alias
|
|
25
|
-
▼
|
|
26
20
|
agent-node
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
└─ 等待下一个任务...
|
|
21
|
+
├── runtime: claude ──→ @anthropic-ai/claude-agent-sdk
|
|
22
|
+
│ └── query() → spawn claude CLI → AI 处理 + 工具调用
|
|
23
|
+
│
|
|
24
|
+
└── runtime: codex ───→ @openai/codex-sdk
|
|
25
|
+
└── exec() → spawn codex CLI → AI 处理 + 工具调用
|
|
33
26
|
```
|
|
34
27
|
|
|
35
|
-
|
|
28
|
+
### Claude Agent SDK(`--runtime claude`,默认)
|
|
36
29
|
|
|
37
|
-
|
|
30
|
+
基于 Anthropic 的 [Claude Agent SDK](https://github.com/anthropics/claude-agent-sdk-typescript),底层 spawn `claude` CLI 进程。
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
32
|
+
**核心调用**:
|
|
33
|
+
```typescript
|
|
34
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
35
|
+
|
|
36
|
+
for await (const message of query({
|
|
37
|
+
prompt: "任务内容",
|
|
38
|
+
options: {
|
|
39
|
+
model: "MiniMax-M2.7", // 支持任意 Anthropic API 兼容模型
|
|
40
|
+
tools: ["Read", "Bash", "Grep"],
|
|
41
|
+
maxTurns: 5,
|
|
42
|
+
permissionMode: "bypassPermissions",
|
|
43
|
+
settingSources: [], // 隔离全局配置,防止串网
|
|
44
|
+
}
|
|
45
|
+
})) {
|
|
46
|
+
if (message.type === "result") console.log(message.result);
|
|
47
|
+
}
|
|
43
48
|
```
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
**MiniMax / 书生模型怎么跑在 Claude SDK 上?**
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
Claude Agent SDK 底层走 Anthropic API。通过设置环境变量将请求重定向到兼容端点,零代码修改:
|
|
48
53
|
|
|
49
54
|
```bash
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
# MiniMax M2.7
|
|
56
|
+
ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic
|
|
57
|
+
ANTHROPIC_AUTH_TOKEN=your-minimax-key
|
|
58
|
+
|
|
59
|
+
# 书生 Intern-S1-Pro
|
|
60
|
+
ANTHROPIC_BASE_URL=https://chat.intern-ai.org.cn
|
|
61
|
+
ANTHROPIC_AUTH_TOKEN=your-intern-key
|
|
52
62
|
```
|
|
53
63
|
|
|
54
|
-
|
|
64
|
+
SDK 不校验模型名,`--model MiniMax-M2.7` 原样传给 API。
|
|
55
65
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
**已验证功能**:
|
|
67
|
+
- ✅ 单轮/多轮对话
|
|
68
|
+
- ✅ tool_use(Read/Write/Edit/Bash/Glob/Grep/WebSearch/WebFetch)
|
|
69
|
+
- ✅ Extended Thinking(`<think>` 标签)
|
|
70
|
+
- ✅ Session Resume(跨 query 保持上下文)
|
|
71
|
+
- ✅ SSE streaming
|
|
72
|
+
- ✅ Hooks(PreToolUse/PostToolUse)
|
|
73
|
+
- ✅ maxBudgetUsd 预算控制
|
|
74
|
+
- ✅ settingSources 隔离(防止读全局 MCP 配置串网)
|
|
59
75
|
|
|
60
|
-
###
|
|
76
|
+
### Codex SDK(`--runtime codex`)
|
|
61
77
|
|
|
62
|
-
|
|
78
|
+
基于 OpenAI 的 [Codex SDK](https://www.npmjs.com/package/@openai/codex-sdk),复用 Codex CLI 登录态。
|
|
63
79
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"hub": "http://YOUR_HUB:9200",
|
|
68
|
-
"model": "MiniMax-M2.7",
|
|
69
|
-
"tools": ["Read", "Grep", "Bash"],
|
|
70
|
-
"maxTurns": 5,
|
|
71
|
-
"systemPrompt": "你是一个有用的 AI 助手,收到任务后认真执行并汇报结果。"
|
|
72
|
-
}
|
|
73
|
-
```
|
|
80
|
+
**核心调用**:
|
|
81
|
+
```typescript
|
|
82
|
+
import Codex from "@openai/codex-sdk";
|
|
74
83
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
const client = new Codex();
|
|
85
|
+
const thread = await client.threads.create();
|
|
86
|
+
|
|
87
|
+
const response = await client.responses.create({
|
|
88
|
+
model: "gpt-5.4",
|
|
89
|
+
thread_id: thread.id,
|
|
90
|
+
input: "任务内容",
|
|
91
|
+
tools: [{ type: "code_interpreter" }, { type: "file_search" }],
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
console.log(response.output_text);
|
|
79
95
|
```
|
|
80
96
|
|
|
81
|
-
|
|
97
|
+
**特点**:
|
|
98
|
+
- 不需要额外 API key(复用 `codex` CLI 登录态)
|
|
99
|
+
- 支持 gpt-5.4(默认)/ o3 / o4-mini
|
|
100
|
+
- Thread 保持上下文(多轮对话)
|
|
101
|
+
- Thread 过期自动重建
|
|
82
102
|
|
|
83
|
-
|
|
84
|
-
|------|---------|--------|------|
|
|
85
|
-
| `--alias` | `ALIAS` | 必填 | Agent 名称 |
|
|
86
|
-
| `--hub` | `COMMHUB_URL` | `http://127.0.0.1:9200` | CommHub Server URL |
|
|
87
|
-
| `--model` | `MODEL` | `claude-sonnet-4-6` | AI 模型 |
|
|
88
|
-
| `--tools` | — | 无(纯对话) | 工具列表,逗号分隔 |
|
|
89
|
-
| `--max-turns` | — | `5` | 每个任务最大轮次 |
|
|
90
|
-
| `--prompt` | — | — | 自定义 system prompt |
|
|
91
|
-
| `--token` | `COMMHUB_TOKEN` | — | CommHub auth token |
|
|
92
|
-
| `--config` | — | `.agent-node.json` | 配置文件路径 |
|
|
103
|
+
---
|
|
93
104
|
|
|
94
|
-
|
|
105
|
+
## 🔄 Agent 主循环
|
|
95
106
|
|
|
96
|
-
|
|
97
|
-
|------|---------|
|
|
98
|
-
| Claude | `ANTHROPIC_API_KEY=your-key` |
|
|
99
|
-
| MiniMax | `ANTHROPIC_BASE_URL=https://api.minimax.chat/anthropic` + `ANTHROPIC_AUTH_TOKEN=your-token` |
|
|
100
|
-
| 其他 Anthropic 兼容 | `ANTHROPIC_BASE_URL=<endpoint>` + `ANTHROPIC_AUTH_TOKEN=<key>` |
|
|
107
|
+
无论哪种 runtime,agent-node 的主循环都一样:
|
|
101
108
|
|
|
102
|
-
|
|
109
|
+
```
|
|
110
|
+
启动
|
|
111
|
+
↓
|
|
112
|
+
注册到 CommHub(report_status: idle)
|
|
113
|
+
↓
|
|
114
|
+
SSE 长连接 /events/:alias
|
|
115
|
+
↓
|
|
116
|
+
┌─→ 收到 new_task 事件
|
|
117
|
+
│ ↓
|
|
118
|
+
│ get_inbox → 拿任务内容
|
|
119
|
+
│ ↓
|
|
120
|
+
│ ack_inbox → 确认收到
|
|
121
|
+
│ ↓
|
|
122
|
+
│ report_status: working
|
|
123
|
+
│ ↓
|
|
124
|
+
│ AI 处理(claude query() 或 codex exec())
|
|
125
|
+
│ ↓
|
|
126
|
+
│ send_task → 回报结果给发送者
|
|
127
|
+
│ ↓
|
|
128
|
+
│ report_status: idle
|
|
129
|
+
│ ↓
|
|
130
|
+
└─── 等待下一个任务
|
|
131
|
+
↓ (每 3 分钟)
|
|
132
|
+
heartbeat → report_status: idle
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**CommHub 通信层**(agent-node 自己的代码,不经过 AI 子进程):
|
|
103
136
|
|
|
104
137
|
```typescript
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
138
|
+
// 直接 HTTP POST 到 CommHub MCP 端点
|
|
139
|
+
async function callCommHub(method: string, params: object) {
|
|
140
|
+
const res = await fetch(`${HUB_URL}/mcp`, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: { "Content-Type": "application/json", "Accept": "application/json, text/event-stream" },
|
|
143
|
+
body: JSON.stringify({
|
|
144
|
+
jsonrpc: "2.0",
|
|
145
|
+
method: "tools/call",
|
|
146
|
+
params: { name: method, arguments: params },
|
|
147
|
+
}),
|
|
148
|
+
});
|
|
149
|
+
// ...
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 🛡️ 隔离策略
|
|
156
|
+
|
|
157
|
+
`settingSources: []` 阻止 claude 子进程读全局 `~/.claude.json`:
|
|
122
158
|
|
|
123
|
-
await agent.start();
|
|
124
159
|
```
|
|
160
|
+
❌ 没有隔离时:
|
|
161
|
+
agent-node → query() → spawn claude
|
|
162
|
+
→ claude 读 ~/.claude.json → 加载全局 commhub MCP
|
|
163
|
+
→ AI 调 send_task → 消息发到主网络(串网!)
|
|
164
|
+
|
|
165
|
+
✅ 有隔离时:
|
|
166
|
+
agent-node → query({ settingSources: [] }) → spawn claude
|
|
167
|
+
→ claude 不读任何全局配置
|
|
168
|
+
→ AI 只能用 agent-node 显式传的工具
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 📊 模型对照表
|
|
174
|
+
|
|
175
|
+
| 模型 | runtime | 环境变量 | 默认 |
|
|
176
|
+
|------|---------|---------|------|
|
|
177
|
+
| MiniMax M2.7(国际) | claude | `ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic` | |
|
|
178
|
+
| MiniMax M2.7(国内) | claude | `ANTHROPIC_BASE_URL=https://api.minimaxi.com/anthropic` | |
|
|
179
|
+
| 书生 Intern-S1-Pro | claude | `ANTHROPIC_BASE_URL=https://chat.intern-ai.org.cn` | |
|
|
180
|
+
| Claude Sonnet 4.6 | claude | `ANTHROPIC_API_KEY=key` | ✅ |
|
|
181
|
+
| GPT-5.4 | codex | 不需要(复用 codex 登录) | ✅ |
|
|
182
|
+
| o3 | codex | 不需要 | |
|
|
183
|
+
| o4-mini | codex | 不需要 | |
|
|
184
|
+
|
|
185
|
+
---
|
|
125
186
|
|
|
126
|
-
##
|
|
187
|
+
## 🚀 快速启动
|
|
127
188
|
|
|
128
189
|
```bash
|
|
129
|
-
#
|
|
130
|
-
|
|
190
|
+
# MiniMax(低成本)
|
|
191
|
+
ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic \
|
|
192
|
+
ANTHROPIC_AUTH_TOKEN=your-key \
|
|
193
|
+
npx @sleep2agi/agent-node --alias 小明 --model MiniMax-M2.7 --hub http://IP:9200 --tools all
|
|
131
194
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
195
|
+
# 书生(国产)
|
|
196
|
+
ANTHROPIC_BASE_URL=https://chat.intern-ai.org.cn \
|
|
197
|
+
ANTHROPIC_AUTH_TOKEN=your-key \
|
|
198
|
+
npx @sleep2agi/agent-node --alias 书生 --model intern-s1-pro --hub http://IP:9200 --tools all
|
|
199
|
+
|
|
200
|
+
# Codex GPT-5.4(OpenAI)
|
|
201
|
+
npx @sleep2agi/agent-node --alias Codex马 --runtime codex --hub http://IP:9200 --tools all
|
|
202
|
+
|
|
203
|
+
# Claude
|
|
204
|
+
ANTHROPIC_API_KEY=your-key \
|
|
205
|
+
npx @sleep2agi/agent-node --alias Claude马 --hub http://IP:9200 --tools all
|
|
136
206
|
```
|
|
137
207
|
|
|
138
|
-
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## ⚙️ CLI 参数
|
|
211
|
+
|
|
212
|
+
| 参数 | 默认值 | 说明 |
|
|
213
|
+
|------|--------|------|
|
|
214
|
+
| `--alias` | 必填 | Agent 名称 |
|
|
215
|
+
| `--hub` | `http://127.0.0.1:9200` | CommHub URL |
|
|
216
|
+
| `--runtime` | `claude` | `claude` 或 `codex` |
|
|
217
|
+
| `--model` | claude: `claude-sonnet-4-6` / codex: `gpt-5.4` | 模型名 |
|
|
218
|
+
| `--tools` | 无 | `all` 或逗号分隔 |
|
|
219
|
+
| `--max-turns` | `5` | 每任务最大轮次 |
|
|
220
|
+
| `--max-budget` | 无 | 每任务预算(美元) |
|
|
221
|
+
| `--session` | 无 | 恢复指定 session/thread |
|
|
222
|
+
| `--prompt` | 无 | 自定义 system prompt |
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 📦 依赖
|
|
227
|
+
|
|
228
|
+
| 包 | 什么时候需要 |
|
|
229
|
+
|---|------------|
|
|
230
|
+
| `@anthropic-ai/claude-agent-sdk` | `--runtime claude` 时(动态 import) |
|
|
231
|
+
| `@openai/codex-sdk` | `--runtime codex` 时(动态 import) |
|
|
232
|
+
|
|
233
|
+
未使用的 runtime 不会加载依赖。
|
|
139
234
|
|
|
140
|
-
|
|
141
|
-
- ✅ Claude Sonnet 4.6 — 完整功能
|
|
142
|
-
- ✅ CommHub 通信 — 注册/SSE/收任务/回报/心跳
|
|
143
|
-
- ⬜ MiniMax tool_use — 待验证
|
|
144
|
-
- ⬜ Codex runtime — 计划中
|
|
235
|
+
---
|
|
145
236
|
|
|
146
|
-
##
|
|
237
|
+
## 🔗 相关
|
|
147
238
|
|
|
148
|
-
|
|
|
149
|
-
|
|
150
|
-
| [@sleep2agi/agent-
|
|
151
|
-
| [@sleep2agi/agent-
|
|
152
|
-
| [@sleep2agi/commhub-server](https://www.npmjs.com/package/@sleep2agi/commhub-server) |
|
|
239
|
+
| | |
|
|
240
|
+
|---|---|
|
|
241
|
+
| **npm** | [@sleep2agi/agent-node](https://www.npmjs.com/package/@sleep2agi/agent-node) |
|
|
242
|
+
| **CLI 管理工具** | [@sleep2agi/agent-network](https://www.npmjs.com/package/@sleep2agi/agent-network) |
|
|
243
|
+
| **通信服务器** | [@sleep2agi/commhub-server](https://www.npmjs.com/package/@sleep2agi/commhub-server) |
|
|
244
|
+
| **Dashboard** | [agent-network-dashboard.vercel.app](https://agent-network-dashboard.vercel.app) |
|
|
153
245
|
|
|
154
246
|
## License
|
|
155
247
|
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{createRequire as
|
|
2
|
+
import{createRequire as Wz}from"node:module";var u=Wz(import.meta.url);import{readFileSync as i,existsSync as g,writeFileSync as Yz}from"fs";import{join as w}from"path";import{hostname as S,homedir as Xz}from"os";import{mkdirSync as s,appendFileSync as Dz}from"fs";var l=Xz(),D=process.argv.slice(2),J={},o=[];for(let z=0;z<D.length;z++){if(D[z]==="-h"||D[z]==="--help")console.log(`
|
|
3
3
|
@sleep2agi/agent-node — AI Agent 节点,一行命令加入 CommHub 网络
|
|
4
4
|
|
|
5
5
|
用法:
|
|
@@ -15,6 +15,7 @@ import{createRequire as n}from"node:module";var c=n(import.meta.url);import{read
|
|
|
15
15
|
--max-turns <n> 每任务最大轮次 (default: 5, claude runtime only)
|
|
16
16
|
--max-budget <usd> 每任务预算上限 (claude runtime only)
|
|
17
17
|
--session <id> 恢复指定 session (claude session ID 或 codex thread ID)
|
|
18
|
+
--channel <type> Channel 类型。P0: telegram
|
|
18
19
|
--prompt <text> 自定义 System Prompt
|
|
19
20
|
--log-dir <path> 日志目录 (default: .anet/nodes/<alias>/logs/)
|
|
20
21
|
--log-level <lvl> debug | info (default) | warn | error
|
|
@@ -24,13 +25,18 @@ import{createRequire as n}from"node:module";var c=n(import.meta.url);import{read
|
|
|
24
25
|
Runtime:
|
|
25
26
|
claude Claude Agent SDK — 支持 Claude/MiniMax,需要 ANTHROPIC_API_KEY 或 ANTHROPIC_BASE_URL+TOKEN
|
|
26
27
|
codex Codex SDK — 支持 GPT-5/o3,复用 codex 登录态(不需要额外 key)
|
|
27
|
-
`),process.exit(0);if(
|
|
28
|
-
用法: npx @sleep2agi/agent-node --alias "我的Agent"`),process.exit(1);var
|
|
29
|
-
`)
|
|
28
|
+
`),process.exit(0);if(D[z]==="--channel"&&z+1<D.length){o.push(D[++z]);continue}if(D[z].startsWith("--")&&z+1<D.length)J[D[z].slice(2)]=D[++z]}function a(z){return z.replace(/^~(?=\/|$)/,l)}function qz(z){let Z=z.indexOf(":");if(Z<0)return{type:z,raw:z};if(Z===0||Z===z.length-1)throw Error(`invalid channel spec "${z}" (expected type or type:path)`);return{type:z.slice(0,Z),path:a(z.slice(Z+1)),raw:z}}function x(z){if(!g(z))return null;try{return JSON.parse(i(z,"utf-8"))}catch{return null}}var K={};if(J.config){let z=x(w(process.cwd(),J.config));if(z)K=z,console.log(`[agent-node] 配置: ${J.config}`)}var G=J.alias||process.env.COMMHUB_ALIAS||process.env.ALIAS||K.alias;if(!J.config&&G){let z=x(w(process.cwd(),".anet","profiles",`${G}.json`));if(z){if(K={...z,...K},console.log(`[agent-node] Profile: .anet/profiles/${G}.json`),z.env&&typeof z.env==="object"){for(let[Z,$]of Object.entries(z.env))if(!process.env[Z]&&typeof $==="string")process.env[Z]=a($)}}}var _=x(w(l,".anet","config.json"))||{};if(_.hub&&!K.hub)K.hub=_.hub;if(_.token&&!K.token)K.token=_.token;if(!J.config&&!Object.keys(K).length){let z=x(w(process.cwd(),".agent-node.json"));if(z)K=z,console.log("[agent-node] 配置: .agent-node.json")}if(!G)console.error(`错误: 必须指定 --alias
|
|
29
|
+
用法: npx @sleep2agi/agent-node --alias "我的Agent"`),process.exit(1);var h=J.runtime||process.env.RUNTIME||K.runtime||"claude",H=h==="agent-sdk"?"claude":h,L=J.url||J.hub||process.env.COMMHUB_URL||K.hub||"http://127.0.0.1:9200",y=J.model||process.env.MODEL||K.model,Kz=["Read","Write","Edit","Bash","Glob","Grep","WebSearch","WebFetch"],f=J.tools||(Array.isArray(K.tools)?K.tools.join(","):K.tools)||"",N=f==="all"?Kz:f.split(",").filter(Boolean),Bz=parseInt(J["max-turns"]||K.flags?.maxTurns||K.maxTurns||"5"),c=parseFloat(J["max-budget"]||K.flags?.maxBudgetUsd||K.maxBudgetUsd||"0"),U=J.session||K.sessionId||"",p=J.prompt||K.systemPrompt||"",k=process.env.COMMHUB_TOKEN||K.token||"",C=J["log-dir"]||w(process.cwd(),".anet","nodes",G,"logs"),Fz={debug:0,info:1,warn:2,error:3},Jz=Fz[J["log-level"]||process.env.LOG_LEVEL||K.logLevel||"info"]??1,Gz=[...(Array.isArray(K.channels)?K.channels:[]).filter((z)=>!z.startsWith("server:")),...o],t=Gz.map((z)=>{try{return qz(z)}catch(Z){console.error(`[agent-node] ${Z.message}`),process.exit(1)}});function jz(z){if(!g(z))return;for(let Z of i(z,"utf-8").split(`
|
|
30
|
+
`)){let $=Z.trim();if(!$||$.startsWith("#"))continue;let Q=$.indexOf("=");if(Q<=0)continue;let V=$.slice(0,Q).trim(),W=$.slice(Q+1).trim().replace(/^['"]|['"]$/g,"");if(!process.env[V])process.env[V]=W}}function wz(z){return w(process.cwd(),".anet","nodes",G,"channels",z)}function Pz(z){let Z=z.path||wz("telegram");jz(w(Z,".env"));let $=process.env.TELEGRAM_BOT_TOKEN||"";if(!$)console.error(`[agent-node] telegram channel needs TELEGRAM_BOT_TOKEN in ${w(Z,".env")}`),process.exit(1);let Q=x(w(Z,"access.json"))||{},V=w(Z,"inbox");try{s(V,{recursive:!0})}catch{}return{type:"telegram",dir:Z,inboxDir:V,token:$,allowFrom:Array.isArray(Q.allowFrom)?Q.allowFrom.map(String):[]}}var E=t.filter((z)=>z.type==="telegram").map(Pz),d=t.find((z)=>z.type!=="telegram");if(d)console.error(`[agent-node] unsupported channel: ${d.raw}`),process.exit(1);try{s(C,{recursive:!0})}catch{}function T(z,Z,$){if(Z<Jz)return;let Q=new Date().toTimeString().slice(0,8),V=z.toUpperCase().padEnd(5),W=`[${Q}] [${V}] [${G}] ${$}`;console.log(W);try{let Y=new Date().toISOString().slice(0,10);Dz(w(C,`${Y}.log`),W+`
|
|
31
|
+
`)}catch{}}var q=(z)=>T("info",1,z),R=(z)=>T("debug",0,z),I=(z)=>T("warn",2,z),e=(z)=>T("error",3,z);async function M(z,Z){let $={"Content-Type":"application/json",Accept:"application/json, text/event-stream"};if(k)$.Authorization=`Bearer ${k}`;let V=await(await fetch(`${L}/mcp`,{method:"POST",headers:$,body:JSON.stringify({jsonrpc:"2.0",id:Date.now(),method:"tools/call",params:{name:z,arguments:Z}})})).text(),W=V.match(/data: (.+)/),Y=W?JSON.parse(W[1]):JSON.parse(V),B=Y?.result?.content?.[0]?.text;return B?JSON.parse(B):Y}var zz=`sdk-${G}-${Date.now().toString(36)}`,bz=()=>M("report_status",{resume_id:zz,alias:G,status:"idle",server:S(),hostname:S(),agent:`agent-node:${H}`,project_dir:process.cwd()}),O=(z,Z)=>M("report_status",{resume_id:zz,alias:G,status:z,task:Z}),vz=async()=>(await M("get_inbox",{alias:G,limit:5}))?.messages||[],Rz=(z)=>M("ack_inbox",{alias:G,message_id:z}),yz=(z,Z)=>M("send_task",{alias:z,task:Z,priority:"normal",from_session:G}),A=U||void 0;async function Hz(z,Z){let{query:$}=await import("@anthropic-ai/claude-agent-sdk"),Q=`你是 ${G},收到来自 ${Z} 的任务:
|
|
30
32
|
|
|
31
33
|
${z}
|
|
32
34
|
|
|
33
|
-
执行完后简要汇报结果。`,
|
|
35
|
+
执行完后简要汇报结果。`,V={model:y||void 0,tools:N.length?N:void 0,maxTurns:Bz,permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0,settingSources:[],env:process.env,cwd:process.cwd(),stderr:(B)=>{if(B.trim())R(`[stderr] ${B.trim().slice(0,200)}`)},hooks:{PreToolUse:[{hooks:[async(B)=>{return q(`[tool] ${B.tool_name}(${JSON.stringify(B.tool_input).slice(0,80)})`),{continue:!0}}]}]}};if(c>0)V.maxBudgetUsd=c;if(p)V.systemPrompt=p;if(A)V.resume=A;let W="",Y=Date.now();for await(let B of $({prompt:Q,options:V})){let X=B;if(X.type==="system"&&X.subtype==="init")A=X.session_id,q(`[claude] session=${X.session_id?.slice(0,8)} model=${y||"default"}`);if(X.type==="result"){let j=Date.now()-Y,b=X.usage||{};q(`[claude] ${X.subtype} | ${j}ms | $${X.total_cost_usd?.toFixed(4)||"?"} | in=${b.input_tokens||0} out=${b.output_tokens||0} | turns=${X.num_turns}`),W=X.subtype==="success"?X.result||"任务完成":`执行出错: ${X.error||X.result||"未知错误"}`}}return W}var v=null;async function Uz(z,Z){let{Codex:$}=await import("@openai/codex-sdk");if(!v){let Y=new $,X={skipGitRepoCheck:!0,approvalPolicy:"never",model:y||"gpt-5.4",sandboxMode:"danger-full-access",modelReasoningEffort:"low"};if(U)v=Y.resumeThread(U,X),q(`codex resumed thread: ${U}`);else v=Y.startThread(X)}q(`[codex] model=${y||"gpt-5.4"} thread=${v?.id||"new"}`);let V=`${z}
|
|
34
36
|
|
|
35
|
-
(直接回答,不要调用任何通信工具,不要发消息给其他人)`,
|
|
36
|
-
|
|
37
|
+
(直接回答,不要调用任何通信工具,不要发消息给其他人)`,W=Date.now();try{let{events:Y}=await v.runStreamed(V),B="",X=null,j=0;for await(let P of Y)if(P.type==="item.started"){let F=P.item;R(`[codex] ${F.type}${F.command?`: ${F.command.slice(0,60)}`:F.tool?`: ${F.server}/${F.tool}`:""}`)}else if(P.type==="item.completed"){j++;let F=P.item;if(F.type==="agent_message")B=F.text||"";if(F.type==="command_execution")R(`[codex] cmd exit=${F.exit_code} | ${F.aggregated_output?.slice(0,80)}`);if(F.type==="reasoning")R(`[codex] thinking: ${F.text?.slice(0,80)}`);if(F.type==="mcp_tool_call")R(`[codex] mcp: ${F.server}/${F.tool} → ${F.status}`)}else if(P.type==="turn.completed")X=P.usage;let b=Date.now()-W;return q(`[codex] done | ${b}ms | in=${X?.input_tokens||0} out=${X?.output_tokens||0} | items=${j}`),B||"(无回复)"}catch(Y){q(`codex thread error: ${Y.message}, 重建`),v=new $().startThread({skipGitRepoCheck:!0,approvalPolicy:"never",model:y||"gpt-5.4",sandboxMode:"danger-full-access",modelReasoningEffort:"low"});let X=await v.run(V),j=Date.now()-W;return q(`[codex] retry done | ${j}ms`),X.finalResponse||"(无回复)"}}var r=Promise.resolve();function Zz(z,Z){let $=async()=>{if(H==="codex")return Uz(z,Z);return Hz(z,Z)},Q=r.then($,$);return r=Q.then(()=>{},()=>{}),Q}async function xz(z,Z){q(`→ processing [${H}]: ${z.slice(0,80)}`),await O("working",z.slice(0,200));let $;try{$=await Zz(z,Z)}catch(Q){$=`${H} 错误: ${Q.message}`,e(`✗ ${Q.message}`)}return await O("idle"),$}async function Mz(){let z=await vz();if(!z.length)return;for(let Z of z){let $=Z.from_session||"hub";q(`← [${$}] (${Z.priority||"normal"}) ${Z.content.slice(0,100)}`),await Rz(Z.id);let Q=await xz(Z.content,$);try{await yz($,`[${G}] ${Q.slice(0,2000)}`),q(`→ [${$}] ${Q.slice(0,100)}`)}catch(V){I(`reply failed: ${V.message}`)}}}function $z(z){return String(z.from?.id||z.chat?.id||"")}function _z(z){return z.from?.username||z.from?.first_name||$z(z)||"telegram"}function Nz(z,Z){if(z.allowFrom.length===0)return!0;let $=$z(Z),Q=Z.from?.username?String(Z.from.username):"";return z.allowFrom.includes($)||!!Q&&z.allowFrom.includes(Q)}async function Qz(z,Z,$){let Q=await fetch(`${z.apiBase}/${Z}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify($)}),V=await Q.json();if(!V.ok)throw Error(`telegram ${Z} failed: ${V.description||Q.statusText}`);return V.result}async function m(z,Z,$,Q){let V=$.match(/[\s\S]{1,4096}/g)||["(无回复)"];for(let W=0;W<V.length;W++)await Qz(z,"sendMessage",{chat_id:Z,text:V[W],...Q&&W===0?{reply_to_message_id:Q}:{}})}async function n(z,Z,$){let Q=await Qz(z,"getFile",{file_id:Z}),V=String(Q.file_path||""),W=await fetch(`${z.fileBase}/${V}`);if(!W.ok)throw Error(`telegram file download failed: ${W.status} ${W.statusText}`);let Y=V.split(".").pop(),B=($||V.split("/").pop()||Z).replace(/[^a-zA-Z0-9._-]/g,"_"),X=B.includes(".")||!Y?B:`${B}.${Y}`,j=w(z.channel.inboxDir,`${Date.now()}_${X}`);return Yz(j,Buffer.from(await W.arrayBuffer())),j}async function kz(z,Z){let $=Z.text||Z.caption||"",Q=[];if(Array.isArray(Z.photo)&&Z.photo.length>0){let W=Z.photo[Z.photo.length-1],Y=await n(z,W.file_id,`photo_${Z.message_id}.jpg`);Q.push(`图片: ${Y}`)}let V=String(Z.document?.mime_type||"");if(Z.document&&V.startsWith("image/")){let W=await n(z,Z.document.file_id,Z.document.file_name||`image_${Z.message_id}`);Q.push(`图片: ${W}`)}if(Q.length)$+=`
|
|
38
|
+
|
|
39
|
+
[Telegram 附件已下载]
|
|
40
|
+
${Q.map((W)=>`- ${W}`).join(`
|
|
41
|
+
`)}`;return $.trim()}async function Oz(z,Z){if(!Nz(z.channel,Z))return;let $=Z.chat?.id,Q=Z.message_id,V=`telegram:${_z(Z)}`,W=await kz(z,Z);if(!$||!Q||!W)return;q(`← [${V}] ${W.slice(0,100)}`);try{let Y=await Zz(W,V);await m(z,$,Y,Q),q(`→ [${V}] ${Y.slice(0,100)}`)}catch(Y){e(`telegram task failed: ${Y.message}`),await m(z,$,`处理出错: ${Y.message}`,Q).catch(()=>{})}}async function Tz(z){let Z={channel:z,apiBase:`https://api.telegram.org/bot${z.token}`,fileBase:`https://api.telegram.org/file/bot${z.token}`,offset:0};q(`Telegram polling: ${z.dir}`);while(!0)try{let Q=await(await fetch(`${Z.apiBase}/getUpdates?offset=${Z.offset}&timeout=30`)).json();if(!Q.ok)throw Error(Q.description||"getUpdates failed");for(let V of Q.result||[])if(Z.offset=V.update_id+1,V.message)await Oz(Z,V.message)}catch($){I(`Telegram polling error: ${$.message}`),await new Promise((Q)=>setTimeout(Q,3000))}}async function Az(){let z=`${L}/events/${encodeURIComponent(G)}`,Z=3000;while(!0){R(`SSE connecting: ${z}`);try{let $={Accept:"text/event-stream","Cache-Control":"no-cache"};if(k)$.Authorization=`Bearer ${k}`;let Q=await fetch(z,{headers:$});if(!Q.ok||!Q.body){await new Promise((B)=>setTimeout(B,Z)),Z=Math.min(Z*1.5,60000);continue}Z=3000;let V=Q.body.getReader(),W=new TextDecoder,Y="";while(!0){let{done:B,value:X}=await V.read();if(B)break;Y+=W.decode(X,{stream:!0});let j=Y.split(`
|
|
42
|
+
`);Y=j.pop()||"";for(let b of j){if(!b.startsWith("data: "))continue;try{let P=JSON.parse(b.slice(6));if(P.type==="connected"){q("SSE connected");continue}if(["new_task","new_message","broadcast"].includes(P.type))q(`← SSE ${P.type}`),await Mz()}catch{}}}}catch($){I(`SSE error: ${$.message}`)}R(`SSE reconnecting (${Z/1000}s)...`),await new Promise(($)=>setTimeout($,Z)),Z=Math.min(Z*1.5,60000)}}q("启动");q(` runtime: ${H}`);q(` model: ${y||(H==="codex"?"gpt-5.4":"claude-sonnet-4-6")} ${y?"":"(default)"}`);q(` hub: ${L}`);q(` tools: ${N.length?`[${N.join(",")}]`:"(none)"}`);q(` channels:${E.length?` telegram(${E.map((z)=>z.dir).join(",")})`:" (none)"}`);q(` session: ${U||"(new)"}`);q(` log-dir: ${C}`);await bz();q("已注册到 CommHub");setInterval(()=>O("idle").catch(()=>{}),180000);var Vz=async()=>{q("shutting down..."),await O("offline").catch(()=>{}),process.exit(0)};process.on("SIGINT",Vz);process.on("SIGTERM",Vz);for(let z of E)Tz(z);Az();
|