@sleep2agi/agent-node 0.7.5 → 0.8.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 (3) hide show
  1. package/README.md +198 -106
  2. package/dist/cli.js +13 -7
  3. 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
- 支持 Claude、MiniMax、或任何 Anthropic API 兼容模型。基于 Claude Agent SDK。
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
- 1. **注册** — 向 CommHub 报到(report_status)
15
- 2. **监听** — SSE 长连接实时接收任务
16
- 3. **处理** — 用 AI 模型处理任务(Claude Agent SDK query())
17
- 4. **回报** 把结果发回给任务发送者
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
- ├─ 收到 new_task 事件
28
- ├─ get_inbox拿任务内容
29
- ├─ ack_inbox → 确认收到
30
- ├─ query() AI 处理(支持工具调用)
31
- ├─ send_task回报结果
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
- ### MiniMax(低成本)
30
+ 基于 Anthropic 的 [Claude Agent SDK](https://github.com/anthropics/claude-agent-sdk-typescript),底层 spawn `claude` CLI 进程。
38
31
 
39
- ```bash
40
- ANTHROPIC_BASE_URL=https://api.minimax.chat/anthropic \
41
- ANTHROPIC_AUTH_TOKEN=your-minimax-token \
42
- npx @sleep2agi/agent-node --alias "MiniMax马" --model MiniMax-M2.7 --hub http://YOUR_HUB:9200
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
- 原理:Claude Agent SDK 底层走 Anthropic API,设 `ANTHROPIC_BASE_URL` 重定向到 MiniMax 的 Anthropic 兼容端点。零代码修改。
50
+ **MiniMax / 书生模型怎么跑在 Claude SDK 上?**
46
51
 
47
- ### Claude
52
+ Claude Agent SDK 底层走 Anthropic API。通过设置环境变量将请求重定向到兼容端点,零代码修改:
48
53
 
49
54
  ```bash
50
- ANTHROPIC_API_KEY=your-key \
51
- npx @sleep2agi/agent-node --alias "Claude马" --model claude-sonnet-4-6 --hub http://YOUR_HUB:9200
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
- ```bash
57
- npx @sleep2agi/agent-node --alias "开发马" --model MiniMax-M2.7 --tools "Read,Bash,Grep"
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
- 创建 `.agent-node.json`:
78
+ 基于 OpenAI 的 [Codex SDK](https://www.npmjs.com/package/@openai/codex-sdk),复用 Codex CLI 登录态。
63
79
 
64
- ```json
65
- {
66
- "alias": "我的Agent",
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
- ```bash
76
- ANTHROPIC_BASE_URL=https://api.minimax.chat/anthropic \
77
- ANTHROPIC_AUTH_TOKEN=your-token \
78
- npx @sleep2agi/agent-node
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
- ## CLI 参数
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
- import { AgentNode } from "@sleep2agi/agent-node";
106
-
107
- const agent = new AgentNode({
108
- alias: "我的Agent",
109
- hub: "http://YOUR_HUB:9200",
110
- model: "MiniMax-M2.7",
111
- tools: ["Read", "Grep"],
112
- maxTurns: 5,
113
- onTask: async (msg) => {
114
- console.log("收到任务:", msg.content);
115
- // 返回 string → 跳过 AI 处理,直接用这个作为结果
116
- // 返回 void → 走默认 AI 处理
117
- },
118
- onResult: async (msg, result) => {
119
- console.log("完成:", result);
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
- ## anet CLI 配合
187
+ ## 🚀 快速启动
127
188
 
128
189
  ```bash
129
- # 用 anet 管理配置和启动
130
- npm install -g @sleep2agi/agent-network
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
- anet init --hub http://YOUR_HUB:9200
133
- anet init project
134
- anet init profile minimax-agent --alias "MiniMax马"
135
- anet start minimax-agent # spawn agent-node
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
- - ✅ MiniMax M2.7 — 单轮/多轮/Extended Thinking/Session Resume/SSE streaming
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-network](https://www.npmjs.com/package/@sleep2agi/agent-network) | CLI (anet) + CommHub SDK |
151
- | [@sleep2agi/agent-node](https://www.npmjs.com/package/@sleep2agi/agent-node) | Agent 运行时(本包) |
152
- | [@sleep2agi/commhub-server](https://www.npmjs.com/package/@sleep2agi/commhub-server) | 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 n}from"node:module";var c=n(import.meta.url);import{readFileSync as r,existsSync as g}from"fs";import{join as b}from"path";import{hostname as L,homedir as a}from"os";import{mkdirSync as o,appendFileSync as e}from"fs";var p=a(),y=process.argv.slice(2),Y={};for(let z=0;z<y.length;z++){if(y[z]==="-h"||y[z]==="--help")console.log(`
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(y[z].startsWith("--")&&z+1<y.length)Y[y[z].slice(2)]=y[++z]}function _(z){if(!g(z))return null;try{return JSON.parse(r(z,"utf-8"))}catch{return null}}var q={};if(Y.config){let z=_(b(process.cwd(),Y.config));if(z)q=z,console.log(`[agent-node] 配置: ${Y.config}`)}var P=Y.alias||process.env.COMMHUB_ALIAS||process.env.ALIAS||q.alias;if(!Y.config&&P){let z=_(b(process.cwd(),".anet","profiles",`${P}.json`));if(z){if(q={...z,...q},console.log(`[agent-node] Profile: .anet/profiles/${P}.json`),z.env&&typeof z.env==="object"){for(let[Q,Z]of Object.entries(z.env))if(!process.env[Q]&&typeof Z==="string")process.env[Q]=Z.replace(/^~/,p)}}}var v=_(b(p,".anet","config.json"))||{};if(v.hub&&!q.hub)q.hub=v.hub;if(v.token&&!q.token)q.token=v.token;if(!Y.config&&!Object.keys(q).length){let z=_(b(process.cwd(),".agent-node.json"));if(z)q=z,console.log("[agent-node] 配置: .agent-node.json")}if(!P)console.error(`错误: 必须指定 --alias
28
- 用法: npx @sleep2agi/agent-node --alias "我的Agent"`),process.exit(1);var C=Y.runtime||process.env.RUNTIME||q.runtime||"claude",x=C==="agent-sdk"?"claude":C,A=Y.url||Y.hub||process.env.COMMHUB_URL||q.hub||"http://127.0.0.1:9200",D=Y.model||process.env.MODEL||q.model,i=["Read","Write","Edit","Bash","Glob","Grep","WebSearch","WebFetch"],I=Y.tools||(Array.isArray(q.tools)?q.tools.join(","):q.tools)||"",R=I==="all"?i:I.split(",").filter(Boolean),l=parseInt(Y["max-turns"]||q.flags?.maxTurns||q.maxTurns||"5"),u=parseFloat(Y["max-budget"]||q.flags?.maxBudgetUsd||q.maxBudgetUsd||"0"),k=Y.session||q.sessionId||"",S=Y.prompt||q.systemPrompt||"",h=process.env.COMMHUB_TOKEN||q.token||"",O=Y["log-dir"]||b(process.cwd(),".anet","nodes",P,"logs"),t={debug:0,info:1,warn:2,error:3},s=t[Y["log-level"]||process.env.LOG_LEVEL||q.logLevel||"info"]??1;try{o(O,{recursive:!0})}catch{}function T(z,Q,Z){if(Q<s)return;let K=new Date().toTimeString().slice(0,8),J=z.toUpperCase().padEnd(5),B=`[${K}] [${J}] [${P}] ${Z}`;console.log(B);try{let X=new Date().toISOString().slice(0,10);e(b(O,`${X}.log`),B+`
29
- `)}catch{}}var F=(z)=>T("info",1,z),N=(z)=>T("debug",0,z),m=(z)=>T("warn",2,z),zz=(z)=>T("error",3,z);async function U(z,Q){let Z={"Content-Type":"application/json",Accept:"application/json, text/event-stream"};if(h)Z.Authorization=`Bearer ${h}`;let J=await(await fetch(`${A}/mcp`,{method:"POST",headers:Z,body:JSON.stringify({jsonrpc:"2.0",id:Date.now(),method:"tools/call",params:{name:z,arguments:Q}})})).text(),B=J.match(/data: (.+)/),X=B?JSON.parse(B[1]):JSON.parse(J),V=X?.result?.content?.[0]?.text;return V?JSON.parse(V):X}var f=`sdk-${P}-${Date.now().toString(36)}`,Qz=()=>U("report_status",{resume_id:f,alias:P,status:"idle",server:L(),hostname:L(),agent:`agent-node:${x}`,project_dir:process.cwd()}),M=(z,Q)=>U("report_status",{resume_id:f,alias:P,status:z,task:Q}),Zz=async()=>(await U("get_inbox",{alias:P,limit:5}))?.messages||[],$z=(z)=>U("ack_inbox",{alias:P,message_id:z}),qz=(z,Q)=>U("send_task",{alias:z,task:Q,priority:"normal",from_session:P}),E=k||void 0;async function Fz(z,Q){let{query:Z}=await import("@anthropic-ai/claude-agent-sdk"),K=`你是 ${P},收到来自 ${Q} 的任务:
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
- 执行完后简要汇报结果。`,J={model:D||void 0,tools:R.length?R:void 0,maxTurns:l,permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0,settingSources:[],env:process.env,cwd:process.cwd(),stderr:(V)=>{if(V.trim())N(`[stderr] ${V.trim().slice(0,200)}`)},hooks:{PreToolUse:[{hooks:[async(V)=>{return F(`[tool] ${V.tool_name}(${JSON.stringify(V.tool_input).slice(0,80)})`),{continue:!0}}]}]}};if(u>0)J.maxBudgetUsd=u;if(S)J.systemPrompt=S;if(E)J.resume=E;let B="",X=Date.now();for await(let V of Z({prompt:K,options:J})){let $=V;if($.type==="system"&&$.subtype==="init")E=$.session_id,F(`[claude] session=${$.session_id?.slice(0,8)} model=${D||"default"}`);if($.type==="result"){let G=Date.now()-X,w=$.usage||{};F(`[claude] ${$.subtype} | ${G}ms | $${$.total_cost_usd?.toFixed(4)||"?"} | in=${w.input_tokens||0} out=${w.output_tokens||0} | turns=${$.num_turns}`),B=$.subtype==="success"?$.result||"任务完成":`执行出错: ${$.error||$.result||"未知错误"}`}}return B}var H=null;async function Vz(z,Q){let{Codex:Z}=await import("@openai/codex-sdk");if(!H){let X=new Z,$={skipGitRepoCheck:!0,approvalPolicy:"never",model:D||"gpt-5.4",sandboxMode:"danger-full-access",modelReasoningEffort:"low"};if(k)H=X.resumeThread(k,$),F(`codex resumed thread: ${k}`);else H=X.startThread($)}F(`[codex] model=${D||"gpt-5.4"} thread=${H?.id||"new"}`);let J=`${z}
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
- (直接回答,不要调用任何通信工具,不要发消息给其他人)`,B=Date.now();try{let{events:X}=await H.runStreamed(J),V="",$=null,G=0;for await(let j of X)if(j.type==="item.started"){let W=j.item;N(`[codex] ${W.type}${W.command?`: ${W.command.slice(0,60)}`:W.tool?`: ${W.server}/${W.tool}`:""}`)}else if(j.type==="item.completed"){G++;let W=j.item;if(W.type==="agent_message")V=W.text||"";if(W.type==="command_execution")N(`[codex] cmd exit=${W.exit_code} | ${W.aggregated_output?.slice(0,80)}`);if(W.type==="reasoning")N(`[codex] thinking: ${W.text?.slice(0,80)}`);if(W.type==="mcp_tool_call")N(`[codex] mcp: ${W.server}/${W.tool} → ${W.status}`)}else if(j.type==="turn.completed")$=j.usage;let w=Date.now()-B;return F(`[codex] done | ${w}ms | in=${$?.input_tokens||0} out=${$?.output_tokens||0} | items=${G}`),V||"(无回复)"}catch(X){F(`codex thread error: ${X.message}, 重建`),H=new Z().startThread({skipGitRepoCheck:!0,approvalPolicy:"never",model:D||"gpt-5.4",sandboxMode:"danger-full-access",modelReasoningEffort:"low"});let $=await H.run(J),G=Date.now()-B;return F(`[codex] retry done | ${G}ms`),$.finalResponse||"(无回复)"}}async function Wz(z,Q){F(`→ processing [${x}]: ${z.slice(0,80)}`),await M("working",z.slice(0,200));let Z;try{if(x==="codex")Z=await Vz(z,Q);else Z=await Fz(z,Q)}catch(K){Z=`${x} 错误: ${K.message}`,zz(`✗ ${K.message}`)}return await M("idle"),Z}async function Yz(){let z=await Zz();if(!z.length)return;for(let Q of z){let Z=Q.from_session||"hub";F(`← [${Z}] (${Q.priority||"normal"}) ${Q.content.slice(0,100)}`),await $z(Q.id);let K=await Wz(Q.content,Z);try{await qz(Z,`[${P}] ${K.slice(0,2000)}`),F(`→ [${Z}] ${K.slice(0,100)}`)}catch(J){m(`reply failed: ${J.message}`)}}}async function Xz(){let z=`${A}/events/${encodeURIComponent(P)}`,Q=3000;while(!0){N(`SSE connecting: ${z}`);try{let Z={Accept:"text/event-stream","Cache-Control":"no-cache"};if(h)Z.Authorization=`Bearer ${h}`;let K=await fetch(z,{headers:Z});if(!K.ok||!K.body){await new Promise((V)=>setTimeout(V,Q)),Q=Math.min(Q*1.5,60000);continue}Q=3000;let J=K.body.getReader(),B=new TextDecoder,X="";while(!0){let{done:V,value:$}=await J.read();if(V)break;X+=B.decode($,{stream:!0});let G=X.split(`
36
- `);X=G.pop()||"";for(let w of G){if(!w.startsWith("data: "))continue;try{let j=JSON.parse(w.slice(6));if(j.type==="connected"){F("SSE connected");continue}if(["new_task","new_message","broadcast"].includes(j.type))F(`← SSE ${j.type}`),await Yz()}catch{}}}}catch(Z){m(`SSE error: ${Z.message}`)}N(`SSE reconnecting (${Q/1000}s)...`),await new Promise((Z)=>setTimeout(Z,Q)),Q=Math.min(Q*1.5,60000)}}F("启动");F(` runtime: ${x}`);F(` model: ${D||(x==="codex"?"gpt-5.4":"claude-sonnet-4-6")} ${D?"":"(default)"}`);F(` hub: ${A}`);F(` tools: ${R.length?`[${R.join(",")}]`:"(none)"}`);F(` session: ${k||"(new)"}`);F(` log-dir: ${O}`);await Qz();F("已注册到 CommHub");setInterval(()=>M("idle").catch(()=>{}),180000);var d=async()=>{F("shutting down..."),await M("offline").catch(()=>{}),process.exit(0)};process.on("SIGINT",d);process.on("SIGTERM",d);Xz();
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/agent-node",
3
- "version": "0.7.5",
3
+ "version": "0.8.0",
4
4
  "description": "One-command AI Agent node for CommHub networks. Claude + Codex dual runtime.",
5
5
  "bin": {
6
6
  "agent-node": "./dist/cli.js"