@sooneocean/agw 1.4.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/README.md +116 -0
- package/bin/agw.ts +5 -0
- package/package.json +59 -0
- package/src/agents/base-adapter.ts +113 -0
- package/src/agents/claude-adapter.ts +29 -0
- package/src/agents/codex-adapter.ts +29 -0
- package/src/agents/gemini-adapter.ts +29 -0
- package/src/cli/commands/agents.ts +55 -0
- package/src/cli/commands/combo.ts +130 -0
- package/src/cli/commands/costs.ts +33 -0
- package/src/cli/commands/daemon.ts +110 -0
- package/src/cli/commands/history.ts +29 -0
- package/src/cli/commands/run.ts +59 -0
- package/src/cli/commands/status.ts +29 -0
- package/src/cli/commands/workflow.ts +73 -0
- package/src/cli/error-handler.ts +8 -0
- package/src/cli/http-client.ts +68 -0
- package/src/cli/index.ts +28 -0
- package/src/config.ts +68 -0
- package/src/daemon/middleware/auth.ts +35 -0
- package/src/daemon/middleware/rate-limiter.ts +63 -0
- package/src/daemon/middleware/tenant.ts +64 -0
- package/src/daemon/middleware/workspace.ts +40 -0
- package/src/daemon/routes/agents.ts +13 -0
- package/src/daemon/routes/combos.ts +103 -0
- package/src/daemon/routes/costs.ts +9 -0
- package/src/daemon/routes/health.ts +62 -0
- package/src/daemon/routes/memory.ts +32 -0
- package/src/daemon/routes/tasks.ts +157 -0
- package/src/daemon/routes/ui.ts +18 -0
- package/src/daemon/routes/workflows.ts +73 -0
- package/src/daemon/server.ts +91 -0
- package/src/daemon/services/agent-learning.ts +77 -0
- package/src/daemon/services/agent-manager.ts +71 -0
- package/src/daemon/services/auto-scaler.ts +77 -0
- package/src/daemon/services/circuit-breaker.ts +95 -0
- package/src/daemon/services/combo-executor.ts +300 -0
- package/src/daemon/services/dag-executor.ts +136 -0
- package/src/daemon/services/metrics.ts +35 -0
- package/src/daemon/services/stream-aggregator.ts +64 -0
- package/src/daemon/services/task-executor.ts +184 -0
- package/src/daemon/services/task-queue.ts +75 -0
- package/src/daemon/services/workflow-executor.ts +150 -0
- package/src/daemon/services/ws-manager.ts +90 -0
- package/src/dsl/parser.ts +124 -0
- package/src/plugins/plugin-loader.ts +72 -0
- package/src/router/keyword-router.ts +63 -0
- package/src/router/llm-router.ts +93 -0
- package/src/store/agent-repo.ts +57 -0
- package/src/store/audit-repo.ts +25 -0
- package/src/store/combo-repo.ts +99 -0
- package/src/store/cost-repo.ts +55 -0
- package/src/store/db.ts +137 -0
- package/src/store/memory-repo.ts +46 -0
- package/src/store/task-repo.ts +127 -0
- package/src/store/workflow-repo.ts +69 -0
- package/src/types.ts +208 -0
- package/tsconfig.json +17 -0
- package/ui/index.html +272 -0
package/src/store/db.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
|
|
5
|
+
const SCHEMA = `
|
|
6
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
7
|
+
task_id TEXT PRIMARY KEY,
|
|
8
|
+
prompt TEXT NOT NULL,
|
|
9
|
+
working_directory TEXT NOT NULL,
|
|
10
|
+
preferred_agent TEXT,
|
|
11
|
+
assigned_agent TEXT,
|
|
12
|
+
routing_reason TEXT,
|
|
13
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
14
|
+
priority INTEGER NOT NULL DEFAULT 3,
|
|
15
|
+
exit_code INTEGER,
|
|
16
|
+
stdout TEXT,
|
|
17
|
+
stderr TEXT,
|
|
18
|
+
stdout_truncated INTEGER NOT NULL DEFAULT 0,
|
|
19
|
+
stderr_truncated INTEGER NOT NULL DEFAULT 0,
|
|
20
|
+
duration_ms INTEGER,
|
|
21
|
+
token_estimate INTEGER,
|
|
22
|
+
cost_estimate REAL,
|
|
23
|
+
created_at TEXT NOT NULL,
|
|
24
|
+
completed_at TEXT,
|
|
25
|
+
workflow_id TEXT,
|
|
26
|
+
step_index INTEGER
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
30
|
+
id TEXT PRIMARY KEY,
|
|
31
|
+
name TEXT NOT NULL,
|
|
32
|
+
command TEXT NOT NULL,
|
|
33
|
+
args TEXT NOT NULL DEFAULT '[]',
|
|
34
|
+
health_check_command TEXT NOT NULL,
|
|
35
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
36
|
+
available INTEGER NOT NULL DEFAULT 0,
|
|
37
|
+
last_health_check TEXT
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
CREATE TABLE IF NOT EXISTS audit_log (
|
|
41
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
42
|
+
task_id TEXT,
|
|
43
|
+
event_type TEXT NOT NULL,
|
|
44
|
+
payload TEXT NOT NULL,
|
|
45
|
+
created_at TEXT NOT NULL
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE TABLE IF NOT EXISTS workflows (
|
|
49
|
+
workflow_id TEXT PRIMARY KEY,
|
|
50
|
+
name TEXT NOT NULL,
|
|
51
|
+
steps TEXT NOT NULL,
|
|
52
|
+
mode TEXT NOT NULL DEFAULT 'sequential',
|
|
53
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
54
|
+
task_ids TEXT NOT NULL DEFAULT '[]',
|
|
55
|
+
current_step INTEGER NOT NULL DEFAULT 0,
|
|
56
|
+
working_directory TEXT,
|
|
57
|
+
priority INTEGER NOT NULL DEFAULT 3,
|
|
58
|
+
created_at TEXT NOT NULL,
|
|
59
|
+
completed_at TEXT
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
CREATE TABLE IF NOT EXISTS cost_records (
|
|
63
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
64
|
+
task_id TEXT NOT NULL,
|
|
65
|
+
agent_id TEXT NOT NULL,
|
|
66
|
+
cost REAL NOT NULL DEFAULT 0,
|
|
67
|
+
tokens INTEGER NOT NULL DEFAULT 0,
|
|
68
|
+
recorded_at TEXT NOT NULL
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_created_at ON tasks(created_at DESC);
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority DESC, created_at ASC);
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_audit_task_id ON audit_log(task_id);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_workflows_status ON workflows(status);
|
|
76
|
+
CREATE TABLE IF NOT EXISTS combos (
|
|
77
|
+
combo_id TEXT PRIMARY KEY,
|
|
78
|
+
name TEXT NOT NULL,
|
|
79
|
+
pattern TEXT NOT NULL,
|
|
80
|
+
steps TEXT NOT NULL,
|
|
81
|
+
input TEXT NOT NULL,
|
|
82
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
83
|
+
task_ids TEXT NOT NULL DEFAULT '[]',
|
|
84
|
+
step_results TEXT NOT NULL DEFAULT '{}',
|
|
85
|
+
final_output TEXT,
|
|
86
|
+
max_iterations INTEGER NOT NULL DEFAULT 3,
|
|
87
|
+
iterations INTEGER NOT NULL DEFAULT 0,
|
|
88
|
+
working_directory TEXT,
|
|
89
|
+
priority INTEGER NOT NULL DEFAULT 3,
|
|
90
|
+
created_at TEXT NOT NULL,
|
|
91
|
+
completed_at TEXT
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
CREATE INDEX IF NOT EXISTS idx_cost_recorded_at ON cost_records(recorded_at);
|
|
95
|
+
CREATE INDEX IF NOT EXISTS idx_cost_agent ON cost_records(agent_id);
|
|
96
|
+
CREATE TABLE IF NOT EXISTS memory (
|
|
97
|
+
key TEXT PRIMARY KEY,
|
|
98
|
+
value TEXT NOT NULL,
|
|
99
|
+
scope TEXT NOT NULL DEFAULT 'global',
|
|
100
|
+
created_at TEXT NOT NULL,
|
|
101
|
+
updated_at TEXT NOT NULL
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_combos_status ON combos(status);
|
|
105
|
+
CREATE INDEX IF NOT EXISTS idx_memory_scope ON memory(scope);
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
const SEED_AGENTS = [
|
|
109
|
+
{ id: 'claude', name: 'Claude Code', command: 'claude', args: '[]', healthCheckCommand: 'claude --version' },
|
|
110
|
+
{ id: 'codex', name: 'Codex CLI', command: 'codex', args: '[]', healthCheckCommand: 'codex --version' },
|
|
111
|
+
{ id: 'gemini', name: 'Gemini CLI', command: 'gemini', args: '[]', healthCheckCommand: 'gemini --version' },
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
export function createDatabase(dbPath: string): Database.Database {
|
|
115
|
+
const dir = path.dirname(dbPath);
|
|
116
|
+
if (!fs.existsSync(dir)) {
|
|
117
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const db = new Database(dbPath);
|
|
121
|
+
db.pragma('journal_mode = WAL');
|
|
122
|
+
db.pragma('foreign_keys = ON');
|
|
123
|
+
db.exec(SCHEMA);
|
|
124
|
+
|
|
125
|
+
// Seed agents if table is empty
|
|
126
|
+
const count = db.prepare('SELECT COUNT(*) as cnt FROM agents').get() as { cnt: number };
|
|
127
|
+
if (count.cnt === 0) {
|
|
128
|
+
const insert = db.prepare(
|
|
129
|
+
'INSERT INTO agents (id, name, command, args, health_check_command) VALUES (?, ?, ?, ?, ?)'
|
|
130
|
+
);
|
|
131
|
+
for (const agent of SEED_AGENTS) {
|
|
132
|
+
insert.run(agent.id, agent.name, agent.command, agent.args, agent.healthCheckCommand);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return db;
|
|
137
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
|
|
3
|
+
export interface MemoryEntry {
|
|
4
|
+
key: string;
|
|
5
|
+
value: string;
|
|
6
|
+
scope: string;
|
|
7
|
+
createdAt: string;
|
|
8
|
+
updatedAt: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class MemoryRepo {
|
|
12
|
+
constructor(private db: Database.Database) {}
|
|
13
|
+
|
|
14
|
+
set(key: string, value: string, scope: string = 'global'): void {
|
|
15
|
+
const now = new Date().toISOString();
|
|
16
|
+
this.db.prepare(
|
|
17
|
+
`INSERT INTO memory (key, value, scope, created_at, updated_at)
|
|
18
|
+
VALUES (?, ?, ?, ?, ?)
|
|
19
|
+
ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = ?`
|
|
20
|
+
).run(key, value, scope, now, now, value, now);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get(key: string): string | undefined {
|
|
24
|
+
const row = this.db.prepare('SELECT value FROM memory WHERE key = ?').get(key) as { value: string } | undefined;
|
|
25
|
+
return row?.value;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getByScope(scope: string): MemoryEntry[] {
|
|
29
|
+
return this.db.prepare('SELECT * FROM memory WHERE scope = ? ORDER BY updated_at DESC').all(scope) as MemoryEntry[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
delete(key: string): boolean {
|
|
33
|
+
const result = this.db.prepare('DELETE FROM memory WHERE key = ?').run(key);
|
|
34
|
+
return result.changes > 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
list(limit: number = 50): MemoryEntry[] {
|
|
38
|
+
return this.db.prepare('SELECT * FROM memory ORDER BY updated_at DESC LIMIT ?').all(limit) as MemoryEntry[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
search(query: string): MemoryEntry[] {
|
|
42
|
+
return this.db.prepare(
|
|
43
|
+
'SELECT * FROM memory WHERE key LIKE ? OR value LIKE ? ORDER BY updated_at DESC LIMIT 20'
|
|
44
|
+
).all(`%${query}%`, `%${query}%`) as MemoryEntry[];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
import type { TaskDescriptor, TaskResult, TaskStatus } from '../types.js';
|
|
3
|
+
|
|
4
|
+
interface TaskRow {
|
|
5
|
+
task_id: string;
|
|
6
|
+
prompt: string;
|
|
7
|
+
working_directory: string;
|
|
8
|
+
preferred_agent: string | null;
|
|
9
|
+
assigned_agent: string | null;
|
|
10
|
+
routing_reason: string | null;
|
|
11
|
+
status: string;
|
|
12
|
+
priority: number;
|
|
13
|
+
exit_code: number | null;
|
|
14
|
+
stdout: string | null;
|
|
15
|
+
stderr: string | null;
|
|
16
|
+
stdout_truncated: number;
|
|
17
|
+
stderr_truncated: number;
|
|
18
|
+
duration_ms: number | null;
|
|
19
|
+
token_estimate: number | null;
|
|
20
|
+
cost_estimate: number | null;
|
|
21
|
+
created_at: string;
|
|
22
|
+
completed_at: string | null;
|
|
23
|
+
workflow_id: string | null;
|
|
24
|
+
step_index: number | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function rowToTask(row: TaskRow): TaskDescriptor {
|
|
28
|
+
const task: TaskDescriptor = {
|
|
29
|
+
taskId: row.task_id,
|
|
30
|
+
prompt: row.prompt,
|
|
31
|
+
workingDirectory: row.working_directory,
|
|
32
|
+
status: row.status as TaskStatus,
|
|
33
|
+
priority: row.priority ?? 3,
|
|
34
|
+
createdAt: row.created_at,
|
|
35
|
+
};
|
|
36
|
+
if (row.preferred_agent) task.preferredAgent = row.preferred_agent;
|
|
37
|
+
if (row.assigned_agent) task.assignedAgent = row.assigned_agent;
|
|
38
|
+
if (row.routing_reason) task.routingReason = row.routing_reason;
|
|
39
|
+
if (row.completed_at) task.completedAt = row.completed_at;
|
|
40
|
+
if (row.workflow_id) task.workflowId = row.workflow_id;
|
|
41
|
+
if (row.step_index !== null) task.stepIndex = row.step_index;
|
|
42
|
+
if (row.exit_code !== null) {
|
|
43
|
+
task.result = {
|
|
44
|
+
exitCode: row.exit_code,
|
|
45
|
+
stdout: row.stdout ?? '',
|
|
46
|
+
stderr: row.stderr ?? '',
|
|
47
|
+
stdoutTruncated: row.stdout_truncated === 1,
|
|
48
|
+
stderrTruncated: row.stderr_truncated === 1,
|
|
49
|
+
durationMs: row.duration_ms ?? 0,
|
|
50
|
+
tokenEstimate: row.token_estimate ?? undefined,
|
|
51
|
+
costEstimate: row.cost_estimate ?? undefined,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return task;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class TaskRepo {
|
|
58
|
+
constructor(private db: Database.Database) {}
|
|
59
|
+
|
|
60
|
+
create(task: Pick<TaskDescriptor, 'taskId' | 'prompt' | 'workingDirectory' | 'status' | 'createdAt' | 'preferredAgent' | 'priority' | 'workflowId' | 'stepIndex'>): void {
|
|
61
|
+
this.db.prepare(
|
|
62
|
+
`INSERT INTO tasks (task_id, prompt, working_directory, preferred_agent, status, priority, created_at, workflow_id, step_index)
|
|
63
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
64
|
+
).run(task.taskId, task.prompt, task.workingDirectory, task.preferredAgent ?? null, task.status, task.priority ?? 3, task.createdAt, task.workflowId ?? null, task.stepIndex ?? null);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
listQueued(): TaskDescriptor[] {
|
|
68
|
+
const rows = this.db.prepare(
|
|
69
|
+
`SELECT * FROM tasks WHERE status = 'pending' ORDER BY priority DESC, created_at ASC`
|
|
70
|
+
).all() as TaskRow[];
|
|
71
|
+
return rows.map(rowToTask);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
countRunningByAgent(agentId: string): number {
|
|
75
|
+
const row = this.db.prepare(
|
|
76
|
+
`SELECT COUNT(*) as cnt FROM tasks WHERE assigned_agent = ? AND status = 'running'`
|
|
77
|
+
).get(agentId) as { cnt: number };
|
|
78
|
+
return row.cnt;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getById(taskId: string): TaskDescriptor | undefined {
|
|
82
|
+
const row = this.db.prepare('SELECT * FROM tasks WHERE task_id = ?').get(taskId) as TaskRow | undefined;
|
|
83
|
+
return row ? rowToTask(row) : undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
updateStatus(taskId: string, status: TaskStatus, assignedAgent?: string, routingReason?: string): void {
|
|
87
|
+
if (assignedAgent !== undefined) {
|
|
88
|
+
this.db.prepare(
|
|
89
|
+
'UPDATE tasks SET status = ?, assigned_agent = ?, routing_reason = ? WHERE task_id = ?'
|
|
90
|
+
).run(status, assignedAgent, routingReason ?? null, taskId);
|
|
91
|
+
} else {
|
|
92
|
+
this.db.prepare('UPDATE tasks SET status = ? WHERE task_id = ?').run(status, taskId);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
updateResult(taskId: string, result: TaskResult): void {
|
|
97
|
+
this.db.prepare(
|
|
98
|
+
`UPDATE tasks SET exit_code = ?, stdout = ?, stderr = ?,
|
|
99
|
+
stdout_truncated = ?, stderr_truncated = ?,
|
|
100
|
+
duration_ms = ?, token_estimate = ?, cost_estimate = ?,
|
|
101
|
+
completed_at = ?
|
|
102
|
+
WHERE task_id = ?`
|
|
103
|
+
).run(
|
|
104
|
+
result.exitCode, result.stdout, result.stderr,
|
|
105
|
+
result.stdoutTruncated ? 1 : 0, result.stderrTruncated ? 1 : 0,
|
|
106
|
+
result.durationMs, result.tokenEstimate ?? null, result.costEstimate ?? null,
|
|
107
|
+
new Date().toISOString(),
|
|
108
|
+
taskId
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
list(limit: number = 20, offset: number = 0): TaskDescriptor[] {
|
|
113
|
+
const rows = this.db.prepare(
|
|
114
|
+
'SELECT * FROM tasks ORDER BY created_at DESC LIMIT ? OFFSET ?'
|
|
115
|
+
).all(limit, offset) as TaskRow[];
|
|
116
|
+
return rows.map(rowToTask);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
countByStatus(): Record<string, number> {
|
|
120
|
+
const rows = this.db.prepare(
|
|
121
|
+
'SELECT status, COUNT(*) as cnt FROM tasks GROUP BY status'
|
|
122
|
+
).all() as { status: string; cnt: number }[];
|
|
123
|
+
const result: Record<string, number> = {};
|
|
124
|
+
for (const r of rows) result[r.status] = r.cnt;
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
import type { WorkflowDescriptor, WorkflowStatus, WorkflowStep, StepMode } from '../types.js';
|
|
3
|
+
|
|
4
|
+
interface WorkflowRow {
|
|
5
|
+
workflow_id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
steps: string;
|
|
8
|
+
mode: string;
|
|
9
|
+
status: string;
|
|
10
|
+
task_ids: string;
|
|
11
|
+
current_step: number;
|
|
12
|
+
working_directory: string | null;
|
|
13
|
+
priority: number;
|
|
14
|
+
created_at: string;
|
|
15
|
+
completed_at: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function rowToWorkflow(row: WorkflowRow): WorkflowDescriptor {
|
|
19
|
+
return {
|
|
20
|
+
workflowId: row.workflow_id,
|
|
21
|
+
name: row.name,
|
|
22
|
+
steps: JSON.parse(row.steps) as WorkflowStep[],
|
|
23
|
+
mode: row.mode as StepMode,
|
|
24
|
+
status: row.status as WorkflowStatus,
|
|
25
|
+
taskIds: JSON.parse(row.task_ids) as string[],
|
|
26
|
+
currentStep: row.current_step,
|
|
27
|
+
createdAt: row.created_at,
|
|
28
|
+
completedAt: row.completed_at ?? undefined,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class WorkflowRepo {
|
|
33
|
+
constructor(private db: Database.Database) {}
|
|
34
|
+
|
|
35
|
+
create(wf: Pick<WorkflowDescriptor, 'workflowId' | 'name' | 'steps' | 'mode' | 'status' | 'createdAt'> & { workingDirectory?: string; priority?: number }): void {
|
|
36
|
+
this.db.prepare(
|
|
37
|
+
`INSERT INTO workflows (workflow_id, name, steps, mode, status, working_directory, priority, created_at)
|
|
38
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
39
|
+
).run(wf.workflowId, wf.name, JSON.stringify(wf.steps), wf.mode, wf.status, wf.workingDirectory ?? null, wf.priority ?? 3, wf.createdAt);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getById(workflowId: string): WorkflowDescriptor | undefined {
|
|
43
|
+
const row = this.db.prepare('SELECT * FROM workflows WHERE workflow_id = ?').get(workflowId) as WorkflowRow | undefined;
|
|
44
|
+
return row ? rowToWorkflow(row) : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
updateStatus(workflowId: string, status: WorkflowStatus): void {
|
|
48
|
+
const completedAt = status === 'completed' || status === 'failed' ? new Date().toISOString() : null;
|
|
49
|
+
this.db.prepare('UPDATE workflows SET status = ?, completed_at = ? WHERE workflow_id = ?').run(status, completedAt, workflowId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
updateStep(workflowId: string, currentStep: number): void {
|
|
53
|
+
this.db.prepare('UPDATE workflows SET current_step = ? WHERE workflow_id = ?').run(currentStep, workflowId);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
addTaskId(workflowId: string, taskId: string): void {
|
|
57
|
+
// Atomic JSON array append — no read-modify-write race
|
|
58
|
+
this.db.prepare(
|
|
59
|
+
`UPDATE workflows SET task_ids = json_insert(task_ids, '$[#]', ?) WHERE workflow_id = ?`
|
|
60
|
+
).run(taskId, workflowId);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
list(limit: number = 20, offset: number = 0): WorkflowDescriptor[] {
|
|
64
|
+
const rows = this.db.prepare(
|
|
65
|
+
'SELECT * FROM workflows ORDER BY created_at DESC LIMIT ? OFFSET ?'
|
|
66
|
+
).all(limit, offset) as WorkflowRow[];
|
|
67
|
+
return rows.map(rowToWorkflow);
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// Task types
|
|
2
|
+
export type TaskStatus = 'pending' | 'routing' | 'running' | 'completed' | 'failed';
|
|
3
|
+
|
|
4
|
+
export interface TaskResult {
|
|
5
|
+
exitCode: number;
|
|
6
|
+
stdout: string;
|
|
7
|
+
stderr: string;
|
|
8
|
+
stdoutTruncated: boolean;
|
|
9
|
+
stderrTruncated: boolean;
|
|
10
|
+
durationMs: number;
|
|
11
|
+
tokenEstimate?: number;
|
|
12
|
+
costEstimate?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TaskDescriptor {
|
|
16
|
+
taskId: string;
|
|
17
|
+
prompt: string;
|
|
18
|
+
preferredAgent?: string;
|
|
19
|
+
workingDirectory: string;
|
|
20
|
+
status: TaskStatus;
|
|
21
|
+
assignedAgent?: string;
|
|
22
|
+
routingReason?: string;
|
|
23
|
+
priority: number;
|
|
24
|
+
createdAt: string;
|
|
25
|
+
completedAt?: string;
|
|
26
|
+
result?: TaskResult;
|
|
27
|
+
workflowId?: string;
|
|
28
|
+
stepIndex?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CreateTaskRequest {
|
|
32
|
+
prompt: string;
|
|
33
|
+
preferredAgent?: string;
|
|
34
|
+
workingDirectory?: string;
|
|
35
|
+
priority?: number;
|
|
36
|
+
workflowId?: string;
|
|
37
|
+
stepIndex?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Priority: 1 = lowest, 5 = highest, 3 = default
|
|
41
|
+
export type TaskPriority = 1 | 2 | 3 | 4 | 5;
|
|
42
|
+
|
|
43
|
+
// Agent types
|
|
44
|
+
export interface AgentDescriptor {
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
command: string;
|
|
48
|
+
args: string[];
|
|
49
|
+
enabled: boolean;
|
|
50
|
+
available: boolean;
|
|
51
|
+
healthCheckCommand: string;
|
|
52
|
+
lastHealthCheck?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface UnifiedAgent {
|
|
56
|
+
describe(): AgentDescriptor;
|
|
57
|
+
execute(task: TaskDescriptor): Promise<TaskResult>;
|
|
58
|
+
healthCheck(): Promise<boolean>;
|
|
59
|
+
on(event: string, listener: (...args: unknown[]) => void): this;
|
|
60
|
+
removeListener(event: string, listener: (...args: unknown[]) => void): this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Router types
|
|
64
|
+
export interface RouteDecision {
|
|
65
|
+
agentId: string;
|
|
66
|
+
reason: string;
|
|
67
|
+
confidence: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Audit types
|
|
71
|
+
export type AuditEventType =
|
|
72
|
+
| 'task.created'
|
|
73
|
+
| 'task.routed'
|
|
74
|
+
| 'task.started'
|
|
75
|
+
| 'task.completed'
|
|
76
|
+
| 'task.failed'
|
|
77
|
+
| 'task.queued'
|
|
78
|
+
| 'agent.health'
|
|
79
|
+
| 'workflow.created'
|
|
80
|
+
| 'workflow.step'
|
|
81
|
+
| 'workflow.completed'
|
|
82
|
+
| 'workflow.failed'
|
|
83
|
+
| 'cost.quota_exceeded'
|
|
84
|
+
| 'combo.created'
|
|
85
|
+
| 'combo.step'
|
|
86
|
+
| 'combo.iteration'
|
|
87
|
+
| 'combo.completed'
|
|
88
|
+
| 'combo.failed';
|
|
89
|
+
|
|
90
|
+
// Workflow types
|
|
91
|
+
export type WorkflowStatus = 'pending' | 'running' | 'completed' | 'failed';
|
|
92
|
+
export type StepMode = 'sequential' | 'parallel';
|
|
93
|
+
|
|
94
|
+
export interface WorkflowStep {
|
|
95
|
+
prompt: string;
|
|
96
|
+
preferredAgent?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface WorkflowDescriptor {
|
|
100
|
+
workflowId: string;
|
|
101
|
+
name: string;
|
|
102
|
+
steps: WorkflowStep[];
|
|
103
|
+
mode: StepMode;
|
|
104
|
+
status: WorkflowStatus;
|
|
105
|
+
taskIds: string[];
|
|
106
|
+
currentStep: number;
|
|
107
|
+
createdAt: string;
|
|
108
|
+
completedAt?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface CreateWorkflowRequest {
|
|
112
|
+
name: string;
|
|
113
|
+
steps: WorkflowStep[];
|
|
114
|
+
mode?: StepMode;
|
|
115
|
+
workingDirectory?: string;
|
|
116
|
+
priority?: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Combo types — multi-agent collaboration patterns
|
|
120
|
+
export type ComboStatus = 'pending' | 'running' | 'completed' | 'failed';
|
|
121
|
+
|
|
122
|
+
export type ComboPattern = 'pipeline' | 'map-reduce' | 'review-loop' | 'debate';
|
|
123
|
+
|
|
124
|
+
export interface ComboStep {
|
|
125
|
+
/** Agent to use for this step */
|
|
126
|
+
agent: string;
|
|
127
|
+
/** Prompt template. Use {{prev}} for previous step output, {{step.N}} for specific step output, {{input}} for original input */
|
|
128
|
+
prompt: string;
|
|
129
|
+
/** Role label for this step (e.g., "analyzer", "reviewer", "synthesizer") */
|
|
130
|
+
role?: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface ComboDescriptor {
|
|
134
|
+
comboId: string;
|
|
135
|
+
name: string;
|
|
136
|
+
pattern: ComboPattern;
|
|
137
|
+
steps: ComboStep[];
|
|
138
|
+
input: string;
|
|
139
|
+
status: ComboStatus;
|
|
140
|
+
taskIds: string[];
|
|
141
|
+
stepResults: Record<number, string>; // step index → stdout
|
|
142
|
+
finalOutput?: string;
|
|
143
|
+
maxIterations?: number; // for review-loop
|
|
144
|
+
iterations?: number;
|
|
145
|
+
createdAt: string;
|
|
146
|
+
completedAt?: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface CreateComboRequest {
|
|
150
|
+
name: string;
|
|
151
|
+
pattern: ComboPattern;
|
|
152
|
+
steps: ComboStep[];
|
|
153
|
+
input: string;
|
|
154
|
+
workingDirectory?: string;
|
|
155
|
+
priority?: number;
|
|
156
|
+
maxIterations?: number; // for review-loop, default 3
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Built-in combo presets
|
|
160
|
+
export interface ComboPreset {
|
|
161
|
+
id: string;
|
|
162
|
+
name: string;
|
|
163
|
+
description: string;
|
|
164
|
+
pattern: ComboPattern;
|
|
165
|
+
steps: ComboStep[];
|
|
166
|
+
maxIterations?: number;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Cost types
|
|
170
|
+
export interface CostSummary {
|
|
171
|
+
daily: number;
|
|
172
|
+
monthly: number;
|
|
173
|
+
allTime: number;
|
|
174
|
+
byAgent: Record<string, number>;
|
|
175
|
+
dailyLimit?: number;
|
|
176
|
+
monthlyLimit?: number;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface AuditEntry {
|
|
180
|
+
id?: number;
|
|
181
|
+
taskId?: string;
|
|
182
|
+
eventType: AuditEventType;
|
|
183
|
+
payload: Record<string, unknown>;
|
|
184
|
+
createdAt: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Config types
|
|
188
|
+
export interface AgentConfig {
|
|
189
|
+
enabled: boolean;
|
|
190
|
+
command: string;
|
|
191
|
+
args: string[];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface AppConfig {
|
|
195
|
+
version?: string;
|
|
196
|
+
port: number;
|
|
197
|
+
anthropicApiKey: string;
|
|
198
|
+
routerModel: string;
|
|
199
|
+
defaultTimeout: number;
|
|
200
|
+
authToken?: string;
|
|
201
|
+
maxConcurrencyPerAgent: number;
|
|
202
|
+
dailyCostLimit?: number;
|
|
203
|
+
monthlyCostLimit?: number;
|
|
204
|
+
allowedWorkspaces?: string[];
|
|
205
|
+
maxPromptLength: number;
|
|
206
|
+
maxWorkflowSteps: number;
|
|
207
|
+
agents: Record<string, AgentConfig>;
|
|
208
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": ".",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"sourceMap": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*", "bin/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist", "tests"]
|
|
17
|
+
}
|