@humanclaw/humanclaw 1.1.5 → 1.2.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 CHANGED
@@ -4,7 +4,6 @@
4
4
 
5
5
  **碳基节点编排框架 —— 将人类抽象为分布式 Worker 节点**
6
6
 
7
- [![CI](https://github.com/humanclaw/humanclaw/actions/workflows/ci.yml/badge.svg)](https://github.com/humanclaw/humanclaw/actions/workflows/ci.yml)
8
7
  [![npm](https://img.shields.io/npm/v/@humanclaw/humanclaw)](https://www.npmjs.com/package/@humanclaw/humanclaw)
9
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
10
9
 
@@ -18,7 +17,9 @@
18
17
 
19
18
  ## 概述
20
19
 
21
- HumanClaw 是一个碳基节点编排框架。系统将真实人类抽象为 Agent(碳基节点),将现实中的任务派发与结果收集抽象为进程的**挂起(Suspend)**与**恢复(Resume)**,最终将碳基节点产生的结构化结果组装并提交给上游数字系统(如 OpenClaw),完成赛博世界与碳基世界的闭环。
20
+ HumanClaw 是一个碳基节点编排框架。系统将真实人类抽象为 Agent(碳基节点),将现实中的任务派发与结果收集抽象为进程的**挂起(Suspend)**与**恢复(Resume)**。
21
+
22
+ 核心流程:输入自然语言需求 → 选人 → AI 自动规划(拆任务 + 生成话术 + 设 DDL)→ 确认分发 → 收交付物 → AI 聚合审查。
22
23
 
23
24
  ## 核心架构
24
25
 
@@ -30,17 +31,17 @@ HumanClaw 是一个碳基节点编排框架。系统将真实人类抽象为 Age
30
31
  │ │ Resume + Result │ │
31
32
  └─────┬───────┘ └──────────────┘
32
33
 
33
- Aggregate
34
+ AI Review
34
35
 
35
36
  ┌─────────────┐
36
- OpenClaw
37
- (数字生命)
37
+ LLM 审查
38
+ (Claude/GPT)│
38
39
  └─────────────┘
39
40
  ```
40
41
 
41
- - **Master 节点**:解析需求,拆解为无依赖的扁平 TODO 列表,分发给碳基节点
42
+ - **Master 节点**:输入需求,AI 自动拆解为独立子任务,分发给碳基节点
42
43
  - **Worker 节点 (HumanAgent)**:接收带 `trace_id` 的独立任务,在碳基世界异步执行
43
- - **状态机与存储**:SQLite 本地持久化,保存上下文快照,释放系统内存
44
+ - **AI 审查**:所有任务完成后,LLM 自动审查交付质量并生成报告
44
45
 
45
46
  ## 快速开始
46
47
 
@@ -65,16 +66,17 @@ humanclaw agent add
65
66
  # 交互式录入:节点名称、技能标签
66
67
  ```
67
68
 
68
- ### 查看算力池
69
+ ### AI 规划任务
69
70
 
70
71
  ```bash
71
- humanclaw agent list
72
+ humanclaw plan "完成首页重构,包括导航栏和页脚的响应式改版"
73
+ # AI 自动拆解任务、匹配碳基节点、生成话术和 DDL
72
74
  ```
73
75
 
74
- ### 查看任务状态
76
+ ### 查看算力池
75
77
 
76
78
  ```bash
77
- humanclaw status
79
+ humanclaw agent list
78
80
  ```
79
81
 
80
82
  ## API 接口
@@ -84,28 +86,21 @@ humanclaw status
84
86
  | `GET` | `/api/v1/nodes/status` | 碳基算力池状态 |
85
87
  | `POST` | `/api/v1/nodes` | 注册碳基节点 |
86
88
  | `PATCH` | `/api/v1/nodes/:id/status` | 更新节点状态 |
89
+ | `POST` | `/api/v1/jobs/plan` | AI 智能规划(不分发) |
87
90
  | `POST` | `/api/v1/jobs/create` | 创建并分发任务 |
88
91
  | `GET` | `/api/v1/jobs/active` | 获取看板数据 |
89
92
  | `POST` | `/api/v1/tasks/resume` | 提交交付物,触发恢复 |
90
93
  | `POST` | `/api/v1/tasks/reject` | 打回重做 |
91
- | `POST` | `/api/v1/jobs/:id/sync` | 聚合结果同步至 OpenClaw |
94
+ | `POST` | `/api/v1/jobs/:id/review` | AI 聚合审查交付质量 |
95
+ | `GET` | `/api/v1/config` | 获取 LLM 配置 |
96
+ | `PUT` | `/api/v1/config` | 更新 LLM 配置 |
92
97
 
93
- ### 创建任务示例
98
+ ### AI 规划示例
94
99
 
95
100
  ```bash
96
- curl -X POST http://localhost:2026/api/v1/jobs/create \
101
+ curl -X POST http://localhost:2026/api/v1/jobs/plan \
97
102
  -H "Content-Type: application/json" \
98
- -d '{
99
- "original_prompt": "完成首页重构",
100
- "openclaw_callback": "",
101
- "tasks": [
102
- {
103
- "assignee_id": "emp_xxxxxxxx",
104
- "todo_description": "实现响应式导航栏",
105
- "deadline": "2026-03-28T18:00:00Z"
106
- }
107
- ]
108
- }'
103
+ -d '{ "prompt": "完成首页重构" }'
109
104
  ```
110
105
 
111
106
  ### 提交交付物
@@ -115,7 +110,7 @@ curl -X POST http://localhost:2026/api/v1/tasks/resume \
115
110
  -H "Content-Type: application/json" \
116
111
  -d '{
117
112
  "trace_id": "TK-9527",
118
- "result_data": { "text": "导航栏已完成,支持移动端适配" }
113
+ "result_data": { "text": "https://github.com/org/repo/pull/42" }
119
114
  }'
120
115
  ```
121
116
 
@@ -123,17 +118,34 @@ curl -X POST http://localhost:2026/api/v1/tasks/resume \
123
118
 
124
119
  Web 看板包含三个核心视图:
125
120
 
126
- - **碳基算力池监控** — 实时查看碳基节点状态(🟢空闲 🟡忙碌 🔴离线 🟣崩溃)
127
- - **碳基编排大盘** — 任务 Kanban(已分发 / 已超时 / 已交付)+ Job 进度条
121
+ - **碳基算力池** — 实时查看碳基节点状态(🟢空闲 🟡忙碌 🔴离线 🟣崩溃),一键添加/删除节点
122
+ - **碳基编排大盘** — AI 智能规划 + 任务 Kanban + 可交互任务卡片(点击直接提交交付/打回)+ AI 聚合审查
128
123
  - **I/O 交付终端** — 输入 trace_id 和交付载荷,触发系统恢复
129
124
 
125
+ ### AI 功能
126
+
127
+ - **智能规划** — 输入需求,AI 自动拆任务、匹配碳基节点、生成布置话术、设 DDL(可调)
128
+ - **聚合审查** — 全部交付后,AI 审查每个交付物质量(支持 GitHub PR/Commit/Issue URL),生成评分报告
129
+ - **可配置 LLM** — 支持 Claude / OpenAI,可自定义 Base URL 接入私有模型服务(vLLM / Ollama / Azure)
130
+
130
131
  ## 核心工作流
131
132
 
132
- 1. **镜像封装** — 录入物理成员信息,构建碳基算力池
133
- 2. **拆分分发**Master 拆解任务,绑定 Agent,生成 trace_id
134
- 3. **挂起快照**上下文序列化持久化,主控进入休眠
135
- 4. **异步恢复** — 碳基节点提交产出物,系统唤醒 Job
136
- 5. **聚合闭环**所有子任务完成后,打包提交 OpenClaw
133
+ 1. **镜像封装** — 录入碳基成员信息,构建碳基算力池
134
+ 2. **AI 规划** 输入需求,AI 拆解任务、匹配节点、生成话术和 DDL
135
+ 3. **确认分发**预览规划结果,调整 DDL,确认后一键分发
136
+ 4. **异步恢复** — 碳基节点提交交付物(支持 GitHub URL),系统唤醒 Job
137
+ 5. **AI 审查** 所有子任务完成后,LLM 审查交付质量并生成报告
138
+
139
+ ## 环境变量
140
+
141
+ | 变量 | 默认值 | 说明 |
142
+ |------|--------|------|
143
+ | `HUMANCLAW_LLM_PROVIDER` | `claude` | LLM 提供商:`claude` 或 `openai` |
144
+ | `HUMANCLAW_LLM_API_KEY` | - | LLM API Key(使用 AI 功能时必填) |
145
+ | `HUMANCLAW_LLM_MODEL` | 按 provider | 可选覆盖模型名 |
146
+ | `HUMANCLAW_LLM_BASE_URL` | 官方地址 | 自定义 API 地址(私有部署) |
147
+
148
+ > Dashboard 设置面板也可以配置以上参数,优先级高于环境变量。
137
149
 
138
150
  ## 数据模型
139
151
 
@@ -154,12 +166,6 @@ interface HumanTask {
154
166
  status: TaskStatus; // PENDING | DISPATCHED | RESOLVED | OVERDUE
155
167
  result_data: unknown;
156
168
  }
157
-
158
- interface OrchestrationJob {
159
- job_id: string;
160
- original_prompt: string;
161
- openclaw_callback: string;
162
- }
163
169
  ```
164
170
 
165
171
  ## 开发
@@ -178,9 +184,10 @@ npm run lint # 类型检查
178
184
  - **Runtime**: Node.js 22+, TypeScript (ESM, strict)
179
185
  - **API**: Express v5
180
186
  - **Storage**: SQLite (better-sqlite3, WAL mode)
187
+ - **LLM**: Claude / OpenAI(原生 fetch,零依赖)
181
188
  - **CLI**: Commander.js + @clack/prompts
182
- - **Dashboard**: Vite + Vanilla TS
183
- - **Testing**: Vitest (32 tests)
189
+ - **Dashboard**: 内联 HTML(无需构建)
190
+ - **Testing**: Vitest (40 tests)
184
191
 
185
192
  ## License
186
193
 
package/README_EN.md CHANGED
@@ -4,7 +4,6 @@
4
4
 
5
5
  **Carbon-Based Node Orchestration Framework — Humans as Distributed Worker Nodes**
6
6
 
7
- [![CI](https://github.com/humanclaw/humanclaw/actions/workflows/ci.yml/badge.svg)](https://github.com/humanclaw/humanclaw/actions/workflows/ci.yml)
8
7
  [![npm](https://img.shields.io/npm/v/@humanclaw/humanclaw)](https://www.npmjs.com/package/@humanclaw/humanclaw)
9
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
10
9
 
@@ -18,7 +17,9 @@
18
17
 
19
18
  ## Overview
20
19
 
21
- HumanClaw is a carbon-based node orchestration framework. The system abstracts real humans as Agents (carbon-based nodes), models task dispatch and result collection as process **Suspend** and **Resume**, and ultimately assembles structured results from carbon-based nodes to submit to upstream digital systems (e.g., OpenClaw), closing the loop between the cyber world and the carbon-based world.
20
+ HumanClaw is a carbon-based node orchestration framework. The system abstracts real humans as Agents (carbon-based nodes), models task dispatch and result collection as process **Suspend** and **Resume**.
21
+
22
+ Core flow: natural language input → select people → AI auto-plans (breaks down tasks + generates briefings + sets deadlines) → confirm dispatch → collect deliverables → AI aggregated review.
22
23
 
23
24
  ## Core Architecture
24
25
 
@@ -30,17 +31,17 @@ HumanClaw is a carbon-based node orchestration framework. The system abstracts r
30
31
  │ │ Resume + Result │ │
31
32
  └─────┬───────┘ └──────────────┘
32
33
 
33
- Aggregate
34
+ AI Review
34
35
 
35
36
  ┌─────────────┐
36
- OpenClaw
37
- │ (Digital AI)│
37
+ LLM Review
38
+ │ (Claude/GPT)│
38
39
  └─────────────┘
39
40
  ```
40
41
 
41
- - **Master Node**: Parses requirements, breaks them into flat, independent TODOs, and dispatches to carbon-based nodes
42
+ - **Master Node**: Input requirements, AI auto-breaks them into independent sub-tasks, dispatches to carbon-based nodes
42
43
  - **Worker Node (HumanAgent)**: Receives independent tasks with a `trace_id`, executes asynchronously in the carbon-based world
43
- - **State Machine & Storage**: SQLite local persistence, saves context snapshots, frees system memory
44
+ - **AI Review**: After all tasks complete, LLM reviews deliverable quality and generates a report
44
45
 
45
46
  ## Quick Start
46
47
 
@@ -65,16 +66,17 @@ humanclaw agent add
65
66
  # Interactive prompts for: node name, capability tags
66
67
  ```
67
68
 
68
- ### View Compute Pool
69
+ ### AI Task Planning
69
70
 
70
71
  ```bash
71
- humanclaw agent list
72
+ humanclaw plan "Rebuild the homepage with responsive navbar and footer"
73
+ # AI auto-breaks tasks, matches nodes, generates briefings and deadlines
72
74
  ```
73
75
 
74
- ### Check Task Status
76
+ ### View Compute Pool
75
77
 
76
78
  ```bash
77
- humanclaw status
79
+ humanclaw agent list
78
80
  ```
79
81
 
80
82
  ## API Endpoints
@@ -84,28 +86,21 @@ humanclaw status
84
86
  | `GET` | `/api/v1/nodes/status` | Carbon compute pool status |
85
87
  | `POST` | `/api/v1/nodes` | Register carbon-based node |
86
88
  | `PATCH` | `/api/v1/nodes/:id/status` | Update node status |
89
+ | `POST` | `/api/v1/jobs/plan` | AI task planning (does not dispatch) |
87
90
  | `POST` | `/api/v1/jobs/create` | Create and dispatch job |
88
91
  | `GET` | `/api/v1/jobs/active` | Get kanban data |
89
92
  | `POST` | `/api/v1/tasks/resume` | Submit deliverable, trigger resume |
90
93
  | `POST` | `/api/v1/tasks/reject` | Reject and retry |
91
- | `POST` | `/api/v1/jobs/:id/sync` | Aggregate results and sync to OpenClaw |
94
+ | `POST` | `/api/v1/jobs/:id/review` | AI aggregated review of deliverables |
95
+ | `GET` | `/api/v1/config` | Get LLM configuration |
96
+ | `PUT` | `/api/v1/config` | Update LLM configuration |
92
97
 
93
- ### Create Job Example
98
+ ### AI Planning Example
94
99
 
95
100
  ```bash
96
- curl -X POST http://localhost:2026/api/v1/jobs/create \
101
+ curl -X POST http://localhost:2026/api/v1/jobs/plan \
97
102
  -H "Content-Type: application/json" \
98
- -d '{
99
- "original_prompt": "Rebuild the homepage",
100
- "openclaw_callback": "",
101
- "tasks": [
102
- {
103
- "assignee_id": "emp_xxxxxxxx",
104
- "todo_description": "Implement responsive navbar",
105
- "deadline": "2026-03-28T18:00:00Z"
106
- }
107
- ]
108
- }'
103
+ -d '{ "prompt": "Rebuild the homepage" }'
109
104
  ```
110
105
 
111
106
  ### Submit Deliverable
@@ -115,7 +110,7 @@ curl -X POST http://localhost:2026/api/v1/tasks/resume \
115
110
  -H "Content-Type: application/json" \
116
111
  -d '{
117
112
  "trace_id": "TK-9527",
118
- "result_data": { "text": "Navbar complete, mobile-responsive" }
113
+ "result_data": { "text": "https://github.com/org/repo/pull/42" }
119
114
  }'
120
115
  ```
121
116
 
@@ -123,17 +118,34 @@ curl -X POST http://localhost:2026/api/v1/tasks/resume \
123
118
 
124
119
  The web dashboard includes three core views:
125
120
 
126
- - **Carbon Compute Pool Monitor** — Real-time carbon-based node status (🟢Idle 🟡Busy 🔴Offline 🟣OOM)
127
- - **Carbon Orchestration Pipeline** — Task Kanban (Dispatched / Overdue / Resolved) + Job progress bars
121
+ - **Carbon Compute Pool** — Real-time carbon-based node status (🟢Idle 🟡Busy 🔴Offline 🟣OOM), add/remove nodes
122
+ - **Carbon Orchestration Pipeline** — AI planning + Task Kanban + interactive task cards (click to submit/reject) + AI review
128
123
  - **I/O Resolution Terminal** — Input trace_id and payload to trigger system resume
129
124
 
125
+ ### AI Features
126
+
127
+ - **Smart Planning** — Input requirements, AI auto-breaks tasks, matches nodes, generates briefings, sets adjustable deadlines
128
+ - **Aggregated Review** — After all deliveries, AI reviews each deliverable (supports GitHub PR/Commit/Issue URLs), generates quality report
129
+ - **Configurable LLM** — Supports Claude / OpenAI, custom Base URL for private deployments (vLLM / Ollama / Azure)
130
+
130
131
  ## Core Workflow
131
132
 
132
133
  1. **Agent Encapsulation** — Register human members, build the carbon compute pool
133
- 2. **Dispatch** — Master breaks down tasks, binds Agents, generates trace_ids
134
- 3. **Suspend & Snapshot** — Serialize context to persistence, main process hibernates
135
- 4. **Async Resume** — Carbon-based nodes submit deliverables, system wakes up the Job
136
- 5. **Aggregation** — When all sub-tasks complete, package and submit to OpenClaw
134
+ 2. **AI Planning** — Input requirements, AI breaks tasks, matches nodes, generates briefings and deadlines
135
+ 3. **Confirm Dispatch** — Preview plan, adjust deadlines, one-click dispatch
136
+ 4. **Async Resume** — Carbon-based nodes submit deliverables (supports GitHub URLs), system wakes up the Job
137
+ 5. **AI Review** — When all sub-tasks complete, LLM reviews deliverable quality and generates a report
138
+
139
+ ## Environment Variables
140
+
141
+ | Variable | Default | Description |
142
+ |----------|---------|-------------|
143
+ | `HUMANCLAW_LLM_PROVIDER` | `claude` | LLM provider: `claude` or `openai` |
144
+ | `HUMANCLAW_LLM_API_KEY` | - | LLM API Key (required for AI features) |
145
+ | `HUMANCLAW_LLM_MODEL` | per provider | Optional model override |
146
+ | `HUMANCLAW_LLM_BASE_URL` | official | Custom API URL (private deployments) |
147
+
148
+ > Dashboard settings panel can also configure these, with higher priority than env vars.
137
149
 
138
150
  ## Data Models
139
151
 
@@ -154,12 +166,6 @@ interface HumanTask {
154
166
  status: TaskStatus; // PENDING | DISPATCHED | RESOLVED | OVERDUE
155
167
  result_data: unknown;
156
168
  }
157
-
158
- interface OrchestrationJob {
159
- job_id: string;
160
- original_prompt: string;
161
- openclaw_callback: string;
162
- }
163
169
  ```
164
170
 
165
171
  ## Development
@@ -178,9 +184,10 @@ npm run lint # Type check
178
184
  - **Runtime**: Node.js 22+, TypeScript (ESM, strict)
179
185
  - **API**: Express v5
180
186
  - **Storage**: SQLite (better-sqlite3, WAL mode)
187
+ - **LLM**: Claude / OpenAI (native fetch, zero dependencies)
181
188
  - **CLI**: Commander.js + @clack/prompts
182
- - **Dashboard**: Vite + Vanilla TS
183
- - **Testing**: Vitest (32 tests)
189
+ - **Dashboard**: Inline HTML (no build step)
190
+ - **Testing**: Vitest (40 tests)
184
191
 
185
192
  ## License
186
193
 
package/dist/index.js CHANGED
@@ -35,12 +35,13 @@ function getDb(dbPath) {
35
35
  function initSchema(db2) {
36
36
  db2.exec(`
37
37
  CREATE TABLE IF NOT EXISTS agents (
38
- agent_id TEXT PRIMARY KEY,
39
- name TEXT NOT NULL,
38
+ agent_id TEXT PRIMARY KEY,
39
+ name TEXT NOT NULL,
40
40
  capabilities TEXT NOT NULL DEFAULT '[]',
41
- status TEXT NOT NULL DEFAULT 'IDLE'
42
- CHECK (status IN ('IDLE', 'BUSY', 'OFFLINE', 'OOM')),
43
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
41
+ relationship TEXT NOT NULL DEFAULT '',
42
+ status TEXT NOT NULL DEFAULT 'IDLE'
43
+ CHECK (status IN ('IDLE', 'BUSY', 'OFFLINE', 'OOM')),
44
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
44
45
  );
45
46
 
46
47
  CREATE TABLE IF NOT EXISTS jobs (
@@ -73,6 +74,10 @@ function initSchema(db2) {
73
74
  value TEXT NOT NULL
74
75
  );
75
76
  `);
77
+ const cols = db2.prepare("PRAGMA table_info(agents)").all();
78
+ if (!cols.some((c) => c.name === "relationship")) {
79
+ db2.exec(`ALTER TABLE agents ADD COLUMN relationship TEXT NOT NULL DEFAULT ''`);
80
+ }
76
81
  }
77
82
 
78
83
  // src/routes/nodes.ts
@@ -89,12 +94,13 @@ function createAgent(agent, db2) {
89
94
  const conn = db2 ?? getDb();
90
95
  const now = (/* @__PURE__ */ new Date()).toISOString();
91
96
  conn.prepare(
92
- `INSERT INTO agents (agent_id, name, capabilities, status, created_at)
93
- VALUES (?, ?, ?, ?, ?)`
97
+ `INSERT INTO agents (agent_id, name, capabilities, relationship, status, created_at)
98
+ VALUES (?, ?, ?, ?, ?, ?)`
94
99
  ).run(
95
100
  agent.agent_id,
96
101
  agent.name,
97
102
  JSON.stringify(agent.capabilities),
103
+ agent.relationship || "",
98
104
  agent.status,
99
105
  now
100
106
  );
@@ -167,7 +173,7 @@ router.get("/status", (_req, res) => {
167
173
  });
168
174
  });
169
175
  router.post("/", (req, res) => {
170
- const { name, capabilities, status } = req.body;
176
+ const { name, capabilities, relationship, status } = req.body;
171
177
  if (!name || !Array.isArray(capabilities)) {
172
178
  res.status(400).json({ error: "name and capabilities[] are required" });
173
179
  return;
@@ -176,6 +182,7 @@ router.post("/", (req, res) => {
176
182
  agent_id: generateId("emp"),
177
183
  name,
178
184
  capabilities,
185
+ relationship: relationship || "",
179
186
  status: status ?? "IDLE"
180
187
  });
181
188
  res.status(201).json(agent);
@@ -536,7 +543,8 @@ function buildUserPrompt(prompt, agents) {
536
543
  const agentList = agents.map((a) => {
537
544
  const load = a.active_task_count > 0 ? `\u5F53\u524D\u6709 ${a.active_task_count} \u4E2A\u8FDB\u884C\u4E2D\u7684\u4EFB\u52A1` : "\u5F53\u524D\u7A7A\u95F2";
538
545
  const speed = a.avg_delivery_hours !== null ? `\u5E73\u5747\u4EA4\u4ED8\u65F6\u95F4 ${a.avg_delivery_hours}h` : "\u6682\u65E0\u5386\u53F2\u6570\u636E";
539
- return `- ${a.name} (ID: ${a.agent_id}) \u6280\u80FD: [${a.capabilities.join(", ")}] ${load} ${speed}`;
546
+ const rel = a.relationship ? ` \u5173\u7CFB: ${a.relationship}` : "";
547
+ return `- ${a.name} (ID: ${a.agent_id}) \u6280\u80FD: [${a.capabilities.join(", ")}]${rel} ${load} ${speed}`;
540
548
  }).join("\n");
541
549
  return `\u5F53\u524D\u65F6\u95F4: ${now.toISOString()}
542
550
 
@@ -738,6 +746,61 @@ function rejectTask(traceId, newDeadline, db2) {
738
746
  return getTask(traceId, conn);
739
747
  }
740
748
 
749
+ // src/services/simulator.ts
750
+ function buildSimulatePrompt(agentName, relationship, capabilities, taskDescription, deadline) {
751
+ return `\u4F60\u73B0\u5728\u626E\u6F14\u4E00\u4E2A\u540D\u53EB\u300C${agentName}\u300D\u7684\u4EBA\uFF0C\u4F60\u7684\u6280\u80FD\u6807\u7B7E\u662F [${capabilities.join(", ")}]\u3002
752
+ ${relationship ? `\u4F60\u548C\u5E03\u7F6E\u4EFB\u52A1\u7684\u4EBA\u7684\u5173\u7CFB\u662F\uFF1A${relationship}\u3002` : ""}
753
+
754
+ \u4F60\u6536\u5230\u4E86\u4E00\u4E2A\u4EFB\u52A1\uFF1A
755
+ ${taskDescription}
756
+
757
+ \u622A\u6B62\u65F6\u95F4\uFF1A${new Date(deadline).toLocaleString("zh-CN")}
758
+
759
+ \u8BF7\u7AD9\u5728\u300C${agentName}\u300D\u7684\u89C6\u89D2\uFF0C\u7528\u8FD9\u4E2A\u4EBA\u7269\u81EA\u7136\u7684\u8BED\u6C14\u548C\u53E3\u543B\uFF0C\u5199\u4E00\u6BB5\u4EFB\u52A1\u4EA4\u4ED8\u6C47\u62A5\u3002
760
+ \u8981\u6C42\uFF1A
761
+ 1. \u5185\u5BB9\u8981\u8D34\u5408\u4EFB\u52A1\u63CF\u8FF0\uFF0C\u4F53\u73B0\u4E13\u4E1A\u80FD\u529B
762
+ 2. \u8BED\u6C14\u7B26\u5408\u4EBA\u7269\u8EAB\u4EFD${relationship ? "\u548C\u4E0E\u4E0A\u7EA7\u7684\u5173\u7CFB" : ""}
763
+ 3. \u6C47\u62A5\u5185\u5BB9\u5177\u4F53\u3001\u6709\u7EC6\u8282\uFF0C\u5305\u542B\u505A\u4E86\u4EC0\u4E48\u3001\u9047\u5230\u4E86\u4EC0\u4E48\u95EE\u9898\u3001\u6700\u7EC8\u7ED3\u679C\u5982\u4F55
764
+ 4. \u5B57\u6570 200-400 \u5B57
765
+ 5. \u76F4\u63A5\u8F93\u51FA\u6C47\u62A5\u5185\u5BB9\uFF0C\u4E0D\u8981\u52A0\u4EFB\u4F55\u683C\u5F0F\u524D\u7F00\u6216\u8BF4\u660E`;
766
+ }
767
+ async function simulateDelivery(traceId, provider, db2) {
768
+ const conn = db2 ?? getDb();
769
+ const task = getTask(traceId, conn);
770
+ if (!task) {
771
+ throw new Error(`Task not found: ${traceId}`);
772
+ }
773
+ const agent = getAgent(task.assignee_id, conn);
774
+ if (!agent) {
775
+ throw new Error(`Agent not found: ${task.assignee_id}`);
776
+ }
777
+ const llm = provider ?? createLlmProvider();
778
+ const response = await llm.complete({
779
+ messages: [
780
+ {
781
+ role: "system",
782
+ content: "\u4F60\u662F\u4E00\u4E2A\u89D2\u8272\u626E\u6F14\u4E13\u5BB6\uFF0C\u64C5\u957F\u6A21\u62DF\u4E0D\u540C\u4EBA\u7269\u7684\u8BED\u6C14\u548C\u6C47\u62A5\u98CE\u683C\u3002"
783
+ },
784
+ {
785
+ role: "user",
786
+ content: buildSimulatePrompt(
787
+ agent.name,
788
+ agent.relationship,
789
+ agent.capabilities,
790
+ task.todo_description,
791
+ task.deadline
792
+ )
793
+ }
794
+ ],
795
+ temperature: 0.7,
796
+ max_tokens: 1024
797
+ });
798
+ return {
799
+ trace_id: traceId,
800
+ simulated_delivery: response.content
801
+ };
802
+ }
803
+
741
804
  // src/routes/tasks.ts
742
805
  var router3 = Router3();
743
806
  router3.post("/resume", (req, res) => {
@@ -776,6 +839,21 @@ router3.post("/reject", (req, res) => {
776
839
  res.status(400).json({ error: message });
777
840
  }
778
841
  });
842
+ router3.post("/simulate", async (req, res) => {
843
+ const { trace_id } = req.body;
844
+ if (!trace_id) {
845
+ res.status(400).json({ error: "trace_id is required" });
846
+ return;
847
+ }
848
+ try {
849
+ const result = await simulateDelivery(trace_id);
850
+ res.json(result);
851
+ } catch (error) {
852
+ const message = error instanceof Error ? error.message : "Unknown error";
853
+ const status = message.includes("API Key") || message.includes("API key") ? 503 : 400;
854
+ res.status(status).json({ error: message });
855
+ }
856
+ });
779
857
  var tasks_default = router3;
780
858
 
781
859
  // src/routes/sync.ts
@@ -803,11 +881,13 @@ function buildReviewUserPrompt(originalPrompt, tasks) {
803
881
  const t = tasks[i];
804
882
  let resultText = "";
805
883
  if (t.result_data) {
806
- try {
807
- const rd = JSON.parse(t.result_data);
808
- resultText = rd.text || JSON.stringify(rd, null, 2);
809
- } catch {
884
+ if (typeof t.result_data === "string") {
810
885
  resultText = t.result_data;
886
+ } else if (typeof t.result_data === "object") {
887
+ const rd = t.result_data;
888
+ resultText = rd.text || JSON.stringify(rd, null, 2);
889
+ } else {
890
+ resultText = String(t.result_data);
811
891
  }
812
892
  }
813
893
  prompt += `--- \u5B50\u4EFB\u52A1 ${i + 1} ---
@@ -1131,6 +1211,7 @@ async function loadFleet(el){
1131
1211
  for(const a of d.agents){
1132
1212
  h+='<div class="card"><div class="agent-header"><span class="dot '+a.status+'"></span><span class="agent-name">'+esc(a.name)+'</span></div>';
1133
1213
  h+='<div class="agent-id">'+a.agent_id+'</div>';
1214
+ if(a.relationship)h+='<div style="font-size:11px;color:var(--purple);margin-bottom:4px">&#128101; '+esc(a.relationship)+'</div>';
1134
1215
  h+='<div class="caps">';for(const c of a.capabilities)h+='<span class="cap">'+esc(c)+'</span>';h+='</div>';
1135
1216
  h+='<div class="agent-meta"><span>\u4EFB\u52A1: '+a.active_task_count+'</span>';
1136
1217
  if(a.avg_delivery_hours!==null)h+='<span>\u5E73\u5747\u4EA4\u4ED8: '+a.avg_delivery_hours+'h</span>';
@@ -1152,6 +1233,7 @@ window.showAddAgent=function(){
1152
1233
  ov.innerHTML='<div class="form-card"><h3>+ \u6DFB\u52A0\u78B3\u57FA\u8282\u70B9</h3>'
1153
1234
  +'<div class="fg"><label>\u8282\u70B9\u540D\u79F0</label><input id="aa-name" placeholder="\u4F8B: \u524D\u7AEF\u8001\u674E"/></div>'
1154
1235
  +'<div class="fg"><label>\u6280\u80FD\u6807\u7B7E</label><input id="aa-caps" placeholder="\u4F8B: UI/UX, \u524D\u7AEF\u5F00\u53D1, \u6297\u538B\u80FD\u529B\u5F3A"/><div class="hint">\u591A\u4E2A\u6807\u7B7E\u7528\u9017\u53F7\u5206\u9694</div></div>'
1236
+ +'<div class="fg"><label>\u4E0E\u4F60\u7684\u5173\u7CFB <span style="color:var(--text-dim);font-weight:400">(\u53EF\u9009)</span></label><input id="aa-rel" placeholder="\u4F8B: \u76F4\u5C5E\u4E0B\u5C5E / \u5B9E\u4E60\u751F / \u5916\u5305\u540C\u4E8B / \u4E49\u5F1F"/><div class="hint">\u63CF\u8FF0\u6B64\u4EBA\u4E0E\u4F60\u7684\u5173\u7CFB\uFF0CAI \u89C4\u5212\u548C\u6A21\u62DF\u4EA4\u4ED8\u65F6\u4F1A\u53C2\u8003</div></div>'
1155
1237
  +'<div class="btn-group"><button class="btn btn-primary" onclick="submitAgent()">\u6CE8\u518C\u8282\u70B9</button><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button></div></div>';
1156
1238
  document.body.appendChild(ov);
1157
1239
  document.getElementById('aa-name').focus();
@@ -1159,10 +1241,11 @@ window.showAddAgent=function(){
1159
1241
  window.submitAgent=async function(){
1160
1242
  const name=document.getElementById('aa-name').value.trim();
1161
1243
  const caps=document.getElementById('aa-caps').value.trim();
1244
+ const rel=document.getElementById('aa-rel').value.trim();
1162
1245
  if(!name){toast('\u8BF7\u8F93\u5165\u8282\u70B9\u540D\u79F0',false);return}
1163
1246
  if(!caps){toast('\u8BF7\u8F93\u5165\u81F3\u5C11\u4E00\u4E2A\u6280\u80FD\u6807\u7B7E',false);return}
1164
1247
  try{
1165
- const r=await fetch(API+'/nodes',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name,capabilities:caps.split(',').map(s=>s.trim()).filter(Boolean)})});
1248
+ const r=await fetch(API+'/nodes',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name,capabilities:caps.split(',').map(s=>s.trim()).filter(Boolean),relationship:rel})});
1166
1249
  const d=await r.json();
1167
1250
  if(!r.ok){toast(d.error||'\u6CE8\u518C\u5931\u8D25',false);return}
1168
1251
  toast('\u8282\u70B9 '+d.agent_id+' \u6CE8\u518C\u6210\u529F\uFF01',true);
@@ -1439,13 +1522,14 @@ window.showTaskDetail=function(traceId){
1439
1522
  if(t.status==='RESOLVED'&&t.result_data){
1440
1523
  // Show result
1441
1524
  let resultText='';
1442
- try{const rd=JSON.parse(t.result_data);resultText=rd.text||JSON.stringify(rd,null,2)}catch{resultText=t.result_data}
1525
+ if(typeof t.result_data==='object'&&t.result_data){resultText=t.result_data.text||JSON.stringify(t.result_data,null,2)}else{resultText=String(t.result_data||'')}
1443
1526
  h+='<div class="detail-row"><div class="detail-label">\u4EA4\u4ED8\u7ED3\u679C</div><div class="result-display">'+esc(resultText)+'</div></div>';
1444
1527
  h+='<div class="btn-group"><button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u5173\u95ED</button></div>';
1445
1528
  }else{
1446
1529
  // Input for resume/reject
1447
1530
  h+='<div class="fg" style="margin-top:16px"><label>\u63D0\u4EA4 Human \u4EA4\u4ED8\u7269</label><textarea id="td-payload" rows="4" placeholder="\u7C98\u8D34\u4EA4\u4ED8\u7269\u5185\u5BB9\u3001GitHub PR/Commit URL\u3001\u5DE5\u4F5C\u6C47\u62A5\u7B49..."></textarea><div class="hint">\u652F\u6301\u8D34 GitHub URL\uFF08PR\u3001Commit\u3001Issue\uFF09\uFF0CAI \u5BA1\u67E5\u65F6\u4F1A\u5206\u6790</div></div>';
1448
1531
  h+='<div class="btn-group">';
1532
+ h+='<button class="btn btn-primary btn-sm" onclick="simulateDelivery(\\''+t.trace_id+'\\')">&#129302; \u6A21\u62DF\u4EA4\u4ED8</button>';
1449
1533
  h+='<button class="btn btn-green" onclick="taskResume(\\''+t.trace_id+'\\')">\u63D0\u4EA4\u4EA4\u4ED8 (Resume)</button>';
1450
1534
  h+='<button class="btn btn-danger" onclick="taskReject(\\''+t.trace_id+'\\')">\u6253\u56DE\u91CD\u505A (Reject)</button>';
1451
1535
  h+='<button class="btn btn-ghost" onclick="document.getElementById(\\'overlay\\').remove()">\u53D6\u6D88</button>';
@@ -1481,6 +1565,21 @@ window.taskReject=async function(traceId){
1481
1565
  }catch{toast('\u7F51\u7EDC\u9519\u8BEF',false)}
1482
1566
  };
1483
1567
 
1568
+ window.simulateDelivery=async function(traceId){
1569
+ const btn=event.target;
1570
+ const origText=btn.innerHTML;
1571
+ btn.disabled=true;btn.innerHTML='&#8987; AI \u751F\u6210\u4E2D...';
1572
+ try{
1573
+ const r=await fetch(API+'/tasks/simulate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({trace_id:traceId})});
1574
+ const d=await r.json();
1575
+ if(!r.ok){toast(d.error||'\u6A21\u62DF\u5931\u8D25',false);btn.disabled=false;btn.innerHTML=origText;return}
1576
+ const ta=document.getElementById('td-payload');
1577
+ if(ta)ta.value=d.simulated_delivery;
1578
+ toast('\u5DF2\u751F\u6210\u6A21\u62DF\u4EA4\u4ED8\u5185\u5BB9',true);
1579
+ btn.disabled=false;btn.innerHTML=origText;
1580
+ }catch(e){toast('\u7F51\u7EDC\u9519\u8BEF: '+e.message,false);btn.disabled=false;btn.innerHTML=origText}
1581
+ };
1582
+
1484
1583
  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1485
1584
  // TERMINAL
1486
1585
  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
@@ -1660,10 +1759,20 @@ agentCmd.command("add").description("Register a new carbon-based node").action(a
1660
1759
  process.exit(0);
1661
1760
  }
1662
1761
  const capabilities = capInput.split(",").map((s) => s.trim()).filter(Boolean);
1762
+ const relInput = await p.text({
1763
+ message: "Relationship with you (optional):",
1764
+ placeholder: "e.g. direct report / intern / contractor",
1765
+ defaultValue: ""
1766
+ });
1767
+ if (p.isCancel(relInput)) {
1768
+ p.cancel("Cancelled.");
1769
+ process.exit(0);
1770
+ }
1663
1771
  const agent = createAgent({
1664
1772
  agent_id: generateId("emp"),
1665
1773
  name,
1666
1774
  capabilities,
1775
+ relationship: relInput || "",
1667
1776
  status: "IDLE"
1668
1777
  });
1669
1778
  p.outro(