agent-tasks 1.6.6 → 1.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -181
- package/dist/context.js +1 -1
- package/dist/context.js.map +1 -1
- package/dist/db.js +38 -38
- package/dist/domain/cleanup.d.ts +8 -1
- package/dist/domain/cleanup.d.ts.map +1 -1
- package/dist/domain/cleanup.js +70 -2
- package/dist/domain/cleanup.js.map +1 -1
- package/dist/domain/rules.js +46 -46
- package/dist/domain/tasks.d.ts +5 -2
- package/dist/domain/tasks.d.ts.map +1 -1
- package/dist/domain/tasks.js +58 -13
- package/dist/domain/tasks.js.map +1 -1
- package/dist/index.js +31 -19
- package/dist/index.js.map +1 -1
- package/dist/storage/database.js +112 -103
- package/dist/storage/database.js.map +1 -1
- package/dist/tasks.js +7 -7
- package/dist/transport/mcp.d.ts +1 -1
- package/dist/transport/mcp.d.ts.map +1 -1
- package/dist/transport/mcp.js +87 -9
- package/dist/transport/mcp.js.map +1 -1
- package/dist/transport/ws.js +6 -6
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ui/index.html +165 -165
- package/dist/ui/styles.css +2512 -2512
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,181 +1,191 @@
|
|
|
1
|
-
# agent-tasks
|
|
2
|
-
|
|
3
|
-
[](LICENSE)
|
|
4
|
-
[](https://nodejs.org/)
|
|
5
|
-
[]()
|
|
6
|
-
[]()
|
|
7
|
-
[]()
|
|
8
|
-
|
|
9
|
-
**Pipeline-driven task management for AI coding agents.** An [MCP](https://modelcontextprotocol.io/) server with stage-gated pipelines, multi-agent collaboration, and a real-time kanban dashboard. Tasks flow through configurable stages — `backlog`, `spec`, `plan`, `implement`, `test`, `review`, `done` — with dependency tracking, approval workflows, artifact versioning, and threaded comments.
|
|
10
|
-
|
|
11
|
-
Built for AI coding agents (Claude Code, Codex CLI, Gemini CLI, Aider) but works equally well with any MCP client, REST consumer, or WebSocket listener.
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
| Light Theme | Dark Theme |
|
|
16
|
-
| -------------------------------------------------------- | ------------------------------------------------------ |
|
|
17
|
-
|  |  |
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Why agent-tasks?
|
|
22
|
-
|
|
23
|
-
When you run multiple AI agents on the same codebase, they need a shared task pipeline — not just a flat todo list. They need stages, dependencies, approvals, and visibility.
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Features
|
|
28
|
-
|
|
29
|
-
- **Pipeline stages** — configurable per project: `backlog` > `spec` > `plan` > `implement` > `test` > `review` > `done`
|
|
30
|
-
- **Task dependencies** — DAG with automatic cycle detection; blocks advancement until resolved
|
|
31
|
-
- **Approval workflows** — stage-gated approve/reject with auto-regress on rejection
|
|
32
|
-
- **Multi-agent collaboration** — roles (collaborator, reviewer, watcher), claiming, assignment
|
|
33
|
-
- **Subtask hierarchies** — parent/child task trees with progress tracking
|
|
34
|
-
- **Threaded comments** — async discussions between agents on any task
|
|
35
|
-
- **Artifact versioning** — per-stage document attachments with automatic versioning and diff viewer
|
|
36
|
-
- **Full-text search** — FTS5 search across task titles and descriptions
|
|
37
|
-
- **Real-time kanban dashboard** — drag-and-drop, side panel, inline creation, dark/light theme
|
|
38
|
-
- **3 transport layers** — MCP (stdio), REST API (HTTP), WebSocket (real-time events)
|
|
39
|
-
- **TodoWrite bridge** — intercepts Claude Code's built-in TodoWrite and syncs to the pipeline
|
|
40
|
-
- **
|
|
41
|
-
- **
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
|
111
|
-
|
|
|
112
|
-
| **
|
|
113
|
-
| **
|
|
114
|
-
| **
|
|
115
|
-
| **
|
|
116
|
-
| **
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
GET /
|
|
128
|
-
GET /api/tasks
|
|
129
|
-
GET /api/tasks/:id
|
|
130
|
-
GET /api/tasks/:id/
|
|
131
|
-
GET /api/tasks/:id/
|
|
132
|
-
GET /api/
|
|
133
|
-
GET /api/
|
|
134
|
-
GET /api/
|
|
135
|
-
GET /api/
|
|
136
|
-
GET /api/
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
npm
|
|
153
|
-
npm run
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
---
|
|
178
|
-
|
|
179
|
-
##
|
|
180
|
-
|
|
181
|
-
|
|
1
|
+
# agent-tasks
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://nodejs.org/)
|
|
5
|
+
[]()
|
|
6
|
+
[]()
|
|
7
|
+
[]()
|
|
8
|
+
|
|
9
|
+
**Pipeline-driven task management for AI coding agents.** An [MCP](https://modelcontextprotocol.io/) server with stage-gated pipelines, multi-agent collaboration, and a real-time kanban dashboard. Tasks flow through configurable stages — `backlog`, `spec`, `plan`, `implement`, `test`, `review`, `done` — with dependency tracking, approval workflows, artifact versioning, and threaded comments.
|
|
10
|
+
|
|
11
|
+
Built for AI coding agents (Claude Code, Codex CLI, Gemini CLI, Aider) but works equally well with any MCP client, REST consumer, or WebSocket listener.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
| Light Theme | Dark Theme |
|
|
16
|
+
| -------------------------------------------------------- | ------------------------------------------------------ |
|
|
17
|
+
|  |  |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Why agent-tasks?
|
|
22
|
+
|
|
23
|
+
When you run multiple AI agents on the same codebase, they need a shared task pipeline — not just a flat todo list. They need stages, dependencies, approvals, and visibility.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- **Pipeline stages** — configurable per project: `backlog` > `spec` > `plan` > `implement` > `test` > `review` > `done`
|
|
30
|
+
- **Task dependencies** — DAG with automatic cycle detection; blocks advancement until resolved
|
|
31
|
+
- **Approval workflows** — stage-gated approve/reject with auto-regress on rejection
|
|
32
|
+
- **Multi-agent collaboration** — roles (collaborator, reviewer, watcher), claiming, assignment
|
|
33
|
+
- **Subtask hierarchies** — parent/child task trees with progress tracking
|
|
34
|
+
- **Threaded comments** — async discussions between agents on any task
|
|
35
|
+
- **Artifact versioning** — per-stage document attachments with automatic versioning and diff viewer
|
|
36
|
+
- **Full-text search** — FTS5 search across task titles and descriptions
|
|
37
|
+
- **Real-time kanban dashboard** — drag-and-drop, side panel, inline creation, dark/light theme
|
|
38
|
+
- **3 transport layers** — MCP (stdio), REST API (HTTP), WebSocket (real-time events)
|
|
39
|
+
- **TodoWrite bridge** — intercepts Claude Code's built-in TodoWrite and syncs to the pipeline
|
|
40
|
+
- **Stage gates** — configurable per-project gates that require comments or artifacts before advancing
|
|
41
|
+
- **Heartbeat-based cleanup** — auto-fails tasks from dead agents using agent-comm heartbeat data
|
|
42
|
+
- **Task cleanup hooks** — auto-fails orphaned tasks on session stop and cleans up stale tasks on session start
|
|
43
|
+
- **Agent bridge** — notifies connected agents on task events
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
### Install from npm
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install -g agent-tasks
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Or clone from source
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
git clone https://github.com/keshrath/agent-tasks.git
|
|
59
|
+
cd agent-tasks
|
|
60
|
+
npm install
|
|
61
|
+
npm run build
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Option 1: MCP server (for AI agents)
|
|
65
|
+
|
|
66
|
+
Add to your MCP client config (Claude Code, Cline, etc.):
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"agent-tasks": {
|
|
72
|
+
"command": "npx",
|
|
73
|
+
"args": ["agent-tasks"]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The dashboard auto-starts at http://localhost:3422 on the first MCP connection.
|
|
80
|
+
|
|
81
|
+
### Option 2: Standalone server (for REST/WebSocket clients)
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
node dist/server.js --port 3422
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Claude Code Integration
|
|
90
|
+
|
|
91
|
+
Add agent-tasks as an MCP server in `~/.claude/settings.json`:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"mcpServers": {
|
|
96
|
+
"agent-tasks": {
|
|
97
|
+
"command": "node",
|
|
98
|
+
"args": ["/path/to/agent-tasks/dist/index.js"]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Once configured, Claude Code can use all 31 MCP tools directly — creating tasks, advancing stages, adding artifacts, commenting, and more. See the [Setup Guide](docs/SETUP.md) for detailed integration steps.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## MCP Tools (33)
|
|
109
|
+
|
|
110
|
+
| Category | Tools |
|
|
111
|
+
| ----------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
112
|
+
| **Task lifecycle** (12) | `task_create`, `task_list`, `task_next`, `task_claim`, `task_advance`, `task_regress`, `task_complete`, ... |
|
|
113
|
+
| **Subtasks & deps** (4) | `task_expand`, `task_get_subtasks`, `task_add_dependency`, `task_remove_dependency` |
|
|
114
|
+
| **Artifacts** (2) | `task_add_artifact`, `task_get_artifacts` |
|
|
115
|
+
| **Comments** (2) | `task_comment`, `task_get_comments` |
|
|
116
|
+
| **Collaboration** (2) | `task_add_collaborator`, `task_remove_collaborator` |
|
|
117
|
+
| **Approvals** (5) | `task_request_approval`, `task_approve`, `task_reject`, `task_pending_approvals`, `task_review_cycle` |
|
|
118
|
+
| **Config & utils** (4) | `task_pipeline_config`, `task_set_session`, `task_cleanup`, `task_generate_rules` |
|
|
119
|
+
|
|
120
|
+
See [full API reference](docs/API.md) for detailed descriptions of every tool and endpoint.
|
|
121
|
+
|
|
122
|
+
## REST API (19 endpoints)
|
|
123
|
+
|
|
124
|
+
All endpoints return JSON. CORS enabled. See [full API reference](docs/API.md#rest-api-18-endpoints) for details.
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
GET /health Health check with version + uptime
|
|
128
|
+
GET /api/tasks List tasks (status, stage, project, assignee filters)
|
|
129
|
+
GET /api/tasks/:id Get a single task
|
|
130
|
+
GET /api/tasks/:id/subtasks Subtasks of a parent
|
|
131
|
+
GET /api/tasks/:id/artifacts Artifacts (filter by stage)
|
|
132
|
+
GET /api/tasks/:id/comments Comments on a task
|
|
133
|
+
GET /api/tasks/:id/dependencies Dependencies for a task
|
|
134
|
+
GET /api/dependencies All dependencies across all tasks
|
|
135
|
+
GET /api/pipeline Pipeline stage configuration
|
|
136
|
+
GET /api/overview Full state dump
|
|
137
|
+
GET /api/agents Online agents
|
|
138
|
+
GET /api/search?q= Full-text search
|
|
139
|
+
|
|
140
|
+
POST /api/tasks Create a new task
|
|
141
|
+
PUT /api/tasks/:id Update task fields
|
|
142
|
+
PUT /api/tasks/:id/stage Change stage (advance or regress)
|
|
143
|
+
POST /api/tasks/:id/comments Add a comment
|
|
144
|
+
POST /api/cleanup Trigger manual cleanup
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Testing
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm test # 337 tests across 12 suites
|
|
153
|
+
npm run test:watch # Watch mode
|
|
154
|
+
npm run test:coverage # Coverage report
|
|
155
|
+
npm run check # Full CI: typecheck + lint + format + test
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Environment variables
|
|
161
|
+
|
|
162
|
+
| Variable | Default | Description |
|
|
163
|
+
| -------------------------- | ------------------------------- | ---------------------------------------------------- |
|
|
164
|
+
| `AGENT_TASKS_DB` | `~/.agent-tasks/agent-tasks.db` | SQLite database file path |
|
|
165
|
+
| `AGENT_TASKS_PORT` | `3422` | Dashboard HTTP/WebSocket port |
|
|
166
|
+
| `AGENT_TASKS_INSTRUCTIONS` | enabled | Set to `0` to disable response-embedded instructions |
|
|
167
|
+
| `AGENT_COMM_URL` | `http://localhost:3421` | Agent-comm REST URL for bridge notifications |
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Dependencies
|
|
172
|
+
|
|
173
|
+
**Required**: Node.js >= 20.11, better-sqlite3 (bundled)
|
|
174
|
+
|
|
175
|
+
**Optional**: [agent-comm](https://github.com/keshrath/agent-comm) — Heartbeat-based task cleanup requires agent-comm running at `AGENT_COMM_URL` (default: `http://localhost:3421`). Without agent-comm, stale agent detection is skipped gracefully. Flow: agent-comm tracks heartbeats → agent-tasks checks heartbeats → auto-fails tasks from dead agents.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Documentation
|
|
180
|
+
|
|
181
|
+
- [API Reference](docs/API.md) — all 31 MCP tools, 19 REST endpoints, WebSocket protocol
|
|
182
|
+
- [Architecture](docs/ARCHITECTURE.md) — source structure, design principles, database schema
|
|
183
|
+
- [Dashboard](docs/DASHBOARD.md) — kanban board features, keyboard shortcuts, screenshots
|
|
184
|
+
- [Setup Guide](docs/SETUP.md) — installation, client setup (Claude Code, OpenCode, Cursor, Windsurf), hooks
|
|
185
|
+
- [Changelog](CHANGELOG.md)
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT — see [LICENSE](LICENSE)
|
package/dist/context.js
CHANGED
|
@@ -22,7 +22,7 @@ export function createContext(dbOptions) {
|
|
|
22
22
|
const approvals = new ApprovalService(db, events);
|
|
23
23
|
const agentBridge = new AgentBridge(events);
|
|
24
24
|
const retentionDays = parseInt(process.env.AGENT_TASKS_RETENTION_DAYS ?? '30', 10);
|
|
25
|
-
const cleanup = new CleanupService(db, retentionDays);
|
|
25
|
+
const cleanup = new CleanupService(db, retentionDays, agentBridge);
|
|
26
26
|
agentBridge.start();
|
|
27
27
|
cleanup.start();
|
|
28
28
|
return {
|
package/dist/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,oCAAoC;AACpC,EAAE;AACF,sEAAsE;AACtE,sEAAsE;AACtE,gFAAgF;AAEhF,OAAO,EAAE,QAAQ,EAA2B,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAcrD,MAAM,UAAU,aAAa,CAAC,SAAqB;IACjD,MAAM,EAAE,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;IAC9B,IAAI,MAAM,GAAG,KAAK,CAAC;IAEnB,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAChD,MAAM,aAAa,GAAG,IAAI,mBAAmB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAC1D,MAAM,SAAS,GAAG,IAAI,eAAe,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAClD,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,aAAa,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;IACnF,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,oCAAoC;AACpC,EAAE;AACF,sEAAsE;AACtE,sEAAsE;AACtE,gFAAgF;AAEhF,OAAO,EAAE,QAAQ,EAA2B,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAcrD,MAAM,UAAU,aAAa,CAAC,SAAqB;IACjD,MAAM,EAAE,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;IAC9B,IAAI,MAAM,GAAG,KAAK,CAAC;IAEnB,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAChD,MAAM,aAAa,GAAG,IAAI,mBAAmB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAC1D,MAAM,SAAS,GAAG,IAAI,eAAe,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAClD,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,aAAa,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;IACnF,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,EAAE,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC;IAEnE,WAAW,CAAC,KAAK,EAAE,CAAC;IACpB,OAAO,CAAC,KAAK,EAAE,CAAC;IAEhB,OAAO;QACL,EAAE;QACF,MAAM;QACN,KAAK;QACL,QAAQ;QACR,aAAa;QACb,SAAS;QACT,WAAW;QACX,OAAO;QACP,KAAK;YACH,IAAI,MAAM;gBAAE,OAAO;YACnB,MAAM,GAAG,IAAI,CAAC;YACd,OAAO,CAAC,IAAI,EAAE,CAAC;YACf,WAAW,CAAC,IAAI,EAAE,CAAC;YACnB,MAAM,CAAC,SAAS,EAAE,CAAC;YACnB,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/db.js
CHANGED
|
@@ -36,50 +36,50 @@ export function transaction(fn) {
|
|
|
36
36
|
return database.transaction(fn)();
|
|
37
37
|
}
|
|
38
38
|
function initSchema(database) {
|
|
39
|
-
database.exec(`
|
|
40
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
41
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
42
|
-
title TEXT NOT NULL,
|
|
43
|
-
description TEXT,
|
|
44
|
-
created_by TEXT NOT NULL,
|
|
45
|
-
assigned_to TEXT,
|
|
46
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
47
|
-
stage TEXT NOT NULL DEFAULT 'backlog',
|
|
48
|
-
priority INTEGER NOT NULL DEFAULT 0,
|
|
49
|
-
project TEXT,
|
|
50
|
-
tags TEXT,
|
|
51
|
-
result TEXT,
|
|
52
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
53
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
54
|
-
)
|
|
39
|
+
database.exec(`
|
|
40
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
41
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
42
|
+
title TEXT NOT NULL,
|
|
43
|
+
description TEXT,
|
|
44
|
+
created_by TEXT NOT NULL,
|
|
45
|
+
assigned_to TEXT,
|
|
46
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
47
|
+
stage TEXT NOT NULL DEFAULT 'backlog',
|
|
48
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
49
|
+
project TEXT,
|
|
50
|
+
tags TEXT,
|
|
51
|
+
result TEXT,
|
|
52
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
53
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
54
|
+
)
|
|
55
55
|
`);
|
|
56
56
|
database.exec(`CREATE INDEX IF NOT EXISTS idx_tasks_assigned ON tasks(assigned_to, status)`);
|
|
57
57
|
database.exec(`CREATE INDEX IF NOT EXISTS idx_tasks_stage ON tasks(stage, priority)`);
|
|
58
|
-
database.exec(`
|
|
59
|
-
CREATE TABLE IF NOT EXISTS task_dependencies (
|
|
60
|
-
task_id INTEGER NOT NULL,
|
|
61
|
-
depends_on INTEGER NOT NULL,
|
|
62
|
-
PRIMARY KEY (task_id, depends_on)
|
|
63
|
-
)
|
|
58
|
+
database.exec(`
|
|
59
|
+
CREATE TABLE IF NOT EXISTS task_dependencies (
|
|
60
|
+
task_id INTEGER NOT NULL,
|
|
61
|
+
depends_on INTEGER NOT NULL,
|
|
62
|
+
PRIMARY KEY (task_id, depends_on)
|
|
63
|
+
)
|
|
64
64
|
`);
|
|
65
|
-
database.exec(`
|
|
66
|
-
CREATE TABLE IF NOT EXISTS task_artifacts (
|
|
67
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
68
|
-
task_id INTEGER NOT NULL,
|
|
69
|
-
stage TEXT NOT NULL,
|
|
70
|
-
name TEXT NOT NULL,
|
|
71
|
-
content TEXT NOT NULL,
|
|
72
|
-
created_by TEXT NOT NULL,
|
|
73
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
74
|
-
)
|
|
65
|
+
database.exec(`
|
|
66
|
+
CREATE TABLE IF NOT EXISTS task_artifacts (
|
|
67
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
68
|
+
task_id INTEGER NOT NULL,
|
|
69
|
+
stage TEXT NOT NULL,
|
|
70
|
+
name TEXT NOT NULL,
|
|
71
|
+
content TEXT NOT NULL,
|
|
72
|
+
created_by TEXT NOT NULL,
|
|
73
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
74
|
+
)
|
|
75
75
|
`);
|
|
76
76
|
database.exec(`CREATE INDEX IF NOT EXISTS idx_task_artifacts_task ON task_artifacts(task_id, stage)`);
|
|
77
|
-
database.exec(`
|
|
78
|
-
CREATE TABLE IF NOT EXISTS pipeline_config (
|
|
79
|
-
project TEXT PRIMARY KEY,
|
|
80
|
-
stages TEXT NOT NULL DEFAULT '["backlog","spec","plan","implement","test","review","done","cancelled"]',
|
|
81
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
82
|
-
)
|
|
77
|
+
database.exec(`
|
|
78
|
+
CREATE TABLE IF NOT EXISTS pipeline_config (
|
|
79
|
+
project TEXT PRIMARY KEY,
|
|
80
|
+
stages TEXT NOT NULL DEFAULT '["backlog","spec","plan","implement","test","review","done","cancelled"]',
|
|
81
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
82
|
+
)
|
|
83
83
|
`);
|
|
84
84
|
}
|
|
85
85
|
export function queryAll(sql, params) {
|
package/dist/domain/cleanup.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { Db } from '../storage/database.js';
|
|
2
|
+
import type { AgentBridge } from './agent-bridge.js';
|
|
3
|
+
import type { Task } from '../types.js';
|
|
2
4
|
export declare class CleanupService {
|
|
3
5
|
private readonly db;
|
|
4
6
|
private readonly retentionDays;
|
|
7
|
+
private readonly agentBridge?;
|
|
5
8
|
private timer;
|
|
6
|
-
constructor(db: Db, retentionDays?: number);
|
|
9
|
+
constructor(db: Db, retentionDays?: number, agentBridge?: AgentBridge | undefined);
|
|
7
10
|
start(): void;
|
|
8
11
|
stop(): void;
|
|
9
12
|
run(): {
|
|
@@ -18,6 +21,10 @@ export declare class CleanupService {
|
|
|
18
21
|
purgedArtifacts: number;
|
|
19
22
|
purgedApprovals: number;
|
|
20
23
|
};
|
|
24
|
+
failStaleAgentTasks(timeoutMinutes?: number): Promise<{
|
|
25
|
+
failed: Task[];
|
|
26
|
+
checked: number;
|
|
27
|
+
}>;
|
|
21
28
|
purgeEverything(): {
|
|
22
29
|
purgedTasks: number;
|
|
23
30
|
purgedComments: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../../src/domain/cleanup.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../../src/domain/cleanup.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC,qBAAa,cAAc;IAIvB,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAL/B,OAAO,CAAC,KAAK,CAA+C;gBAGzC,EAAE,EAAE,EAAE,EACN,aAAa,GAAE,MAAW,EAC1B,WAAW,CAAC,EAAE,WAAW,YAAA;IAG5C,KAAK,IAAI,IAAI;IAWb,IAAI,IAAI,IAAI;IAOZ,GAAG,IAAI;QACL,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;QACxB,eAAe,EAAE,MAAM,CAAC;KACzB;IAmCD,QAAQ,IAAI;QACV,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;QACxB,eAAe,EAAE,MAAM,CAAC;KACzB;IAoBK,mBAAmB,CACvB,cAAc,GAAE,MAAW,GAC1B,OAAO,CAAC;QAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IA6E/C,eAAe,IAAI;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;QACxB,eAAe,EAAE,MAAM,CAAC;KACzB;CAaF"}
|
package/dist/domain/cleanup.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
export class CleanupService {
|
|
2
2
|
db;
|
|
3
3
|
retentionDays;
|
|
4
|
+
agentBridge;
|
|
4
5
|
timer = null;
|
|
5
|
-
constructor(db, retentionDays = 30) {
|
|
6
|
+
constructor(db, retentionDays = 30, agentBridge) {
|
|
6
7
|
this.db = db;
|
|
7
8
|
this.retentionDays = retentionDays;
|
|
9
|
+
this.agentBridge = agentBridge;
|
|
8
10
|
}
|
|
9
11
|
start() {
|
|
10
12
|
// Run cleanup every hour
|
|
11
13
|
this.timer = setInterval(() => this.run(), 60 * 60 * 1000);
|
|
12
14
|
this.timer.unref();
|
|
13
15
|
// Run once on start (delayed 10s)
|
|
14
|
-
setTimeout(() =>
|
|
16
|
+
setTimeout(() => {
|
|
17
|
+
this.run();
|
|
18
|
+
this.failStaleAgentTasks().catch(() => { });
|
|
19
|
+
}, 10_000).unref();
|
|
15
20
|
}
|
|
16
21
|
stop() {
|
|
17
22
|
if (this.timer) {
|
|
@@ -51,6 +56,69 @@ export class CleanupService {
|
|
|
51
56
|
purgedApprovals: approvals.changes,
|
|
52
57
|
};
|
|
53
58
|
}
|
|
59
|
+
async failStaleAgentTasks(timeoutMinutes = 30) {
|
|
60
|
+
if (!this.agentBridge) {
|
|
61
|
+
return { failed: [], checked: 0 };
|
|
62
|
+
}
|
|
63
|
+
const inProgressTasks = this.db.queryAll(`SELECT * FROM tasks WHERE status = 'in_progress' AND assigned_to IS NOT NULL`);
|
|
64
|
+
if (inProgressTasks.length === 0) {
|
|
65
|
+
return { failed: [], checked: 0 };
|
|
66
|
+
}
|
|
67
|
+
const agentNames = [...new Set(inProgressTasks.map((t) => t.assigned_to))];
|
|
68
|
+
let agents;
|
|
69
|
+
try {
|
|
70
|
+
agents = (await this.agentBridge.fetchAgents());
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return { failed: [], checked: agentNames.length };
|
|
74
|
+
}
|
|
75
|
+
if (!Array.isArray(agents) || agents.length === 0) {
|
|
76
|
+
return { failed: [], checked: agentNames.length };
|
|
77
|
+
}
|
|
78
|
+
const agentMap = new Map();
|
|
79
|
+
for (const a of agents) {
|
|
80
|
+
if (a.name)
|
|
81
|
+
agentMap.set(a.name, a);
|
|
82
|
+
}
|
|
83
|
+
const cutoff = Date.now() - timeoutMinutes * 60 * 1000;
|
|
84
|
+
const staleAgents = new Set();
|
|
85
|
+
for (const name of agentNames) {
|
|
86
|
+
const agent = agentMap.get(name);
|
|
87
|
+
if (!agent) {
|
|
88
|
+
staleAgents.add(name);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (agent.status === 'offline') {
|
|
92
|
+
staleAgents.add(name);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (agent.last_heartbeat) {
|
|
96
|
+
const hbTime = new Date(agent.last_heartbeat).getTime();
|
|
97
|
+
if (hbTime < cutoff) {
|
|
98
|
+
staleAgents.add(name);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const failed = [];
|
|
103
|
+
for (const task of inProgressTasks) {
|
|
104
|
+
if (!staleAgents.has(task.assigned_to))
|
|
105
|
+
continue;
|
|
106
|
+
const agent = agentMap.get(task.assigned_to);
|
|
107
|
+
const reason = agent
|
|
108
|
+
? `Auto-failed: agent "${task.assigned_to}" has not heartbeated for ${timeoutMinutes} minutes`
|
|
109
|
+
: `Auto-failed: agent "${task.assigned_to}" is no longer registered`;
|
|
110
|
+
try {
|
|
111
|
+
this.db.run(`UPDATE tasks SET status = 'failed', result = ?, updated_at = datetime('now') WHERE id = ?`, [reason, task.id]);
|
|
112
|
+
const updated = this.db.queryOne('SELECT * FROM tasks WHERE id = ?', [task.id]);
|
|
113
|
+
if (updated)
|
|
114
|
+
failed.push(updated);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
/* skip tasks that can't be failed */
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return { failed, checked: agentNames.length };
|
|
121
|
+
}
|
|
54
122
|
purgeEverything() {
|
|
55
123
|
// Nuclear option — delete ALL tasks regardless of status/stage
|
|
56
124
|
const tasks = this.db.run(`DELETE FROM tasks`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/domain/cleanup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/domain/cleanup.ts"],"names":[],"mappings":"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"}
|