@oxgeneral/orch 0.2.3 → 0.3.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/dist/App-TW35IULR.js +18 -0
- package/dist/agent-FRQKL7YI.js +9 -0
- package/dist/{orchestrator-VGYKSOZJ.js → chunk-2UC4SVJB.js} +236 -71
- package/dist/chunk-2UC4SVJB.js.map +1 -0
- package/dist/chunk-5AJ4LYO5.js +8 -0
- package/dist/{chunk-45K2XID7.js → chunk-6DWHQPTE.js} +2 -1
- package/dist/chunk-6DWHQPTE.js.map +1 -0
- package/dist/{chunk-POUC4CPC.js → chunk-6MJ7V6VY.js} +2 -2
- package/dist/{chunk-HNKJ4IF7.js → chunk-B4JQM4NU.js} +34 -10
- package/dist/chunk-B4JQM4NU.js.map +1 -0
- package/dist/{chunk-6HENRUYZ.js → chunk-CDFA4IIQ.js} +2 -2
- package/dist/chunk-CHRW4CLD.js +2 -0
- package/dist/{chunk-ZU6AY2VU.js → chunk-GZ2Q56YZ.js} +2 -2
- package/dist/chunk-HMMPM7MF.js +3 -0
- package/dist/{chunk-AELEEEV3.js → chunk-HSBYJ5C5.js} +27 -7
- package/dist/chunk-HXOMNULD.js +2 -0
- package/dist/{chunk-O5AO5QIR.js → chunk-IQXRQBUK.js} +9 -2
- package/dist/chunk-IQXRQBUK.js.map +1 -0
- package/dist/chunk-L26TK7Y5.js +2 -0
- package/dist/chunk-L3FYR45M.js +2 -0
- package/dist/chunk-LXNRCJ22.js +2 -0
- package/dist/{chunk-TX7WOFCW.js → chunk-MGFMVPRD.js} +4 -7
- package/dist/chunk-MGFMVPRD.js.map +1 -0
- package/dist/chunk-MNXU3KCD.js +2 -0
- package/dist/{chunk-CHIP7O6V.js → chunk-O2MSGW3V.js} +3 -1
- package/dist/chunk-O2MSGW3V.js.map +1 -0
- package/dist/chunk-PJ5DKXGR.js +2 -0
- package/dist/{chunk-VTA74YWX.js → chunk-QEEM67OA.js} +11 -17
- package/dist/chunk-QEEM67OA.js.map +1 -0
- package/dist/chunk-UMZEA3JT.js +5 -0
- package/dist/{shell-OGTSH4RJ.js → chunk-UW6GUUE6.js} +3 -3
- package/dist/chunk-XDVMX2FO.js +8 -0
- package/dist/chunk-XDVMX2FO.js.map +1 -0
- package/dist/chunk-ZA5Z33GO.js +11 -0
- package/dist/claude-E36EGXUV.js +2 -0
- package/dist/{chunk-IRN2U2NE.js → claude-RIB3RQS5.js} +5 -2
- package/dist/claude-RIB3RQS5.js.map +1 -0
- package/dist/cli.js +1 -199
- package/dist/clipboard-service-PDTSZIR5.js +25 -0
- package/dist/codex-OTZKVESD.js +2 -0
- package/dist/{codex-U7LTJTX6.js → codex-VBUSA2GJ.js} +5 -3
- package/dist/codex-VBUSA2GJ.js.map +1 -0
- package/dist/config-CCSS2P7R.js +2 -0
- package/dist/container-OIXLFSX2.js +6 -0
- package/dist/context-GSMQHQES.js +7 -0
- package/dist/cursor-3DJA6LWS.js +2 -0
- package/dist/{cursor-3DI5GKRF.js → cursor-4QIOTDBW.js} +5 -3
- package/dist/cursor-4QIOTDBW.js.map +1 -0
- package/dist/doctor-KBK5JZBZ.js +2 -0
- package/dist/doctor-service-F2SXDWHS.js +91 -0
- package/dist/doctor-service-F2SXDWHS.js.map +1 -0
- package/dist/doctor-service-PB7YBH3F.js +2 -0
- package/dist/goal-RFKFPR7M.js +8 -0
- package/dist/index.d.ts +124 -46
- package/dist/index.js +1817 -5
- package/dist/index.js.map +1 -1
- package/dist/init-WRDFAFS2.js +53 -0
- package/dist/logs-5QHJWMEG.js +12 -0
- package/dist/msg-4SCLBO4K.js +9 -0
- package/dist/orchestrator-FGGXK3N3.js +5 -0
- package/dist/{orchestrator-TAFBYQQ5.js.map → orchestrator-FGGXK3N3.js.map} +1 -1
- package/dist/orchestrator-R7IWZUT6.js +13 -0
- package/dist/process-manager-33H27MQF.js +2 -0
- package/dist/process-manager-A36Y7LHP.js +3 -0
- package/dist/{process-manager-TLZOTO4Y.js.map → process-manager-A36Y7LHP.js.map} +1 -1
- package/dist/registry-BO2PPRNG.js +2 -0
- package/dist/registry-JXXRLJ5J.js +3 -0
- package/dist/{registry-UQAHK77P.js.map → registry-JXXRLJ5J.js.map} +1 -1
- package/dist/run-HSHRELOP.js +3 -0
- package/dist/shell-EOJBDWTH.js +2 -0
- package/dist/{chunk-CIIE6LNG.js → shell-IH2MMTVP.js} +3 -2
- package/dist/shell-IH2MMTVP.js.map +1 -0
- package/dist/status-DLBNWSWM.js +2 -0
- package/dist/task-J6ZN7ALI.js +20 -0
- package/dist/team-MSIBKOQC.js +4 -0
- package/dist/template-engine-MFL5B677.js +3 -0
- package/dist/{template-engine-322SCRR6.js.map → template-engine-MFL5B677.js.map} +1 -1
- package/dist/template-engine-ONIDVD4F.js +2 -0
- package/dist/tui-G4XUFAIP.js +2 -0
- package/dist/update-PC2ENCKU.js +2 -0
- package/dist/update-check-HGMBDYHL.js +2 -0
- package/dist/workspace-manager-KOOYTO7E.js +3 -0
- package/dist/{workspace-manager-47KI7B27.js → workspace-manager-T6AXG7XL.js} +40 -3
- package/dist/workspace-manager-T6AXG7XL.js.map +1 -0
- package/package.json +2 -1
- package/readme.md +5 -4
- package/scripts/benchmark.ts +304 -0
- package/dist/App-KDZSTAMR.js +0 -4864
- package/dist/agent-V5M2C3OC.js +0 -157
- package/dist/chunk-2B32FPEB.js +0 -11
- package/dist/chunk-2B32FPEB.js.map +0 -1
- package/dist/chunk-33QNTNR6.js +0 -46
- package/dist/chunk-6GFVB6EK.js +0 -101
- package/dist/chunk-6HENRUYZ.js.map +0 -1
- package/dist/chunk-AELEEEV3.js.map +0 -1
- package/dist/chunk-E3TCKHU6.js +0 -13
- package/dist/chunk-E3TCKHU6.js.map +0 -1
- package/dist/chunk-ED47GL3F.js +0 -29
- package/dist/chunk-HXYAZGLP.js +0 -15
- package/dist/chunk-I5WEMARW.js +0 -166
- package/dist/chunk-IZYSGYXG.js +0 -2
- package/dist/chunk-IZYSGYXG.js.map +0 -1
- package/dist/chunk-P6ATSXGL.js +0 -107
- package/dist/chunk-PBFE5V3G.js +0 -2
- package/dist/chunk-PBFE5V3G.js.map +0 -1
- package/dist/chunk-PNE6LQRF.js +0 -5
- package/dist/chunk-POUC4CPC.js.map +0 -1
- package/dist/chunk-XI4TU6VU.js +0 -50
- package/dist/chunk-ZU6AY2VU.js.map +0 -1
- package/dist/claude-GH6P2DC5.js +0 -4
- package/dist/claude-S47YTIHU.js +0 -2
- package/dist/claude-S47YTIHU.js.map +0 -1
- package/dist/codex-2CH57B7G.js +0 -2
- package/dist/codex-2CH57B7G.js.map +0 -1
- package/dist/config-LJFM55LN.js +0 -75
- package/dist/container-JV7TAUP5.js +0 -1532
- package/dist/context-EPSDCJTU.js +0 -83
- package/dist/cursor-QFUNKPCQ.js +0 -2
- package/dist/cursor-QFUNKPCQ.js.map +0 -1
- package/dist/doctor-IO4PV4D6.js +0 -67
- package/dist/doctor-service-A34DHPKI.js +0 -2
- package/dist/doctor-service-NTWBWOM2.js +0 -2
- package/dist/doctor-service-NTWBWOM2.js.map +0 -1
- package/dist/goal-I56QP7HS.js +0 -110
- package/dist/init-BE5VKWOM.js +0 -149
- package/dist/logs-IAUAS5TX.js +0 -207
- package/dist/msg-SQWQLJP6.js +0 -95
- package/dist/orchestrator-TAFBYQQ5.js +0 -2
- package/dist/process-manager-HUVNAPQV.js +0 -2
- package/dist/process-manager-TLZOTO4Y.js +0 -2
- package/dist/registry-PQWRVNF2.js +0 -2
- package/dist/registry-UQAHK77P.js +0 -2
- package/dist/run-PSZURVVL.js +0 -95
- package/dist/shell-5ZNXFGXV.js +0 -3
- package/dist/shell-OGTSH4RJ.js.map +0 -1
- package/dist/status-DTF7D3DV.js +0 -56
- package/dist/task-5OJTXW27.js +0 -209
- package/dist/team-AISPLEJB.js +0 -97
- package/dist/template-engine-322SCRR6.js +0 -2
- package/dist/template-engine-3CDRZNMJ.js +0 -3
- package/dist/tui-XDJE3IUA.js +0 -225
- package/dist/update-72GZMF65.js +0 -64
- package/dist/update-check-4RV7Z6WT.js +0 -2
- package/dist/workspace-manager-7M46ESUL.js +0 -2
- package/dist/workspace-manager-7M46ESUL.js.map +0 -1
|
@@ -1,1532 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { DEFAULT_CONFIG } from './chunk-ED47GL3F.js';
|
|
3
|
-
import { isGoalTerminal, GOAL_STATUS_ORDER } from './chunk-HXYAZGLP.js';
|
|
4
|
-
import { canTransition, isTerminal } from './chunk-33QNTNR6.js';
|
|
5
|
-
import { AUTONOMOUS_LABEL } from './chunk-PNE6LQRF.js';
|
|
6
|
-
import { readLines } from './chunk-CHIP7O6V.js';
|
|
7
|
-
import { Paths, readYaml, writeYaml, ensureDir, listFiles, writeJson, readJson, appendJsonl, readJsonl, readJsonlTail, pathExists } from './chunk-VTA74YWX.js';
|
|
8
|
-
import { InvalidArgumentsError, TeamNotFoundError, GoalNotFoundError, AgentNotFoundError, TaskNotFoundError, InvalidTransitionError } from './chunk-O5AO5QIR.js';
|
|
9
|
-
import fs, { mkdir } from 'fs/promises';
|
|
10
|
-
import { createReadStream } from 'fs';
|
|
11
|
-
import path from 'path';
|
|
12
|
-
import { homedir } from 'os';
|
|
13
|
-
import { nanoid } from 'nanoid';
|
|
14
|
-
|
|
15
|
-
var TaskStore = class {
|
|
16
|
-
constructor(paths) {
|
|
17
|
-
this.paths = paths;
|
|
18
|
-
}
|
|
19
|
-
async list(filter) {
|
|
20
|
-
await ensureDir(this.paths.tasksDir);
|
|
21
|
-
const files = await listFiles(this.paths.tasksDir, ".yml");
|
|
22
|
-
const tasksResults = await Promise.all(
|
|
23
|
-
files.map((file) => {
|
|
24
|
-
const id = file.replace(".yml", "");
|
|
25
|
-
return readYaml(this.paths.taskPath(id));
|
|
26
|
-
})
|
|
27
|
-
);
|
|
28
|
-
const tasks = tasksResults.filter(
|
|
29
|
-
(task) => task !== null && (!filter?.status || task.status === filter.status)
|
|
30
|
-
);
|
|
31
|
-
return tasks.sort((a, b) => {
|
|
32
|
-
const statusOrder = statusPriority(a.status) - statusPriority(b.status);
|
|
33
|
-
if (statusOrder !== 0) return statusOrder;
|
|
34
|
-
const bTime = b.updated_at ?? "";
|
|
35
|
-
const aTime = a.updated_at ?? "";
|
|
36
|
-
return bTime < aTime ? -1 : bTime > aTime ? 1 : 0;
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
async get(id) {
|
|
40
|
-
return readYaml(this.paths.taskPath(id));
|
|
41
|
-
}
|
|
42
|
-
async save(task) {
|
|
43
|
-
await ensureDir(this.paths.tasksDir);
|
|
44
|
-
await writeYaml(this.paths.taskPath(task.id), task);
|
|
45
|
-
}
|
|
46
|
-
async delete(id) {
|
|
47
|
-
try {
|
|
48
|
-
await fs.unlink(this.paths.taskPath(id));
|
|
49
|
-
} catch (err) {
|
|
50
|
-
if (err.code !== "ENOENT") throw err;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
function statusPriority(status) {
|
|
55
|
-
const order = {
|
|
56
|
-
in_progress: 0,
|
|
57
|
-
retrying: 1,
|
|
58
|
-
review: 2,
|
|
59
|
-
todo: 3,
|
|
60
|
-
done: 4,
|
|
61
|
-
failed: 5,
|
|
62
|
-
cancelled: 6
|
|
63
|
-
};
|
|
64
|
-
return order[status];
|
|
65
|
-
}
|
|
66
|
-
var AgentStore = class {
|
|
67
|
-
constructor(paths) {
|
|
68
|
-
this.paths = paths;
|
|
69
|
-
}
|
|
70
|
-
async list() {
|
|
71
|
-
await ensureDir(this.paths.agentsDir);
|
|
72
|
-
const files = await listFiles(this.paths.agentsDir, ".yml");
|
|
73
|
-
const results = await Promise.all(
|
|
74
|
-
files.map((file) => {
|
|
75
|
-
const id = file.replace(".yml", "");
|
|
76
|
-
return readYaml(this.paths.agentPath(id));
|
|
77
|
-
})
|
|
78
|
-
);
|
|
79
|
-
return results.filter((agent) => agent !== null);
|
|
80
|
-
}
|
|
81
|
-
async get(id) {
|
|
82
|
-
return readYaml(this.paths.agentPath(id));
|
|
83
|
-
}
|
|
84
|
-
async getByName(name) {
|
|
85
|
-
const agents = await this.list();
|
|
86
|
-
return agents.find((a) => a.name === name) ?? null;
|
|
87
|
-
}
|
|
88
|
-
async save(agent) {
|
|
89
|
-
await ensureDir(this.paths.agentsDir);
|
|
90
|
-
await writeYaml(this.paths.agentPath(agent.id), agent);
|
|
91
|
-
}
|
|
92
|
-
async delete(id) {
|
|
93
|
-
try {
|
|
94
|
-
await fs.unlink(this.paths.agentPath(id));
|
|
95
|
-
} catch (err) {
|
|
96
|
-
if (err.code !== "ENOENT") throw err;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
var RunStore = class {
|
|
101
|
-
constructor(paths) {
|
|
102
|
-
this.paths = paths;
|
|
103
|
-
}
|
|
104
|
-
async save(run) {
|
|
105
|
-
await ensureDir(this.paths.runsDir);
|
|
106
|
-
await writeJson(this.paths.runPath(run.id), run);
|
|
107
|
-
}
|
|
108
|
-
async get(id) {
|
|
109
|
-
return readJson(this.paths.runPath(id));
|
|
110
|
-
}
|
|
111
|
-
async listAll() {
|
|
112
|
-
return this.listFiltered(() => true);
|
|
113
|
-
}
|
|
114
|
-
async listForTask(taskId) {
|
|
115
|
-
return this.listFiltered((run) => run.task_id === taskId);
|
|
116
|
-
}
|
|
117
|
-
async listForAgent(agentId) {
|
|
118
|
-
return this.listFiltered((run) => run.agent_id === agentId);
|
|
119
|
-
}
|
|
120
|
-
async appendEvent(runId, event) {
|
|
121
|
-
await ensureDir(this.paths.runsDir);
|
|
122
|
-
await appendJsonl(this.paths.runEventsPath(runId), event);
|
|
123
|
-
}
|
|
124
|
-
async readEvents(runId) {
|
|
125
|
-
return readJsonl(this.paths.runEventsPath(runId));
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Read the last N events for a run without loading the entire JSONL file.
|
|
129
|
-
*/
|
|
130
|
-
async readEventsTail(runId, count) {
|
|
131
|
-
return readJsonlTail(this.paths.runEventsPath(runId), count);
|
|
132
|
-
}
|
|
133
|
-
async *streamEvents(runId, signal) {
|
|
134
|
-
const filePath = this.paths.runEventsPath(runId);
|
|
135
|
-
const deadline = Date.now() + 3e4;
|
|
136
|
-
while (!signal?.aborted && Date.now() < deadline) {
|
|
137
|
-
if (await pathExists(filePath)) break;
|
|
138
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
139
|
-
}
|
|
140
|
-
if (signal?.aborted || Date.now() >= deadline) return;
|
|
141
|
-
const stream = createReadStream(filePath);
|
|
142
|
-
try {
|
|
143
|
-
for await (const line of readLines(stream)) {
|
|
144
|
-
if (signal?.aborted) break;
|
|
145
|
-
if (line.trim()) {
|
|
146
|
-
try {
|
|
147
|
-
yield JSON.parse(line);
|
|
148
|
-
} catch {
|
|
149
|
-
process.stderr.write(`[RunStore] skipping corrupt JSONL line: ${line.slice(0, 200)}
|
|
150
|
-
`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
} finally {
|
|
155
|
-
stream.destroy();
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
async listFiltered(predicate) {
|
|
159
|
-
await ensureDir(this.paths.runsDir);
|
|
160
|
-
const files = await listFiles(this.paths.runsDir, ".json");
|
|
161
|
-
const BATCH = 64;
|
|
162
|
-
const all = [];
|
|
163
|
-
for (let i = 0; i < files.length; i += BATCH) {
|
|
164
|
-
const batch = files.slice(i, i + BATCH);
|
|
165
|
-
const results = await Promise.all(
|
|
166
|
-
batch.map((file) => {
|
|
167
|
-
const id = file.endsWith(".json") ? file.slice(0, -5) : file;
|
|
168
|
-
return readJson(this.paths.runPath(id));
|
|
169
|
-
})
|
|
170
|
-
);
|
|
171
|
-
for (const run of results) {
|
|
172
|
-
if (run !== null && predicate(run)) all.push(run);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return all.sort(
|
|
176
|
-
(a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime()
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// src/domain/state.ts
|
|
182
|
-
var DEFAULT_STATE = {
|
|
183
|
-
version: 1,
|
|
184
|
-
running: {},
|
|
185
|
-
claimed: [],
|
|
186
|
-
retry_queue: [],
|
|
187
|
-
stats: {
|
|
188
|
-
total_runs: 0,
|
|
189
|
-
total_tasks_completed: 0,
|
|
190
|
-
total_tasks_failed: 0,
|
|
191
|
-
total_tokens: { input: 0, output: 0, total: 0 },
|
|
192
|
-
total_runtime_ms: 0
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
// src/infrastructure/storage/state-store.ts
|
|
197
|
-
var StateStore = class {
|
|
198
|
-
constructor(paths) {
|
|
199
|
-
this.paths = paths;
|
|
200
|
-
}
|
|
201
|
-
async read() {
|
|
202
|
-
const raw = await readJson(this.paths.statePath);
|
|
203
|
-
if (!raw) return structuredClone(DEFAULT_STATE);
|
|
204
|
-
const defaults = structuredClone(DEFAULT_STATE);
|
|
205
|
-
return {
|
|
206
|
-
version: raw.version ?? defaults.version,
|
|
207
|
-
pid: raw.pid,
|
|
208
|
-
started_at: raw.started_at,
|
|
209
|
-
running: raw.running && typeof raw.running === "object" ? raw.running : defaults.running,
|
|
210
|
-
claimed: Array.isArray(raw.claimed) ? raw.claimed : defaults.claimed,
|
|
211
|
-
retry_queue: Array.isArray(raw.retry_queue) ? raw.retry_queue : defaults.retry_queue,
|
|
212
|
-
stats: {
|
|
213
|
-
total_runs: raw.stats?.total_runs ?? defaults.stats.total_runs,
|
|
214
|
-
total_tasks_completed: raw.stats?.total_tasks_completed ?? defaults.stats.total_tasks_completed,
|
|
215
|
-
total_tasks_failed: raw.stats?.total_tasks_failed ?? defaults.stats.total_tasks_failed,
|
|
216
|
-
total_tokens: raw.stats?.total_tokens ?? defaults.stats.total_tokens,
|
|
217
|
-
total_runtime_ms: raw.stats?.total_runtime_ms ?? defaults.stats.total_runtime_ms
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
async write(state) {
|
|
222
|
-
await writeJson(this.paths.statePath, state);
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
// src/infrastructure/storage/config-store.ts
|
|
227
|
-
var ConfigStore = class {
|
|
228
|
-
constructor(paths) {
|
|
229
|
-
this.paths = paths;
|
|
230
|
-
}
|
|
231
|
-
async read() {
|
|
232
|
-
const config = await readYaml(this.paths.configPath);
|
|
233
|
-
return deepMerge(
|
|
234
|
-
DEFAULT_CONFIG,
|
|
235
|
-
config ?? {}
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
async write(config) {
|
|
239
|
-
await writeYaml(this.paths.configPath, config);
|
|
240
|
-
}
|
|
241
|
-
async get(keyPath) {
|
|
242
|
-
const config = await this.read();
|
|
243
|
-
return getByPath(config, keyPath);
|
|
244
|
-
}
|
|
245
|
-
async set(keyPath, value) {
|
|
246
|
-
const config = await this.read();
|
|
247
|
-
setByPath(config, keyPath, value);
|
|
248
|
-
await this.write(config);
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
function getByPath(obj, keyPath) {
|
|
252
|
-
const keys = keyPath.split(".");
|
|
253
|
-
let current = obj;
|
|
254
|
-
for (const key of keys) {
|
|
255
|
-
if (current === null || current === void 0 || typeof current !== "object") {
|
|
256
|
-
return void 0;
|
|
257
|
-
}
|
|
258
|
-
current = current[key];
|
|
259
|
-
}
|
|
260
|
-
return current;
|
|
261
|
-
}
|
|
262
|
-
function setByPath(obj, keyPath, value) {
|
|
263
|
-
const keys = keyPath.split(".");
|
|
264
|
-
let current = obj;
|
|
265
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
266
|
-
const key = keys[i];
|
|
267
|
-
if (typeof current[key] !== "object" || current[key] === null) {
|
|
268
|
-
current[key] = {};
|
|
269
|
-
}
|
|
270
|
-
current = current[key];
|
|
271
|
-
}
|
|
272
|
-
const lastKey = keys[keys.length - 1];
|
|
273
|
-
current[lastKey] = value;
|
|
274
|
-
}
|
|
275
|
-
function deepMerge(target, source) {
|
|
276
|
-
const result = { ...target };
|
|
277
|
-
for (const key of Object.keys(source)) {
|
|
278
|
-
const sourceVal = source[key];
|
|
279
|
-
const targetVal = result[key];
|
|
280
|
-
if (sourceVal !== null && sourceVal !== void 0 && typeof sourceVal === "object" && !Array.isArray(sourceVal) && typeof targetVal === "object" && targetVal !== null && !Array.isArray(targetVal)) {
|
|
281
|
-
result[key] = deepMerge(
|
|
282
|
-
targetVal,
|
|
283
|
-
sourceVal
|
|
284
|
-
);
|
|
285
|
-
} else {
|
|
286
|
-
result[key] = sourceVal;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return result;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// src/domain/global-config.ts
|
|
293
|
-
var DEFAULT_GLOBAL_CONFIG = {
|
|
294
|
-
tui: {
|
|
295
|
-
activity_filter: "all"
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
// src/infrastructure/storage/global-config-store.ts
|
|
300
|
-
var GLOBAL_DIR = path.join(homedir(), ".orchestry");
|
|
301
|
-
var GLOBAL_CONFIG_PATH = path.join(GLOBAL_DIR, "global.yml");
|
|
302
|
-
var GlobalConfigStore = class {
|
|
303
|
-
async read() {
|
|
304
|
-
const data = await readYaml(GLOBAL_CONFIG_PATH);
|
|
305
|
-
if (!data) return { ...DEFAULT_GLOBAL_CONFIG };
|
|
306
|
-
return {
|
|
307
|
-
tui: {
|
|
308
|
-
activity_filter: data.tui?.activity_filter ?? DEFAULT_GLOBAL_CONFIG.tui.activity_filter
|
|
309
|
-
}
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
async write(config) {
|
|
313
|
-
await mkdir(GLOBAL_DIR, { recursive: true });
|
|
314
|
-
await writeYaml(GLOBAL_CONFIG_PATH, config);
|
|
315
|
-
}
|
|
316
|
-
async set(key, value) {
|
|
317
|
-
const config = await this.read();
|
|
318
|
-
config.tui[key] = value;
|
|
319
|
-
await this.write(config);
|
|
320
|
-
}
|
|
321
|
-
};
|
|
322
|
-
var ContextStore = class _ContextStore {
|
|
323
|
-
constructor(paths) {
|
|
324
|
-
this.paths = paths;
|
|
325
|
-
}
|
|
326
|
-
async get(key) {
|
|
327
|
-
const entry = await readJson(this.paths.contextPath(key));
|
|
328
|
-
if (!entry) return null;
|
|
329
|
-
if (isExpired(entry)) {
|
|
330
|
-
await this.delete(key);
|
|
331
|
-
return null;
|
|
332
|
-
}
|
|
333
|
-
return entry;
|
|
334
|
-
}
|
|
335
|
-
/** Max TTL: 30 days in milliseconds */
|
|
336
|
-
static MAX_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
337
|
-
async set(key, value, ttlMs) {
|
|
338
|
-
if (ttlMs !== void 0) {
|
|
339
|
-
if (!Number.isFinite(ttlMs) || ttlMs <= 0 || ttlMs > _ContextStore.MAX_TTL_MS) {
|
|
340
|
-
throw new Error(`TTL must be a positive number up to ${_ContextStore.MAX_TTL_MS}ms (30 days)`);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
await ensureDir(this.paths.contextDir);
|
|
344
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
345
|
-
const existing = await readJson(this.paths.contextPath(key));
|
|
346
|
-
const entry = {
|
|
347
|
-
key,
|
|
348
|
-
value,
|
|
349
|
-
created_at: existing?.created_at ?? now,
|
|
350
|
-
updated_at: now,
|
|
351
|
-
ttl_ms: ttlMs,
|
|
352
|
-
expires_at: ttlMs ? new Date(Date.now() + ttlMs).toISOString() : void 0
|
|
353
|
-
};
|
|
354
|
-
await writeJson(this.paths.contextPath(key), entry);
|
|
355
|
-
}
|
|
356
|
-
async delete(key) {
|
|
357
|
-
try {
|
|
358
|
-
await fs.unlink(this.paths.contextPath(key));
|
|
359
|
-
} catch (err) {
|
|
360
|
-
if (err.code !== "ENOENT") throw err;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
async list() {
|
|
364
|
-
await ensureDir(this.paths.contextDir);
|
|
365
|
-
const files = await listFiles(this.paths.contextDir, ".json");
|
|
366
|
-
const results = await Promise.all(
|
|
367
|
-
files.map((file) => {
|
|
368
|
-
const key = file.replace(".json", "");
|
|
369
|
-
return readJson(this.paths.contextPath(key));
|
|
370
|
-
})
|
|
371
|
-
);
|
|
372
|
-
const entries = [];
|
|
373
|
-
for (const entry of results) {
|
|
374
|
-
if (!entry) continue;
|
|
375
|
-
if (isExpired(entry)) {
|
|
376
|
-
await this.delete(entry.key);
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
entries.push(entry);
|
|
380
|
-
}
|
|
381
|
-
return entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
382
|
-
}
|
|
383
|
-
async getAll() {
|
|
384
|
-
const entries = await this.list();
|
|
385
|
-
const result = {};
|
|
386
|
-
for (const entry of entries) {
|
|
387
|
-
result[entry.key] = entry.value;
|
|
388
|
-
}
|
|
389
|
-
return result;
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
function isExpired(entry) {
|
|
393
|
-
if (!entry.expires_at) return false;
|
|
394
|
-
return new Date(entry.expires_at).getTime() < Date.now();
|
|
395
|
-
}
|
|
396
|
-
var MessageStore = class {
|
|
397
|
-
constructor(paths) {
|
|
398
|
-
this.paths = paths;
|
|
399
|
-
}
|
|
400
|
-
async save(message) {
|
|
401
|
-
await ensureDir(this.paths.messagesDir);
|
|
402
|
-
await writeJson(this.paths.messagePath(message.id), message);
|
|
403
|
-
}
|
|
404
|
-
async get(id) {
|
|
405
|
-
return readJson(this.paths.messagePath(id));
|
|
406
|
-
}
|
|
407
|
-
async list() {
|
|
408
|
-
const files = await listFiles(this.paths.messagesDir, ".json");
|
|
409
|
-
const results = await Promise.all(
|
|
410
|
-
files.map((f) => readJson(this.paths.messagePath(f.replace(".json", ""))))
|
|
411
|
-
);
|
|
412
|
-
return results.filter((m) => m !== null).sort((a, b) => a.created_at.localeCompare(b.created_at));
|
|
413
|
-
}
|
|
414
|
-
async listPending(agentId) {
|
|
415
|
-
const all = await this.list();
|
|
416
|
-
const now = Date.now();
|
|
417
|
-
return all.filter((m) => {
|
|
418
|
-
if (m.status !== "pending") return false;
|
|
419
|
-
if (m.expires_at && new Date(m.expires_at).getTime() < now) return false;
|
|
420
|
-
return m.to_agent_id === agentId;
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
async markDelivered(id) {
|
|
424
|
-
const msg = await this.get(id);
|
|
425
|
-
if (!msg) return;
|
|
426
|
-
msg.status = "delivered";
|
|
427
|
-
msg.delivered_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
428
|
-
await writeJson(this.paths.messagePath(id), msg);
|
|
429
|
-
}
|
|
430
|
-
async delete(id) {
|
|
431
|
-
try {
|
|
432
|
-
await fs.unlink(this.paths.messagePath(id));
|
|
433
|
-
} catch (err) {
|
|
434
|
-
if (err.code !== "ENOENT") throw err;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
async purgeExpired() {
|
|
438
|
-
const all = await this.list();
|
|
439
|
-
const now = Date.now();
|
|
440
|
-
const toDelete = all.filter((m) => {
|
|
441
|
-
const isExpired2 = m.expires_at && new Date(m.expires_at).getTime() < now;
|
|
442
|
-
const isOldDelivered = m.delivered_at && now - new Date(m.delivered_at).getTime() > 36e5;
|
|
443
|
-
return isExpired2 || isOldDelivered;
|
|
444
|
-
});
|
|
445
|
-
await Promise.all(toDelete.map((m) => this.delete(m.id)));
|
|
446
|
-
return toDelete.length;
|
|
447
|
-
}
|
|
448
|
-
};
|
|
449
|
-
var GoalStore = class {
|
|
450
|
-
constructor(paths) {
|
|
451
|
-
this.paths = paths;
|
|
452
|
-
}
|
|
453
|
-
async list(filter) {
|
|
454
|
-
const files = await listFiles(this.paths.goalsDir, ".yml");
|
|
455
|
-
const results = await Promise.all(
|
|
456
|
-
files.map((file) => {
|
|
457
|
-
const id = file.replace(".yml", "");
|
|
458
|
-
return readYaml(this.paths.goalPath(id));
|
|
459
|
-
})
|
|
460
|
-
);
|
|
461
|
-
const goals = results.filter(
|
|
462
|
-
(goal) => goal !== null && (!filter?.status || goal.status === filter.status)
|
|
463
|
-
);
|
|
464
|
-
return goals.sort((a, b) => {
|
|
465
|
-
const statusOrder = GOAL_STATUS_ORDER[a.status] - GOAL_STATUS_ORDER[b.status];
|
|
466
|
-
if (statusOrder !== 0) return statusOrder;
|
|
467
|
-
const bTime = b.updated_at ?? "";
|
|
468
|
-
const aTime = a.updated_at ?? "";
|
|
469
|
-
return bTime < aTime ? -1 : bTime > aTime ? 1 : 0;
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
async get(id) {
|
|
473
|
-
return readYaml(this.paths.goalPath(id));
|
|
474
|
-
}
|
|
475
|
-
async save(goal) {
|
|
476
|
-
await writeYaml(this.paths.goalPath(goal.id), goal);
|
|
477
|
-
}
|
|
478
|
-
async delete(id) {
|
|
479
|
-
try {
|
|
480
|
-
await fs.unlink(this.paths.goalPath(id));
|
|
481
|
-
} catch (err) {
|
|
482
|
-
if (err.code !== "ENOENT") throw err;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
};
|
|
486
|
-
var TeamStore = class {
|
|
487
|
-
constructor(paths) {
|
|
488
|
-
this.paths = paths;
|
|
489
|
-
}
|
|
490
|
-
async save(team) {
|
|
491
|
-
await ensureDir(this.paths.teamsDir);
|
|
492
|
-
await writeYaml(this.paths.teamPath(team.id), team);
|
|
493
|
-
}
|
|
494
|
-
async get(id) {
|
|
495
|
-
return readYaml(this.paths.teamPath(id));
|
|
496
|
-
}
|
|
497
|
-
async getByName(name) {
|
|
498
|
-
const teams = await this.list();
|
|
499
|
-
return teams.find((t) => t.name === name) ?? null;
|
|
500
|
-
}
|
|
501
|
-
async list() {
|
|
502
|
-
await ensureDir(this.paths.teamsDir);
|
|
503
|
-
const files = await listFiles(this.paths.teamsDir, ".yml");
|
|
504
|
-
const results = await Promise.all(
|
|
505
|
-
files.map((f) => readYaml(this.paths.teamPath(f.replace(".yml", ""))))
|
|
506
|
-
);
|
|
507
|
-
return results.filter((t) => t !== null);
|
|
508
|
-
}
|
|
509
|
-
async delete(id) {
|
|
510
|
-
try {
|
|
511
|
-
await fs.unlink(this.paths.teamPath(id));
|
|
512
|
-
} catch (err) {
|
|
513
|
-
if (err.code !== "ENOENT") throw err;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
// src/application/event-bus.ts
|
|
519
|
-
var EventBus = class {
|
|
520
|
-
handlers = /* @__PURE__ */ new Map();
|
|
521
|
-
wildcardHandlers = /* @__PURE__ */ new Set();
|
|
522
|
-
maxListeners = 10;
|
|
523
|
-
warnedTypes = /* @__PURE__ */ new Set();
|
|
524
|
-
/**
|
|
525
|
-
* Set the maximum number of listeners per event type before a warning is emitted.
|
|
526
|
-
* Helps detect memory leaks from repeated subscriptions in watch mode.
|
|
527
|
-
*/
|
|
528
|
-
setMaxListeners(n) {
|
|
529
|
-
this.maxListeners = n;
|
|
530
|
-
}
|
|
531
|
-
getMaxListeners() {
|
|
532
|
-
return this.maxListeners;
|
|
533
|
-
}
|
|
534
|
-
/**
|
|
535
|
-
* Get the number of listeners for a specific event type.
|
|
536
|
-
*/
|
|
537
|
-
listenerCount(type) {
|
|
538
|
-
return this.handlers.get(type)?.size ?? 0;
|
|
539
|
-
}
|
|
540
|
-
/**
|
|
541
|
-
* Subscribe to events of a specific type.
|
|
542
|
-
* Returns an unsubscribe function.
|
|
543
|
-
*/
|
|
544
|
-
on(type, handler) {
|
|
545
|
-
if (!this.handlers.has(type)) {
|
|
546
|
-
this.handlers.set(type, /* @__PURE__ */ new Set());
|
|
547
|
-
}
|
|
548
|
-
const set = this.handlers.get(type);
|
|
549
|
-
set.add(handler);
|
|
550
|
-
if (this.maxListeners > 0 && set.size > this.maxListeners && !this.warnedTypes.has(type)) {
|
|
551
|
-
this.warnedTypes.add(type);
|
|
552
|
-
console.warn(
|
|
553
|
-
`EventBus: possible memory leak detected. ${set.size} listeners added for "${type}". Use setMaxListeners() to increase limit if this is intentional.`
|
|
554
|
-
);
|
|
555
|
-
}
|
|
556
|
-
return () => this.off(type, handler);
|
|
557
|
-
}
|
|
558
|
-
/**
|
|
559
|
-
* Subscribe to an event type, auto-unsubscribe after first call.
|
|
560
|
-
*/
|
|
561
|
-
once(type, handler) {
|
|
562
|
-
const wrapper = (event) => {
|
|
563
|
-
this.off(type, wrapper);
|
|
564
|
-
handler(event);
|
|
565
|
-
};
|
|
566
|
-
return this.on(type, wrapper);
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* Unsubscribe a handler from an event type.
|
|
570
|
-
*/
|
|
571
|
-
off(type, handler) {
|
|
572
|
-
this.handlers.get(type)?.delete(handler);
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* Emit an event synchronously to all subscribed handlers.
|
|
576
|
-
*/
|
|
577
|
-
emit(event) {
|
|
578
|
-
const typed = this.handlers.get(event.type);
|
|
579
|
-
if (typed) this.dispatchToSet(typed, event, "handler");
|
|
580
|
-
this.dispatchToSet(this.wildcardHandlers, event, "wildcard handler");
|
|
581
|
-
}
|
|
582
|
-
dispatchToSet(handlers, event, label) {
|
|
583
|
-
for (const handler of handlers) {
|
|
584
|
-
try {
|
|
585
|
-
handler(event);
|
|
586
|
-
} catch (err) {
|
|
587
|
-
console.error(`EventBus ${label} error for "${event.type}":`, err);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
/**
|
|
592
|
-
* Subscribe to ALL events regardless of type.
|
|
593
|
-
*/
|
|
594
|
-
onAny(handler) {
|
|
595
|
-
this.wildcardHandlers.add(handler);
|
|
596
|
-
if (this.maxListeners > 0 && this.wildcardHandlers.size > this.maxListeners && !this.warnedTypes.has("*")) {
|
|
597
|
-
this.warnedTypes.add("*");
|
|
598
|
-
console.warn(
|
|
599
|
-
`EventBus: possible memory leak detected. ${this.wildcardHandlers.size} wildcard listeners added. Use setMaxListeners() to increase limit if this is intentional.`
|
|
600
|
-
);
|
|
601
|
-
}
|
|
602
|
-
return () => {
|
|
603
|
-
this.wildcardHandlers.delete(handler);
|
|
604
|
-
};
|
|
605
|
-
}
|
|
606
|
-
/**
|
|
607
|
-
* Remove all handlers.
|
|
608
|
-
*/
|
|
609
|
-
clear() {
|
|
610
|
-
this.handlers.clear();
|
|
611
|
-
this.wildcardHandlers.clear();
|
|
612
|
-
this.warnedTypes.clear();
|
|
613
|
-
}
|
|
614
|
-
};
|
|
615
|
-
var TaskService = class {
|
|
616
|
-
constructor(taskStore, eventBus, config) {
|
|
617
|
-
this.taskStore = taskStore;
|
|
618
|
-
this.eventBus = eventBus;
|
|
619
|
-
this.config = config;
|
|
620
|
-
}
|
|
621
|
-
async create(input) {
|
|
622
|
-
if (!input.title.trim()) {
|
|
623
|
-
throw new InvalidArgumentsError("Task title is required");
|
|
624
|
-
}
|
|
625
|
-
const priority = input.priority ?? this.config.defaults.task.priority;
|
|
626
|
-
if (!Number.isInteger(priority) || priority < 1 || priority > 4) {
|
|
627
|
-
throw new InvalidArgumentsError("Priority must be an integer between 1 and 4");
|
|
628
|
-
}
|
|
629
|
-
if (input.depends_on?.length) {
|
|
630
|
-
const results = await Promise.all(
|
|
631
|
-
input.depends_on.map(async (depId) => ({ depId, exists: !!await this.taskStore.get(depId) }))
|
|
632
|
-
);
|
|
633
|
-
const missing = results.filter((r) => !r.exists).map((r) => r.depId);
|
|
634
|
-
if (missing.length > 0) {
|
|
635
|
-
throw new InvalidArgumentsError(
|
|
636
|
-
`Unknown depends_on task ID(s): ${missing.join(", ")}`
|
|
637
|
-
);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
641
|
-
const task = {
|
|
642
|
-
id: `tsk_${nanoid(7)}`,
|
|
643
|
-
title: input.title.trim(),
|
|
644
|
-
description: input.description?.trim() ?? "",
|
|
645
|
-
status: "todo",
|
|
646
|
-
priority,
|
|
647
|
-
assignee: input.assignee,
|
|
648
|
-
labels: input.labels ?? [],
|
|
649
|
-
depends_on: input.depends_on ?? [],
|
|
650
|
-
created_at: now,
|
|
651
|
-
updated_at: now,
|
|
652
|
-
attempts: 0,
|
|
653
|
-
max_attempts: input.max_attempts ?? this.config.defaults.task.max_attempts,
|
|
654
|
-
workspace_mode: input.workspace_mode,
|
|
655
|
-
review_criteria: input.review_criteria,
|
|
656
|
-
scope: input.scope
|
|
657
|
-
};
|
|
658
|
-
await this.taskStore.save(task);
|
|
659
|
-
this.eventBus.emit({ type: "task:created", task });
|
|
660
|
-
return task;
|
|
661
|
-
}
|
|
662
|
-
async list(filter) {
|
|
663
|
-
return this.taskStore.list(filter);
|
|
664
|
-
}
|
|
665
|
-
async get(id) {
|
|
666
|
-
const task = await this.taskStore.get(id);
|
|
667
|
-
if (!task) throw new TaskNotFoundError(id);
|
|
668
|
-
return task;
|
|
669
|
-
}
|
|
670
|
-
async updateStatus(id, newStatus) {
|
|
671
|
-
const task = await this.get(id);
|
|
672
|
-
const oldStatus = task.status;
|
|
673
|
-
if (!canTransition(oldStatus, newStatus)) {
|
|
674
|
-
throw new InvalidTransitionError(id, oldStatus, newStatus);
|
|
675
|
-
}
|
|
676
|
-
task.status = newStatus;
|
|
677
|
-
task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
678
|
-
await this.taskStore.save(task);
|
|
679
|
-
this.eventBus.emit({
|
|
680
|
-
type: "task:status_changed",
|
|
681
|
-
taskId: id,
|
|
682
|
-
from: oldStatus,
|
|
683
|
-
to: newStatus
|
|
684
|
-
});
|
|
685
|
-
return task;
|
|
686
|
-
}
|
|
687
|
-
async assign(taskId, agentId) {
|
|
688
|
-
const task = await this.get(taskId);
|
|
689
|
-
task.assignee = agentId;
|
|
690
|
-
task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
691
|
-
await this.taskStore.save(task);
|
|
692
|
-
this.eventBus.emit({
|
|
693
|
-
type: "task:assigned",
|
|
694
|
-
taskId,
|
|
695
|
-
agentId
|
|
696
|
-
});
|
|
697
|
-
return task;
|
|
698
|
-
}
|
|
699
|
-
async cancel(id) {
|
|
700
|
-
const task = await this.get(id);
|
|
701
|
-
if (isTerminal(task.status)) {
|
|
702
|
-
throw new InvalidTransitionError(id, task.status, "cancelled");
|
|
703
|
-
}
|
|
704
|
-
return this.updateStatus(id, "cancelled");
|
|
705
|
-
}
|
|
706
|
-
async retry(id) {
|
|
707
|
-
const task = await this.get(id);
|
|
708
|
-
if (task.status !== "failed" && task.status !== "cancelled") {
|
|
709
|
-
throw new InvalidTransitionError(id, task.status, "todo");
|
|
710
|
-
}
|
|
711
|
-
const oldStatus = task.status;
|
|
712
|
-
task.status = "todo";
|
|
713
|
-
task.attempts = 0;
|
|
714
|
-
task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
715
|
-
await this.taskStore.save(task);
|
|
716
|
-
this.eventBus.emit({
|
|
717
|
-
type: "task:status_changed",
|
|
718
|
-
taskId: id,
|
|
719
|
-
from: oldStatus,
|
|
720
|
-
to: "todo"
|
|
721
|
-
});
|
|
722
|
-
return task;
|
|
723
|
-
}
|
|
724
|
-
async reject(id, feedback) {
|
|
725
|
-
const task = await this.get(id);
|
|
726
|
-
if (task.status !== "review") {
|
|
727
|
-
throw new InvalidTransitionError(id, task.status, "todo");
|
|
728
|
-
}
|
|
729
|
-
const oldStatus = task.status;
|
|
730
|
-
task.status = "todo";
|
|
731
|
-
task.attempts = 0;
|
|
732
|
-
task.feedback = feedback;
|
|
733
|
-
task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
734
|
-
await this.taskStore.save(task);
|
|
735
|
-
this.eventBus.emit({
|
|
736
|
-
type: "task:status_changed",
|
|
737
|
-
taskId: id,
|
|
738
|
-
from: oldStatus,
|
|
739
|
-
to: "todo"
|
|
740
|
-
});
|
|
741
|
-
return task;
|
|
742
|
-
}
|
|
743
|
-
async update(id, fields) {
|
|
744
|
-
const task = await this.get(id);
|
|
745
|
-
if (fields.title !== void 0) {
|
|
746
|
-
if (!fields.title.trim()) throw new InvalidArgumentsError("Task title cannot be empty");
|
|
747
|
-
task.title = fields.title.trim();
|
|
748
|
-
}
|
|
749
|
-
if (fields.description !== void 0) task.description = fields.description.trim();
|
|
750
|
-
if (fields.priority !== void 0) {
|
|
751
|
-
if (!Number.isInteger(fields.priority) || fields.priority < 1 || fields.priority > 4) {
|
|
752
|
-
throw new InvalidArgumentsError("Priority must be an integer between 1 and 4");
|
|
753
|
-
}
|
|
754
|
-
task.priority = fields.priority;
|
|
755
|
-
}
|
|
756
|
-
if (fields.labels !== void 0) task.labels = fields.labels;
|
|
757
|
-
task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
758
|
-
await this.taskStore.save(task);
|
|
759
|
-
return task;
|
|
760
|
-
}
|
|
761
|
-
async delete(id) {
|
|
762
|
-
const task = await this.get(id);
|
|
763
|
-
if (task.status === "in_progress") {
|
|
764
|
-
throw new InvalidArgumentsError("Cannot delete a running task. Cancel it first.");
|
|
765
|
-
}
|
|
766
|
-
await this.taskStore.delete(id);
|
|
767
|
-
}
|
|
768
|
-
async incrementAttempts(id) {
|
|
769
|
-
const task = await this.get(id);
|
|
770
|
-
task.attempts += 1;
|
|
771
|
-
task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
772
|
-
await this.taskStore.save(task);
|
|
773
|
-
return task;
|
|
774
|
-
}
|
|
775
|
-
};
|
|
776
|
-
var AgentService = class {
|
|
777
|
-
constructor(agentStore, stateStore, eventBus, config) {
|
|
778
|
-
this.agentStore = agentStore;
|
|
779
|
-
this.stateStore = stateStore;
|
|
780
|
-
this.eventBus = eventBus;
|
|
781
|
-
this.config = config;
|
|
782
|
-
}
|
|
783
|
-
async create(input) {
|
|
784
|
-
if (!input.name.trim()) {
|
|
785
|
-
throw new InvalidArgumentsError("Agent name is required");
|
|
786
|
-
}
|
|
787
|
-
const existing = await this.agentStore.getByName(input.name);
|
|
788
|
-
if (existing) {
|
|
789
|
-
throw new InvalidArgumentsError(`Agent "${input.name}" already exists`);
|
|
790
|
-
}
|
|
791
|
-
const agent = {
|
|
792
|
-
id: `agt_${nanoid(7)}`,
|
|
793
|
-
name: input.name.trim(),
|
|
794
|
-
adapter: input.adapter || this.config.defaults.agent.adapter,
|
|
795
|
-
role: input.role,
|
|
796
|
-
config: {
|
|
797
|
-
command: input.command,
|
|
798
|
-
model: input.model,
|
|
799
|
-
approval_policy: input.approval_policy ?? this.config.defaults.agent.approval_policy,
|
|
800
|
-
max_turns: input.max_turns ?? this.config.defaults.agent.max_turns,
|
|
801
|
-
timeout_ms: input.timeout_ms ?? this.config.defaults.agent.timeout_ms,
|
|
802
|
-
stall_timeout_ms: input.stall_timeout_ms ?? this.config.defaults.agent.stall_timeout_ms,
|
|
803
|
-
env: input.env,
|
|
804
|
-
system_prompt: input.system_prompt,
|
|
805
|
-
workspace_mode: input.workspace_mode,
|
|
806
|
-
skills: input.skills
|
|
807
|
-
},
|
|
808
|
-
status: "idle",
|
|
809
|
-
stats: {
|
|
810
|
-
tasks_completed: 0,
|
|
811
|
-
tasks_failed: 0,
|
|
812
|
-
total_runs: 0,
|
|
813
|
-
total_runtime_ms: 0
|
|
814
|
-
}
|
|
815
|
-
};
|
|
816
|
-
await this.agentStore.save(agent);
|
|
817
|
-
return agent;
|
|
818
|
-
}
|
|
819
|
-
async list() {
|
|
820
|
-
return this.agentStore.list();
|
|
821
|
-
}
|
|
822
|
-
async get(id) {
|
|
823
|
-
const agent = await this.agentStore.get(id);
|
|
824
|
-
if (!agent) throw new AgentNotFoundError(id);
|
|
825
|
-
return agent;
|
|
826
|
-
}
|
|
827
|
-
async remove(id) {
|
|
828
|
-
const agent = await this.get(id);
|
|
829
|
-
if (agent.status === "running") {
|
|
830
|
-
const state = await this.stateStore.read();
|
|
831
|
-
const isActuallyRunning = Object.values(state.running).some((e) => e.agent_id === id);
|
|
832
|
-
if (isActuallyRunning) {
|
|
833
|
-
throw new InvalidArgumentsError("Cannot remove a running agent. Stop it first.");
|
|
834
|
-
}
|
|
835
|
-
agent.status = "idle";
|
|
836
|
-
await this.agentStore.save(agent);
|
|
837
|
-
}
|
|
838
|
-
await this.agentStore.delete(id);
|
|
839
|
-
}
|
|
840
|
-
async update(id, fields) {
|
|
841
|
-
const agent = await this.get(id);
|
|
842
|
-
if (fields.name !== void 0) {
|
|
843
|
-
if (!fields.name.trim()) throw new InvalidArgumentsError("Agent name cannot be empty");
|
|
844
|
-
const existing = await this.agentStore.getByName(fields.name.trim());
|
|
845
|
-
if (existing && existing.id !== id) {
|
|
846
|
-
throw new InvalidArgumentsError(`Agent "${fields.name}" already exists`);
|
|
847
|
-
}
|
|
848
|
-
agent.name = fields.name.trim();
|
|
849
|
-
}
|
|
850
|
-
if (fields.role !== void 0) agent.role = fields.role || void 0;
|
|
851
|
-
if (fields.model !== void 0) agent.config.model = fields.model || void 0;
|
|
852
|
-
if (fields.approval_policy !== void 0) agent.config.approval_policy = fields.approval_policy;
|
|
853
|
-
await this.agentStore.save(agent);
|
|
854
|
-
return agent;
|
|
855
|
-
}
|
|
856
|
-
async disable(id) {
|
|
857
|
-
return this.setStatus(id, "disabled");
|
|
858
|
-
}
|
|
859
|
-
async enable(id) {
|
|
860
|
-
return this.setStatus(id, "idle");
|
|
861
|
-
}
|
|
862
|
-
async setAutonomous(id, enabled) {
|
|
863
|
-
const agent = await this.get(id);
|
|
864
|
-
agent.autonomous = enabled;
|
|
865
|
-
await this.agentStore.save(agent);
|
|
866
|
-
this.eventBus.emit({ type: "agent:autonomous_toggled", agentId: id, autonomous: enabled });
|
|
867
|
-
return agent;
|
|
868
|
-
}
|
|
869
|
-
async setStatus(id, status) {
|
|
870
|
-
const agent = await this.get(id);
|
|
871
|
-
agent.status = status;
|
|
872
|
-
await this.agentStore.save(agent);
|
|
873
|
-
return agent;
|
|
874
|
-
}
|
|
875
|
-
async updateStats(id, update) {
|
|
876
|
-
const agent = await this.get(id);
|
|
877
|
-
Object.assign(agent.stats, update);
|
|
878
|
-
await this.agentStore.save(agent);
|
|
879
|
-
return agent;
|
|
880
|
-
}
|
|
881
|
-
/**
|
|
882
|
-
* Find the best available agent for a task using scoring.
|
|
883
|
-
*
|
|
884
|
-
* Scoring:
|
|
885
|
-
* - Explicit assignee match = 100
|
|
886
|
-
* - Skill match with task labels = 50 per match
|
|
887
|
-
* - Role match with task labels = 30
|
|
888
|
-
* - Idle status bonus = 20
|
|
889
|
-
* - Success rate bonus = 0–10 (scaled by completed / total)
|
|
890
|
-
*/
|
|
891
|
-
async findBestAgent(task) {
|
|
892
|
-
const agents = await this.agentStore.list();
|
|
893
|
-
const available = agents.filter(
|
|
894
|
-
(a) => a.status === "idle"
|
|
895
|
-
);
|
|
896
|
-
if (available.length === 0) return null;
|
|
897
|
-
if (task.assignee) {
|
|
898
|
-
const assigned = agents.find((a) => a.id === task.assignee);
|
|
899
|
-
if (assigned && assigned.status === "idle") return assigned;
|
|
900
|
-
return null;
|
|
901
|
-
}
|
|
902
|
-
const scored = available.map((agent) => {
|
|
903
|
-
let score = 0;
|
|
904
|
-
if (task.labels?.length > 0 && agent.config.skills?.length) {
|
|
905
|
-
for (const label of task.labels) {
|
|
906
|
-
const lowerLabel = label.toLowerCase();
|
|
907
|
-
if (agent.config.skills.some((s) => s.toLowerCase() === lowerLabel)) {
|
|
908
|
-
score += 50;
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
if (task.labels?.length > 0 && agent.role) {
|
|
913
|
-
const lowerRole = agent.role.toLowerCase();
|
|
914
|
-
if (task.labels.some((l) => lowerRole.includes(l.toLowerCase()))) {
|
|
915
|
-
score += 30;
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
if (agent.status === "idle") {
|
|
919
|
-
score += 20;
|
|
920
|
-
}
|
|
921
|
-
const totalTasks = agent.stats.tasks_completed + agent.stats.tasks_failed;
|
|
922
|
-
if (totalTasks > 0) {
|
|
923
|
-
score += Math.round(agent.stats.tasks_completed / totalTasks * 10);
|
|
924
|
-
}
|
|
925
|
-
return { agent, score };
|
|
926
|
-
});
|
|
927
|
-
scored.sort((a, b) => b.score - a.score);
|
|
928
|
-
return scored[0]?.agent ?? null;
|
|
929
|
-
}
|
|
930
|
-
};
|
|
931
|
-
var RunService = class {
|
|
932
|
-
constructor(runStore, eventBus) {
|
|
933
|
-
this.runStore = runStore;
|
|
934
|
-
this.eventBus = eventBus;
|
|
935
|
-
}
|
|
936
|
-
async create(params) {
|
|
937
|
-
const run = {
|
|
938
|
-
id: `run_${nanoid(7)}`,
|
|
939
|
-
task_id: params.taskId,
|
|
940
|
-
agent_id: params.agentId,
|
|
941
|
-
attempt: params.attempt,
|
|
942
|
-
status: "preparing",
|
|
943
|
-
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
944
|
-
workspace_path: params.workspacePath,
|
|
945
|
-
prompt: params.prompt
|
|
946
|
-
};
|
|
947
|
-
await this.runStore.save(run);
|
|
948
|
-
return run;
|
|
949
|
-
}
|
|
950
|
-
async get(id) {
|
|
951
|
-
return this.runStore.get(id);
|
|
952
|
-
}
|
|
953
|
-
async start(id, pid) {
|
|
954
|
-
const run = await this.runStore.get(id);
|
|
955
|
-
if (!run) throw new Error(`Run not found: ${id}`);
|
|
956
|
-
run.status = "running";
|
|
957
|
-
run.pid = pid;
|
|
958
|
-
await this.runStore.save(run);
|
|
959
|
-
this.eventBus.emit({
|
|
960
|
-
type: "agent:started",
|
|
961
|
-
agentId: run.agent_id,
|
|
962
|
-
taskId: run.task_id,
|
|
963
|
-
runId: id
|
|
964
|
-
});
|
|
965
|
-
return run;
|
|
966
|
-
}
|
|
967
|
-
async finish(id, status, tokens, error) {
|
|
968
|
-
const run = await this.runStore.get(id);
|
|
969
|
-
if (!run) throw new Error(`Run not found: ${id}`);
|
|
970
|
-
run.status = status;
|
|
971
|
-
run.finished_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
972
|
-
run.tokens = tokens;
|
|
973
|
-
run.error = error;
|
|
974
|
-
await this.runStore.save(run);
|
|
975
|
-
this.eventBus.emit({
|
|
976
|
-
type: "agent:completed",
|
|
977
|
-
runId: id,
|
|
978
|
-
agentId: run.agent_id,
|
|
979
|
-
success: status === "succeeded"
|
|
980
|
-
});
|
|
981
|
-
return run;
|
|
982
|
-
}
|
|
983
|
-
async appendEvent(runId, event) {
|
|
984
|
-
await this.runStore.appendEvent(runId, event);
|
|
985
|
-
}
|
|
986
|
-
async listAll() {
|
|
987
|
-
return this.runStore.listAll();
|
|
988
|
-
}
|
|
989
|
-
async listForTask(taskId) {
|
|
990
|
-
return this.runStore.listForTask(taskId);
|
|
991
|
-
}
|
|
992
|
-
async listForAgent(agentId) {
|
|
993
|
-
return this.runStore.listForAgent(agentId);
|
|
994
|
-
}
|
|
995
|
-
async readEvents(runId) {
|
|
996
|
-
return this.runStore.readEvents(runId);
|
|
997
|
-
}
|
|
998
|
-
async readEventsTail(runId, count) {
|
|
999
|
-
return this.runStore.readEventsTail(runId, count);
|
|
1000
|
-
}
|
|
1001
|
-
/**
|
|
1002
|
-
* Get error and last N lines of output from the most recent failed run for a task.
|
|
1003
|
-
* Used to provide retry context so agents can learn from previous failures.
|
|
1004
|
-
*/
|
|
1005
|
-
async getLastFailedRunContext(taskId, maxOutputLines = 50) {
|
|
1006
|
-
const runs = await this.runStore.listForTask(taskId);
|
|
1007
|
-
const failedRun = runs.filter((r) => r.status === "failed").sort((a, b) => (b.finished_at ?? "").localeCompare(a.finished_at ?? ""))[0];
|
|
1008
|
-
if (!failedRun) return null;
|
|
1009
|
-
const error = failedRun.error ?? "Unknown error";
|
|
1010
|
-
let output = "";
|
|
1011
|
-
try {
|
|
1012
|
-
const events = await this.runStore.readEventsTail(failedRun.id, maxOutputLines * 2);
|
|
1013
|
-
const outputLines = events.filter((e) => e.type === "agent_output" || e.type === "error").map((e) => typeof e.data === "string" ? e.data : JSON.stringify(e.data)).join("\n").split("\n");
|
|
1014
|
-
output = outputLines.slice(-maxOutputLines).join("\n");
|
|
1015
|
-
} catch {
|
|
1016
|
-
}
|
|
1017
|
-
return { error, output };
|
|
1018
|
-
}
|
|
1019
|
-
};
|
|
1020
|
-
|
|
1021
|
-
// src/domain/message.ts
|
|
1022
|
-
var MAX_MESSAGE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1023
|
-
var DEFAULT_MESSAGE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1024
|
-
|
|
1025
|
-
// src/application/message-service.ts
|
|
1026
|
-
var MessageService = class {
|
|
1027
|
-
constructor(messageStore, agentStore, teamStore, eventBus) {
|
|
1028
|
-
this.messageStore = messageStore;
|
|
1029
|
-
this.agentStore = agentStore;
|
|
1030
|
-
this.teamStore = teamStore;
|
|
1031
|
-
this.eventBus = eventBus;
|
|
1032
|
-
}
|
|
1033
|
-
/**
|
|
1034
|
-
* Send a message. For broadcast, creates one message per recipient agent.
|
|
1035
|
-
* For 'lead' channel, resolves team lead and sends direct.
|
|
1036
|
-
*/
|
|
1037
|
-
async send(input) {
|
|
1038
|
-
if (!input.body.trim()) throw new InvalidArgumentsError("Message body is required");
|
|
1039
|
-
const ttlMs = input.ttl_ms ?? DEFAULT_MESSAGE_TTL_MS;
|
|
1040
|
-
if (ttlMs <= 0 || ttlMs > MAX_MESSAGE_TTL_MS) {
|
|
1041
|
-
throw new InvalidArgumentsError(`TTL must be between 1ms and ${MAX_MESSAGE_TTL_MS}ms`);
|
|
1042
|
-
}
|
|
1043
|
-
const sender = await this.agentStore.get(input.from_agent_id);
|
|
1044
|
-
if (!sender && input.from_agent_id !== "cli") {
|
|
1045
|
-
throw new InvalidArgumentsError(`Sender agent not found: ${input.from_agent_id}`);
|
|
1046
|
-
}
|
|
1047
|
-
const now = /* @__PURE__ */ new Date();
|
|
1048
|
-
const baseMessage = {
|
|
1049
|
-
channel: input.channel,
|
|
1050
|
-
from_agent_id: input.from_agent_id,
|
|
1051
|
-
subject: (input.subject || "(no subject)").slice(0, 200),
|
|
1052
|
-
body: input.body.slice(0, 4e3),
|
|
1053
|
-
created_at: now.toISOString(),
|
|
1054
|
-
expires_at: new Date(now.getTime() + ttlMs).toISOString(),
|
|
1055
|
-
status: "pending",
|
|
1056
|
-
team_id: input.team_id,
|
|
1057
|
-
reply_to: input.reply_to
|
|
1058
|
-
};
|
|
1059
|
-
const messages = [];
|
|
1060
|
-
if (input.channel === "broadcast") {
|
|
1061
|
-
let agents = await this.agentStore.list();
|
|
1062
|
-
if (input.team_id) {
|
|
1063
|
-
const team = await this.teamStore.get(input.team_id);
|
|
1064
|
-
if (team) {
|
|
1065
|
-
const memberIds = new Set(team.members.map((m) => m.agent_id));
|
|
1066
|
-
agents = agents.filter((a) => memberIds.has(a.id));
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
const recipients = agents.filter((a) => a.id !== input.from_agent_id && a.status !== "disabled");
|
|
1070
|
-
const broadcastMsgs = recipients.map((agent) => ({
|
|
1071
|
-
...baseMessage,
|
|
1072
|
-
id: `msg_${nanoid(7)}`,
|
|
1073
|
-
to_agent_id: agent.id
|
|
1074
|
-
}));
|
|
1075
|
-
await Promise.all(broadcastMsgs.map((msg) => this.messageStore.save(msg)));
|
|
1076
|
-
for (const msg of broadcastMsgs) {
|
|
1077
|
-
messages.push(msg);
|
|
1078
|
-
this.emitSent(msg);
|
|
1079
|
-
}
|
|
1080
|
-
} else if (input.channel === "lead") {
|
|
1081
|
-
if (!input.team_id) throw new InvalidArgumentsError("team_id is required for lead channel");
|
|
1082
|
-
const team = await this.teamStore.get(input.team_id);
|
|
1083
|
-
if (!team) throw new InvalidArgumentsError(`Team not found: ${input.team_id}`);
|
|
1084
|
-
const msg = {
|
|
1085
|
-
...baseMessage,
|
|
1086
|
-
id: `msg_${nanoid(7)}`,
|
|
1087
|
-
to_agent_id: team.lead_agent_id
|
|
1088
|
-
};
|
|
1089
|
-
await this.messageStore.save(msg);
|
|
1090
|
-
messages.push(msg);
|
|
1091
|
-
this.emitSent(msg);
|
|
1092
|
-
} else {
|
|
1093
|
-
if (!input.to_agent_id) throw new InvalidArgumentsError("to_agent_id is required for direct messages");
|
|
1094
|
-
const recipient = await this.agentStore.get(input.to_agent_id);
|
|
1095
|
-
if (!recipient) throw new InvalidArgumentsError(`Recipient agent not found: ${input.to_agent_id}`);
|
|
1096
|
-
const msg = {
|
|
1097
|
-
...baseMessage,
|
|
1098
|
-
id: `msg_${nanoid(7)}`,
|
|
1099
|
-
to_agent_id: input.to_agent_id
|
|
1100
|
-
};
|
|
1101
|
-
await this.messageStore.save(msg);
|
|
1102
|
-
messages.push(msg);
|
|
1103
|
-
this.emitSent(msg);
|
|
1104
|
-
}
|
|
1105
|
-
return messages;
|
|
1106
|
-
}
|
|
1107
|
-
/**
|
|
1108
|
-
* Drain mailbox: fetch pending messages for an agent and mark them delivered.
|
|
1109
|
-
* Called by the orchestrator during dispatchTask.
|
|
1110
|
-
*/
|
|
1111
|
-
async drainMailbox(agentId, taskId) {
|
|
1112
|
-
const pending = await this.messageStore.listPending(agentId);
|
|
1113
|
-
await Promise.all(pending.map((msg) => this.messageStore.markDelivered(msg.id)));
|
|
1114
|
-
for (const msg of pending) {
|
|
1115
|
-
this.eventBus.emit({
|
|
1116
|
-
type: "message:delivered",
|
|
1117
|
-
messageId: msg.id,
|
|
1118
|
-
toAgentId: agentId,
|
|
1119
|
-
taskId
|
|
1120
|
-
});
|
|
1121
|
-
}
|
|
1122
|
-
return pending;
|
|
1123
|
-
}
|
|
1124
|
-
async listAll() {
|
|
1125
|
-
return this.messageStore.list();
|
|
1126
|
-
}
|
|
1127
|
-
async listPendingForAgent(agentId) {
|
|
1128
|
-
return this.messageStore.listPending(agentId);
|
|
1129
|
-
}
|
|
1130
|
-
async listForAgent(agentId) {
|
|
1131
|
-
const all = await this.messageStore.list();
|
|
1132
|
-
return all.filter((m) => m.to_agent_id === agentId || m.from_agent_id === agentId);
|
|
1133
|
-
}
|
|
1134
|
-
async purgeExpired() {
|
|
1135
|
-
return this.messageStore.purgeExpired();
|
|
1136
|
-
}
|
|
1137
|
-
emitSent(msg) {
|
|
1138
|
-
this.eventBus.emit({
|
|
1139
|
-
type: "message:sent",
|
|
1140
|
-
messageId: msg.id,
|
|
1141
|
-
fromAgentId: msg.from_agent_id,
|
|
1142
|
-
toAgentId: msg.to_agent_id,
|
|
1143
|
-
channel: msg.channel
|
|
1144
|
-
});
|
|
1145
|
-
}
|
|
1146
|
-
};
|
|
1147
|
-
var VALID_TRANSITIONS = {
|
|
1148
|
-
active: ["paused", "achieved", "abandoned"],
|
|
1149
|
-
paused: ["active", "abandoned"],
|
|
1150
|
-
achieved: [],
|
|
1151
|
-
abandoned: []
|
|
1152
|
-
};
|
|
1153
|
-
var GoalService = class {
|
|
1154
|
-
constructor(goalStore, eventBus, agentService, taskService) {
|
|
1155
|
-
this.goalStore = goalStore;
|
|
1156
|
-
this.eventBus = eventBus;
|
|
1157
|
-
this.agentService = agentService;
|
|
1158
|
-
this.taskService = taskService;
|
|
1159
|
-
}
|
|
1160
|
-
async create(input) {
|
|
1161
|
-
if (!input.title.trim()) {
|
|
1162
|
-
throw new InvalidArgumentsError("Goal title is required");
|
|
1163
|
-
}
|
|
1164
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1165
|
-
const goal = {
|
|
1166
|
-
id: `goal_${nanoid(7)}`,
|
|
1167
|
-
title: input.title.trim(),
|
|
1168
|
-
description: input.description?.trim() ?? "",
|
|
1169
|
-
status: "active",
|
|
1170
|
-
assignee: input.assignee,
|
|
1171
|
-
created_at: now,
|
|
1172
|
-
updated_at: now
|
|
1173
|
-
};
|
|
1174
|
-
await this.goalStore.save(goal);
|
|
1175
|
-
this.eventBus.emit({ type: "goal:created", goalId: goal.id, title: goal.title });
|
|
1176
|
-
if (goal.assignee) {
|
|
1177
|
-
await this.enableAutonomous(goal.assignee);
|
|
1178
|
-
}
|
|
1179
|
-
return goal;
|
|
1180
|
-
}
|
|
1181
|
-
async list(filter) {
|
|
1182
|
-
return this.goalStore.list(filter);
|
|
1183
|
-
}
|
|
1184
|
-
async get(id) {
|
|
1185
|
-
const goal = await this.goalStore.get(id);
|
|
1186
|
-
if (!goal) throw new GoalNotFoundError(id);
|
|
1187
|
-
return goal;
|
|
1188
|
-
}
|
|
1189
|
-
async updateStatus(id, newStatus) {
|
|
1190
|
-
const goal = await this.get(id);
|
|
1191
|
-
const oldStatus = goal.status;
|
|
1192
|
-
if (!VALID_TRANSITIONS[oldStatus].includes(newStatus)) {
|
|
1193
|
-
throw new InvalidArgumentsError(
|
|
1194
|
-
`Cannot transition goal from '${oldStatus}' to '${newStatus}'`
|
|
1195
|
-
);
|
|
1196
|
-
}
|
|
1197
|
-
goal.status = newStatus;
|
|
1198
|
-
goal.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1199
|
-
await this.goalStore.save(goal);
|
|
1200
|
-
this.eventBus.emit({ type: "goal:status_changed", goalId: id, from: oldStatus, to: newStatus });
|
|
1201
|
-
if (goal.assignee) {
|
|
1202
|
-
if (newStatus === "paused") {
|
|
1203
|
-
await this.maybeDisableAutonomous(goal.assignee);
|
|
1204
|
-
await this.cancelPendingAutonomousTasks(goal.assignee);
|
|
1205
|
-
} else if (newStatus === "active" && oldStatus === "paused") {
|
|
1206
|
-
await this.enableAutonomous(goal.assignee);
|
|
1207
|
-
} else if (isGoalTerminal(newStatus)) {
|
|
1208
|
-
await this.maybeDisableAutonomous(goal.assignee);
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
return goal;
|
|
1212
|
-
}
|
|
1213
|
-
async update(id, fields) {
|
|
1214
|
-
const goal = await this.get(id);
|
|
1215
|
-
const oldAssignee = goal.assignee;
|
|
1216
|
-
if (fields.title !== void 0) {
|
|
1217
|
-
if (!fields.title.trim()) throw new InvalidArgumentsError("Goal title cannot be empty");
|
|
1218
|
-
goal.title = fields.title.trim();
|
|
1219
|
-
}
|
|
1220
|
-
if (fields.description !== void 0) goal.description = fields.description.trim();
|
|
1221
|
-
if (fields.assignee !== void 0) goal.assignee = fields.assignee || void 0;
|
|
1222
|
-
goal.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1223
|
-
await this.goalStore.save(goal);
|
|
1224
|
-
this.eventBus.emit({ type: "goal:updated", goalId: id });
|
|
1225
|
-
const newAssignee = goal.assignee;
|
|
1226
|
-
if (newAssignee !== oldAssignee) {
|
|
1227
|
-
const ops = [];
|
|
1228
|
-
if (newAssignee) ops.push(this.enableAutonomous(newAssignee));
|
|
1229
|
-
if (oldAssignee) ops.push(this.maybeDisableAutonomous(oldAssignee));
|
|
1230
|
-
await Promise.all(ops);
|
|
1231
|
-
}
|
|
1232
|
-
return goal;
|
|
1233
|
-
}
|
|
1234
|
-
async delete(id) {
|
|
1235
|
-
const goal = await this.get(id);
|
|
1236
|
-
const { assignee } = goal;
|
|
1237
|
-
await this.goalStore.delete(id);
|
|
1238
|
-
this.eventBus.emit({ type: "goal:deleted", goalId: id });
|
|
1239
|
-
if (assignee) {
|
|
1240
|
-
await this.maybeDisableAutonomous(assignee);
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
/** Enable autonomous mode on an agent. */
|
|
1244
|
-
async enableAutonomous(agentId) {
|
|
1245
|
-
if (!this.agentService) return;
|
|
1246
|
-
try {
|
|
1247
|
-
await this.agentService.setAutonomous(agentId, true);
|
|
1248
|
-
} catch {
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
/** Check if an agent has at least one active goal. */
|
|
1252
|
-
async hasActiveGoalsForAgent(agentId) {
|
|
1253
|
-
const activeGoals = await this.goalStore.list({ status: "active" });
|
|
1254
|
-
return activeGoals.some((g) => g.assignee === agentId);
|
|
1255
|
-
}
|
|
1256
|
-
/** Cancel dispatchable (todo/retrying) autonomous tasks assigned to the agent. */
|
|
1257
|
-
async cancelPendingAutonomousTasks(agentId) {
|
|
1258
|
-
if (!this.taskService) return;
|
|
1259
|
-
try {
|
|
1260
|
-
const [todos, retrying] = await Promise.all([
|
|
1261
|
-
this.taskService.list({ status: "todo" }),
|
|
1262
|
-
this.taskService.list({ status: "retrying" })
|
|
1263
|
-
]);
|
|
1264
|
-
const pending = [...todos, ...retrying].filter(
|
|
1265
|
-
(t) => t.assignee === agentId && t.labels?.includes(AUTONOMOUS_LABEL)
|
|
1266
|
-
);
|
|
1267
|
-
await Promise.all(pending.map((t) => this.taskService.cancel(t.id).catch(() => {
|
|
1268
|
-
})));
|
|
1269
|
-
} catch {
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
/** Disable autonomous if agent has no other active goals. */
|
|
1273
|
-
async maybeDisableAutonomous(agentId) {
|
|
1274
|
-
if (!this.agentService) return;
|
|
1275
|
-
try {
|
|
1276
|
-
if (!await this.hasActiveGoalsForAgent(agentId)) {
|
|
1277
|
-
await this.agentService.setAutonomous(agentId, false);
|
|
1278
|
-
}
|
|
1279
|
-
} catch {
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
};
|
|
1283
|
-
|
|
1284
|
-
// src/domain/team.ts
|
|
1285
|
-
var DEFAULT_TEAM_CONFIG = {
|
|
1286
|
-
auto_claim: true,
|
|
1287
|
-
message_ttl_ms: 24 * 60 * 60 * 1e3
|
|
1288
|
-
};
|
|
1289
|
-
|
|
1290
|
-
// src/application/team-service.ts
|
|
1291
|
-
var TeamService = class {
|
|
1292
|
-
constructor(teamStore, agentStore, taskStore, eventBus) {
|
|
1293
|
-
this.teamStore = teamStore;
|
|
1294
|
-
this.agentStore = agentStore;
|
|
1295
|
-
this.taskStore = taskStore;
|
|
1296
|
-
this.eventBus = eventBus;
|
|
1297
|
-
}
|
|
1298
|
-
async create(input) {
|
|
1299
|
-
if (!input.name.trim()) throw new InvalidArgumentsError("Team name is required");
|
|
1300
|
-
const lead = await this.agentStore.get(input.lead_agent_id);
|
|
1301
|
-
if (!lead) throw new InvalidArgumentsError(`Lead agent not found: ${input.lead_agent_id}`);
|
|
1302
|
-
const existing = await this.teamStore.getByName(input.name.trim());
|
|
1303
|
-
if (existing) throw new InvalidArgumentsError(`Team "${input.name}" already exists`);
|
|
1304
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1305
|
-
const leadMember = { agent_id: input.lead_agent_id, role: "lead", joined_at: now };
|
|
1306
|
-
const additionalMembers = [];
|
|
1307
|
-
for (const agentId of input.member_agent_ids ?? []) {
|
|
1308
|
-
if (agentId === input.lead_agent_id) continue;
|
|
1309
|
-
const agent = await this.agentStore.get(agentId);
|
|
1310
|
-
if (!agent) throw new InvalidArgumentsError(`Member agent not found: ${agentId}`);
|
|
1311
|
-
additionalMembers.push({ agent_id: agentId, role: "member", joined_at: now });
|
|
1312
|
-
}
|
|
1313
|
-
const team = {
|
|
1314
|
-
id: `team_${nanoid(7)}`,
|
|
1315
|
-
name: input.name.trim(),
|
|
1316
|
-
description: input.description,
|
|
1317
|
-
status: "active",
|
|
1318
|
-
members: [leadMember, ...additionalMembers],
|
|
1319
|
-
task_pool: [],
|
|
1320
|
-
lead_agent_id: input.lead_agent_id,
|
|
1321
|
-
created_at: now,
|
|
1322
|
-
updated_at: now,
|
|
1323
|
-
config: { ...DEFAULT_TEAM_CONFIG, ...input.config ?? {} }
|
|
1324
|
-
};
|
|
1325
|
-
await this.teamStore.save(team);
|
|
1326
|
-
this.eventBus.emit({ type: "team:created", teamId: team.id, name: team.name, leadAgentId: team.lead_agent_id });
|
|
1327
|
-
for (const member of additionalMembers) {
|
|
1328
|
-
this.eventBus.emit({ type: "team:member_joined", teamId: team.id, agentId: member.agent_id });
|
|
1329
|
-
}
|
|
1330
|
-
return team;
|
|
1331
|
-
}
|
|
1332
|
-
async get(id) {
|
|
1333
|
-
const team = await this.teamStore.get(id);
|
|
1334
|
-
if (!team) throw new TeamNotFoundError(id);
|
|
1335
|
-
return team;
|
|
1336
|
-
}
|
|
1337
|
-
async list() {
|
|
1338
|
-
return this.teamStore.list();
|
|
1339
|
-
}
|
|
1340
|
-
async join(teamId, agentId) {
|
|
1341
|
-
const team = await this.get(teamId);
|
|
1342
|
-
if (team.members.some((m) => m.agent_id === agentId)) {
|
|
1343
|
-
throw new InvalidArgumentsError(`Agent ${agentId} is already a member of team ${teamId}`);
|
|
1344
|
-
}
|
|
1345
|
-
const agent = await this.agentStore.get(agentId);
|
|
1346
|
-
if (!agent) throw new InvalidArgumentsError(`Agent not found: ${agentId}`);
|
|
1347
|
-
team.members.push({ agent_id: agentId, role: "member", joined_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1348
|
-
team.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1349
|
-
await this.teamStore.save(team);
|
|
1350
|
-
this.eventBus.emit({ type: "team:member_joined", teamId, agentId });
|
|
1351
|
-
return team;
|
|
1352
|
-
}
|
|
1353
|
-
async leave(teamId, agentId) {
|
|
1354
|
-
const team = await this.get(teamId);
|
|
1355
|
-
if (agentId === team.lead_agent_id) {
|
|
1356
|
-
throw new InvalidArgumentsError("Lead cannot leave team. Disband the team or transfer lead first.");
|
|
1357
|
-
}
|
|
1358
|
-
team.members = team.members.filter((m) => m.agent_id !== agentId);
|
|
1359
|
-
team.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1360
|
-
await this.teamStore.save(team);
|
|
1361
|
-
this.eventBus.emit({ type: "team:member_left", teamId, agentId });
|
|
1362
|
-
return team;
|
|
1363
|
-
}
|
|
1364
|
-
async addTask(teamId, taskId) {
|
|
1365
|
-
const team = await this.get(teamId);
|
|
1366
|
-
const task = await this.taskStore.get(taskId);
|
|
1367
|
-
if (!task) throw new InvalidArgumentsError(`Task not found: ${taskId}`);
|
|
1368
|
-
if (!team.task_pool.includes(taskId)) {
|
|
1369
|
-
team.task_pool.push(taskId);
|
|
1370
|
-
team.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1371
|
-
await this.teamStore.save(team);
|
|
1372
|
-
this.eventBus.emit({ type: "team:task_added", teamId, taskId });
|
|
1373
|
-
}
|
|
1374
|
-
return team;
|
|
1375
|
-
}
|
|
1376
|
-
async removeTask(teamId, taskId) {
|
|
1377
|
-
const team = await this.get(teamId);
|
|
1378
|
-
team.task_pool = team.task_pool.filter((id) => id !== taskId);
|
|
1379
|
-
team.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1380
|
-
await this.teamStore.save(team);
|
|
1381
|
-
return team;
|
|
1382
|
-
}
|
|
1383
|
-
async setLead(teamId, agentId) {
|
|
1384
|
-
const team = await this.get(teamId);
|
|
1385
|
-
const member = team.members.find((m) => m.agent_id === agentId);
|
|
1386
|
-
if (!member) throw new InvalidArgumentsError(`Agent ${agentId} is not a member of team ${teamId}`);
|
|
1387
|
-
const currentLead = team.members.find((m) => m.agent_id === team.lead_agent_id);
|
|
1388
|
-
if (currentLead) currentLead.role = "member";
|
|
1389
|
-
member.role = "lead";
|
|
1390
|
-
team.lead_agent_id = agentId;
|
|
1391
|
-
team.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1392
|
-
await this.teamStore.save(team);
|
|
1393
|
-
return team;
|
|
1394
|
-
}
|
|
1395
|
-
async disband(teamId) {
|
|
1396
|
-
const team = await this.get(teamId);
|
|
1397
|
-
team.status = "disbanded";
|
|
1398
|
-
team.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1399
|
-
await this.teamStore.save(team);
|
|
1400
|
-
this.eventBus.emit({ type: "team:disbanded", teamId });
|
|
1401
|
-
}
|
|
1402
|
-
/**
|
|
1403
|
-
* Find the team an agent belongs to (if any).
|
|
1404
|
-
*/
|
|
1405
|
-
async findTeamForAgent(agentId) {
|
|
1406
|
-
const teams = await this.teamStore.list();
|
|
1407
|
-
return teams.find((t) => t.status === "active" && t.members.some((m) => m.agent_id === agentId)) ?? null;
|
|
1408
|
-
}
|
|
1409
|
-
};
|
|
1410
|
-
|
|
1411
|
-
// src/container.ts
|
|
1412
|
-
async function buildLightContainer(context) {
|
|
1413
|
-
const paths = new Paths(context.projectRoot);
|
|
1414
|
-
await paths.requireInit();
|
|
1415
|
-
const configStore = new ConfigStore(paths);
|
|
1416
|
-
const globalConfigStore = new GlobalConfigStore();
|
|
1417
|
-
const [config, globalConfig] = await Promise.all([
|
|
1418
|
-
configStore.read(),
|
|
1419
|
-
globalConfigStore.read()
|
|
1420
|
-
]);
|
|
1421
|
-
const taskStore = new TaskStore(paths);
|
|
1422
|
-
const agentStore = new AgentStore(paths);
|
|
1423
|
-
const runStore = new RunStore(paths);
|
|
1424
|
-
const stateStore = new StateStore(paths);
|
|
1425
|
-
const contextStore = new ContextStore(paths);
|
|
1426
|
-
const messageStore = new MessageStore(paths);
|
|
1427
|
-
const goalStore = new GoalStore(paths);
|
|
1428
|
-
const teamStore = new TeamStore(paths);
|
|
1429
|
-
const eventBus = new EventBus();
|
|
1430
|
-
const taskService = new TaskService(taskStore, eventBus, config);
|
|
1431
|
-
const agentService = new AgentService(agentStore, stateStore, eventBus, config);
|
|
1432
|
-
const runService = new RunService(runStore, eventBus);
|
|
1433
|
-
const messageService = new MessageService(messageStore, agentStore, teamStore, eventBus);
|
|
1434
|
-
const goalService = new GoalService(goalStore, eventBus, agentService, taskService);
|
|
1435
|
-
const teamService = new TeamService(teamStore, agentStore, taskStore, eventBus);
|
|
1436
|
-
return {
|
|
1437
|
-
context,
|
|
1438
|
-
paths,
|
|
1439
|
-
config,
|
|
1440
|
-
taskStore,
|
|
1441
|
-
agentStore,
|
|
1442
|
-
runStore,
|
|
1443
|
-
stateStore,
|
|
1444
|
-
configStore,
|
|
1445
|
-
globalConfigStore,
|
|
1446
|
-
globalConfig,
|
|
1447
|
-
contextStore,
|
|
1448
|
-
messageStore,
|
|
1449
|
-
goalStore,
|
|
1450
|
-
teamStore,
|
|
1451
|
-
eventBus,
|
|
1452
|
-
taskService,
|
|
1453
|
-
agentService,
|
|
1454
|
-
runService,
|
|
1455
|
-
messageService,
|
|
1456
|
-
goalService,
|
|
1457
|
-
teamService
|
|
1458
|
-
};
|
|
1459
|
-
}
|
|
1460
|
-
async function buildFullContainer(context) {
|
|
1461
|
-
const light = await buildLightContainer(context);
|
|
1462
|
-
const [
|
|
1463
|
-
{ ProcessManager },
|
|
1464
|
-
{ AdapterRegistry },
|
|
1465
|
-
{ ClaudeAdapter },
|
|
1466
|
-
{ CodexAdapter },
|
|
1467
|
-
{ CursorAdapter },
|
|
1468
|
-
{ ShellAdapter },
|
|
1469
|
-
{ WorkspaceManager },
|
|
1470
|
-
{ LiquidTemplateEngine },
|
|
1471
|
-
{ Orchestrator },
|
|
1472
|
-
{ DoctorService }
|
|
1473
|
-
] = await Promise.all([
|
|
1474
|
-
import('./process-manager-HUVNAPQV.js'),
|
|
1475
|
-
import('./registry-PQWRVNF2.js'),
|
|
1476
|
-
import('./claude-GH6P2DC5.js'),
|
|
1477
|
-
import('./codex-U7LTJTX6.js'),
|
|
1478
|
-
import('./cursor-3DI5GKRF.js'),
|
|
1479
|
-
import('./shell-5ZNXFGXV.js'),
|
|
1480
|
-
import('./workspace-manager-47KI7B27.js'),
|
|
1481
|
-
import('./template-engine-3CDRZNMJ.js'),
|
|
1482
|
-
import('./orchestrator-VGYKSOZJ.js'),
|
|
1483
|
-
import('./doctor-service-A34DHPKI.js')
|
|
1484
|
-
]);
|
|
1485
|
-
const processManager = new ProcessManager();
|
|
1486
|
-
const templateEngine = new LiquidTemplateEngine();
|
|
1487
|
-
const workspaceManager = new WorkspaceManager(
|
|
1488
|
-
context.projectRoot,
|
|
1489
|
-
light.paths.root,
|
|
1490
|
-
processManager
|
|
1491
|
-
);
|
|
1492
|
-
const adapterRegistry = new AdapterRegistry();
|
|
1493
|
-
adapterRegistry.register(new ClaudeAdapter(processManager));
|
|
1494
|
-
adapterRegistry.register(new CodexAdapter(processManager));
|
|
1495
|
-
adapterRegistry.register(new CursorAdapter(processManager));
|
|
1496
|
-
adapterRegistry.register(new ShellAdapter(processManager));
|
|
1497
|
-
const doctorService = new DoctorService(adapterRegistry, processManager);
|
|
1498
|
-
const orchestrator = new Orchestrator({
|
|
1499
|
-
taskStore: light.taskStore,
|
|
1500
|
-
agentStore: light.agentStore,
|
|
1501
|
-
runStore: light.runStore,
|
|
1502
|
-
stateStore: light.stateStore,
|
|
1503
|
-
adapterRegistry,
|
|
1504
|
-
workspaceManager,
|
|
1505
|
-
templateEngine,
|
|
1506
|
-
processManager,
|
|
1507
|
-
eventBus: light.eventBus,
|
|
1508
|
-
taskService: light.taskService,
|
|
1509
|
-
agentService: light.agentService,
|
|
1510
|
-
runService: light.runService,
|
|
1511
|
-
contextStore: light.contextStore,
|
|
1512
|
-
messageService: light.messageService,
|
|
1513
|
-
goalStore: light.goalStore,
|
|
1514
|
-
config: light.config,
|
|
1515
|
-
projectRoot: context.projectRoot,
|
|
1516
|
-
lockPath: light.paths.lockPath
|
|
1517
|
-
});
|
|
1518
|
-
return {
|
|
1519
|
-
...light,
|
|
1520
|
-
processManager,
|
|
1521
|
-
adapterRegistry,
|
|
1522
|
-
workspaceManager,
|
|
1523
|
-
templateEngine,
|
|
1524
|
-
doctorService,
|
|
1525
|
-
orchestrator
|
|
1526
|
-
};
|
|
1527
|
-
}
|
|
1528
|
-
async function buildContainer(context) {
|
|
1529
|
-
return buildFullContainer(context);
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
export { buildContainer, buildFullContainer, buildLightContainer };
|