agent-tasks 1.1.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 +74 -0
- package/dist/context.d.ts +17 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +37 -0
- package/dist/context.js.map +1 -0
- package/dist/db.d.ts +10 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +112 -0
- package/dist/db.js.map +1 -0
- package/dist/domain/agent-bridge.d.ts +13 -0
- package/dist/domain/agent-bridge.d.ts.map +1 -0
- package/dist/domain/agent-bridge.js +99 -0
- package/dist/domain/agent-bridge.js.map +1 -0
- package/dist/domain/approvals.d.ts +18 -0
- package/dist/domain/approvals.d.ts.map +1 -0
- package/dist/domain/approvals.js +89 -0
- package/dist/domain/approvals.js.map +1 -0
- package/dist/domain/cleanup.d.ts +28 -0
- package/dist/domain/cleanup.d.ts.map +1 -0
- package/dist/domain/cleanup.js +68 -0
- package/dist/domain/cleanup.js.map +1 -0
- package/dist/domain/collaborators.d.ts +14 -0
- package/dist/domain/collaborators.d.ts.map +1 -0
- package/dist/domain/collaborators.js +59 -0
- package/dist/domain/collaborators.js.map +1 -0
- package/dist/domain/comments.d.ts +14 -0
- package/dist/domain/comments.d.ts.map +1 -0
- package/dist/domain/comments.js +63 -0
- package/dist/domain/comments.js.map +1 -0
- package/dist/domain/events.d.ts +9 -0
- package/dist/domain/events.d.ts.map +1 -0
- package/dist/domain/events.js +52 -0
- package/dist/domain/events.js.map +1 -0
- package/dist/domain/rules.d.ts +2 -0
- package/dist/domain/rules.d.ts.map +1 -0
- package/dist/domain/rules.js +67 -0
- package/dist/domain/rules.js.map +1 -0
- package/dist/domain/tasks.d.ts +60 -0
- package/dist/domain/tasks.d.ts.map +1 -0
- package/dist/domain/tasks.js +616 -0
- package/dist/domain/tasks.js.map +1 -0
- package/dist/domain/validate.d.ts +14 -0
- package/dist/domain/validate.d.ts.map +1 -0
- package/dist/domain/validate.js +29 -0
- package/dist/domain/validate.js.map +1 -0
- package/dist/event-bus.d.ts +10 -0
- package/dist/event-bus.d.ts.map +1 -0
- package/dist/event-bus.js +38 -0
- package/dist/event-bus.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +121 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +95 -0
- package/dist/server.js.map +1 -0
- package/dist/session.d.ts +7 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +11 -0
- package/dist/session.js.map +1 -0
- package/dist/storage/database.d.ts +15 -0
- package/dist/storage/database.d.ts.map +1 -0
- package/dist/storage/database.js +215 -0
- package/dist/storage/database.js.map +1 -0
- package/dist/tasks.d.ts +32 -0
- package/dist/tasks.d.ts.map +1 -0
- package/dist/tasks.js +410 -0
- package/dist/tasks.js.map +1 -0
- package/dist/transport/mcp.d.ts +6 -0
- package/dist/transport/mcp.d.ts.map +1 -0
- package/dist/transport/mcp.js +573 -0
- package/dist/transport/mcp.js.map +1 -0
- package/dist/transport/rest.d.ts +4 -0
- package/dist/transport/rest.d.ts.map +1 -0
- package/dist/transport/rest.js +382 -0
- package/dist/transport/rest.js.map +1 -0
- package/dist/transport/ws.d.ts +10 -0
- package/dist/transport/ws.d.ts.map +1 -0
- package/dist/transport/ws.js +177 -0
- package/dist/transport/ws.js.map +1 -0
- package/dist/types.d.ts +146 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +35 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/app.js +648 -0
- package/dist/ui/index.html +82 -0
- package/dist/ui/morphdom.min.js +1 -0
- package/dist/ui/styles.css +805 -0
- package/package.json +78 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/domain/validate.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAC7C,eAAO,MAAM,iBAAiB,QAAS,CAAC;AACxC,eAAO,MAAM,2BAA2B,SAAU,CAAC;AACnD,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,cAAc,MAAM,CAAC;AAKlC,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAIrE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAIlE"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// agent-tasks — Input validation constants
|
|
3
|
+
//
|
|
4
|
+
// Shared limits and patterns for domain-layer validation.
|
|
5
|
+
// =============================================================================
|
|
6
|
+
export const MAX_TITLE_LENGTH = 500;
|
|
7
|
+
export const MAX_DESCRIPTION_LENGTH = 50_000;
|
|
8
|
+
export const MAX_RESULT_LENGTH = 50_000;
|
|
9
|
+
export const MAX_ARTIFACT_CONTENT_LENGTH = 100_000;
|
|
10
|
+
export const MAX_ARTIFACT_NAME_LENGTH = 128;
|
|
11
|
+
export const MAX_PROJECT_NAME_LENGTH = 128;
|
|
12
|
+
export const MAX_TAG_LENGTH = 64;
|
|
13
|
+
export const MAX_TAGS_COUNT = 20;
|
|
14
|
+
export const MAX_STAGE_NAME_LENGTH = 64;
|
|
15
|
+
export const MAX_STAGES_COUNT = 20;
|
|
16
|
+
export const MAX_LIST_LIMIT = 500;
|
|
17
|
+
// eslint-disable-next-line no-control-regex
|
|
18
|
+
const CONTROL_CHAR_PATTERN = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
19
|
+
export function rejectControlChars(value, field) {
|
|
20
|
+
if (CONTROL_CHAR_PATTERN.test(value)) {
|
|
21
|
+
throw new Error(`"${field}" must not contain control characters.`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function rejectNullBytes(value, field) {
|
|
25
|
+
if (value.includes('\0')) {
|
|
26
|
+
throw new Error(`"${field}" must not contain null bytes.`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/domain/validate.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,2CAA2C;AAC3C,EAAE;AACF,0DAA0D;AAC1D,gFAAgF;AAEhF,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AACpC,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAC7C,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC;AACxC,MAAM,CAAC,MAAM,2BAA2B,GAAG,OAAO,CAAC;AACnD,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAC5C,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAC3C,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AACjC,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AACjC,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AACxC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AACnC,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,CAAC;AAElC,4CAA4C;AAC5C,MAAM,oBAAoB,GAAG,kCAAkC,CAAC;AAEhE,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,KAAa;IAC7D,IAAI,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,wCAAwC,CAAC,CAAC;IACrE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,KAAa;IAC1D,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,gCAAgC,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type EventType = 'task:create' | 'task:update' | 'task:delete' | 'pipeline:config';
|
|
2
|
+
type Handler = (data: unknown) => void;
|
|
3
|
+
declare class EventBus {
|
|
4
|
+
private listeners;
|
|
5
|
+
emit(type: EventType, data?: unknown): void;
|
|
6
|
+
on(type: EventType | '*', handler: Handler): () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare const eventBus: EventBus;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=event-bus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../src/event-bus.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,aAAa,GAAG,aAAa,GAAG,iBAAiB,CAAC;AAE1F,KAAK,OAAO,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;AAEvC,cAAM,QAAQ;IACZ,OAAO,CAAC,SAAS,CAA4C;IAE7D,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI;IAqB3C,EAAE,CAAC,IAAI,EAAE,SAAS,GAAG,GAAG,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,IAAI;CAWxD;AAED,eAAO,MAAM,QAAQ,UAAiB,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class EventBus {
|
|
2
|
+
listeners = new Map();
|
|
3
|
+
emit(type, data) {
|
|
4
|
+
const handlers = this.listeners.get(type);
|
|
5
|
+
if (handlers)
|
|
6
|
+
for (const h of handlers) {
|
|
7
|
+
try {
|
|
8
|
+
h(data);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
/* ignore */
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const wildcards = this.listeners.get('*');
|
|
15
|
+
if (wildcards)
|
|
16
|
+
for (const h of wildcards) {
|
|
17
|
+
try {
|
|
18
|
+
h({ type, data });
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
/* ignore */
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
on(type, handler) {
|
|
26
|
+
let set = this.listeners.get(type);
|
|
27
|
+
if (!set) {
|
|
28
|
+
set = new Set();
|
|
29
|
+
this.listeners.set(type, set);
|
|
30
|
+
}
|
|
31
|
+
set.add(handler);
|
|
32
|
+
return () => {
|
|
33
|
+
set.delete(handler);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export const eventBus = new EventBus();
|
|
38
|
+
//# sourceMappingURL=event-bus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-bus.js","sourceRoot":"","sources":["../src/event-bus.ts"],"names":[],"mappings":"AAIA,MAAM,QAAQ;IACJ,SAAS,GAAG,IAAI,GAAG,EAAiC,CAAC;IAE7D,IAAI,CAAC,IAAe,EAAE,IAAc;QAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,QAAQ;YACV,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;gBACzB,IAAI,CAAC;oBACH,CAAC,CAAC,IAAI,CAAC,CAAC;gBACV,CAAC;gBAAC,MAAM,CAAC;oBACP,YAAY;gBACd,CAAC;YACH,CAAC;QACH,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,SAAS;YACX,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;gBAC1B,IAAI,CAAC;oBACH,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;gBACpB,CAAC;gBAAC,MAAM,CAAC;oBACP,YAAY;gBACd,CAAC;YACH,CAAC;IACL,CAAC;IAED,EAAE,CAAC,IAAqB,EAAE,OAAgB;QACxC,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAChC,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACjB,OAAO,GAAG,EAAE;YACV,GAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACvB,CAAC,CAAC;IACJ,CAAC;CACF;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// agent-tasks — MCP server entry point
|
|
4
|
+
//
|
|
5
|
+
// Pipeline-driven task management for AI coding agents.
|
|
6
|
+
// Communicates via JSON-RPC 2.0 over stdio (Model Context Protocol).
|
|
7
|
+
// =============================================================================
|
|
8
|
+
import { createInterface } from 'readline';
|
|
9
|
+
import { createContext } from './context.js';
|
|
10
|
+
import { tools, createToolHandler } from './transport/mcp.js';
|
|
11
|
+
import { startDashboard } from './server.js';
|
|
12
|
+
const DASHBOARD_PORT = parseInt(process.env.AGENT_TASKS_PORT ?? '3422', 10);
|
|
13
|
+
const SERVER_INFO = { name: 'agent-tasks', version: '1.0.0' };
|
|
14
|
+
const CAPABILITIES = { tools: {} };
|
|
15
|
+
function send(response) {
|
|
16
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
17
|
+
}
|
|
18
|
+
function main() {
|
|
19
|
+
const ctx = createContext();
|
|
20
|
+
const handleTool = createToolHandler(ctx);
|
|
21
|
+
let dashboard = null;
|
|
22
|
+
let dashboardStarted = false;
|
|
23
|
+
function tryStartDashboard() {
|
|
24
|
+
if (dashboardStarted)
|
|
25
|
+
return;
|
|
26
|
+
dashboardStarted = true;
|
|
27
|
+
startDashboard(ctx, DASHBOARD_PORT)
|
|
28
|
+
.then((d) => {
|
|
29
|
+
dashboard = d;
|
|
30
|
+
})
|
|
31
|
+
.catch(() => {
|
|
32
|
+
/* port in use — another instance is serving the dashboard */
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function handleRequest(request) {
|
|
36
|
+
const { method, params, id } = request;
|
|
37
|
+
switch (method) {
|
|
38
|
+
case 'initialize':
|
|
39
|
+
tryStartDashboard();
|
|
40
|
+
return {
|
|
41
|
+
jsonrpc: '2.0',
|
|
42
|
+
id,
|
|
43
|
+
result: {
|
|
44
|
+
protocolVersion: '2024-11-05',
|
|
45
|
+
serverInfo: SERVER_INFO,
|
|
46
|
+
capabilities: CAPABILITIES,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
case 'notifications/initialized':
|
|
50
|
+
return null;
|
|
51
|
+
case 'tools/list':
|
|
52
|
+
return { jsonrpc: '2.0', id, result: { tools } };
|
|
53
|
+
case 'tools/call': {
|
|
54
|
+
const toolName = params.name;
|
|
55
|
+
const toolArgs = params.arguments || {};
|
|
56
|
+
try {
|
|
57
|
+
const result = handleTool(toolName, toolArgs);
|
|
58
|
+
return {
|
|
59
|
+
jsonrpc: '2.0',
|
|
60
|
+
id,
|
|
61
|
+
result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
return {
|
|
66
|
+
jsonrpc: '2.0',
|
|
67
|
+
id,
|
|
68
|
+
result: {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: 'text',
|
|
72
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
isError: true,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
case 'ping':
|
|
81
|
+
return { jsonrpc: '2.0', id, result: {} };
|
|
82
|
+
default:
|
|
83
|
+
return {
|
|
84
|
+
jsonrpc: '2.0',
|
|
85
|
+
id,
|
|
86
|
+
error: { code: -32601, message: `Method not found: ${method}` },
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
91
|
+
rl.on('line', (line) => {
|
|
92
|
+
if (!line.trim())
|
|
93
|
+
return;
|
|
94
|
+
try {
|
|
95
|
+
const request = JSON.parse(line);
|
|
96
|
+
const response = handleRequest(request);
|
|
97
|
+
if (response)
|
|
98
|
+
send(response);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
// --- Graceful shutdown ---
|
|
105
|
+
function cleanup() {
|
|
106
|
+
if (dashboard)
|
|
107
|
+
dashboard.close();
|
|
108
|
+
ctx.close();
|
|
109
|
+
}
|
|
110
|
+
process.on('SIGINT', () => {
|
|
111
|
+
cleanup();
|
|
112
|
+
process.exit(0);
|
|
113
|
+
});
|
|
114
|
+
process.on('SIGTERM', () => {
|
|
115
|
+
cleanup();
|
|
116
|
+
process.exit(0);
|
|
117
|
+
});
|
|
118
|
+
process.on('exit', cleanup);
|
|
119
|
+
}
|
|
120
|
+
main();
|
|
121
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { type Server } from 'http';
|
|
3
|
+
import { type AppContext } from './context.js';
|
|
4
|
+
export interface DashboardServer {
|
|
5
|
+
httpServer: Server;
|
|
6
|
+
port: number;
|
|
7
|
+
close(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare function startDashboard(ctx: AppContext, port?: number): Promise<DashboardServer>;
|
|
10
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AAUA,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,MAAM,CAAC;AAIjD,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAO9D,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,SAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CA+BrF"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// agent-tasks — HTTP + WebSocket server entry point
|
|
4
|
+
//
|
|
5
|
+
// Standalone web server for the pipeline dashboard and REST API.
|
|
6
|
+
// Can be started manually: node dist/server.js [--port 3422]
|
|
7
|
+
// Or auto-started from the MCP server via leader election.
|
|
8
|
+
// =============================================================================
|
|
9
|
+
import { createServer } from 'http';
|
|
10
|
+
import { watch } from 'fs';
|
|
11
|
+
import { resolve, join, dirname } from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import { createContext } from './context.js';
|
|
14
|
+
import { createRouter } from './transport/rest.js';
|
|
15
|
+
import { setupWebSocket } from './transport/ws.js';
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
export function startDashboard(ctx, port = 3422) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const router = createRouter(ctx);
|
|
20
|
+
const httpServer = createServer(router);
|
|
21
|
+
let wsHandle = null;
|
|
22
|
+
let fileWatcher = null;
|
|
23
|
+
httpServer.on('error', (err) => {
|
|
24
|
+
if (err.code === 'EADDRINUSE') {
|
|
25
|
+
reject(new Error(`Port ${port} in use`));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
reject(err);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
httpServer.listen(port, () => {
|
|
32
|
+
wsHandle = setupWebSocket(httpServer, ctx);
|
|
33
|
+
fileWatcher = startFileWatcher(wsHandle);
|
|
34
|
+
process.stderr.write(`agent-tasks dashboard: http://localhost:${port}\n`);
|
|
35
|
+
resolve({
|
|
36
|
+
httpServer,
|
|
37
|
+
port,
|
|
38
|
+
close() {
|
|
39
|
+
if (fileWatcher)
|
|
40
|
+
fileWatcher.close();
|
|
41
|
+
if (wsHandle)
|
|
42
|
+
wsHandle.close();
|
|
43
|
+
httpServer.close();
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// UI file watcher — triggers livereload on connected clients
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
function startFileWatcher(wsHandle) {
|
|
53
|
+
const uiDir = resolve(join(__dirname, 'ui'));
|
|
54
|
+
let debounce = null;
|
|
55
|
+
const watcher = watch(uiDir, { recursive: true }, (_event, filename) => {
|
|
56
|
+
if (!filename)
|
|
57
|
+
return;
|
|
58
|
+
if (debounce)
|
|
59
|
+
clearTimeout(debounce);
|
|
60
|
+
debounce = setTimeout(() => {
|
|
61
|
+
wsHandle.broadcast(JSON.stringify({ type: 'reload' }));
|
|
62
|
+
}, 200);
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
close() {
|
|
66
|
+
if (debounce)
|
|
67
|
+
clearTimeout(debounce);
|
|
68
|
+
watcher.close();
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (process.argv[1]?.endsWith('server.js') || process.argv[1]?.endsWith('server.ts')) {
|
|
73
|
+
const port = parseInt(process.argv.find((_a, i, arr) => arr[i - 1] === '--port') ?? '3422', 10);
|
|
74
|
+
const dbPath = process.argv.find((_a, i, arr) => arr[i - 1] === '--db') ?? undefined;
|
|
75
|
+
const dbOptions = dbPath ? { path: dbPath } : {};
|
|
76
|
+
const ctx = createContext(dbOptions);
|
|
77
|
+
startDashboard(ctx, port)
|
|
78
|
+
.then((dashboard) => {
|
|
79
|
+
process.on('SIGINT', () => {
|
|
80
|
+
dashboard.close();
|
|
81
|
+
ctx.close();
|
|
82
|
+
process.exit(0);
|
|
83
|
+
});
|
|
84
|
+
process.on('SIGTERM', () => {
|
|
85
|
+
dashboard.close();
|
|
86
|
+
ctx.close();
|
|
87
|
+
process.exit(0);
|
|
88
|
+
});
|
|
89
|
+
})
|
|
90
|
+
.catch((err) => {
|
|
91
|
+
process.stderr.write(`Failed to start dashboard: ${err.message}\n`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAEA,wBAAgB,iBAAiB;QAFP,MAAM;UAAQ,MAAM;SAI7C;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED,wBAAgB,YAAY,IAAI,IAAI,CAEnC"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
let currentSession = null;
|
|
2
|
+
export function getCurrentSession() {
|
|
3
|
+
return currentSession;
|
|
4
|
+
}
|
|
5
|
+
export function setSession(id, name) {
|
|
6
|
+
currentSession = { id, name };
|
|
7
|
+
}
|
|
8
|
+
export function clearSession() {
|
|
9
|
+
currentSession = null;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,IAAI,cAAc,GAAwC,IAAI,CAAC;AAE/D,MAAM,UAAU,iBAAiB;IAC/B,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,EAAU,EAAE,IAAY;IACjD,cAAc,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,cAAc,GAAG,IAAI,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
export interface DbOptions {
|
|
3
|
+
path?: string;
|
|
4
|
+
verbose?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface Db {
|
|
7
|
+
readonly raw: Database.Database;
|
|
8
|
+
run(sql: string, params?: unknown[]): Database.RunResult;
|
|
9
|
+
queryAll<T>(sql: string, params?: unknown[]): T[];
|
|
10
|
+
queryOne<T>(sql: string, params?: unknown[]): T | null;
|
|
11
|
+
transaction<T>(fn: () => T): T;
|
|
12
|
+
close(): void;
|
|
13
|
+
}
|
|
14
|
+
export declare function createDb(options?: DbOptions): Db;
|
|
15
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/storage/database.ts"],"names":[],"mappings":"AAOA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAKtC,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,EAAE;IACjB,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC;IAChC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,QAAQ,CAAC,SAAS,CAAC;IACzD,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;IAClD,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IACvD,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/B,KAAK,IAAI,IAAI,CAAC;CACf;AAID,wBAAgB,QAAQ,CAAC,OAAO,GAAE,SAAc,GAAG,EAAE,CA4CpD"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// agent-tasks — Storage layer
|
|
3
|
+
//
|
|
4
|
+
// Thin wrapper around better-sqlite3 with schema management and migrations.
|
|
5
|
+
// Provides a simplified query interface used by domain services.
|
|
6
|
+
// =============================================================================
|
|
7
|
+
import Database from 'better-sqlite3';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { mkdirSync } from 'fs';
|
|
11
|
+
const SCHEMA_VERSION = 2;
|
|
12
|
+
export function createDb(options = {}) {
|
|
13
|
+
const dbPath = resolveDbPath(options.path);
|
|
14
|
+
const raw = new Database(dbPath, {
|
|
15
|
+
verbose: options.verbose ? (msg) => process.stderr.write(`[sql] ${msg}\n`) : undefined,
|
|
16
|
+
});
|
|
17
|
+
raw.pragma('journal_mode = WAL');
|
|
18
|
+
raw.pragma('busy_timeout = 5000');
|
|
19
|
+
raw.pragma('synchronous = NORMAL');
|
|
20
|
+
raw.pragma('foreign_keys = ON');
|
|
21
|
+
applySchema(raw);
|
|
22
|
+
return {
|
|
23
|
+
raw,
|
|
24
|
+
run(sql, params) {
|
|
25
|
+
const stmt = raw.prepare(sql);
|
|
26
|
+
return params?.length ? stmt.run(...params) : stmt.run();
|
|
27
|
+
},
|
|
28
|
+
queryAll(sql, params) {
|
|
29
|
+
const stmt = raw.prepare(sql);
|
|
30
|
+
return (params?.length ? stmt.all(...params) : stmt.all());
|
|
31
|
+
},
|
|
32
|
+
queryOne(sql, params) {
|
|
33
|
+
const stmt = raw.prepare(sql);
|
|
34
|
+
const row = params?.length ? stmt.get(...params) : stmt.get();
|
|
35
|
+
return row ?? null;
|
|
36
|
+
},
|
|
37
|
+
transaction(fn) {
|
|
38
|
+
return raw.transaction(fn)();
|
|
39
|
+
},
|
|
40
|
+
close() {
|
|
41
|
+
try {
|
|
42
|
+
raw.close();
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
/* ignore */
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function resolveDbPath(path) {
|
|
51
|
+
if (path === ':memory:')
|
|
52
|
+
return path;
|
|
53
|
+
if (path)
|
|
54
|
+
return path;
|
|
55
|
+
const envPath = process.env.AGENT_TASKS_DB;
|
|
56
|
+
if (envPath)
|
|
57
|
+
return envPath;
|
|
58
|
+
const dir = join(homedir(), '.agent-tasks');
|
|
59
|
+
mkdirSync(dir, { recursive: true });
|
|
60
|
+
return join(dir, 'agent-tasks.db');
|
|
61
|
+
}
|
|
62
|
+
function applySchema(db) {
|
|
63
|
+
db.exec(`
|
|
64
|
+
CREATE TABLE IF NOT EXISTS _meta (
|
|
65
|
+
key TEXT PRIMARY KEY,
|
|
66
|
+
value TEXT NOT NULL
|
|
67
|
+
)
|
|
68
|
+
`);
|
|
69
|
+
const row = db.prepare(`SELECT value FROM _meta WHERE key = 'schema_version'`).get();
|
|
70
|
+
const currentVersion = row ? parseInt(row.value, 10) : 0;
|
|
71
|
+
if (currentVersion < 1)
|
|
72
|
+
migrateV1(db);
|
|
73
|
+
if (currentVersion < 2)
|
|
74
|
+
migrateV2(db);
|
|
75
|
+
db.prepare(`INSERT OR REPLACE INTO _meta (key, value) VALUES ('schema_version', ?)`).run(String(SCHEMA_VERSION));
|
|
76
|
+
}
|
|
77
|
+
function migrateV1(db) {
|
|
78
|
+
db.exec(`
|
|
79
|
+
-- Tasks
|
|
80
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
81
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
82
|
+
title TEXT NOT NULL,
|
|
83
|
+
description TEXT,
|
|
84
|
+
created_by TEXT NOT NULL,
|
|
85
|
+
assigned_to TEXT,
|
|
86
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
87
|
+
stage TEXT NOT NULL DEFAULT 'backlog',
|
|
88
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
89
|
+
project TEXT,
|
|
90
|
+
tags TEXT,
|
|
91
|
+
result TEXT,
|
|
92
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
93
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
94
|
+
);
|
|
95
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_assigned ON tasks(assigned_to, status);
|
|
96
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_stage ON tasks(stage, priority);
|
|
97
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project, status);
|
|
98
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
99
|
+
|
|
100
|
+
-- Dependencies (with foreign keys)
|
|
101
|
+
CREATE TABLE IF NOT EXISTS task_dependencies (
|
|
102
|
+
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
103
|
+
depends_on INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
104
|
+
PRIMARY KEY (task_id, depends_on)
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
-- Artifacts (with foreign key)
|
|
108
|
+
CREATE TABLE IF NOT EXISTS task_artifacts (
|
|
109
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
110
|
+
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
111
|
+
stage TEXT NOT NULL,
|
|
112
|
+
name TEXT NOT NULL,
|
|
113
|
+
content TEXT NOT NULL,
|
|
114
|
+
created_by TEXT NOT NULL,
|
|
115
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
116
|
+
);
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_task_artifacts_task ON task_artifacts(task_id, stage);
|
|
118
|
+
|
|
119
|
+
-- Pipeline configuration per project
|
|
120
|
+
CREATE TABLE IF NOT EXISTS pipeline_config (
|
|
121
|
+
project TEXT PRIMARY KEY,
|
|
122
|
+
stages TEXT NOT NULL DEFAULT '["backlog","spec","plan","implement","test","review","done","cancelled"]',
|
|
123
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
124
|
+
);
|
|
125
|
+
`);
|
|
126
|
+
}
|
|
127
|
+
function migrateV2(db) {
|
|
128
|
+
// -- Subtask support
|
|
129
|
+
const cols = db.prepare(`PRAGMA table_info(tasks)`).all();
|
|
130
|
+
if (!cols.some((c) => c.name === 'parent_id')) {
|
|
131
|
+
db.exec(`ALTER TABLE tasks ADD COLUMN parent_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE`);
|
|
132
|
+
}
|
|
133
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_id)`);
|
|
134
|
+
// -- Artifact versioning
|
|
135
|
+
const artCols = db.prepare(`PRAGMA table_info(task_artifacts)`).all();
|
|
136
|
+
if (!artCols.some((c) => c.name === 'version')) {
|
|
137
|
+
db.exec(`ALTER TABLE task_artifacts ADD COLUMN version INTEGER NOT NULL DEFAULT 1`);
|
|
138
|
+
}
|
|
139
|
+
if (!artCols.some((c) => c.name === 'previous_id')) {
|
|
140
|
+
db.exec(`ALTER TABLE task_artifacts ADD COLUMN previous_id INTEGER REFERENCES task_artifacts(id)`);
|
|
141
|
+
}
|
|
142
|
+
// -- Comments with threading
|
|
143
|
+
db.exec(`
|
|
144
|
+
CREATE TABLE IF NOT EXISTS task_comments (
|
|
145
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
146
|
+
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
147
|
+
agent_id TEXT NOT NULL,
|
|
148
|
+
content TEXT NOT NULL,
|
|
149
|
+
parent_comment_id INTEGER REFERENCES task_comments(id) ON DELETE CASCADE,
|
|
150
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
151
|
+
);
|
|
152
|
+
CREATE INDEX IF NOT EXISTS idx_task_comments_task ON task_comments(task_id, created_at);
|
|
153
|
+
`);
|
|
154
|
+
// -- Collaborators
|
|
155
|
+
db.exec(`
|
|
156
|
+
CREATE TABLE IF NOT EXISTS task_collaborators (
|
|
157
|
+
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
158
|
+
agent_id TEXT NOT NULL,
|
|
159
|
+
role TEXT NOT NULL DEFAULT 'collaborator',
|
|
160
|
+
added_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
161
|
+
PRIMARY KEY (task_id, agent_id)
|
|
162
|
+
);
|
|
163
|
+
CREATE INDEX IF NOT EXISTS idx_collaborators_agent ON task_collaborators(agent_id);
|
|
164
|
+
`);
|
|
165
|
+
// -- Approvals
|
|
166
|
+
db.exec(`
|
|
167
|
+
CREATE TABLE IF NOT EXISTS task_approvals (
|
|
168
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
169
|
+
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
170
|
+
stage TEXT NOT NULL,
|
|
171
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
172
|
+
reviewer TEXT,
|
|
173
|
+
requested_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
174
|
+
resolved_at TEXT,
|
|
175
|
+
comment TEXT
|
|
176
|
+
);
|
|
177
|
+
CREATE INDEX IF NOT EXISTS idx_approvals_task ON task_approvals(task_id, stage);
|
|
178
|
+
CREATE INDEX IF NOT EXISTS idx_approvals_reviewer ON task_approvals(reviewer, status);
|
|
179
|
+
`);
|
|
180
|
+
// -- Pipeline config extensions
|
|
181
|
+
const pcCols = db.prepare(`PRAGMA table_info(pipeline_config)`).all();
|
|
182
|
+
if (!pcCols.some((c) => c.name === 'approval_config')) {
|
|
183
|
+
db.exec(`ALTER TABLE pipeline_config ADD COLUMN approval_config TEXT`);
|
|
184
|
+
}
|
|
185
|
+
if (!pcCols.some((c) => c.name === 'assignment_config')) {
|
|
186
|
+
db.exec(`ALTER TABLE pipeline_config ADD COLUMN assignment_config TEXT`);
|
|
187
|
+
}
|
|
188
|
+
// -- FTS5 on tasks
|
|
189
|
+
db.exec(`
|
|
190
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS tasks_fts USING fts5(
|
|
191
|
+
title, description,
|
|
192
|
+
content=tasks, content_rowid=id
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
CREATE TRIGGER IF NOT EXISTS tasks_fts_insert AFTER INSERT ON tasks BEGIN
|
|
196
|
+
INSERT INTO tasks_fts(rowid, title, description) VALUES (new.id, new.title, COALESCE(new.description, ''));
|
|
197
|
+
END;
|
|
198
|
+
|
|
199
|
+
CREATE TRIGGER IF NOT EXISTS tasks_fts_update AFTER UPDATE OF title, description ON tasks BEGIN
|
|
200
|
+
INSERT INTO tasks_fts(tasks_fts, rowid, title, description) VALUES ('delete', old.id, old.title, COALESCE(old.description, ''));
|
|
201
|
+
INSERT INTO tasks_fts(rowid, title, description) VALUES (new.id, new.title, COALESCE(new.description, ''));
|
|
202
|
+
END;
|
|
203
|
+
|
|
204
|
+
CREATE TRIGGER IF NOT EXISTS tasks_fts_delete AFTER DELETE ON tasks BEGIN
|
|
205
|
+
INSERT INTO tasks_fts(tasks_fts, rowid, title, description) VALUES ('delete', old.id, old.title, COALESCE(old.description, ''));
|
|
206
|
+
END;
|
|
207
|
+
`);
|
|
208
|
+
// -- Backfill FTS for existing tasks
|
|
209
|
+
const existing = db.prepare(`SELECT id, title, description FROM tasks`).all();
|
|
210
|
+
const ftsInsert = db.prepare(`INSERT OR IGNORE INTO tasks_fts(rowid, title, description) VALUES (?, ?, ?)`);
|
|
211
|
+
for (const t of existing) {
|
|
212
|
+
ftsInsert.run(t.id, t.title, t.description ?? '');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/storage/database.ts"],"names":[],"mappings":"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"}
|