@humanclaw/humanclaw 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +187 -0
- package/README_EN.md +187 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +710 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HumanClaw
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# HumanClaw
|
|
4
|
+
|
|
5
|
+
**异步物理节点编排框架 —— 将人类抽象为分布式 Worker 节点**
|
|
6
|
+
|
|
7
|
+
[](https://github.com/humanclaw/humanclaw/actions/workflows/ci.yml)
|
|
8
|
+
[](https://www.npmjs.com/package/@humanclaw/humanclaw)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
|
|
11
|
+
[English](./README_EN.md) | **中文**
|
|
12
|
+
|
|
13
|
+
[文档站点](https://humanclaw.github.io/humanclaw/)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 概述
|
|
20
|
+
|
|
21
|
+
HumanClaw 是一个基于「物理世界异步 I/O」的单层分布式任务编排框架。系统将真实人类抽象为 Agent(物理节点),将现实中的任务派发与结果收集抽象为进程的**挂起(Suspend)**与**恢复(Resume)**,最终将物理节点产生的结构化结果组装并提交给上游数字系统(如 OpenClaw),完成赛博世界与物理世界的闭环。
|
|
22
|
+
|
|
23
|
+
## 核心架构
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
┌─────────────┐ Dispatch ┌──────────────┐
|
|
27
|
+
│ │ ──────────────► │ │
|
|
28
|
+
│ Master │ trace_id │ HumanAgent │
|
|
29
|
+
│ (老板/PM) │ ◄────────────── │ (碳基算力) │
|
|
30
|
+
│ │ Resume + Result │ │
|
|
31
|
+
└─────┬───────┘ └──────────────┘
|
|
32
|
+
│
|
|
33
|
+
│ Aggregate
|
|
34
|
+
▼
|
|
35
|
+
┌─────────────┐
|
|
36
|
+
│ OpenClaw │
|
|
37
|
+
│ (数字生命) │
|
|
38
|
+
└─────────────┘
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- **Master 节点**:解析需求,拆解为无依赖的扁平 TODO 列表,分发给物理节点
|
|
42
|
+
- **Worker 节点 (HumanAgent)**:接收带 `trace_id` 的独立任务,在物理世界异步执行
|
|
43
|
+
- **状态机与存储**:SQLite 本地持久化,保存上下文快照,释放系统内存
|
|
44
|
+
|
|
45
|
+
## 快速开始
|
|
46
|
+
|
|
47
|
+
### 安装
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install -g @humanclaw/humanclaw
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 启动服务
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
humanclaw serve
|
|
57
|
+
# 服务运行在 http://localhost:3000
|
|
58
|
+
# Dashboard 看板:http://localhost:3000
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 注册物理节点
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
humanclaw agent add
|
|
65
|
+
# 交互式录入:节点名称、技能标签
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 查看算力池
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
humanclaw agent list
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 查看任务状态
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
humanclaw status
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API 接口
|
|
81
|
+
|
|
82
|
+
| 方法 | 路径 | 说明 |
|
|
83
|
+
|------|------|------|
|
|
84
|
+
| `GET` | `/api/v1/nodes/status` | 碳基算力池状态 |
|
|
85
|
+
| `POST` | `/api/v1/nodes` | 注册物理节点 |
|
|
86
|
+
| `PATCH` | `/api/v1/nodes/:id/status` | 更新节点状态 |
|
|
87
|
+
| `POST` | `/api/v1/jobs/create` | 创建并分发任务 |
|
|
88
|
+
| `GET` | `/api/v1/jobs/active` | 获取看板数据 |
|
|
89
|
+
| `POST` | `/api/v1/tasks/resume` | 提交交付物,触发恢复 |
|
|
90
|
+
| `POST` | `/api/v1/tasks/reject` | 打回重做 |
|
|
91
|
+
| `POST` | `/api/v1/jobs/:id/sync` | 聚合结果同步至 OpenClaw |
|
|
92
|
+
|
|
93
|
+
### 创建任务示例
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
curl -X POST http://localhost:3000/api/v1/jobs/create \
|
|
97
|
+
-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
|
+
}'
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 提交交付物
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
curl -X POST http://localhost:3000/api/v1/tasks/resume \
|
|
115
|
+
-H "Content-Type: application/json" \
|
|
116
|
+
-d '{
|
|
117
|
+
"trace_id": "TK-9527",
|
|
118
|
+
"result_data": { "text": "导航栏已完成,支持移动端适配" }
|
|
119
|
+
}'
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Dashboard 看板
|
|
123
|
+
|
|
124
|
+
Web 看板包含三个核心视图:
|
|
125
|
+
|
|
126
|
+
- **碳基算力池监控** — 实时查看物理节点状态(🟢空闲 🟡忙碌 🔴离线 🟣崩溃)
|
|
127
|
+
- **异步编排大盘** — 任务 Kanban(已分发 / 已超时 / 已交付)+ Job 进度条
|
|
128
|
+
- **I/O 交付终端** — 输入 trace_id 和交付载荷,触发系统恢复
|
|
129
|
+
|
|
130
|
+
## 核心工作流
|
|
131
|
+
|
|
132
|
+
1. **镜像封装** — 录入物理成员信息,构建碳基算力池
|
|
133
|
+
2. **拆分分发** — Master 拆解任务,绑定 Agent,生成 trace_id
|
|
134
|
+
3. **挂起快照** — 上下文序列化持久化,主控进入休眠
|
|
135
|
+
4. **异步恢复** — 物理节点提交产出物,系统唤醒 Job
|
|
136
|
+
5. **聚合闭环** — 所有子任务完成后,打包提交 OpenClaw
|
|
137
|
+
|
|
138
|
+
## 数据模型
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
interface HumanAgent {
|
|
142
|
+
agent_id: string; // emp_xxxxxxxx
|
|
143
|
+
name: string; // "前端老李"
|
|
144
|
+
capabilities: string[]; // ["UI/UX", "前端开发"]
|
|
145
|
+
status: AgentStatus; // IDLE | BUSY | OFFLINE | OOM
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface HumanTask {
|
|
149
|
+
trace_id: string; // TK-9527
|
|
150
|
+
job_id: string;
|
|
151
|
+
assignee_id: string;
|
|
152
|
+
todo_description: string;
|
|
153
|
+
deadline: string;
|
|
154
|
+
status: TaskStatus; // PENDING | DISPATCHED | RESOLVED | OVERDUE
|
|
155
|
+
result_data: unknown;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface OrchestrationJob {
|
|
159
|
+
job_id: string;
|
|
160
|
+
original_prompt: string;
|
|
161
|
+
openclaw_callback: string;
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## 开发
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
git clone https://github.com/humanclaw/humanclaw.git
|
|
169
|
+
cd humanclaw
|
|
170
|
+
npm install
|
|
171
|
+
npm run dev # 启动开发服务器
|
|
172
|
+
npm test # 运行测试
|
|
173
|
+
npm run lint # 类型检查
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## 技术栈
|
|
177
|
+
|
|
178
|
+
- **Runtime**: Node.js 22+, TypeScript (ESM, strict)
|
|
179
|
+
- **API**: Express v5
|
|
180
|
+
- **Storage**: SQLite (better-sqlite3, WAL mode)
|
|
181
|
+
- **CLI**: Commander.js + @clack/prompts
|
|
182
|
+
- **Dashboard**: Vite + Vanilla TS
|
|
183
|
+
- **Testing**: Vitest (32 tests)
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
[MIT](./LICENSE)
|
package/README_EN.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# HumanClaw
|
|
4
|
+
|
|
5
|
+
**Async Physical Node Orchestration Framework — Humans as Distributed Worker Nodes**
|
|
6
|
+
|
|
7
|
+
[](https://github.com/humanclaw/humanclaw/actions/workflows/ci.yml)
|
|
8
|
+
[](https://www.npmjs.com/package/@humanclaw/humanclaw)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
|
|
11
|
+
**中文** | [English](./README_EN.md)
|
|
12
|
+
|
|
13
|
+
[Documentation Site](https://humanclaw.github.io/humanclaw/)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
HumanClaw is a flat-layer distributed task orchestration framework based on "physical-world async I/O". The system abstracts real humans as Agents (physical nodes), models task dispatch and result collection as process **Suspend** and **Resume**, and ultimately assembles structured results from physical nodes to submit to upstream digital systems (e.g., OpenClaw), closing the loop between the cyber world and the physical world.
|
|
22
|
+
|
|
23
|
+
## Core Architecture
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
┌─────────────┐ Dispatch ┌──────────────┐
|
|
27
|
+
│ │ ──────────────► │ │
|
|
28
|
+
│ Master │ trace_id │ HumanAgent │
|
|
29
|
+
│ (Boss/PM) │ ◄────────────── │ (Carbon CPU)│
|
|
30
|
+
│ │ Resume + Result │ │
|
|
31
|
+
└─────┬───────┘ └──────────────┘
|
|
32
|
+
│
|
|
33
|
+
│ Aggregate
|
|
34
|
+
▼
|
|
35
|
+
┌─────────────┐
|
|
36
|
+
│ OpenClaw │
|
|
37
|
+
│ (Digital AI)│
|
|
38
|
+
└─────────────┘
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- **Master Node**: Parses requirements, breaks them into flat, independent TODOs, and dispatches to physical nodes
|
|
42
|
+
- **Worker Node (HumanAgent)**: Receives independent tasks with a `trace_id`, executes asynchronously in the physical world
|
|
43
|
+
- **State Machine & Storage**: SQLite local persistence, saves context snapshots, frees system memory
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
### Install
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install -g @humanclaw/humanclaw
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Start Server
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
humanclaw serve
|
|
57
|
+
# Server runs at http://localhost:3000
|
|
58
|
+
# Dashboard: http://localhost:3000
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Register a Physical Node
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
humanclaw agent add
|
|
65
|
+
# Interactive prompts for: node name, capability tags
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### View Compute Pool
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
humanclaw agent list
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Check Task Status
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
humanclaw status
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API Endpoints
|
|
81
|
+
|
|
82
|
+
| Method | Path | Description |
|
|
83
|
+
|--------|------|-------------|
|
|
84
|
+
| `GET` | `/api/v1/nodes/status` | Carbon compute pool status |
|
|
85
|
+
| `POST` | `/api/v1/nodes` | Register physical node |
|
|
86
|
+
| `PATCH` | `/api/v1/nodes/:id/status` | Update node status |
|
|
87
|
+
| `POST` | `/api/v1/jobs/create` | Create and dispatch job |
|
|
88
|
+
| `GET` | `/api/v1/jobs/active` | Get kanban data |
|
|
89
|
+
| `POST` | `/api/v1/tasks/resume` | Submit deliverable, trigger resume |
|
|
90
|
+
| `POST` | `/api/v1/tasks/reject` | Reject and retry |
|
|
91
|
+
| `POST` | `/api/v1/jobs/:id/sync` | Aggregate results and sync to OpenClaw |
|
|
92
|
+
|
|
93
|
+
### Create Job Example
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
curl -X POST http://localhost:3000/api/v1/jobs/create \
|
|
97
|
+
-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
|
+
}'
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Submit Deliverable
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
curl -X POST http://localhost:3000/api/v1/tasks/resume \
|
|
115
|
+
-H "Content-Type: application/json" \
|
|
116
|
+
-d '{
|
|
117
|
+
"trace_id": "TK-9527",
|
|
118
|
+
"result_data": { "text": "Navbar complete, mobile-responsive" }
|
|
119
|
+
}'
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Dashboard
|
|
123
|
+
|
|
124
|
+
The web dashboard includes three core views:
|
|
125
|
+
|
|
126
|
+
- **Carbon Compute Pool Monitor** — Real-time physical node status (🟢Idle 🟡Busy 🔴Offline 🟣OOM)
|
|
127
|
+
- **Async Task Pipeline** — Task Kanban (Dispatched / Overdue / Resolved) + Job progress bars
|
|
128
|
+
- **I/O Resolution Terminal** — Input trace_id and payload to trigger system resume
|
|
129
|
+
|
|
130
|
+
## Core Workflow
|
|
131
|
+
|
|
132
|
+
1. **Agent Encapsulation** — Register physical 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** — Physical nodes submit deliverables, system wakes up the Job
|
|
136
|
+
5. **Aggregation** — When all sub-tasks complete, package and submit to OpenClaw
|
|
137
|
+
|
|
138
|
+
## Data Models
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
interface HumanAgent {
|
|
142
|
+
agent_id: string; // emp_xxxxxxxx
|
|
143
|
+
name: string; // "Frontend Dev Li"
|
|
144
|
+
capabilities: string[]; // ["UI/UX", "Frontend"]
|
|
145
|
+
status: AgentStatus; // IDLE | BUSY | OFFLINE | OOM
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface HumanTask {
|
|
149
|
+
trace_id: string; // TK-9527
|
|
150
|
+
job_id: string;
|
|
151
|
+
assignee_id: string;
|
|
152
|
+
todo_description: string;
|
|
153
|
+
deadline: string;
|
|
154
|
+
status: TaskStatus; // PENDING | DISPATCHED | RESOLVED | OVERDUE
|
|
155
|
+
result_data: unknown;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface OrchestrationJob {
|
|
159
|
+
job_id: string;
|
|
160
|
+
original_prompt: string;
|
|
161
|
+
openclaw_callback: string;
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Development
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
git clone https://github.com/humanclaw/humanclaw.git
|
|
169
|
+
cd humanclaw
|
|
170
|
+
npm install
|
|
171
|
+
npm run dev # Start dev server
|
|
172
|
+
npm test # Run tests
|
|
173
|
+
npm run lint # Type check
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Tech Stack
|
|
177
|
+
|
|
178
|
+
- **Runtime**: Node.js 22+, TypeScript (ESM, strict)
|
|
179
|
+
- **API**: Express v5
|
|
180
|
+
- **Storage**: SQLite (better-sqlite3, WAL mode)
|
|
181
|
+
- **CLI**: Commander.js + @clack/prompts
|
|
182
|
+
- **Dashboard**: Vite + Vanilla TS
|
|
183
|
+
- **Testing**: Vitest (32 tests)
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
[MIT](./LICENSE)
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import * as p from "@clack/prompts";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
|
|
8
|
+
// src/server.ts
|
|
9
|
+
import express from "express";
|
|
10
|
+
import cors from "cors";
|
|
11
|
+
import path2 from "path";
|
|
12
|
+
import fs2 from "fs";
|
|
13
|
+
|
|
14
|
+
// src/db/connection.ts
|
|
15
|
+
import Database from "better-sqlite3";
|
|
16
|
+
import path from "path";
|
|
17
|
+
import fs from "fs";
|
|
18
|
+
var db = null;
|
|
19
|
+
var DEFAULT_DB_PATH = path.join(
|
|
20
|
+
process.env.HUMANCLAW_DATA_DIR ?? process.cwd(),
|
|
21
|
+
"humanclaw.db"
|
|
22
|
+
);
|
|
23
|
+
function getDb(dbPath) {
|
|
24
|
+
if (db) return db;
|
|
25
|
+
const resolvedPath = dbPath ?? DEFAULT_DB_PATH;
|
|
26
|
+
const dir = path.dirname(resolvedPath);
|
|
27
|
+
if (!fs.existsSync(dir)) {
|
|
28
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
db = new Database(resolvedPath);
|
|
31
|
+
db.pragma("journal_mode = WAL");
|
|
32
|
+
db.pragma("foreign_keys = ON");
|
|
33
|
+
return db;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/db/schema.ts
|
|
37
|
+
function initSchema(db2) {
|
|
38
|
+
db2.exec(`
|
|
39
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
40
|
+
agent_id TEXT PRIMARY KEY,
|
|
41
|
+
name TEXT NOT NULL,
|
|
42
|
+
capabilities TEXT NOT NULL DEFAULT '[]',
|
|
43
|
+
status TEXT NOT NULL DEFAULT 'IDLE'
|
|
44
|
+
CHECK (status IN ('IDLE', 'BUSY', 'OFFLINE', 'OOM')),
|
|
45
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE TABLE IF NOT EXISTS jobs (
|
|
49
|
+
job_id TEXT PRIMARY KEY,
|
|
50
|
+
original_prompt TEXT NOT NULL,
|
|
51
|
+
openclaw_callback TEXT NOT NULL DEFAULT '',
|
|
52
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
56
|
+
trace_id TEXT PRIMARY KEY,
|
|
57
|
+
job_id TEXT NOT NULL REFERENCES jobs(job_id) ON DELETE CASCADE,
|
|
58
|
+
assignee_id TEXT NOT NULL REFERENCES agents(agent_id),
|
|
59
|
+
todo_description TEXT NOT NULL,
|
|
60
|
+
deadline TEXT NOT NULL,
|
|
61
|
+
payload TEXT NOT NULL DEFAULT '{}',
|
|
62
|
+
status TEXT NOT NULL DEFAULT 'PENDING'
|
|
63
|
+
CHECK (status IN ('PENDING', 'DISPATCHED', 'RESOLVED', 'OVERDUE')),
|
|
64
|
+
result_data TEXT,
|
|
65
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
66
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_job_id ON tasks(job_id);
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_id ON tasks(assignee_id);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
72
|
+
`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/routes/nodes.ts
|
|
76
|
+
import { Router } from "express";
|
|
77
|
+
|
|
78
|
+
// src/models/agent.ts
|
|
79
|
+
function rowToAgent(row) {
|
|
80
|
+
return {
|
|
81
|
+
...row,
|
|
82
|
+
capabilities: JSON.parse(row.capabilities)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function createAgent(agent, db2) {
|
|
86
|
+
const conn = db2 ?? getDb();
|
|
87
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
88
|
+
conn.prepare(
|
|
89
|
+
`INSERT INTO agents (agent_id, name, capabilities, status, created_at)
|
|
90
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
91
|
+
).run(
|
|
92
|
+
agent.agent_id,
|
|
93
|
+
agent.name,
|
|
94
|
+
JSON.stringify(agent.capabilities),
|
|
95
|
+
agent.status,
|
|
96
|
+
now
|
|
97
|
+
);
|
|
98
|
+
return { ...agent, created_at: now };
|
|
99
|
+
}
|
|
100
|
+
function getAgent(agentId, db2) {
|
|
101
|
+
const conn = db2 ?? getDb();
|
|
102
|
+
const row = conn.prepare("SELECT * FROM agents WHERE agent_id = ?").get(agentId);
|
|
103
|
+
return row ? rowToAgent(row) : void 0;
|
|
104
|
+
}
|
|
105
|
+
function listAgents(db2) {
|
|
106
|
+
const conn = db2 ?? getDb();
|
|
107
|
+
const rows = conn.prepare("SELECT * FROM agents ORDER BY created_at").all();
|
|
108
|
+
return rows.map(rowToAgent);
|
|
109
|
+
}
|
|
110
|
+
function updateAgentStatus(agentId, status, db2) {
|
|
111
|
+
const conn = db2 ?? getDb();
|
|
112
|
+
const result = conn.prepare("UPDATE agents SET status = ? WHERE agent_id = ?").run(status, agentId);
|
|
113
|
+
return result.changes > 0;
|
|
114
|
+
}
|
|
115
|
+
function deleteAgent(agentId, db2) {
|
|
116
|
+
const conn = db2 ?? getDb();
|
|
117
|
+
const result = conn.prepare("DELETE FROM agents WHERE agent_id = ?").run(agentId);
|
|
118
|
+
return result.changes > 0;
|
|
119
|
+
}
|
|
120
|
+
function listAgentsWithMetrics(db2) {
|
|
121
|
+
const conn = db2 ?? getDb();
|
|
122
|
+
const rows = conn.prepare(
|
|
123
|
+
`SELECT
|
|
124
|
+
a.*,
|
|
125
|
+
COUNT(CASE WHEN t.status IN ('DISPATCHED', 'PENDING') THEN 1 END) AS active_task_count,
|
|
126
|
+
AVG(
|
|
127
|
+
CASE WHEN t.status = 'RESOLVED'
|
|
128
|
+
THEN (julianday(t.updated_at) - julianday(t.created_at)) * 24
|
|
129
|
+
END
|
|
130
|
+
) AS avg_delivery_hours
|
|
131
|
+
FROM agents a
|
|
132
|
+
LEFT JOIN tasks t ON a.agent_id = t.assignee_id
|
|
133
|
+
GROUP BY a.agent_id
|
|
134
|
+
ORDER BY a.created_at`
|
|
135
|
+
).all();
|
|
136
|
+
return rows.map((row) => ({
|
|
137
|
+
...rowToAgent(row),
|
|
138
|
+
active_task_count: row.active_task_count,
|
|
139
|
+
avg_delivery_hours: row.avg_delivery_hours ? Math.round(row.avg_delivery_hours * 100) / 100 : null
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/utils/trace-id.ts
|
|
144
|
+
import { nanoid } from "nanoid";
|
|
145
|
+
function generateTraceId() {
|
|
146
|
+
const num = Math.floor(Math.random() * 1e4).toString().padStart(4, "0");
|
|
147
|
+
return `TK-${num}`;
|
|
148
|
+
}
|
|
149
|
+
function generateId(prefix) {
|
|
150
|
+
return `${prefix}_${nanoid(8)}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/routes/nodes.ts
|
|
154
|
+
var router = Router();
|
|
155
|
+
router.get("/status", (_req, res) => {
|
|
156
|
+
const agents = listAgentsWithMetrics();
|
|
157
|
+
res.json({
|
|
158
|
+
total: agents.length,
|
|
159
|
+
idle: agents.filter((a) => a.status === "IDLE").length,
|
|
160
|
+
busy: agents.filter((a) => a.status === "BUSY").length,
|
|
161
|
+
offline: agents.filter((a) => a.status === "OFFLINE").length,
|
|
162
|
+
oom: agents.filter((a) => a.status === "OOM").length,
|
|
163
|
+
agents
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
router.post("/", (req, res) => {
|
|
167
|
+
const { name, capabilities, status } = req.body;
|
|
168
|
+
if (!name || !Array.isArray(capabilities)) {
|
|
169
|
+
res.status(400).json({ error: "name and capabilities[] are required" });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const agent = createAgent({
|
|
173
|
+
agent_id: generateId("emp"),
|
|
174
|
+
name,
|
|
175
|
+
capabilities,
|
|
176
|
+
status: status ?? "IDLE"
|
|
177
|
+
});
|
|
178
|
+
res.status(201).json(agent);
|
|
179
|
+
});
|
|
180
|
+
router.patch("/:agent_id/status", (req, res) => {
|
|
181
|
+
const { agent_id } = req.params;
|
|
182
|
+
const { status } = req.body;
|
|
183
|
+
const validStatuses = ["IDLE", "BUSY", "OFFLINE", "OOM"];
|
|
184
|
+
if (!validStatuses.includes(status)) {
|
|
185
|
+
res.status(400).json({
|
|
186
|
+
error: `Invalid status. Must be one of: ${validStatuses.join(", ")}`
|
|
187
|
+
});
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const updated = updateAgentStatus(agent_id, status);
|
|
191
|
+
if (!updated) {
|
|
192
|
+
res.status(404).json({ error: `Agent not found: ${agent_id}` });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
res.json({ agent_id, status });
|
|
196
|
+
});
|
|
197
|
+
router.delete("/:agent_id", (req, res) => {
|
|
198
|
+
const { agent_id } = req.params;
|
|
199
|
+
const deleted = deleteAgent(agent_id);
|
|
200
|
+
if (!deleted) {
|
|
201
|
+
res.status(404).json({ error: `Agent not found: ${agent_id}` });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
res.json({ deleted: agent_id });
|
|
205
|
+
});
|
|
206
|
+
var nodes_default = router;
|
|
207
|
+
|
|
208
|
+
// src/routes/jobs.ts
|
|
209
|
+
import { Router as Router2 } from "express";
|
|
210
|
+
|
|
211
|
+
// src/models/task.ts
|
|
212
|
+
function rowToTask(row) {
|
|
213
|
+
return {
|
|
214
|
+
...row,
|
|
215
|
+
payload: JSON.parse(row.payload),
|
|
216
|
+
result_data: row.result_data ? JSON.parse(row.result_data) : null
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function createTask(task, db2) {
|
|
220
|
+
const conn = db2 ?? getDb();
|
|
221
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
222
|
+
conn.prepare(
|
|
223
|
+
`INSERT INTO tasks (trace_id, job_id, assignee_id, todo_description, deadline, payload, status, created_at, updated_at)
|
|
224
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
225
|
+
).run(
|
|
226
|
+
task.trace_id,
|
|
227
|
+
task.job_id,
|
|
228
|
+
task.assignee_id,
|
|
229
|
+
task.todo_description,
|
|
230
|
+
task.deadline,
|
|
231
|
+
JSON.stringify(task.payload),
|
|
232
|
+
task.status,
|
|
233
|
+
now,
|
|
234
|
+
now
|
|
235
|
+
);
|
|
236
|
+
return { ...task, result_data: null, created_at: now, updated_at: now };
|
|
237
|
+
}
|
|
238
|
+
function getTask(traceId, db2) {
|
|
239
|
+
const conn = db2 ?? getDb();
|
|
240
|
+
const row = conn.prepare("SELECT * FROM tasks WHERE trace_id = ?").get(traceId);
|
|
241
|
+
return row ? rowToTask(row) : void 0;
|
|
242
|
+
}
|
|
243
|
+
function listTasksByJob(jobId, db2) {
|
|
244
|
+
const conn = db2 ?? getDb();
|
|
245
|
+
const rows = conn.prepare("SELECT * FROM tasks WHERE job_id = ? ORDER BY created_at").all(jobId);
|
|
246
|
+
return rows.map(rowToTask);
|
|
247
|
+
}
|
|
248
|
+
function listTasksByAssignee(assigneeId, db2) {
|
|
249
|
+
const conn = db2 ?? getDb();
|
|
250
|
+
const rows = conn.prepare("SELECT * FROM tasks WHERE assignee_id = ? ORDER BY created_at").all(assigneeId);
|
|
251
|
+
return rows.map(rowToTask);
|
|
252
|
+
}
|
|
253
|
+
function resolveTask(traceId, resultData, db2) {
|
|
254
|
+
const conn = db2 ?? getDb();
|
|
255
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
256
|
+
const result = conn.prepare(
|
|
257
|
+
`UPDATE tasks
|
|
258
|
+
SET status = 'RESOLVED', result_data = ?, updated_at = ?
|
|
259
|
+
WHERE trace_id = ? AND status IN ('DISPATCHED', 'OVERDUE')`
|
|
260
|
+
).run(JSON.stringify(resultData), now, traceId);
|
|
261
|
+
return result.changes > 0;
|
|
262
|
+
}
|
|
263
|
+
function markOverdueTasks(db2) {
|
|
264
|
+
const conn = db2 ?? getDb();
|
|
265
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
266
|
+
const result = conn.prepare(
|
|
267
|
+
`UPDATE tasks
|
|
268
|
+
SET status = 'OVERDUE', updated_at = ?
|
|
269
|
+
WHERE status = 'DISPATCHED' AND deadline < ?`
|
|
270
|
+
).run(now, now);
|
|
271
|
+
return result.changes;
|
|
272
|
+
}
|
|
273
|
+
function resetTaskDeadline(traceId, newDeadline, db2) {
|
|
274
|
+
const conn = db2 ?? getDb();
|
|
275
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
276
|
+
const result = conn.prepare(
|
|
277
|
+
`UPDATE tasks
|
|
278
|
+
SET status = 'DISPATCHED', deadline = ?, result_data = NULL, updated_at = ?
|
|
279
|
+
WHERE trace_id = ?`
|
|
280
|
+
).run(newDeadline, now, traceId);
|
|
281
|
+
return result.changes > 0;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/models/job.ts
|
|
285
|
+
function createJob(job, db2) {
|
|
286
|
+
const conn = db2 ?? getDb();
|
|
287
|
+
conn.prepare(
|
|
288
|
+
`INSERT INTO jobs (job_id, original_prompt, openclaw_callback, created_at)
|
|
289
|
+
VALUES (?, ?, ?, ?)`
|
|
290
|
+
).run(job.job_id, job.original_prompt, job.openclaw_callback, job.created_at);
|
|
291
|
+
return job;
|
|
292
|
+
}
|
|
293
|
+
function getJobWithTasks(jobId, db2) {
|
|
294
|
+
const conn = db2 ?? getDb();
|
|
295
|
+
const job = conn.prepare("SELECT * FROM jobs WHERE job_id = ?").get(jobId);
|
|
296
|
+
if (!job) return void 0;
|
|
297
|
+
const tasks = listTasksByJob(jobId, conn);
|
|
298
|
+
return { ...job, tasks };
|
|
299
|
+
}
|
|
300
|
+
function listActiveJobs(db2) {
|
|
301
|
+
const conn = db2 ?? getDb();
|
|
302
|
+
const jobs = conn.prepare(
|
|
303
|
+
`SELECT DISTINCT j.*
|
|
304
|
+
FROM jobs j
|
|
305
|
+
INNER JOIN tasks t ON j.job_id = t.job_id
|
|
306
|
+
WHERE t.status != 'RESOLVED'
|
|
307
|
+
ORDER BY j.created_at DESC`
|
|
308
|
+
).all();
|
|
309
|
+
const allJobs = conn.prepare("SELECT * FROM jobs ORDER BY created_at DESC").all();
|
|
310
|
+
const activeJobIds = new Set(jobs.map((j) => j.job_id));
|
|
311
|
+
const result = [];
|
|
312
|
+
for (const job of allJobs) {
|
|
313
|
+
const tasks = listTasksByJob(job.job_id, conn);
|
|
314
|
+
if (tasks.length > 0) {
|
|
315
|
+
result.push({ ...job, tasks });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
320
|
+
function isJobComplete(jobId, db2) {
|
|
321
|
+
const conn = db2 ?? getDb();
|
|
322
|
+
const row = conn.prepare(
|
|
323
|
+
`SELECT COUNT(*) as total,
|
|
324
|
+
SUM(CASE WHEN status = 'RESOLVED' THEN 1 ELSE 0 END) as resolved
|
|
325
|
+
FROM tasks WHERE job_id = ?`
|
|
326
|
+
).get(jobId);
|
|
327
|
+
return row.total > 0 && row.total === row.resolved;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/services/dispatch.ts
|
|
331
|
+
function dispatchJob(request, db2) {
|
|
332
|
+
const conn = db2 ?? getDb();
|
|
333
|
+
const jobId = generateId("job");
|
|
334
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
335
|
+
for (const taskReq of request.tasks) {
|
|
336
|
+
const agent = getAgent(taskReq.assignee_id, conn);
|
|
337
|
+
if (!agent) {
|
|
338
|
+
throw new Error(`Agent not found: ${taskReq.assignee_id}`);
|
|
339
|
+
}
|
|
340
|
+
if (agent.status === "OFFLINE") {
|
|
341
|
+
throw new Error(`Agent is offline: ${taskReq.assignee_id} (${agent.name})`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const job = createJob(
|
|
345
|
+
{
|
|
346
|
+
job_id: jobId,
|
|
347
|
+
original_prompt: request.original_prompt,
|
|
348
|
+
openclaw_callback: request.openclaw_callback,
|
|
349
|
+
created_at: now
|
|
350
|
+
},
|
|
351
|
+
conn
|
|
352
|
+
);
|
|
353
|
+
const tasks = request.tasks.map((taskReq) => {
|
|
354
|
+
const traceId = generateTraceId();
|
|
355
|
+
const task = createTask(
|
|
356
|
+
{
|
|
357
|
+
trace_id: traceId,
|
|
358
|
+
job_id: jobId,
|
|
359
|
+
assignee_id: taskReq.assignee_id,
|
|
360
|
+
todo_description: taskReq.todo_description,
|
|
361
|
+
deadline: taskReq.deadline,
|
|
362
|
+
payload: taskReq.payload ?? {},
|
|
363
|
+
status: "DISPATCHED"
|
|
364
|
+
},
|
|
365
|
+
conn
|
|
366
|
+
);
|
|
367
|
+
updateAgentStatus(taskReq.assignee_id, "BUSY", conn);
|
|
368
|
+
return task;
|
|
369
|
+
});
|
|
370
|
+
return { ...job, tasks };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/routes/jobs.ts
|
|
374
|
+
var router2 = Router2();
|
|
375
|
+
router2.post("/create", (req, res) => {
|
|
376
|
+
const body = req.body;
|
|
377
|
+
if (!body.original_prompt || !Array.isArray(body.tasks) || body.tasks.length === 0) {
|
|
378
|
+
res.status(400).json({
|
|
379
|
+
error: "original_prompt and non-empty tasks[] are required"
|
|
380
|
+
});
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
for (const task of body.tasks) {
|
|
384
|
+
if (!task.assignee_id || !task.todo_description || !task.deadline) {
|
|
385
|
+
res.status(400).json({
|
|
386
|
+
error: "Each task requires assignee_id, todo_description, and deadline"
|
|
387
|
+
});
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
const job = dispatchJob(body);
|
|
393
|
+
res.status(201).json(job);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
396
|
+
res.status(400).json({ error: message });
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
router2.get("/active", (_req, res) => {
|
|
400
|
+
markOverdueTasks();
|
|
401
|
+
const jobs = listActiveJobs();
|
|
402
|
+
res.json({ jobs });
|
|
403
|
+
});
|
|
404
|
+
router2.get("/:job_id", (req, res) => {
|
|
405
|
+
const { job_id } = req.params;
|
|
406
|
+
const job = getJobWithTasks(job_id);
|
|
407
|
+
if (!job) {
|
|
408
|
+
res.status(404).json({ error: `Job not found: ${job_id}` });
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
res.json(job);
|
|
412
|
+
});
|
|
413
|
+
var jobs_default = router2;
|
|
414
|
+
|
|
415
|
+
// src/routes/tasks.ts
|
|
416
|
+
import { Router as Router3 } from "express";
|
|
417
|
+
|
|
418
|
+
// src/services/resume.ts
|
|
419
|
+
function resumeTask(traceId, resultData, db2) {
|
|
420
|
+
const conn = db2 ?? getDb();
|
|
421
|
+
const task = getTask(traceId, conn);
|
|
422
|
+
if (!task) {
|
|
423
|
+
throw new Error(`Task not found: ${traceId}`);
|
|
424
|
+
}
|
|
425
|
+
if (task.status === "RESOLVED") {
|
|
426
|
+
throw new Error(`Task already resolved: ${traceId}`);
|
|
427
|
+
}
|
|
428
|
+
if (task.status === "PENDING") {
|
|
429
|
+
throw new Error(`Task not yet dispatched: ${traceId}`);
|
|
430
|
+
}
|
|
431
|
+
const updated = resolveTask(traceId, resultData, conn);
|
|
432
|
+
if (!updated) {
|
|
433
|
+
throw new Error(`Failed to resolve task: ${traceId}`);
|
|
434
|
+
}
|
|
435
|
+
const activeTasks = listTasksByAssignee(task.assignee_id, conn).filter(
|
|
436
|
+
(t) => t.status === "DISPATCHED" || t.status === "PENDING"
|
|
437
|
+
);
|
|
438
|
+
if (activeTasks.length === 0) {
|
|
439
|
+
updateAgentStatus(task.assignee_id, "IDLE", conn);
|
|
440
|
+
}
|
|
441
|
+
const jobComplete = isJobComplete(task.job_id, conn);
|
|
442
|
+
const job = jobComplete ? getJobWithTasks(task.job_id, conn) : void 0;
|
|
443
|
+
const resolvedTask = getTask(traceId, conn);
|
|
444
|
+
return { task: resolvedTask, jobComplete, job };
|
|
445
|
+
}
|
|
446
|
+
function rejectTask(traceId, newDeadline, db2) {
|
|
447
|
+
const conn = db2 ?? getDb();
|
|
448
|
+
const task = getTask(traceId, conn);
|
|
449
|
+
if (!task) {
|
|
450
|
+
throw new Error(`Task not found: ${traceId}`);
|
|
451
|
+
}
|
|
452
|
+
const deadline = newDeadline ?? new Date(Date.now() + 24 * 60 * 60 * 1e3).toISOString();
|
|
453
|
+
resetTaskDeadline(traceId, deadline, conn);
|
|
454
|
+
return getTask(traceId, conn);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/routes/tasks.ts
|
|
458
|
+
var router3 = Router3();
|
|
459
|
+
router3.post("/resume", (req, res) => {
|
|
460
|
+
const { trace_id, result_data } = req.body;
|
|
461
|
+
if (!trace_id) {
|
|
462
|
+
res.status(400).json({ error: "trace_id is required" });
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (result_data === void 0) {
|
|
466
|
+
res.status(400).json({ error: "result_data is required" });
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
try {
|
|
470
|
+
const result = resumeTask(trace_id, result_data);
|
|
471
|
+
res.json({
|
|
472
|
+
task: result.task,
|
|
473
|
+
job_complete: result.jobComplete,
|
|
474
|
+
job: result.job ?? null
|
|
475
|
+
});
|
|
476
|
+
} catch (error) {
|
|
477
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
478
|
+
res.status(400).json({ error: message });
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
router3.post("/reject", (req, res) => {
|
|
482
|
+
const { trace_id, new_deadline } = req.body;
|
|
483
|
+
if (!trace_id) {
|
|
484
|
+
res.status(400).json({ error: "trace_id is required" });
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
try {
|
|
488
|
+
const task = rejectTask(trace_id, new_deadline);
|
|
489
|
+
res.json({ task });
|
|
490
|
+
} catch (error) {
|
|
491
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
492
|
+
res.status(400).json({ error: message });
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
var tasks_default = router3;
|
|
496
|
+
|
|
497
|
+
// src/routes/sync.ts
|
|
498
|
+
import { Router as Router4 } from "express";
|
|
499
|
+
|
|
500
|
+
// src/services/aggregation.ts
|
|
501
|
+
function aggregateJob(jobId, db2) {
|
|
502
|
+
const conn = db2 ?? getDb();
|
|
503
|
+
const job = getJobWithTasks(jobId, conn);
|
|
504
|
+
if (!job) {
|
|
505
|
+
throw new Error(`Job not found: ${jobId}`);
|
|
506
|
+
}
|
|
507
|
+
if (!isJobComplete(jobId, conn)) {
|
|
508
|
+
const resolved = job.tasks.filter((t) => t.status === "RESOLVED").length;
|
|
509
|
+
throw new Error(
|
|
510
|
+
`Job not complete: ${resolved}/${job.tasks.length} tasks resolved`
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
return {
|
|
514
|
+
job_id: job.job_id,
|
|
515
|
+
original_prompt: job.original_prompt,
|
|
516
|
+
openclaw_callback: job.openclaw_callback,
|
|
517
|
+
results: job.tasks.map((t) => ({
|
|
518
|
+
trace_id: t.trace_id,
|
|
519
|
+
assignee_id: t.assignee_id,
|
|
520
|
+
todo_description: t.todo_description,
|
|
521
|
+
result_data: t.result_data
|
|
522
|
+
})),
|
|
523
|
+
aggregated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
async function syncToOpenClaw(aggregation) {
|
|
527
|
+
if (!aggregation.openclaw_callback) {
|
|
528
|
+
return {
|
|
529
|
+
success: true,
|
|
530
|
+
message: "No OpenClaw callback configured, skipping sync"
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
try {
|
|
534
|
+
const response = await fetch(aggregation.openclaw_callback, {
|
|
535
|
+
method: "POST",
|
|
536
|
+
headers: { "Content-Type": "application/json" },
|
|
537
|
+
body: JSON.stringify(aggregation)
|
|
538
|
+
});
|
|
539
|
+
if (!response.ok) {
|
|
540
|
+
return {
|
|
541
|
+
success: false,
|
|
542
|
+
message: `OpenClaw sync failed: ${response.status} ${response.statusText}`
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
return { success: true, message: "Synced to OpenClaw successfully" };
|
|
546
|
+
} catch (error) {
|
|
547
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
548
|
+
return { success: false, message: `OpenClaw sync error: ${message}` };
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// src/routes/sync.ts
|
|
553
|
+
var router4 = Router4();
|
|
554
|
+
router4.post("/:job_id/sync", async (req, res) => {
|
|
555
|
+
const { job_id } = req.params;
|
|
556
|
+
try {
|
|
557
|
+
const aggregation = aggregateJob(job_id);
|
|
558
|
+
const syncResult = await syncToOpenClaw(aggregation);
|
|
559
|
+
res.json({
|
|
560
|
+
aggregation,
|
|
561
|
+
sync: syncResult
|
|
562
|
+
});
|
|
563
|
+
} catch (error) {
|
|
564
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
565
|
+
res.status(400).json({ error: message });
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
var sync_default = router4;
|
|
569
|
+
|
|
570
|
+
// src/server.ts
|
|
571
|
+
function createServer(port = 3e3) {
|
|
572
|
+
const db2 = getDb();
|
|
573
|
+
initSchema(db2);
|
|
574
|
+
const app = express();
|
|
575
|
+
app.use(cors());
|
|
576
|
+
app.use(express.json({ limit: "10mb" }));
|
|
577
|
+
app.use("/api/v1/nodes", nodes_default);
|
|
578
|
+
app.use("/api/v1/jobs", jobs_default);
|
|
579
|
+
app.use("/api/v1/tasks", tasks_default);
|
|
580
|
+
app.use("/api/v1/jobs", sync_default);
|
|
581
|
+
const uiDistPath = path2.join(import.meta.dirname, "..", "dist", "ui");
|
|
582
|
+
const uiDevPath = path2.join(import.meta.dirname, "..", "ui");
|
|
583
|
+
if (fs2.existsSync(uiDistPath)) {
|
|
584
|
+
app.use(express.static(uiDistPath));
|
|
585
|
+
app.get("/", (_req, res) => {
|
|
586
|
+
res.sendFile(path2.join(uiDistPath, "index.html"));
|
|
587
|
+
});
|
|
588
|
+
} else if (fs2.existsSync(uiDevPath)) {
|
|
589
|
+
app.use(express.static(uiDevPath));
|
|
590
|
+
app.get("/", (_req, res) => {
|
|
591
|
+
res.sendFile(path2.join(uiDevPath, "index.html"));
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
app.use(
|
|
595
|
+
(err, _req, res, _next) => {
|
|
596
|
+
console.error("Server error:", err.message);
|
|
597
|
+
res.status(500).json({ error: "Internal server error" });
|
|
598
|
+
}
|
|
599
|
+
);
|
|
600
|
+
return { app, port };
|
|
601
|
+
}
|
|
602
|
+
function startServer(port = 3e3) {
|
|
603
|
+
const { app } = createServer(port);
|
|
604
|
+
app.listen(port, () => {
|
|
605
|
+
console.log(`
|
|
606
|
+
HumanClaw server running at http://localhost:${port}`);
|
|
607
|
+
console.log(` Dashboard: http://localhost:${port}`);
|
|
608
|
+
console.log(` API base: http://localhost:${port}/api/v1
|
|
609
|
+
`);
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/index.ts
|
|
614
|
+
var program = new Command();
|
|
615
|
+
program.name("humanclaw").description(
|
|
616
|
+
"Async physical node orchestration framework - treating humans as distributed worker nodes"
|
|
617
|
+
).version("1.0.0");
|
|
618
|
+
program.command("serve").description("Start the HumanClaw server").option("-p, --port <port>", "Server port", "3000").action((opts) => {
|
|
619
|
+
const port = parseInt(opts.port, 10);
|
|
620
|
+
startServer(port);
|
|
621
|
+
});
|
|
622
|
+
var agentCmd = program.command("agent").description("Manage physical nodes (HumanAgent)");
|
|
623
|
+
agentCmd.command("add").description("Register a new physical node").action(async () => {
|
|
624
|
+
const db2 = getDb();
|
|
625
|
+
initSchema(db2);
|
|
626
|
+
p.intro(chalk.bgCyan(" Register New Physical Node "));
|
|
627
|
+
const name = await p.text({
|
|
628
|
+
message: "Node alias (name):",
|
|
629
|
+
placeholder: "e.g. Frontend Lao Li",
|
|
630
|
+
validate: (v) => !v ? "Name is required" : void 0
|
|
631
|
+
});
|
|
632
|
+
if (p.isCancel(name)) {
|
|
633
|
+
p.cancel("Cancelled.");
|
|
634
|
+
process.exit(0);
|
|
635
|
+
}
|
|
636
|
+
const capInput = await p.text({
|
|
637
|
+
message: "Capabilities (comma-separated):",
|
|
638
|
+
placeholder: "e.g. UI/UX, Frontend Dev, Stress Resistant",
|
|
639
|
+
validate: (v) => !v ? "At least one capability required" : void 0
|
|
640
|
+
});
|
|
641
|
+
if (p.isCancel(capInput)) {
|
|
642
|
+
p.cancel("Cancelled.");
|
|
643
|
+
process.exit(0);
|
|
644
|
+
}
|
|
645
|
+
const capabilities = capInput.split(",").map((s) => s.trim()).filter(Boolean);
|
|
646
|
+
const agent = createAgent({
|
|
647
|
+
agent_id: generateId("emp"),
|
|
648
|
+
name,
|
|
649
|
+
capabilities,
|
|
650
|
+
status: "IDLE"
|
|
651
|
+
});
|
|
652
|
+
p.outro(
|
|
653
|
+
`${chalk.green("Node registered!")} ID: ${chalk.bold(agent.agent_id)}`
|
|
654
|
+
);
|
|
655
|
+
});
|
|
656
|
+
agentCmd.command("list").description("Show fleet status").action(() => {
|
|
657
|
+
const db2 = getDb();
|
|
658
|
+
initSchema(db2);
|
|
659
|
+
const agents = listAgents();
|
|
660
|
+
if (agents.length === 0) {
|
|
661
|
+
console.log(chalk.dim(" No physical nodes registered."));
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const statusIcon = {
|
|
665
|
+
IDLE: "\u{1F7E2}",
|
|
666
|
+
BUSY: "\u{1F7E1}",
|
|
667
|
+
OFFLINE: "\u{1F534}",
|
|
668
|
+
OOM: "\u{1F7E3}"
|
|
669
|
+
};
|
|
670
|
+
console.log(chalk.bold("\n Carbon Compute Pool\n"));
|
|
671
|
+
for (const agent of agents) {
|
|
672
|
+
const icon = statusIcon[agent.status];
|
|
673
|
+
const caps = agent.capabilities.join(", ");
|
|
674
|
+
console.log(
|
|
675
|
+
` ${icon} ${chalk.bold(agent.name)} (${chalk.dim(agent.agent_id)})`
|
|
676
|
+
);
|
|
677
|
+
console.log(` Capabilities: ${chalk.cyan(caps)}`);
|
|
678
|
+
console.log(` Status: ${agent.status}
|
|
679
|
+
`);
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
program.command("status").description("Show active jobs overview").action(() => {
|
|
683
|
+
const db2 = getDb();
|
|
684
|
+
initSchema(db2);
|
|
685
|
+
markOverdueTasks();
|
|
686
|
+
const jobs = listActiveJobs();
|
|
687
|
+
if (jobs.length === 0) {
|
|
688
|
+
console.log(chalk.dim("\n No active jobs.\n"));
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
console.log(chalk.bold("\n Async Orchestration Dashboard\n"));
|
|
692
|
+
for (const job of jobs) {
|
|
693
|
+
const resolved = job.tasks.filter((t) => t.status === "RESOLVED").length;
|
|
694
|
+
const total = job.tasks.length;
|
|
695
|
+
const pct = Math.round(resolved / total * 100);
|
|
696
|
+
const bar = "\u2588".repeat(Math.round(pct / 5)) + "\u2591".repeat(20 - Math.round(pct / 5));
|
|
697
|
+
console.log(` ${chalk.bold(job.job_id)} - ${job.original_prompt}`);
|
|
698
|
+
console.log(` Progress: [${bar}] ${resolved}/${total} (${pct}%)`);
|
|
699
|
+
for (const task of job.tasks) {
|
|
700
|
+
const statusColor = task.status === "RESOLVED" ? chalk.green : task.status === "OVERDUE" ? chalk.red : chalk.yellow;
|
|
701
|
+
console.log(
|
|
702
|
+
` ${statusColor(task.status.padEnd(10))} ${task.trace_id} -> ${chalk.dim(task.assignee_id)}`
|
|
703
|
+
);
|
|
704
|
+
console.log(` ${task.todo_description}`);
|
|
705
|
+
}
|
|
706
|
+
console.log();
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
program.parse();
|
|
710
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/db/connection.ts","../src/db/schema.ts","../src/routes/nodes.ts","../src/models/agent.ts","../src/utils/trace-id.ts","../src/routes/jobs.ts","../src/models/task.ts","../src/models/job.ts","../src/services/dispatch.ts","../src/routes/tasks.ts","../src/services/resume.ts","../src/routes/sync.ts","../src/services/aggregation.ts"],"sourcesContent":["import { Command } from 'commander';\nimport * as p from '@clack/prompts';\nimport chalk from 'chalk';\nimport { startServer } from './server.js';\nimport { getDb } from './db/connection.js';\nimport { initSchema } from './db/schema.js';\nimport { createAgent, listAgents } from './models/agent.js';\nimport { listActiveJobs } from './models/job.js';\nimport { markOverdueTasks } from './models/task.js';\nimport { generateId } from './utils/trace-id.js';\nimport type { AgentStatus } from './models/types.js';\n\nconst program = new Command();\n\nprogram\n .name('humanclaw')\n .description(\n 'Async physical node orchestration framework - treating humans as distributed worker nodes'\n )\n .version('1.0.0');\n\n// ─── serve ───────────────────────────────────────────────────────────────────\n\nprogram\n .command('serve')\n .description('Start the HumanClaw server')\n .option('-p, --port <port>', 'Server port', '3000')\n .action(opts => {\n const port = parseInt(opts.port, 10);\n startServer(port);\n });\n\n// ─── agent ───────────────────────────────────────────────────────────────────\n\nconst agentCmd = program\n .command('agent')\n .description('Manage physical nodes (HumanAgent)');\n\nagentCmd\n .command('add')\n .description('Register a new physical node')\n .action(async () => {\n const db = getDb();\n initSchema(db);\n\n p.intro(chalk.bgCyan(' Register New Physical Node '));\n\n const name = await p.text({\n message: 'Node alias (name):',\n placeholder: 'e.g. Frontend Lao Li',\n validate: v => (!v ? 'Name is required' : undefined),\n });\n\n if (p.isCancel(name)) {\n p.cancel('Cancelled.');\n process.exit(0);\n }\n\n const capInput = await p.text({\n message: 'Capabilities (comma-separated):',\n placeholder: 'e.g. UI/UX, Frontend Dev, Stress Resistant',\n validate: v => (!v ? 'At least one capability required' : undefined),\n });\n\n if (p.isCancel(capInput)) {\n p.cancel('Cancelled.');\n process.exit(0);\n }\n\n const capabilities = (capInput as string)\n .split(',')\n .map(s => s.trim())\n .filter(Boolean);\n\n const agent = createAgent({\n agent_id: generateId('emp'),\n name: name as string,\n capabilities,\n status: 'IDLE',\n });\n\n p.outro(\n `${chalk.green('Node registered!')} ID: ${chalk.bold(agent.agent_id)}`\n );\n });\n\nagentCmd\n .command('list')\n .description('Show fleet status')\n .action(() => {\n const db = getDb();\n initSchema(db);\n\n const agents = listAgents();\n if (agents.length === 0) {\n console.log(chalk.dim(' No physical nodes registered.'));\n return;\n }\n\n const statusIcon: Record<AgentStatus, string> = {\n IDLE: '🟢',\n BUSY: '🟡',\n OFFLINE: '🔴',\n OOM: '🟣',\n };\n\n console.log(chalk.bold('\\n Carbon Compute Pool\\n'));\n\n for (const agent of agents) {\n const icon = statusIcon[agent.status];\n const caps = agent.capabilities.join(', ');\n console.log(\n ` ${icon} ${chalk.bold(agent.name)} (${chalk.dim(agent.agent_id)})`\n );\n console.log(` Capabilities: ${chalk.cyan(caps)}`);\n console.log(` Status: ${agent.status}\\n`);\n }\n });\n\n// ─── status ──────────────────────────────────────────────────────────────────\n\nprogram\n .command('status')\n .description('Show active jobs overview')\n .action(() => {\n const db = getDb();\n initSchema(db);\n\n markOverdueTasks();\n const jobs = listActiveJobs();\n\n if (jobs.length === 0) {\n console.log(chalk.dim('\\n No active jobs.\\n'));\n return;\n }\n\n console.log(chalk.bold('\\n Async Orchestration Dashboard\\n'));\n\n for (const job of jobs) {\n const resolved = job.tasks.filter(t => t.status === 'RESOLVED').length;\n const total = job.tasks.length;\n const pct = Math.round((resolved / total) * 100);\n const bar = '█'.repeat(Math.round(pct / 5)) + '░'.repeat(20 - Math.round(pct / 5));\n\n console.log(` ${chalk.bold(job.job_id)} - ${job.original_prompt}`);\n console.log(` Progress: [${bar}] ${resolved}/${total} (${pct}%)`);\n\n for (const task of job.tasks) {\n const statusColor =\n task.status === 'RESOLVED'\n ? chalk.green\n : task.status === 'OVERDUE'\n ? chalk.red\n : chalk.yellow;\n console.log(\n ` ${statusColor(task.status.padEnd(10))} ${task.trace_id} -> ${chalk.dim(task.assignee_id)}`\n );\n console.log(` ${task.todo_description}`);\n }\n console.log();\n }\n });\n\nprogram.parse();\n","import express from 'express';\nimport cors from 'cors';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport { getDb } from './db/connection.js';\nimport { initSchema } from './db/schema.js';\nimport nodesRouter from './routes/nodes.js';\nimport jobsRouter from './routes/jobs.js';\nimport tasksRouter from './routes/tasks.js';\nimport syncRouter from './routes/sync.js';\n\nexport function createServer(port = 3000) {\n // Initialize database\n const db = getDb();\n initSchema(db);\n\n const app = express();\n\n // Middleware\n app.use(cors());\n app.use(express.json({ limit: '10mb' }));\n\n // API routes\n app.use('/api/v1/nodes', nodesRouter);\n app.use('/api/v1/jobs', jobsRouter);\n app.use('/api/v1/tasks', tasksRouter);\n app.use('/api/v1/jobs', syncRouter);\n\n // Serve dashboard UI (static files)\n const uiDistPath = path.join(import.meta.dirname, '..', 'dist', 'ui');\n const uiDevPath = path.join(import.meta.dirname, '..', 'ui');\n\n if (fs.existsSync(uiDistPath)) {\n app.use(express.static(uiDistPath));\n app.get('/', (_req, res) => {\n res.sendFile(path.join(uiDistPath, 'index.html'));\n });\n } else if (fs.existsSync(uiDevPath)) {\n app.use(express.static(uiDevPath));\n app.get('/', (_req, res) => {\n res.sendFile(path.join(uiDevPath, 'index.html'));\n });\n }\n\n // Error handler\n app.use(\n (\n err: Error,\n _req: express.Request,\n res: express.Response,\n _next: express.NextFunction\n ) => {\n console.error('Server error:', err.message);\n res.status(500).json({ error: 'Internal server error' });\n }\n );\n\n return { app, port };\n}\n\nexport function startServer(port = 3000) {\n const { app } = createServer(port);\n\n app.listen(port, () => {\n console.log(`\\n HumanClaw server running at http://localhost:${port}`);\n console.log(` Dashboard: http://localhost:${port}`);\n console.log(` API base: http://localhost:${port}/api/v1\\n`);\n });\n}\n","import Database from 'better-sqlite3';\nimport path from 'node:path';\nimport fs from 'node:fs';\n\nlet db: Database.Database | null = null;\n\nconst DEFAULT_DB_PATH = path.join(\n process.env.HUMANCLAW_DATA_DIR ?? process.cwd(),\n 'humanclaw.db'\n);\n\nexport function getDb(dbPath?: string): Database.Database {\n if (db) return db;\n\n const resolvedPath = dbPath ?? DEFAULT_DB_PATH;\n const dir = path.dirname(resolvedPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n db = new Database(resolvedPath);\n db.pragma('journal_mode = WAL');\n db.pragma('foreign_keys = ON');\n\n return db;\n}\n\nexport function createInMemoryDb(): Database.Database {\n const memDb = new Database(':memory:');\n memDb.pragma('foreign_keys = ON');\n return memDb;\n}\n\nexport function closeDb(): void {\n if (db) {\n db.close();\n db = null;\n }\n}\n\nexport function setDb(newDb: Database.Database): void {\n db = newDb;\n}\n","import type Database from 'better-sqlite3';\n\nexport function initSchema(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS agents (\n agent_id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n capabilities TEXT NOT NULL DEFAULT '[]',\n status TEXT NOT NULL DEFAULT 'IDLE'\n CHECK (status IN ('IDLE', 'BUSY', 'OFFLINE', 'OOM')),\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE TABLE IF NOT EXISTS jobs (\n job_id TEXT PRIMARY KEY,\n original_prompt TEXT NOT NULL,\n openclaw_callback TEXT NOT NULL DEFAULT '',\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE TABLE IF NOT EXISTS tasks (\n trace_id TEXT PRIMARY KEY,\n job_id TEXT NOT NULL REFERENCES jobs(job_id) ON DELETE CASCADE,\n assignee_id TEXT NOT NULL REFERENCES agents(agent_id),\n todo_description TEXT NOT NULL,\n deadline TEXT NOT NULL,\n payload TEXT NOT NULL DEFAULT '{}',\n status TEXT NOT NULL DEFAULT 'PENDING'\n CHECK (status IN ('PENDING', 'DISPATCHED', 'RESOLVED', 'OVERDUE')),\n result_data TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE INDEX IF NOT EXISTS idx_tasks_job_id ON tasks(job_id);\n CREATE INDEX IF NOT EXISTS idx_tasks_assignee_id ON tasks(assignee_id);\n CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);\n `);\n}\n","import { Router } from 'express';\nimport {\n listAgentsWithMetrics,\n createAgent,\n updateAgentStatus,\n deleteAgent,\n} from '../models/agent.js';\nimport { generateId } from '../utils/trace-id.js';\nimport type { AgentStatus } from '../models/types.js';\n\nconst router = Router();\n\n// GET /api/v1/nodes/status - Fleet status with metrics\nrouter.get('/status', (_req, res) => {\n const agents = listAgentsWithMetrics();\n res.json({\n total: agents.length,\n idle: agents.filter(a => a.status === 'IDLE').length,\n busy: agents.filter(a => a.status === 'BUSY').length,\n offline: agents.filter(a => a.status === 'OFFLINE').length,\n oom: agents.filter(a => a.status === 'OOM').length,\n agents,\n });\n});\n\n// POST /api/v1/nodes - Register a new agent\nrouter.post('/', (req, res) => {\n const { name, capabilities, status } = req.body;\n\n if (!name || !Array.isArray(capabilities)) {\n res.status(400).json({ error: 'name and capabilities[] are required' });\n return;\n }\n\n const agent = createAgent({\n agent_id: generateId('emp'),\n name,\n capabilities,\n status: (status as AgentStatus) ?? 'IDLE',\n });\n\n res.status(201).json(agent);\n});\n\n// PATCH /api/v1/nodes/:agent_id/status - Update agent status\nrouter.patch('/:agent_id/status', (req, res) => {\n const { agent_id } = req.params;\n const { status } = req.body;\n\n const validStatuses: AgentStatus[] = ['IDLE', 'BUSY', 'OFFLINE', 'OOM'];\n if (!validStatuses.includes(status)) {\n res.status(400).json({\n error: `Invalid status. Must be one of: ${validStatuses.join(', ')}`,\n });\n return;\n }\n\n const updated = updateAgentStatus(agent_id, status);\n if (!updated) {\n res.status(404).json({ error: `Agent not found: ${agent_id}` });\n return;\n }\n\n res.json({ agent_id, status });\n});\n\n// DELETE /api/v1/nodes/:agent_id\nrouter.delete('/:agent_id', (req, res) => {\n const { agent_id } = req.params;\n const deleted = deleteAgent(agent_id);\n if (!deleted) {\n res.status(404).json({ error: `Agent not found: ${agent_id}` });\n return;\n }\n res.json({ deleted: agent_id });\n});\n\nexport default router;\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport type {\n HumanAgent,\n AgentRow,\n AgentStatus,\n AgentWithMetrics,\n} from './types.js';\n\nfunction rowToAgent(row: AgentRow): HumanAgent {\n return {\n ...row,\n capabilities: JSON.parse(row.capabilities) as string[],\n };\n}\n\nexport function createAgent(\n agent: Omit<HumanAgent, 'created_at'>,\n db?: Database.Database\n): HumanAgent {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n conn\n .prepare(\n `INSERT INTO agents (agent_id, name, capabilities, status, created_at)\n VALUES (?, ?, ?, ?, ?)`\n )\n .run(\n agent.agent_id,\n agent.name,\n JSON.stringify(agent.capabilities),\n agent.status,\n now\n );\n return { ...agent, created_at: now };\n}\n\nexport function getAgent(\n agentId: string,\n db?: Database.Database\n): HumanAgent | undefined {\n const conn = db ?? getDb();\n const row = conn\n .prepare('SELECT * FROM agents WHERE agent_id = ?')\n .get(agentId) as AgentRow | undefined;\n return row ? rowToAgent(row) : undefined;\n}\n\nexport function listAgents(db?: Database.Database): HumanAgent[] {\n const conn = db ?? getDb();\n const rows = conn.prepare('SELECT * FROM agents ORDER BY created_at').all() as AgentRow[];\n return rows.map(rowToAgent);\n}\n\nexport function updateAgentStatus(\n agentId: string,\n status: AgentStatus,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const result = conn\n .prepare('UPDATE agents SET status = ? WHERE agent_id = ?')\n .run(status, agentId);\n return result.changes > 0;\n}\n\nexport function deleteAgent(\n agentId: string,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const result = conn\n .prepare('DELETE FROM agents WHERE agent_id = ?')\n .run(agentId);\n return result.changes > 0;\n}\n\nexport function listAgentsWithMetrics(\n db?: Database.Database\n): AgentWithMetrics[] {\n const conn = db ?? getDb();\n const rows = conn\n .prepare(\n `SELECT\n a.*,\n COUNT(CASE WHEN t.status IN ('DISPATCHED', 'PENDING') THEN 1 END) AS active_task_count,\n AVG(\n CASE WHEN t.status = 'RESOLVED'\n THEN (julianday(t.updated_at) - julianday(t.created_at)) * 24\n END\n ) AS avg_delivery_hours\n FROM agents a\n LEFT JOIN tasks t ON a.agent_id = t.assignee_id\n GROUP BY a.agent_id\n ORDER BY a.created_at`\n )\n .all() as (AgentRow & { active_task_count: number; avg_delivery_hours: number | null })[];\n\n return rows.map(row => ({\n ...rowToAgent(row),\n active_task_count: row.active_task_count,\n avg_delivery_hours: row.avg_delivery_hours\n ? Math.round(row.avg_delivery_hours * 100) / 100\n : null,\n }));\n}\n","import { nanoid } from 'nanoid';\n\nexport function generateTraceId(): string {\n const num = Math.floor(Math.random() * 10000)\n .toString()\n .padStart(4, '0');\n return `TK-${num}`;\n}\n\nexport function generateId(prefix: string): string {\n return `${prefix}_${nanoid(8)}`;\n}\n","import { Router } from 'express';\nimport { dispatchJob } from '../services/dispatch.js';\nimport { listActiveJobs, getJobWithTasks } from '../models/job.js';\nimport { markOverdueTasks } from '../models/task.js';\nimport type { CreateJobRequest } from '../models/types.js';\n\nconst router = Router();\n\n// POST /api/v1/jobs/create - Create and dispatch a new job\nrouter.post('/create', (req, res) => {\n const body = req.body as CreateJobRequest;\n\n if (!body.original_prompt || !Array.isArray(body.tasks) || body.tasks.length === 0) {\n res.status(400).json({\n error: 'original_prompt and non-empty tasks[] are required',\n });\n return;\n }\n\n for (const task of body.tasks) {\n if (!task.assignee_id || !task.todo_description || !task.deadline) {\n res.status(400).json({\n error: 'Each task requires assignee_id, todo_description, and deadline',\n });\n return;\n }\n }\n\n try {\n const job = dispatchJob(body);\n res.status(201).json(job);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n res.status(400).json({ error: message });\n }\n});\n\n// GET /api/v1/jobs/active - List active jobs for kanban\nrouter.get('/active', (_req, res) => {\n // Mark overdue tasks before returning\n markOverdueTasks();\n const jobs = listActiveJobs();\n res.json({ jobs });\n});\n\n// GET /api/v1/jobs/:job_id - Get a single job with tasks\nrouter.get('/:job_id', (req, res) => {\n const { job_id } = req.params;\n const job = getJobWithTasks(job_id);\n if (!job) {\n res.status(404).json({ error: `Job not found: ${job_id}` });\n return;\n }\n res.json(job);\n});\n\nexport default router;\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport type { HumanTask, TaskRow, TaskStatus } from './types.js';\n\nfunction rowToTask(row: TaskRow): HumanTask {\n return {\n ...row,\n payload: JSON.parse(row.payload) as Record<string, unknown>,\n result_data: row.result_data ? JSON.parse(row.result_data) : null,\n };\n}\n\nexport function createTask(\n task: Omit<HumanTask, 'created_at' | 'updated_at' | 'result_data'>,\n db?: Database.Database\n): HumanTask {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n conn\n .prepare(\n `INSERT INTO tasks (trace_id, job_id, assignee_id, todo_description, deadline, payload, status, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`\n )\n .run(\n task.trace_id,\n task.job_id,\n task.assignee_id,\n task.todo_description,\n task.deadline,\n JSON.stringify(task.payload),\n task.status,\n now,\n now\n );\n return { ...task, result_data: null, created_at: now, updated_at: now };\n}\n\nexport function getTask(\n traceId: string,\n db?: Database.Database\n): HumanTask | undefined {\n const conn = db ?? getDb();\n const row = conn\n .prepare('SELECT * FROM tasks WHERE trace_id = ?')\n .get(traceId) as TaskRow | undefined;\n return row ? rowToTask(row) : undefined;\n}\n\nexport function listTasksByJob(\n jobId: string,\n db?: Database.Database\n): HumanTask[] {\n const conn = db ?? getDb();\n const rows = conn\n .prepare('SELECT * FROM tasks WHERE job_id = ? ORDER BY created_at')\n .all(jobId) as TaskRow[];\n return rows.map(rowToTask);\n}\n\nexport function listTasksByAssignee(\n assigneeId: string,\n db?: Database.Database\n): HumanTask[] {\n const conn = db ?? getDb();\n const rows = conn\n .prepare('SELECT * FROM tasks WHERE assignee_id = ? ORDER BY created_at')\n .all(assigneeId) as TaskRow[];\n return rows.map(rowToTask);\n}\n\nexport function updateTaskStatus(\n traceId: string,\n status: TaskStatus,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n const result = conn\n .prepare('UPDATE tasks SET status = ?, updated_at = ? WHERE trace_id = ?')\n .run(status, now, traceId);\n return result.changes > 0;\n}\n\nexport function resolveTask(\n traceId: string,\n resultData: unknown,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n const result = conn\n .prepare(\n `UPDATE tasks\n SET status = 'RESOLVED', result_data = ?, updated_at = ?\n WHERE trace_id = ? AND status IN ('DISPATCHED', 'OVERDUE')`\n )\n .run(JSON.stringify(resultData), now, traceId);\n return result.changes > 0;\n}\n\nexport function markOverdueTasks(db?: Database.Database): number {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n const result = conn\n .prepare(\n `UPDATE tasks\n SET status = 'OVERDUE', updated_at = ?\n WHERE status = 'DISPATCHED' AND deadline < ?`\n )\n .run(now, now);\n return result.changes;\n}\n\nexport function resetTaskDeadline(\n traceId: string,\n newDeadline: string,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const now = new Date().toISOString();\n const result = conn\n .prepare(\n `UPDATE tasks\n SET status = 'DISPATCHED', deadline = ?, result_data = NULL, updated_at = ?\n WHERE trace_id = ?`\n )\n .run(newDeadline, now, traceId);\n return result.changes > 0;\n}\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport type { OrchestrationJob, JobRow, JobWithTasks } from './types.js';\nimport { listTasksByJob } from './task.js';\n\nexport function createJob(\n job: OrchestrationJob,\n db?: Database.Database\n): OrchestrationJob {\n const conn = db ?? getDb();\n conn\n .prepare(\n `INSERT INTO jobs (job_id, original_prompt, openclaw_callback, created_at)\n VALUES (?, ?, ?, ?)`\n )\n .run(job.job_id, job.original_prompt, job.openclaw_callback, job.created_at);\n return job;\n}\n\nexport function getJob(\n jobId: string,\n db?: Database.Database\n): OrchestrationJob | undefined {\n const conn = db ?? getDb();\n return conn\n .prepare('SELECT * FROM jobs WHERE job_id = ?')\n .get(jobId) as JobRow | undefined;\n}\n\nexport function getJobWithTasks(\n jobId: string,\n db?: Database.Database\n): JobWithTasks | undefined {\n const conn = db ?? getDb();\n const job = conn\n .prepare('SELECT * FROM jobs WHERE job_id = ?')\n .get(jobId) as JobRow | undefined;\n if (!job) return undefined;\n\n const tasks = listTasksByJob(jobId, conn);\n return { ...job, tasks };\n}\n\nexport function listActiveJobs(db?: Database.Database): JobWithTasks[] {\n const conn = db ?? getDb();\n const jobs = conn\n .prepare(\n `SELECT DISTINCT j.*\n FROM jobs j\n INNER JOIN tasks t ON j.job_id = t.job_id\n WHERE t.status != 'RESOLVED'\n ORDER BY j.created_at DESC`\n )\n .all() as JobRow[];\n\n // Also include jobs where all tasks are resolved but not yet synced\n const allJobs = conn\n .prepare('SELECT * FROM jobs ORDER BY created_at DESC')\n .all() as JobRow[];\n\n const activeJobIds = new Set(jobs.map(j => j.job_id));\n const result: JobWithTasks[] = [];\n\n for (const job of allJobs) {\n const tasks = listTasksByJob(job.job_id, conn);\n if (tasks.length > 0) {\n result.push({ ...job, tasks });\n }\n }\n\n return result;\n}\n\nexport function isJobComplete(\n jobId: string,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const row = conn\n .prepare(\n `SELECT COUNT(*) as total,\n SUM(CASE WHEN status = 'RESOLVED' THEN 1 ELSE 0 END) as resolved\n FROM tasks WHERE job_id = ?`\n )\n .get(jobId) as { total: number; resolved: number };\n return row.total > 0 && row.total === row.resolved;\n}\n\nexport function deleteJob(\n jobId: string,\n db?: Database.Database\n): boolean {\n const conn = db ?? getDb();\n const result = conn\n .prepare('DELETE FROM jobs WHERE job_id = ?')\n .run(jobId);\n return result.changes > 0;\n}\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport { createJob } from '../models/job.js';\nimport { createTask } from '../models/task.js';\nimport { getAgent, updateAgentStatus } from '../models/agent.js';\nimport { generateTraceId, generateId } from '../utils/trace-id.js';\nimport type { CreateJobRequest, JobWithTasks } from '../models/types.js';\n\nexport function dispatchJob(\n request: CreateJobRequest,\n db?: Database.Database\n): JobWithTasks {\n const conn = db ?? getDb();\n const jobId = generateId('job');\n const now = new Date().toISOString();\n\n // Validate all assignees exist\n for (const taskReq of request.tasks) {\n const agent = getAgent(taskReq.assignee_id, conn);\n if (!agent) {\n throw new Error(`Agent not found: ${taskReq.assignee_id}`);\n }\n if (agent.status === 'OFFLINE') {\n throw new Error(`Agent is offline: ${taskReq.assignee_id} (${agent.name})`);\n }\n }\n\n // Create the job\n const job = createJob(\n {\n job_id: jobId,\n original_prompt: request.original_prompt,\n openclaw_callback: request.openclaw_callback,\n created_at: now,\n },\n conn\n );\n\n // Create and dispatch all tasks\n const tasks = request.tasks.map(taskReq => {\n const traceId = generateTraceId();\n const task = createTask(\n {\n trace_id: traceId,\n job_id: jobId,\n assignee_id: taskReq.assignee_id,\n todo_description: taskReq.todo_description,\n deadline: taskReq.deadline,\n payload: taskReq.payload ?? {},\n status: 'DISPATCHED',\n },\n conn\n );\n\n // Mark the agent as busy\n updateAgentStatus(taskReq.assignee_id, 'BUSY', conn);\n\n return task;\n });\n\n return { ...job, tasks };\n}\n","import { Router } from 'express';\nimport { resumeTask, rejectTask } from '../services/resume.js';\n\nconst router = Router();\n\n// POST /api/v1/tasks/resume - Submit result for a task\nrouter.post('/resume', (req, res) => {\n const { trace_id, result_data } = req.body;\n\n if (!trace_id) {\n res.status(400).json({ error: 'trace_id is required' });\n return;\n }\n\n if (result_data === undefined) {\n res.status(400).json({ error: 'result_data is required' });\n return;\n }\n\n try {\n const result = resumeTask(trace_id, result_data);\n res.json({\n task: result.task,\n job_complete: result.jobComplete,\n job: result.job ?? null,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n res.status(400).json({ error: message });\n }\n});\n\n// POST /api/v1/tasks/reject - Reject and retry a task\nrouter.post('/reject', (req, res) => {\n const { trace_id, new_deadline } = req.body;\n\n if (!trace_id) {\n res.status(400).json({ error: 'trace_id is required' });\n return;\n }\n\n try {\n const task = rejectTask(trace_id, new_deadline);\n res.json({ task });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n res.status(400).json({ error: message });\n }\n});\n\nexport default router;\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport { getTask, resolveTask, resetTaskDeadline } from '../models/task.js';\nimport { isJobComplete, getJobWithTasks } from '../models/job.js';\nimport { updateAgentStatus } from '../models/agent.js';\nimport { listTasksByAssignee } from '../models/task.js';\nimport type { HumanTask, JobWithTasks } from '../models/types.js';\n\nexport interface ResumeResult {\n task: HumanTask;\n jobComplete: boolean;\n job: JobWithTasks | undefined;\n}\n\nexport function resumeTask(\n traceId: string,\n resultData: unknown,\n db?: Database.Database\n): ResumeResult {\n const conn = db ?? getDb();\n\n // Validate trace_id exists\n const task = getTask(traceId, conn);\n if (!task) {\n throw new Error(`Task not found: ${traceId}`);\n }\n\n if (task.status === 'RESOLVED') {\n throw new Error(`Task already resolved: ${traceId}`);\n }\n\n if (task.status === 'PENDING') {\n throw new Error(`Task not yet dispatched: ${traceId}`);\n }\n\n // Resolve the task\n const updated = resolveTask(traceId, resultData, conn);\n if (!updated) {\n throw new Error(`Failed to resolve task: ${traceId}`);\n }\n\n // Check if the agent has other active tasks; if not, mark IDLE\n const activeTasks = listTasksByAssignee(task.assignee_id, conn).filter(\n t => t.status === 'DISPATCHED' || t.status === 'PENDING'\n );\n if (activeTasks.length === 0) {\n updateAgentStatus(task.assignee_id, 'IDLE', conn);\n }\n\n // Check if the whole job is now complete\n const jobComplete = isJobComplete(task.job_id, conn);\n const job = jobComplete ? getJobWithTasks(task.job_id, conn) : undefined;\n\n const resolvedTask = getTask(traceId, conn)!;\n\n return { task: resolvedTask, jobComplete, job };\n}\n\nexport function rejectTask(\n traceId: string,\n newDeadline?: string,\n db?: Database.Database\n): HumanTask {\n const conn = db ?? getDb();\n\n const task = getTask(traceId, conn);\n if (!task) {\n throw new Error(`Task not found: ${traceId}`);\n }\n\n // Default: extend deadline by 24h from now\n const deadline =\n newDeadline ?? new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();\n\n resetTaskDeadline(traceId, deadline, conn);\n\n return getTask(traceId, conn)!;\n}\n","import { Router } from 'express';\nimport { aggregateJob, syncToOpenClaw } from '../services/aggregation.js';\n\nconst router = Router();\n\n// POST /api/v1/jobs/:job_id/sync - Aggregate and sync to OpenClaw\nrouter.post('/:job_id/sync', async (req, res) => {\n const { job_id } = req.params;\n\n try {\n const aggregation = aggregateJob(job_id);\n const syncResult = await syncToOpenClaw(aggregation);\n\n res.json({\n aggregation,\n sync: syncResult,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n res.status(400).json({ error: message });\n }\n});\n\nexport default router;\n","import type Database from 'better-sqlite3';\nimport { getDb } from '../db/connection.js';\nimport { getJobWithTasks, isJobComplete } from '../models/job.js';\nimport type { JobWithTasks } from '../models/types.js';\n\nexport interface AggregationResult {\n job_id: string;\n original_prompt: string;\n openclaw_callback: string;\n results: Array<{\n trace_id: string;\n assignee_id: string;\n todo_description: string;\n result_data: unknown;\n }>;\n aggregated_at: string;\n}\n\nexport function aggregateJob(\n jobId: string,\n db?: Database.Database\n): AggregationResult {\n const conn = db ?? getDb();\n\n const job = getJobWithTasks(jobId, conn);\n if (!job) {\n throw new Error(`Job not found: ${jobId}`);\n }\n\n if (!isJobComplete(jobId, conn)) {\n const resolved = job.tasks.filter(t => t.status === 'RESOLVED').length;\n throw new Error(\n `Job not complete: ${resolved}/${job.tasks.length} tasks resolved`\n );\n }\n\n return {\n job_id: job.job_id,\n original_prompt: job.original_prompt,\n openclaw_callback: job.openclaw_callback,\n results: job.tasks.map(t => ({\n trace_id: t.trace_id,\n assignee_id: t.assignee_id,\n todo_description: t.todo_description,\n result_data: t.result_data,\n })),\n aggregated_at: new Date().toISOString(),\n };\n}\n\nexport async function syncToOpenClaw(\n aggregation: AggregationResult\n): Promise<{ success: boolean; message: string }> {\n if (!aggregation.openclaw_callback) {\n return {\n success: true,\n message: 'No OpenClaw callback configured, skipping sync',\n };\n }\n\n try {\n const response = await fetch(aggregation.openclaw_callback, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(aggregation),\n });\n\n if (!response.ok) {\n return {\n success: false,\n message: `OpenClaw sync failed: ${response.status} ${response.statusText}`,\n };\n }\n\n return { success: true, message: 'Synced to OpenClaw successfully' };\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Unknown error';\n return { success: false, message: `OpenClaw sync error: ${message}` };\n }\n}\n"],"mappings":"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","names":["path","fs","db","db","Router","db","db","db","db","db","router","Router","Router","db","router","Router","Router","db","router","Router","db","path","fs","db"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@humanclaw/humanclaw",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Async physical node orchestration framework - treating humans as distributed worker nodes",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/humanclaw/humanclaw.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://humanclaw.github.io/humanclaw/",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/humanclaw/humanclaw/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"orchestration",
|
|
17
|
+
"task-management",
|
|
18
|
+
"distributed",
|
|
19
|
+
"human-in-the-loop",
|
|
20
|
+
"async",
|
|
21
|
+
"workflow"
|
|
22
|
+
],
|
|
23
|
+
"bin": {
|
|
24
|
+
"humanclaw": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"main": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"README_EN.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"dev": "tsx src/index.ts serve",
|
|
39
|
+
"dev:ui": "vite ui",
|
|
40
|
+
"build": "tsup && vite build",
|
|
41
|
+
"build:server": "tsup",
|
|
42
|
+
"build:ui": "vite build",
|
|
43
|
+
"prepublishOnly": "npm run build:server",
|
|
44
|
+
"start": "node dist/index.js serve",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"test:coverage": "vitest run --coverage",
|
|
48
|
+
"lint": "tsc --noEmit"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@clack/prompts": "^0.9.1",
|
|
52
|
+
"better-sqlite3": "^11.7.0",
|
|
53
|
+
"chalk": "^5.4.1",
|
|
54
|
+
"commander": "^13.1.0",
|
|
55
|
+
"cors": "^2.8.5",
|
|
56
|
+
"express": "^5.1.0",
|
|
57
|
+
"nanoid": "^5.1.2"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
61
|
+
"@types/cors": "^2.8.17",
|
|
62
|
+
"@types/express": "^5.0.2",
|
|
63
|
+
"@types/node": "^22.15.3",
|
|
64
|
+
"tsup": "^8.4.0",
|
|
65
|
+
"tsx": "^4.19.4",
|
|
66
|
+
"typescript": "^5.8.3",
|
|
67
|
+
"vite": "^6.3.3",
|
|
68
|
+
"vitest": "^3.1.2"
|
|
69
|
+
}
|
|
70
|
+
}
|