taskmeld 0.1.1 → 0.1.41
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 +1 -1
- package/README.md +176 -172
- package/README.zh-CN.md +176 -172
- package/dist/src/app/app-context-env.js +1 -1
- package/dist/src/app/create-app-context.js +3 -3
- package/dist/src/app/data-dir.js +13 -3
- package/dist/src/app/pipeline-config.js +4 -4
- package/dist/src/app/pipeline-registry.js +11 -11
- package/dist/src/app/pipeline-runtime.js +6 -9
- package/dist/src/app/runtime-store.js +3 -3
- package/dist/src/artifacts/artifact-cleanup.js +17 -17
- package/dist/src/artifacts/artifact-index.js +14 -14
- package/dist/src/artifacts/artifact-rebuilder.js +3 -3
- package/dist/src/artifacts/storage-service.js +18 -18
- package/dist/src/cli/bootstrap.js +7 -7
- package/dist/src/cli/commands/agent.js +12 -11
- package/dist/src/cli/commands/artifact.js +31 -30
- package/dist/src/cli/commands/init.js +49 -47
- package/dist/src/cli/commands/pipeline/result.js +9 -8
- package/dist/src/cli/commands/pipeline/selector.js +1 -1
- package/dist/src/cli/commands/pipeline/watch.js +2 -2
- package/dist/src/cli/commands/pipeline.js +54 -53
- package/dist/src/cli/commands/scheduler.js +9 -8
- package/dist/src/cli/commands/server.js +12 -11
- package/dist/src/cli/commands/system.js +4 -3
- package/dist/src/cli/errors.js +2 -2
- package/dist/src/cli/help.js +18 -17
- package/dist/src/cli/i18n.js +46 -0
- package/dist/src/cli/locales/en.json +244 -0
- package/dist/src/cli/locales/zh.json +244 -0
- package/dist/src/cli/output.js +3 -3
- package/dist/src/cli/renderers/engine/markdown.js +1 -1
- package/dist/src/cli/renderers/specs/index.js +1 -1
- package/dist/src/cli/router.js +1 -1
- package/dist/src/cli/server-runtime-client.js +54 -95
- package/dist/src/cli/ui-prompts.js +96 -0
- package/dist/src/cli/ws-runtime-client.js +51 -0
- package/dist/src/gateway/gateway-client.js +4 -4
- package/dist/src/index.js +28 -2
- package/dist/src/logs/run-log-reader.js +1 -1
- package/dist/src/pipeline/agent-activity.js +2 -2
- package/dist/src/pipeline/artifact-storage.js +11 -11
- package/dist/src/pipeline/diagnostics/dependency-diagnostic.js +11 -11
- package/dist/src/pipeline/dispatch/pipeline-inbound-queue.js +2 -2
- package/dist/src/pipeline/execution/group-item-executor.js +1 -1
- package/dist/src/pipeline/execution/node-item-executor.js +3 -3
- package/dist/src/pipeline/execution/node-runner.js +7 -7
- package/dist/src/pipeline/execution/readiness-state.js +1 -1
- package/dist/src/pipeline/execution/reject-handler.js +5 -5
- package/dist/src/pipeline/execution/rejected-artifact-archiver.js +1 -1
- package/dist/src/pipeline/execution/route-item-manager.js +4 -4
- package/dist/src/pipeline/execution/run-abort-controller.js +5 -5
- package/dist/src/pipeline/execution/run-state-helpers.js +2 -2
- package/dist/src/pipeline/execution/service.js +4 -4
- package/dist/src/pipeline/execution/structured-node-runner.js +24 -24
- package/dist/src/pipeline/execution-timeout.js +3 -3
- package/dist/src/pipeline/identity/index.js +3 -3
- package/dist/src/pipeline/item-batch-controller.js +6 -6
- package/dist/src/pipeline/scheduler/dependency-state.js +5 -5
- package/dist/src/pipeline/scheduler-service.js +24 -24
- package/dist/src/pipeline/state-machine.js +2 -2
- package/dist/src/pipeline/structured-output/contract.js +4 -4
- package/dist/src/pipeline/structured-output/index.js +2 -2
- package/dist/src/pipeline/structured-output/parser.js +5 -5
- package/dist/src/pipeline/structured-output/prompt.js +38 -38
- package/dist/src/pipeline/structured-output/waiter.js +6 -6
- package/dist/src/pipeline/template.js +5 -5
- package/dist/src/pipeline/timeline-log-store.js +5 -5
- package/dist/src/pipeline/tool-activity.js +3 -3
- package/dist/src/pipeline/types/pipeline-output.js +1 -1
- package/dist/src/pipeline/workflow/branch-rules.js +19 -19
- package/dist/src/pipeline/workflow/io.js +1 -1
- package/dist/src/pipeline/workflow/normalize.js +18 -18
- package/dist/src/pipeline/workflow/template-mapper.js +3 -3
- package/dist/src/pipeline/workflow/validate.js +39 -39
- package/dist/src/pipeline/workflow-graph.js +10 -10
- package/dist/src/server/http-handler.js +74 -0
- package/dist/src/services/agent-service.js +2 -2
- package/dist/src/services/gateway-read-helpers.js +1 -1
- package/dist/src/services/pipeline-service.js +19 -19
- package/dist/src/services/pipeline-status.js +4 -4
- package/dist/src/services/read-services.js +1 -1
- package/dist/src/services/session-service.js +6 -6
- package/dist/src/services/system-service.js +1 -1
- package/dist/src/transport/ws-broker.js +12 -1
- package/dist/src/transport/ws-handler.js +60 -0
- package/dist/src/transport/ws-methods/agents.js +144 -0
- package/dist/src/transport/ws-methods/artifacts.js +171 -0
- package/dist/src/transport/ws-methods/gateway.js +16 -0
- package/dist/src/transport/ws-methods/logs.js +43 -0
- package/dist/src/transport/ws-methods/pipeline-batch.js +68 -0
- package/dist/src/transport/ws-methods/pipeline-links.js +100 -0
- package/dist/src/transport/ws-methods/pipeline-queue.js +51 -0
- package/dist/src/transport/ws-methods/pipeline-runtime.js +151 -0
- package/dist/src/transport/ws-methods/pipeline-scheduler.js +48 -0
- package/dist/src/transport/ws-methods/pipeline-workflow.js +127 -0
- package/dist/src/transport/ws-methods/pipelines.js +56 -0
- package/dist/src/transport/ws-methods/register-all.js +32 -0
- package/dist/src/transport/ws-methods/sessions.js +154 -0
- package/dist/src/transport/ws-methods/timeline.js +10 -0
- package/dist/src/{server/routes/pipeline-identity.js → transport/ws-methods/utils.js} +14 -9
- package/dist/src/version.js +1 -1
- package/package.json +16 -7
- package/web/dist/assets/agent-DP6TMcLj.js +1 -0
- package/web/dist/assets/agent-DmJHzLyj.js +1 -0
- package/web/dist/assets/artifact-BqnoZy2M.js +1 -0
- package/web/dist/assets/artifact-DfDkgkno.js +1 -0
- package/web/dist/assets/common-DRMTVwE9.js +1 -0
- package/web/dist/assets/common-DeXccbr2.js +1 -0
- package/web/dist/assets/dispatch-CBskGCQI.js +1 -0
- package/web/dist/assets/dispatch-sk4Wp30e.js +1 -0
- package/web/dist/assets/index-C8wTjZvH.css +1 -0
- package/web/dist/assets/index-DYDQZRLk.js +58 -0
- package/web/dist/assets/log-DN8cjb0w.js +1 -0
- package/web/dist/assets/log-HSeA_dYy.js +1 -0
- package/web/dist/assets/modal-BdNai9jf.js +1 -0
- package/web/dist/assets/modal-D9_KDpFD.js +1 -0
- package/web/dist/assets/nav-BmF7oAKg.js +1 -0
- package/web/dist/assets/nav-IjC2xqXQ.js +1 -0
- package/web/dist/assets/node-detail-CENRXcrh.js +1 -0
- package/web/dist/assets/node-detail-bndPr0IM.js +1 -0
- package/web/dist/assets/overview-B87zWAxq.js +1 -0
- package/web/dist/assets/overview-gQvk-NOK.js +1 -0
- package/web/dist/assets/pipeline-D4dSJRDz.js +1 -0
- package/web/dist/assets/pipeline-DZzyOqQa.js +1 -0
- package/web/dist/assets/session-CUWvU14v.js +5 -0
- package/web/dist/assets/session-DQ6UuCaJ.js +5 -0
- package/web/dist/assets/timeline-8y_2_0Em.js +1 -0
- package/web/dist/assets/timeline-CAPsXUTC.js +1 -0
- package/web/dist/index.html +3 -3
- package/dist/src/app/pipeline-plugin-config.js +0 -2
- package/dist/src/server/api-handler.js +0 -163
- package/dist/src/server/http-utils.js +0 -34
- package/dist/src/server/middleware.js +0 -61
- package/dist/src/server/router.js +0 -105
- package/dist/src/server/routes/agents.js +0 -189
- package/dist/src/server/routes/artifacts.js +0 -163
- package/dist/src/server/routes/gateway.js +0 -18
- package/dist/src/server/routes/health.js +0 -16
- package/dist/src/server/routes/logs.js +0 -73
- package/dist/src/server/routes/pipeline-batch.js +0 -163
- package/dist/src/server/routes/pipeline-diagnostics.js +0 -33
- package/dist/src/server/routes/pipeline-links.js +0 -117
- package/dist/src/server/routes/pipeline-outputs.js +0 -27
- package/dist/src/server/routes/pipeline-queue.js +0 -62
- package/dist/src/server/routes/pipeline-runtime.js +0 -162
- package/dist/src/server/routes/pipeline-scheduler.js +0 -69
- package/dist/src/server/routes/pipeline-workflow.js +0 -180
- package/dist/src/server/routes/pipelines.js +0 -96
- package/dist/src/server/routes/sessions.js +0 -244
- package/dist/src/server/routes/timeline.js +0 -14
- package/dist/src/server/serve-static.js +0 -42
- package/web/dist/assets/index-CWnfhkn-.js +0 -65
- package/web/dist/assets/index-gZ0xOfSO.css +0 -1
- /package/dist/src/{server → transport/ws-methods}/types.js +0 -0
package/README.zh-CN.md
CHANGED
|
@@ -1,172 +1,176 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<a href="./README.md">English</a>
|
|
3
|
-
·
|
|
4
|
-
<strong>简体中文</strong>
|
|
5
|
-
·
|
|
6
|
-
<a href="https://taskmeld.com">Website</a>
|
|
7
|
-
</p>
|
|
8
|
-
|
|
9
|
-
<p align="center">
|
|
10
|
-
<a href="
|
|
11
|
-
<a href="./package.json"><img src="https://img.shields.io/node/v/taskmeld.svg?style=flat-square&color=5fa04e&labelColor=161b22&logo=nodedotjs&logoColor=white" alt="node"/></a>
|
|
12
|
-
</p>
|
|
13
|
-
|
|
14
|
-
<br/>
|
|
15
|
-
|
|
16
|
-
<h3 align="center">Agent 流水线编排平台</h3>
|
|
17
|
-
<p align="center">将 OpenClaw Agent 编排为可执行流水线——定义、运行、观察、迭代。文件持久化,零外部数据库。</p>
|
|
18
|
-
|
|
19
|
-
<br/>
|
|
20
|
-
|
|
21
|
-
> [!TIP]
|
|
22
|
-
> TaskMeld 是 OpenClaw 的流水线运行时。OpenClaw 负责 Agent 执行,TaskMeld 负责将 Agent 串成 DAG 工作流,含路由分流、重试和产物追踪。
|
|
23
|
-
|
|
24
|
-
<br/>
|
|
25
|
-
|
|
26
|
-
##
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
|
60
|
-
|
|
61
|
-
| `taskmeld pipeline
|
|
62
|
-
| `taskmeld pipeline
|
|
63
|
-
| `taskmeld
|
|
64
|
-
| `taskmeld
|
|
65
|
-
| `taskmeld
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
- **
|
|
78
|
-
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
|
101
|
-
|
|
102
|
-
| `src/
|
|
103
|
-
| `src/
|
|
104
|
-
| `src/
|
|
105
|
-
| `src/
|
|
106
|
-
| `src/
|
|
107
|
-
| `
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
npm
|
|
142
|
-
npm run
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
|
150
|
-
|
|
151
|
-
|
|
|
152
|
-
|
|
|
153
|
-
|
|
|
154
|
-
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
- [
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
<
|
|
171
|
-
|
|
172
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="./README.md">English</a>
|
|
3
|
+
·
|
|
4
|
+
<strong>简体中文</strong>
|
|
5
|
+
·
|
|
6
|
+
<a href="https://taskmeld.com">Website</a>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/taskmeld"><img src="https://img.shields.io/npm/v/taskmeld.svg?style=flat-square&color=cb3837&labelColor=161b22&logo=npm&logoColor=white" alt="npm version"/></a>
|
|
11
|
+
<a href="./package.json"><img src="https://img.shields.io/node/v/taskmeld.svg?style=flat-square&color=5fa04e&labelColor=161b22&logo=nodedotjs&logoColor=white" alt="node"/></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<br/>
|
|
15
|
+
|
|
16
|
+
<h3 align="center">Agent 流水线编排平台</h3>
|
|
17
|
+
<p align="center">将 OpenClaw Agent 编排为可执行流水线——定义、运行、观察、迭代。文件持久化,零外部数据库。</p>
|
|
18
|
+
|
|
19
|
+
<br/>
|
|
20
|
+
|
|
21
|
+
> [!TIP]
|
|
22
|
+
> TaskMeld 是 OpenClaw 的流水线运行时。OpenClaw 负责 Agent 执行,TaskMeld 负责将 Agent 串成 DAG 工作流,含路由分流、重试和产物追踪。
|
|
23
|
+
|
|
24
|
+
<br/>
|
|
25
|
+
|
|
26
|
+
## 环境要求
|
|
27
|
+
|
|
28
|
+
- Node ≥ 18
|
|
29
|
+
- OpenClaw ≥ 5.20
|
|
30
|
+
- Windows(macOS 和 Linux 尚未测试验证)
|
|
31
|
+
|
|
32
|
+
## 安装
|
|
33
|
+
|
|
34
|
+
~~~bash
|
|
35
|
+
npm install -g taskmeld
|
|
36
|
+
~~~
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
## 快速开始
|
|
41
|
+
|
|
42
|
+
~~~bash
|
|
43
|
+
# 初始化 — 引导式配置 OpenClaw Gateway 连接
|
|
44
|
+
taskmeld init
|
|
45
|
+
|
|
46
|
+
# 启动后端守护进程
|
|
47
|
+
taskmeld server start
|
|
48
|
+
|
|
49
|
+
# 查看可用流水线
|
|
50
|
+
taskmeld pipeline list
|
|
51
|
+
|
|
52
|
+
# 运行一条流水线
|
|
53
|
+
taskmeld pipeline start <pipelineId>
|
|
54
|
+
|
|
55
|
+
# 实时监听流水线运行
|
|
56
|
+
taskmeld pipeline watch <pipelineId>
|
|
57
|
+
~~~
|
|
58
|
+
|
|
59
|
+
| 命令 | 场景 |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `taskmeld pipeline list` | 查看有哪些流水线 |
|
|
62
|
+
| `taskmeld pipeline start <id>` | 启动一条流水线 |
|
|
63
|
+
| `taskmeld pipeline watch <id>` | 通过 WebSocket 实时跟踪运行 |
|
|
64
|
+
| `taskmeld pipeline status <id>` | 一次性状态快照 |
|
|
65
|
+
| `taskmeld pipeline stop <id>` | 停止运行中的流水线 |
|
|
66
|
+
| `taskmeld pipeline retry-node <id> <node>` | 重试失败的节点 |
|
|
67
|
+
| `taskmeld server start` | 启动后端守护进程 |
|
|
68
|
+
| `taskmeld agent list` | 列出已注册的 Agent |
|
|
69
|
+
| `taskmeld artifact list` | 浏览流水线产物 |
|
|
70
|
+
|
|
71
|
+
完整命令参考:`taskmeld --help` 或 [CLI 文档](docs/cli.md)。
|
|
72
|
+
|
|
73
|
+
<br/>
|
|
74
|
+
|
|
75
|
+
## 特性
|
|
76
|
+
|
|
77
|
+
- **DAG 流水线引擎** — 节点依赖图、并行组、路由分支、节点级重试、状态持久化
|
|
78
|
+
- **CLI 工具** — 全生命周期管理:list, run, status, stop, retry, watch(WebSocket 流式监听)
|
|
79
|
+
- **WebSocket API** — 统一 WS 传输层,用于控制面和实时可观测性
|
|
80
|
+
- **Web 控制台** — React 19 仪表盘,含 DAG 可视化、Agent 会话、产物浏览器、日志查看器
|
|
81
|
+
- **Gateway 集成** — WebSocket 客户端,对接 OpenClaw Gateway 鉴权、事件中继与 Agent 会话委托
|
|
82
|
+
- **文件持久化** — 所有状态以 JSON 和日志文件默认存储在 `~/.taskmeld/` 下,可用 `TASKMELD_DATA_DIR` 覆盖,无需外部数据库
|
|
83
|
+
|
|
84
|
+
<br/>
|
|
85
|
+
|
|
86
|
+
## 架构
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
CLI (taskmeld) · Web 控制台 (React)
|
|
90
|
+
│ │
|
|
91
|
+
WS RPC ─────── WS Broker
|
|
92
|
+
│ │
|
|
93
|
+
App Assembly (注册表 + 运行时)
|
|
94
|
+
│
|
|
95
|
+
Pipeline Engine (DAG · 调度器 · 状态机)
|
|
96
|
+
│
|
|
97
|
+
Gateway Client (OpenClaw — 鉴权、事件、会话)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
| 目录 | 说明 |
|
|
101
|
+
|------|------|
|
|
102
|
+
| `src/cli/` | CLI 入口、路由、输出渲染 |
|
|
103
|
+
| `src/pipeline/` | 流水线引擎(运行时、调度器、执行、DAG) |
|
|
104
|
+
| `src/server/` | HTTP 服务(健康检查 + 静态文件) |
|
|
105
|
+
| `src/transport/` | WebSocket 传输层(广播 + RPC 方法) |
|
|
106
|
+
| `src/gateway/` | 外部 Gateway WebSocket 客户端 |
|
|
107
|
+
| `src/services/` | 服务层(读写 facade) |
|
|
108
|
+
| `src/app/` | 应用装配(注册表、运行时、上下文) |
|
|
109
|
+
| `src/artifacts/` | 产物存储 |
|
|
110
|
+
| `src/logs/` | 时间线日志 |
|
|
111
|
+
| `web/` | React 管理前端 |
|
|
112
|
+
|
|
113
|
+
<br/>
|
|
114
|
+
|
|
115
|
+
## 开发状态
|
|
116
|
+
|
|
117
|
+
> [!WARNING]
|
|
118
|
+
> TaskMeld 当前处于初始测试阶段。功能正在逐步构建,API 可能在版本之间变化,部分界面仍较为粗糙。生产环境使用请自行评估——欢迎早期用户试用和反馈。
|
|
119
|
+
|
|
120
|
+
<br/>
|
|
121
|
+
|
|
122
|
+
## 后续规划
|
|
123
|
+
|
|
124
|
+
### 现状
|
|
125
|
+
|
|
126
|
+
- **流水线执行** — 节点驱动模式,每个节点绑定一个 OpenClaw Agent。CLI 已暴露完整命令集,外部 Agent 可通过编程方式调用(`pipeline list`、`pipeline start`、`pipeline status` 等)。
|
|
127
|
+
- **Agent 管理** — 以只读操作为主(对话、编辑核心文件如 `agent.md` / `memory.md` / `soul.md`)。创建 Agent、配置 Skill 等操作仍需切换到 OpenClaw 中完成。
|
|
128
|
+
- **数据存储** — 基于文件持久化(默认 `~/.taskmeld/` 下的 JSON + 日志文件),零外部依赖。
|
|
129
|
+
|
|
130
|
+
### 计划
|
|
131
|
+
|
|
132
|
+
- **内置自主 Agent** — 作为一等运行时组件,全权负责流水线的完整生命周期:调度运行、创建与审查流水线定义、故障分类、产物整理。目标是从*你来操作流水线*过渡到*Agent 操作流水线,你来指挥*。
|
|
133
|
+
- **Agent 生命周期管理** — 创建 Agent、配置 Skill、编辑核心文件等操作统一收敛到 TaskMeld 内,由内置 Agent 驱动,不再需要切换到 OpenClaw。
|
|
134
|
+
- **数据库存储层** — 从文件持久化演进为数据库存储,提升查询性能、并发访问和可扩展性,同时保留单节点零依赖的轻量体验。
|
|
135
|
+
|
|
136
|
+
<br/>
|
|
137
|
+
|
|
138
|
+
## 开发
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npm install # 安装依赖
|
|
142
|
+
npm run build # 构建
|
|
143
|
+
npm run typecheck # 仅类型检查
|
|
144
|
+
npm run lint # 代码检查
|
|
145
|
+
npm test # 运行测试
|
|
146
|
+
npm run dev:web # 启动前端开发服务器(Vite HMR)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
| 层面 | 技术 |
|
|
150
|
+
|------|------|
|
|
151
|
+
| 语言 | TypeScript(strict, CommonJS) |
|
|
152
|
+
| 运行时 | Node.js |
|
|
153
|
+
| 后端 HTTP | Node.js 内置 `http` |
|
|
154
|
+
| WebSocket | `ws` |
|
|
155
|
+
| 前端 | React 19 + Vite 7 |
|
|
156
|
+
| CSS | Tailwind CSS 4 |
|
|
157
|
+
| 测试 | Vitest |
|
|
158
|
+
| Lint | ESLint 9 |
|
|
159
|
+
|
|
160
|
+
<br/>
|
|
161
|
+
|
|
162
|
+
## 文档
|
|
163
|
+
|
|
164
|
+
- [CLI 参考](docs/cli.md)
|
|
165
|
+
- [后端架构](docs/backend.md)
|
|
166
|
+
- [前端架构](docs/web.md)
|
|
167
|
+
- [流水线引擎](docs/pipeline/)
|
|
168
|
+
- [参与贡献](CONTRIBUTING.md)
|
|
169
|
+
|
|
170
|
+
<br/>
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
<p align="center">
|
|
175
|
+
<sub>MIT — 详见 <a href="./LICENSE">LICENSE</a></sub>
|
|
176
|
+
</p>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.resolveAppContextConfig = exports.DEFAULT_ITEM_KEYS = exports.DEFAULT_GATEWAY_SCOPES = exports.DEFAULT_WEB_ORIGIN = exports.DEFAULT_API_HOST = exports.DEFAULT_API_PORT = void 0;
|
|
4
|
-
exports.DEFAULT_API_PORT =
|
|
4
|
+
exports.DEFAULT_API_PORT = 54320;
|
|
5
5
|
exports.DEFAULT_API_HOST = "0.0.0.0";
|
|
6
6
|
exports.DEFAULT_WEB_ORIGIN = "*";
|
|
7
7
|
exports.DEFAULT_GATEWAY_SCOPES = ["operator.read", "operator.write", "operator.admin"];
|
|
@@ -35,8 +35,8 @@ const createAppContext = (options = {}) => {
|
|
|
35
35
|
const ensureGatewayClient = () => {
|
|
36
36
|
if (clientRef)
|
|
37
37
|
return clientRef;
|
|
38
|
-
// dev/server
|
|
39
|
-
//
|
|
38
|
+
// dev/server processes allow HTTP/WS to start even before gateway is configured/ready,
|
|
39
|
+
// deferring credential validation to when connect/sendReq actually fires, to support both local debugging and gateway-required commands.
|
|
40
40
|
const credentials = resolveGatewayCredentials(options, env);
|
|
41
41
|
clientRef = (0, gateway_1.createGatewayClient)({
|
|
42
42
|
gatewayUrl: credentials.url,
|
|
@@ -83,7 +83,7 @@ const createAppContext = (options = {}) => {
|
|
|
83
83
|
});
|
|
84
84
|
appRef = app;
|
|
85
85
|
const appServices = (0, services_1.createAppServices)(app);
|
|
86
|
-
//
|
|
86
|
+
// After the gateway handshake succeeds, feed the hello payload back into the registry so runtime context stays complete.
|
|
87
87
|
const connect = async () => {
|
|
88
88
|
const hello = await client.connect();
|
|
89
89
|
app.onGatewayReady(hello);
|
package/dist/src/app/data-dir.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveTaskMeldDataPath = exports.getTaskMeldDataDir = exports.isTaskMeldTestRuntime = void 0;
|
|
3
|
+
exports.resolveTaskMeldDataPath = exports.getTaskMeldDataDir = exports.isTaskMeldDevRuntime = exports.isTaskMeldTestRuntime = void 0;
|
|
4
4
|
const node_os_1 = require("node:os");
|
|
5
5
|
const node_path_1 = require("node:path");
|
|
6
6
|
const TEST_ARG_PATTERN = /(^|[\\/])(test|dist[\\/]test)[\\/]/;
|
|
7
|
+
// Detect tsx / ts-node etc. dev-mode run (e.g. `npx tsx src/index.ts`)
|
|
8
|
+
const DEV_ARG_PATTERN = /(^|[\\/])tsx[\\/]|(^|[\\/])(src[\\/]index\.ts)$/;
|
|
7
9
|
const isTaskMeldTestRuntime = (options = {}) => {
|
|
8
10
|
const env = options.env ?? process.env;
|
|
9
11
|
const argv = options.argv ?? process.argv;
|
|
@@ -13,13 +15,21 @@ const isTaskMeldTestRuntime = (options = {}) => {
|
|
|
13
15
|
return argv.some((arg) => TEST_ARG_PATTERN.test(arg));
|
|
14
16
|
};
|
|
15
17
|
exports.isTaskMeldTestRuntime = isTaskMeldTestRuntime;
|
|
18
|
+
const isTaskMeldDevRuntime = (options = {}) => {
|
|
19
|
+
const env = options.env ?? process.env;
|
|
20
|
+
const argv = options.argv ?? process.argv;
|
|
21
|
+
if (env.NODE_ENV === "development")
|
|
22
|
+
return true;
|
|
23
|
+
return argv.some((arg) => DEV_ARG_PATTERN.test(arg));
|
|
24
|
+
};
|
|
25
|
+
exports.isTaskMeldDevRuntime = isTaskMeldDevRuntime;
|
|
16
26
|
const getTaskMeldDataDir = (options = {}) => {
|
|
17
27
|
const env = options.env ?? process.env;
|
|
18
28
|
const override = env.TASKMELD_DATA_DIR?.trim();
|
|
19
29
|
if (override)
|
|
20
30
|
return override;
|
|
21
|
-
//
|
|
22
|
-
if ((0, exports.isTaskMeldTestRuntime)({ env, argv: options.argv })) {
|
|
31
|
+
// Test/dev cases default to isolation in the current repo to avoid polluting the user's real ~/.taskmeld data.
|
|
32
|
+
if ((0, exports.isTaskMeldTestRuntime)({ env, argv: options.argv }) || (0, exports.isTaskMeldDevRuntime)({ env, argv: options.argv })) {
|
|
23
33
|
return (0, node_path_1.join)(options.cwd ?? process.cwd(), ".data");
|
|
24
34
|
}
|
|
25
35
|
return (0, node_path_1.join)(options.homeDir ?? (0, node_os_1.homedir)(), ".taskmeld");
|
|
@@ -15,7 +15,7 @@ const createPipelineDefinition = (id, title) => {
|
|
|
15
15
|
const baseDir = (0, node_path_1.join)(PIPELINE_ROOT_DIR, id);
|
|
16
16
|
return {
|
|
17
17
|
id,
|
|
18
|
-
title: title?.trim() ||
|
|
18
|
+
title: title?.trim() || `Pipeline ${id}`,
|
|
19
19
|
workflowFilePath: (0, node_path_1.join)(baseDir, "workflow.json"),
|
|
20
20
|
runStateFile: (0, node_path_1.join)(baseDir, "run-state.json"),
|
|
21
21
|
artifactDir: (0, node_path_1.join)(baseDir, "artifacts"),
|
|
@@ -27,7 +27,7 @@ const createDefaultDefinitionsDocument = () => ({
|
|
|
27
27
|
defaultPipelineId: DEFAULT_PIPELINE_ID_FALLBACK,
|
|
28
28
|
items: DEFAULT_PIPELINE_IDS.map((pipelineId) => ({
|
|
29
29
|
id: pipelineId,
|
|
30
|
-
title:
|
|
30
|
+
title: `Pipeline ${pipelineId}`,
|
|
31
31
|
})),
|
|
32
32
|
});
|
|
33
33
|
const isValidPipelineId = (value) => typeof value === "string" && PIPELINE_ID_PATTERN.test(value.trim());
|
|
@@ -43,7 +43,7 @@ const normalizeDefinitionItems = (items) => {
|
|
|
43
43
|
if (!(0, exports.isValidPipelineId)(record.id))
|
|
44
44
|
continue;
|
|
45
45
|
const id = record.id.trim();
|
|
46
|
-
const title = typeof record.title === "string" && record.title.trim() ? record.title.trim() :
|
|
46
|
+
const title = typeof record.title === "string" && record.title.trim() ? record.title.trim() : `Pipeline ${id}`;
|
|
47
47
|
deduped.set(id, { id, title });
|
|
48
48
|
}
|
|
49
49
|
return [...deduped.values()];
|
|
@@ -82,7 +82,7 @@ exports.savePipelineDefinitions = savePipelineDefinitions;
|
|
|
82
82
|
const ensurePipelineDefinitions = () => {
|
|
83
83
|
const fromDisk = readDefinitionsDocumentFromDisk();
|
|
84
84
|
if (fromDisk) {
|
|
85
|
-
//
|
|
85
|
+
// Rewrite on every startup according to current constraints, to prevent defaultPipelineId pointing to an invalid entry or title being empty.
|
|
86
86
|
return (0, exports.savePipelineDefinitions)(fromDisk);
|
|
87
87
|
}
|
|
88
88
|
return (0, exports.savePipelineDefinitions)(createDefaultDefinitionsDocument());
|
|
@@ -30,7 +30,7 @@ const buildArchivedPipelineDirPath = (pipelineId) => {
|
|
|
30
30
|
(0, node_fs_1.mkdirSync)(deletedRootDir, { recursive: true });
|
|
31
31
|
let archiveDirPath = (0, node_path_1.join)(deletedRootDir, createArchivedPipelineDirName(pipelineId));
|
|
32
32
|
let suffix = 1;
|
|
33
|
-
//
|
|
33
|
+
// Archive directory must be unique to avoid collisions when multiple deletes happen within the same second.
|
|
34
34
|
while ((0, node_fs_1.existsSync)(archiveDirPath)) {
|
|
35
35
|
archiveDirPath = (0, node_path_1.join)(deletedRootDir, `${createArchivedPipelineDirName(pipelineId)}-${suffix}`);
|
|
36
36
|
suffix += 1;
|
|
@@ -184,7 +184,7 @@ const createPipelineRegistry = (options) => {
|
|
|
184
184
|
});
|
|
185
185
|
return;
|
|
186
186
|
}
|
|
187
|
-
//
|
|
187
|
+
// Gateway status/handshake is a globally shared event; only relay one copy from the primary pipeline to avoid duplicate broadcasts to frontends.
|
|
188
188
|
if ((event.type === "gateway.status" || event.type === "gateway.ready" || event.type === "gateway.frame") &&
|
|
189
189
|
definition.id !== getPrimaryPipelineId()) {
|
|
190
190
|
return;
|
|
@@ -241,7 +241,7 @@ const createPipelineRegistry = (options) => {
|
|
|
241
241
|
timelineHasMore: combined.length > MAX_BOOTSTRAP_TIMELINE,
|
|
242
242
|
status: getPrimaryRuntime().gateway.getLatestStatus() ?? options.client.getStatus(),
|
|
243
243
|
hello: getPrimaryRuntime().gateway.getLatestHello(),
|
|
244
|
-
//
|
|
244
|
+
// Keep the old top-level fields as a fallback to avoid breaking frontends during phased migration.
|
|
245
245
|
run: primary?.run,
|
|
246
246
|
pipeline: primary?.pipeline,
|
|
247
247
|
runId: primary?.runId,
|
|
@@ -249,8 +249,8 @@ const createPipelineRegistry = (options) => {
|
|
|
249
249
|
};
|
|
250
250
|
};
|
|
251
251
|
const broadcastBootstrapPayload = () => {
|
|
252
|
-
//
|
|
253
|
-
//
|
|
252
|
+
// When pipeline resources are added, deleted, or renamed, broadcast a full bootstrap immediately;
|
|
253
|
+
// this way connected frontends get the latest definitions + runtime snapshot in one message, avoiding hand-rolled patch merging with missing fields.
|
|
254
254
|
broadcast({
|
|
255
255
|
type: "bootstrap",
|
|
256
256
|
payload: getBootstrapPayload(),
|
|
@@ -285,7 +285,7 @@ const createPipelineRegistry = (options) => {
|
|
|
285
285
|
};
|
|
286
286
|
const initializePipelineWorkflowFile = (definition, cloneFrom) => {
|
|
287
287
|
if (cloneFrom) {
|
|
288
|
-
//
|
|
288
|
+
// Clone only copies the workflow definition; runtime state and artifact directories are independently initialized for the new pipeline.
|
|
289
289
|
const sourceWorkflow = (0, template_1.loadWorkflowDefinitionWithStorage)({ workflowFilePath: cloneFrom.workflowFilePath });
|
|
290
290
|
(0, template_1.saveWorkflowDefinitionWithStorage)(sourceWorkflow, { workflowFilePath: definition.workflowFilePath });
|
|
291
291
|
return;
|
|
@@ -309,7 +309,7 @@ const createPipelineRegistry = (options) => {
|
|
|
309
309
|
continue;
|
|
310
310
|
await runtime.initialize();
|
|
311
311
|
}
|
|
312
|
-
//
|
|
312
|
+
// On startup, scan all pipelines and trigger drain for any queue that already has pending jobs.
|
|
313
313
|
for (const definition of pipelineDefinitions) {
|
|
314
314
|
const pendingCount = inboundQueue.getPendingCount(definition.id);
|
|
315
315
|
if (pendingCount > 0) {
|
|
@@ -356,8 +356,8 @@ const createPipelineRegistry = (options) => {
|
|
|
356
356
|
persistDefinitions([...currentDocument.items, { id: definition.id, title: definition.title }], currentDocument.defaultPipelineId);
|
|
357
357
|
let runtime = null;
|
|
358
358
|
try {
|
|
359
|
-
//
|
|
360
|
-
//
|
|
359
|
+
// Initialize the target pipeline's workflow file on disk first, then create the runtime;
|
|
360
|
+
// otherwise the runtime constructor would read the default workflow, causing a "disk already cloned, memory still default" phantom-clone issue.
|
|
361
361
|
initializePipelineWorkflowFile(definition, cloneSourceDefinition ?? undefined);
|
|
362
362
|
runtime = createRuntimeForDefinition(definition);
|
|
363
363
|
bindRuntimeBroadcast(definition, runtime);
|
|
@@ -419,7 +419,7 @@ const createPipelineRegistry = (options) => {
|
|
|
419
419
|
archivePipelineDirectory(pipelineDefinitions[definitionIndex]);
|
|
420
420
|
}
|
|
421
421
|
catch (error) {
|
|
422
|
-
//
|
|
422
|
+
// If archiving fails, immediately rollback definitions so the page listing doesn't remove the pipeline while the directory stays in place.
|
|
423
423
|
(0, pipeline_config_1.savePipelineDefinitions)(currentDocument);
|
|
424
424
|
throw error;
|
|
425
425
|
}
|
|
@@ -484,7 +484,7 @@ const createPipelineRegistry = (options) => {
|
|
|
484
484
|
if (routed)
|
|
485
485
|
return;
|
|
486
486
|
}
|
|
487
|
-
//
|
|
487
|
+
// When sessionKey can't be determined or no runtime matches, deliver to all
|
|
488
488
|
for (const runtime of runtimeById.values()) {
|
|
489
489
|
runtime.onGatewayRawFrame(rawFrame);
|
|
490
490
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createPipelineRuntime = void 0;
|
|
4
|
-
const http_utils_1 = require("../server/http-utils");
|
|
5
4
|
const template_1 = require("../pipeline/template");
|
|
6
5
|
const execution_timeout_1 = require("../pipeline/execution-timeout");
|
|
7
6
|
const runtime_model_1 = require("../pipeline/runtime-model");
|
|
@@ -45,7 +44,6 @@ const createPipelineRuntime = (options) => {
|
|
|
45
44
|
getBatchRunId = () => schedulerService.getBatchRunState().batchRunId;
|
|
46
45
|
schedulerStateAccessor = schedulerService.getSchedulerState;
|
|
47
46
|
batchRunStateGetter = () => schedulerService.getBatchRunState();
|
|
48
|
-
const sendJson = (res, code, data) => (0, http_utils_1.sendJson)(res, code, data, options.webOrigin);
|
|
49
47
|
const setRun = (nextRun) => {
|
|
50
48
|
runtimeStore.setRun(nextRun);
|
|
51
49
|
graph.syncRunGroupsFromWorkflow(nextRun);
|
|
@@ -64,7 +62,7 @@ const createPipelineRuntime = (options) => {
|
|
|
64
62
|
};
|
|
65
63
|
const onGatewayStatus = (status) => {
|
|
66
64
|
runtimeStore.setLatestStatus(status);
|
|
67
|
-
runtimeStore.pushTimeline(
|
|
65
|
+
runtimeStore.pushTimeline(`Gateway status: ${status.status}`, status.status.includes("failed") ? "error" : "info");
|
|
68
66
|
runtimeStore.broadcast({
|
|
69
67
|
type: "gateway.status",
|
|
70
68
|
payload: status,
|
|
@@ -76,7 +74,7 @@ const createPipelineRuntime = (options) => {
|
|
|
76
74
|
const isSilentEvent = isHealthEvent || isTickEvent;
|
|
77
75
|
runtimeStore.setLastFrame(frame);
|
|
78
76
|
if (frame.type === "event" && !isSilentEvent) {
|
|
79
|
-
runtimeStore.pushTimeline(
|
|
77
|
+
runtimeStore.pushTimeline(`Event: ${frame.event}`, "info", {
|
|
80
78
|
type: "event",
|
|
81
79
|
event: frame.event,
|
|
82
80
|
seq: frame.seq ?? null,
|
|
@@ -84,8 +82,8 @@ const createPipelineRuntime = (options) => {
|
|
|
84
82
|
payload: frame.payload ?? null,
|
|
85
83
|
});
|
|
86
84
|
}
|
|
87
|
-
// health / tick
|
|
88
|
-
//
|
|
85
|
+
// health / tick are high-frequency events; only store as lastFrame, don't push to timeline/WS,
|
|
86
|
+
// to avoid flooding the frontend and logs, while still allowing diagnostic debugging that depends on the last frame.
|
|
89
87
|
if (!isSilentEvent) {
|
|
90
88
|
runtimeStore.broadcast({
|
|
91
89
|
type: "gateway.frame",
|
|
@@ -97,7 +95,7 @@ const createPipelineRuntime = (options) => {
|
|
|
97
95
|
executionService.onGatewayFrame(rawFrame);
|
|
98
96
|
};
|
|
99
97
|
const onGatewayError = (error) => {
|
|
100
|
-
runtimeStore.pushTimeline(
|
|
98
|
+
runtimeStore.pushTimeline(`Gateway error: ${String(error)}`, "error");
|
|
101
99
|
runtimeStore.broadcast({
|
|
102
100
|
type: "gateway.error",
|
|
103
101
|
payload: { message: String(error) },
|
|
@@ -105,7 +103,7 @@ const createPipelineRuntime = (options) => {
|
|
|
105
103
|
};
|
|
106
104
|
const onGatewayReady = (hello) => {
|
|
107
105
|
runtimeStore.setLatestHello(hello);
|
|
108
|
-
runtimeStore.pushTimeline("
|
|
106
|
+
runtimeStore.pushTimeline("Gateway handshake completed");
|
|
109
107
|
runtimeStore.emitPipeline();
|
|
110
108
|
runtimeStore.broadcast({
|
|
111
109
|
type: "gateway.ready",
|
|
@@ -135,7 +133,6 @@ const createPipelineRuntime = (options) => {
|
|
|
135
133
|
return {
|
|
136
134
|
initialize,
|
|
137
135
|
dispose: () => executionService.dispose(),
|
|
138
|
-
sendJson,
|
|
139
136
|
getBootstrapPayload,
|
|
140
137
|
onGatewayStatus,
|
|
141
138
|
onGatewayFrame,
|
|
@@ -31,7 +31,7 @@ const createRuntimeStore = (options) => {
|
|
|
31
31
|
}
|
|
32
32
|
catch (error) {
|
|
33
33
|
// Persistence failures should not break pipeline execution.
|
|
34
|
-
pushTimeline(
|
|
34
|
+
pushTimeline(`Failed to persist run state: ${error instanceof Error ? error.message : String(error)}`, "warn");
|
|
35
35
|
}
|
|
36
36
|
})().finally(() => {
|
|
37
37
|
persistRunStateInFlight = null;
|
|
@@ -69,7 +69,7 @@ const createRuntimeStore = (options) => {
|
|
|
69
69
|
return savedRun;
|
|
70
70
|
}
|
|
71
71
|
catch (error) {
|
|
72
|
-
pushTimeline(
|
|
72
|
+
pushTimeline(`Failed to load persisted run state: ${error instanceof Error ? error.message : String(error)}`, "warn");
|
|
73
73
|
return null;
|
|
74
74
|
}
|
|
75
75
|
};
|
|
@@ -107,7 +107,7 @@ const createRuntimeStore = (options) => {
|
|
|
107
107
|
options.graph.syncRunGroupsFromWorkflow(run);
|
|
108
108
|
(0, runtime_model_1.syncRunNodeStatusFromItemRuns)(run);
|
|
109
109
|
(0, runtime_model_1.touchRun)(run);
|
|
110
|
-
pushTimeline(
|
|
110
|
+
pushTimeline(`Restored previous run state: ${run.id}`);
|
|
111
111
|
emitPipeline();
|
|
112
112
|
};
|
|
113
113
|
const bootstrapRun = () => {
|