@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.
Files changed (59) hide show
  1. package/README.md +116 -0
  2. package/bin/agw.ts +5 -0
  3. package/package.json +59 -0
  4. package/src/agents/base-adapter.ts +113 -0
  5. package/src/agents/claude-adapter.ts +29 -0
  6. package/src/agents/codex-adapter.ts +29 -0
  7. package/src/agents/gemini-adapter.ts +29 -0
  8. package/src/cli/commands/agents.ts +55 -0
  9. package/src/cli/commands/combo.ts +130 -0
  10. package/src/cli/commands/costs.ts +33 -0
  11. package/src/cli/commands/daemon.ts +110 -0
  12. package/src/cli/commands/history.ts +29 -0
  13. package/src/cli/commands/run.ts +59 -0
  14. package/src/cli/commands/status.ts +29 -0
  15. package/src/cli/commands/workflow.ts +73 -0
  16. package/src/cli/error-handler.ts +8 -0
  17. package/src/cli/http-client.ts +68 -0
  18. package/src/cli/index.ts +28 -0
  19. package/src/config.ts +68 -0
  20. package/src/daemon/middleware/auth.ts +35 -0
  21. package/src/daemon/middleware/rate-limiter.ts +63 -0
  22. package/src/daemon/middleware/tenant.ts +64 -0
  23. package/src/daemon/middleware/workspace.ts +40 -0
  24. package/src/daemon/routes/agents.ts +13 -0
  25. package/src/daemon/routes/combos.ts +103 -0
  26. package/src/daemon/routes/costs.ts +9 -0
  27. package/src/daemon/routes/health.ts +62 -0
  28. package/src/daemon/routes/memory.ts +32 -0
  29. package/src/daemon/routes/tasks.ts +157 -0
  30. package/src/daemon/routes/ui.ts +18 -0
  31. package/src/daemon/routes/workflows.ts +73 -0
  32. package/src/daemon/server.ts +91 -0
  33. package/src/daemon/services/agent-learning.ts +77 -0
  34. package/src/daemon/services/agent-manager.ts +71 -0
  35. package/src/daemon/services/auto-scaler.ts +77 -0
  36. package/src/daemon/services/circuit-breaker.ts +95 -0
  37. package/src/daemon/services/combo-executor.ts +300 -0
  38. package/src/daemon/services/dag-executor.ts +136 -0
  39. package/src/daemon/services/metrics.ts +35 -0
  40. package/src/daemon/services/stream-aggregator.ts +64 -0
  41. package/src/daemon/services/task-executor.ts +184 -0
  42. package/src/daemon/services/task-queue.ts +75 -0
  43. package/src/daemon/services/workflow-executor.ts +150 -0
  44. package/src/daemon/services/ws-manager.ts +90 -0
  45. package/src/dsl/parser.ts +124 -0
  46. package/src/plugins/plugin-loader.ts +72 -0
  47. package/src/router/keyword-router.ts +63 -0
  48. package/src/router/llm-router.ts +93 -0
  49. package/src/store/agent-repo.ts +57 -0
  50. package/src/store/audit-repo.ts +25 -0
  51. package/src/store/combo-repo.ts +99 -0
  52. package/src/store/cost-repo.ts +55 -0
  53. package/src/store/db.ts +137 -0
  54. package/src/store/memory-repo.ts +46 -0
  55. package/src/store/task-repo.ts +127 -0
  56. package/src/store/workflow-repo.ts +69 -0
  57. package/src/types.ts +208 -0
  58. package/tsconfig.json +17 -0
  59. package/ui/index.html +272 -0
@@ -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
+ }