@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,12 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
import { DEFAULT_PROMPT_TEMPLATE, buildPromptContext } from './chunk-
|
|
3
|
-
import { resolveFailureStatus, isDispatchable, isTerminal, isBlocked, resolveCompletionStatus, calculateRetryDelay } from './chunk-33QNTNR6.js';
|
|
4
|
-
import { AUTONOMOUS_LABEL } from './chunk-PNE6LQRF.js';
|
|
5
|
-
import { LockConflictError, TaskAlreadyRunningError, NoAgentsError } from './chunk-O5AO5QIR.js';
|
|
1
|
+
import { LockConflictError, WorkspaceError, TaskAlreadyRunningError, NoAgentsError } from './chunk-IQXRQBUK.js';
|
|
2
|
+
import { AUTONOMOUS_LABEL, DEFAULT_PROMPT_TEMPLATE, buildPromptContext } from './chunk-B4JQM4NU.js';
|
|
6
3
|
import { dirname } from 'path';
|
|
7
4
|
import fs from 'fs/promises';
|
|
8
5
|
import { execFile } from 'child_process';
|
|
9
6
|
|
|
7
|
+
// src/domain/transitions.ts
|
|
8
|
+
var VALID_TRANSITIONS = {
|
|
9
|
+
todo: ["in_progress", "cancelled"],
|
|
10
|
+
in_progress: ["review", "retrying", "failed", "cancelled"],
|
|
11
|
+
retrying: ["in_progress", "failed", "cancelled"],
|
|
12
|
+
review: ["done", "todo", "cancelled"],
|
|
13
|
+
done: [],
|
|
14
|
+
failed: ["todo", "retrying"],
|
|
15
|
+
cancelled: ["todo"]
|
|
16
|
+
};
|
|
17
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
18
|
+
function canTransition(from, to) {
|
|
19
|
+
return VALID_TRANSITIONS[from].includes(to);
|
|
20
|
+
}
|
|
21
|
+
function isTerminal(status) {
|
|
22
|
+
return TERMINAL_STATUSES.has(status);
|
|
23
|
+
}
|
|
24
|
+
function isDispatchable(status) {
|
|
25
|
+
return status === "todo" || status === "retrying";
|
|
26
|
+
}
|
|
27
|
+
function isBlocked(task, allTasks) {
|
|
28
|
+
if (task.depends_on.length === 0) return false;
|
|
29
|
+
if (allTasks instanceof Map) {
|
|
30
|
+
return task.depends_on.some((depId) => {
|
|
31
|
+
const dep = allTasks.get(depId);
|
|
32
|
+
return !dep || dep.status !== "done";
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return task.depends_on.some((depId) => {
|
|
36
|
+
const dep = allTasks.find((t) => t.id === depId);
|
|
37
|
+
return !dep || dep.status !== "done";
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function resolveFailureStatus(task) {
|
|
41
|
+
if (task.attempts < task.max_attempts) {
|
|
42
|
+
return "retrying";
|
|
43
|
+
}
|
|
44
|
+
return "failed";
|
|
45
|
+
}
|
|
46
|
+
function resolveCompletionStatus(task, success, _autoApprove) {
|
|
47
|
+
{
|
|
48
|
+
return "review";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function calculateRetryDelay(attempt, baseDelayMs, maxDelayMs) {
|
|
52
|
+
const delay = baseDelayMs * Math.pow(2, attempt);
|
|
53
|
+
return Math.min(delay, maxDelayMs);
|
|
54
|
+
}
|
|
10
55
|
function scopesOverlap(a, b) {
|
|
11
56
|
if (!a?.length || !b?.length) return false;
|
|
12
57
|
for (const pa of a) {
|
|
@@ -16,6 +61,54 @@ function scopesOverlap(a, b) {
|
|
|
16
61
|
}
|
|
17
62
|
return false;
|
|
18
63
|
}
|
|
64
|
+
function computePatternInfo(pattern) {
|
|
65
|
+
const base = pattern.split("*")[0];
|
|
66
|
+
const isFile = !base.endsWith("/");
|
|
67
|
+
const dir = isFile ? dirname(base) : "";
|
|
68
|
+
return { raw: pattern, base, isFile, dir };
|
|
69
|
+
}
|
|
70
|
+
var ScopeIndex = class {
|
|
71
|
+
entries;
|
|
72
|
+
constructor(scopes) {
|
|
73
|
+
this.entries = [];
|
|
74
|
+
for (const scope of scopes) {
|
|
75
|
+
if (scope?.length) {
|
|
76
|
+
for (const p of scope) {
|
|
77
|
+
this.entries.push(computePatternInfo(p));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Returns true if the given scope overlaps with any pattern in the index. */
|
|
83
|
+
overlapsAny(scope) {
|
|
84
|
+
if (!scope?.length || this.entries.length === 0) return false;
|
|
85
|
+
for (const raw of scope) {
|
|
86
|
+
const info = computePatternInfo(raw);
|
|
87
|
+
for (const entry of this.entries) {
|
|
88
|
+
if (patternsOverlapInfo(info, entry)) return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
/** Add patterns to the index (e.g. from an approved candidate). */
|
|
94
|
+
add(scope) {
|
|
95
|
+
if (!scope?.length) return;
|
|
96
|
+
for (const p of scope) {
|
|
97
|
+
this.entries.push(computePatternInfo(p));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
get size() {
|
|
101
|
+
return this.entries.length;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
function patternsOverlapInfo(a, b) {
|
|
105
|
+
if (a.raw === b.raw) return true;
|
|
106
|
+
if (a.base.startsWith(b.base) || b.base.startsWith(a.base)) return true;
|
|
107
|
+
if (a.isFile && b.isFile) {
|
|
108
|
+
return a.dir === b.dir && a.dir !== ".";
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
19
112
|
function patternsOverlap(a, b) {
|
|
20
113
|
if (a === b) return true;
|
|
21
114
|
const aBase = a.split("*")[0];
|
|
@@ -28,29 +121,37 @@ function patternsOverlap(a, b) {
|
|
|
28
121
|
}
|
|
29
122
|
return false;
|
|
30
123
|
}
|
|
124
|
+
var acquireMutex = Promise.resolve();
|
|
31
125
|
async function acquireLock(lockPath) {
|
|
32
|
-
|
|
126
|
+
let release;
|
|
127
|
+
const gate = new Promise((r) => {
|
|
128
|
+
release = r;
|
|
129
|
+
});
|
|
130
|
+
const prev = acquireMutex;
|
|
131
|
+
acquireMutex = gate;
|
|
132
|
+
await prev;
|
|
133
|
+
try {
|
|
134
|
+
return await doAcquire(lockPath);
|
|
135
|
+
} finally {
|
|
136
|
+
release();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function doAcquire(lockPath) {
|
|
33
140
|
const existing = await readLockPid(lockPath);
|
|
34
141
|
if (existing !== null) {
|
|
35
142
|
if (isProcessAlive(existing)) {
|
|
36
143
|
return { acquired: false, pid: existing };
|
|
37
144
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
} catch {
|
|
41
|
-
}
|
|
145
|
+
await fs.unlink(lockPath).catch(() => {
|
|
146
|
+
});
|
|
42
147
|
}
|
|
43
148
|
try {
|
|
44
149
|
const fd = await fs.open(lockPath, "wx");
|
|
45
150
|
await fd.writeFile(String(process.pid), "utf-8");
|
|
46
151
|
await fd.close();
|
|
47
|
-
await fs.unlink(bakPath).catch(() => {
|
|
48
|
-
});
|
|
49
152
|
return { acquired: true, pid: process.pid };
|
|
50
153
|
} catch (err) {
|
|
51
154
|
if (err.code === "EEXIST") {
|
|
52
|
-
await fs.rename(bakPath, lockPath).catch(() => {
|
|
53
|
-
});
|
|
54
155
|
const pid = await readLockPid(lockPath);
|
|
55
156
|
return { acquired: false, pid: pid ?? void 0 };
|
|
56
157
|
}
|
|
@@ -87,7 +188,7 @@ var CachedTaskStore = class {
|
|
|
87
188
|
}
|
|
88
189
|
cache = /* @__PURE__ */ new Map();
|
|
89
190
|
async list(filter) {
|
|
90
|
-
const key = filter
|
|
191
|
+
const key = filter ? `${filter.status ?? ""}:${filter.goalId ?? ""}` : "__all__";
|
|
91
192
|
if (this.cache.has(key)) {
|
|
92
193
|
return this.cache.get(key);
|
|
93
194
|
}
|
|
@@ -115,6 +216,7 @@ var CachedAgentStore = class {
|
|
|
115
216
|
this.inner = inner;
|
|
116
217
|
}
|
|
117
218
|
listCache = null;
|
|
219
|
+
nameCache = /* @__PURE__ */ new Map();
|
|
118
220
|
async list() {
|
|
119
221
|
if (this.listCache) {
|
|
120
222
|
return this.listCache;
|
|
@@ -127,18 +229,26 @@ var CachedAgentStore = class {
|
|
|
127
229
|
return this.inner.get(id);
|
|
128
230
|
}
|
|
129
231
|
async getByName(name) {
|
|
130
|
-
|
|
232
|
+
if (this.nameCache.has(name)) {
|
|
233
|
+
return this.nameCache.get(name) ?? null;
|
|
234
|
+
}
|
|
235
|
+
const result = await this.inner.getByName(name);
|
|
236
|
+
this.nameCache.set(name, result);
|
|
237
|
+
return result;
|
|
131
238
|
}
|
|
132
239
|
async save(agent) {
|
|
133
240
|
await this.inner.save(agent);
|
|
134
241
|
this.listCache = null;
|
|
242
|
+
this.nameCache.clear();
|
|
135
243
|
}
|
|
136
244
|
async delete(id) {
|
|
137
245
|
await this.inner.delete(id);
|
|
138
246
|
this.listCache = null;
|
|
247
|
+
this.nameCache.clear();
|
|
139
248
|
}
|
|
140
249
|
invalidate() {
|
|
141
250
|
this.listCache = null;
|
|
251
|
+
this.nameCache.clear();
|
|
142
252
|
}
|
|
143
253
|
};
|
|
144
254
|
var CachedGoalStore = class {
|
|
@@ -285,21 +395,40 @@ var Orchestrator = class {
|
|
|
285
395
|
});
|
|
286
396
|
}
|
|
287
397
|
/**
|
|
288
|
-
* Run a single task by ID.
|
|
398
|
+
* Run a single task by ID.
|
|
399
|
+
* If watch mode is active (lock already held), dispatches inline via stateMutex.
|
|
400
|
+
* Otherwise acquires a temporary lock for the duration of the run.
|
|
289
401
|
*/
|
|
290
402
|
async runTask(taskId) {
|
|
291
|
-
|
|
292
|
-
await this.
|
|
293
|
-
|
|
294
|
-
}
|
|
403
|
+
if (this.lockAcquired) {
|
|
404
|
+
await this.freshDispatch(() => this.dispatchTask(taskId));
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
await this.withTemporaryLock(() => this.freshDispatch(() => this.dispatchTask(taskId)));
|
|
295
408
|
}
|
|
296
409
|
/**
|
|
297
|
-
* Run all dispatchable tasks.
|
|
410
|
+
* Run all dispatchable tasks.
|
|
411
|
+
* If watch mode is active (lock already held), dispatches inline via stateMutex.
|
|
412
|
+
* Otherwise acquires a temporary lock for the duration of the run.
|
|
298
413
|
*/
|
|
299
414
|
async runAll() {
|
|
300
|
-
|
|
415
|
+
if (this.lockAcquired) {
|
|
416
|
+
await this.freshDispatch(() => this.dispatchAll());
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
await this.withTemporaryLock(() => this.freshDispatch(() => this.dispatchAll()));
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Invalidate caches → loadState → run dispatch fn → saveState.
|
|
423
|
+
* Shared by runTask, runAll, and immediateDispatch.
|
|
424
|
+
*/
|
|
425
|
+
async freshDispatch(fn) {
|
|
426
|
+
await this.withStateLock(async () => {
|
|
427
|
+
this.cachedTaskStore.invalidate();
|
|
428
|
+
this.cachedAgentStore.invalidate();
|
|
301
429
|
await this.loadState();
|
|
302
|
-
await
|
|
430
|
+
await fn();
|
|
431
|
+
await this.saveState();
|
|
303
432
|
});
|
|
304
433
|
}
|
|
305
434
|
/**
|
|
@@ -427,7 +556,7 @@ var Orchestrator = class {
|
|
|
427
556
|
await this.deps.agentService.setStatus(entry.agent_id, "idle");
|
|
428
557
|
}
|
|
429
558
|
this.state.running = {};
|
|
430
|
-
this.state.claimed =
|
|
559
|
+
this.state.claimed = /* @__PURE__ */ new Set();
|
|
431
560
|
this.state.pid = void 0;
|
|
432
561
|
this.state.started_at = void 0;
|
|
433
562
|
await this.saveState();
|
|
@@ -539,13 +668,18 @@ var Orchestrator = class {
|
|
|
539
668
|
/**
|
|
540
669
|
* Schedule an immediate dispatch with 500ms debounce.
|
|
541
670
|
* Called on task:created to avoid waiting for the next 30s tick.
|
|
671
|
+
* Retries up to 10 times (5s) if a tick is in progress.
|
|
542
672
|
*/
|
|
543
|
-
scheduleImmediateDispatch() {
|
|
673
|
+
scheduleImmediateDispatch(retries = 0) {
|
|
544
674
|
if (this.shuttingDown) return;
|
|
545
675
|
if (this.immediateDispatchTimer) return;
|
|
546
676
|
this.immediateDispatchTimer = setTimeout(() => {
|
|
547
677
|
this.immediateDispatchTimer = null;
|
|
548
|
-
if (this.shuttingDown
|
|
678
|
+
if (this.shuttingDown) return;
|
|
679
|
+
if (this.tickInProgress) {
|
|
680
|
+
if (retries < 10) this.scheduleImmediateDispatch(retries + 1);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
549
683
|
this.immediateDispatch().catch((err) => {
|
|
550
684
|
this.deps.eventBus.emit({
|
|
551
685
|
type: "orchestrator:error",
|
|
@@ -562,14 +696,7 @@ var Orchestrator = class {
|
|
|
562
696
|
*/
|
|
563
697
|
async immediateDispatch() {
|
|
564
698
|
if (this.shuttingDown) return;
|
|
565
|
-
await this.
|
|
566
|
-
if (this.shuttingDown) return;
|
|
567
|
-
this.cachedTaskStore.invalidate();
|
|
568
|
-
this.cachedAgentStore.invalidate();
|
|
569
|
-
await this.loadState();
|
|
570
|
-
await this.dispatchAll();
|
|
571
|
-
await this.saveState();
|
|
572
|
-
});
|
|
699
|
+
await this.freshDispatch(() => this.shuttingDown ? Promise.resolve() : this.dispatchAll());
|
|
573
700
|
}
|
|
574
701
|
/**
|
|
575
702
|
* Reconcile: check PID liveness, detect stalls, process retry queue.
|
|
@@ -577,8 +704,13 @@ var Orchestrator = class {
|
|
|
577
704
|
async reconcile() {
|
|
578
705
|
const state = this.state;
|
|
579
706
|
const now = Date.now();
|
|
580
|
-
|
|
581
|
-
|
|
707
|
+
const runningEntries = Object.entries(state.running);
|
|
708
|
+
const runningTaskData = await Promise.all(
|
|
709
|
+
runningEntries.map(([taskId]) => this.deps.taskStore.get(taskId))
|
|
710
|
+
);
|
|
711
|
+
for (let i = 0; i < runningEntries.length; i++) {
|
|
712
|
+
const [taskId, entry] = runningEntries[i];
|
|
713
|
+
const taskData = runningTaskData[i];
|
|
582
714
|
if (!taskData || isTerminal(taskData.status)) {
|
|
583
715
|
this.abortControllers.delete(taskId);
|
|
584
716
|
delete state.running[taskId];
|
|
@@ -647,12 +779,16 @@ var Orchestrator = class {
|
|
|
647
779
|
});
|
|
648
780
|
}
|
|
649
781
|
}
|
|
650
|
-
|
|
651
|
-
|
|
782
|
+
const dueRetries = [];
|
|
783
|
+
state.retry_queue = state.retry_queue.filter((retry) => {
|
|
652
784
|
if (now >= new Date(retry.due_at).getTime()) {
|
|
653
|
-
|
|
654
|
-
|
|
785
|
+
dueRetries.push(retry.task_id);
|
|
786
|
+
return false;
|
|
655
787
|
}
|
|
788
|
+
return true;
|
|
789
|
+
});
|
|
790
|
+
for (const taskId of dueRetries) {
|
|
791
|
+
await this.dispatchTask(taskId);
|
|
656
792
|
}
|
|
657
793
|
await this.saveState();
|
|
658
794
|
}
|
|
@@ -697,7 +833,8 @@ Agent role: ${role}` : `Autonomous work cycle. Agent role: ${role}`;
|
|
|
697
833
|
description,
|
|
698
834
|
assignee: agent.id,
|
|
699
835
|
labels: [AUTONOMOUS_LABEL],
|
|
700
|
-
priority: 3
|
|
836
|
+
priority: 3,
|
|
837
|
+
goalId: goal?.id
|
|
701
838
|
});
|
|
702
839
|
anyCreated = true;
|
|
703
840
|
} catch (err) {
|
|
@@ -721,8 +858,9 @@ Agent role: ${role}` : `Autonomous work cycle. Agent role: ${role}`;
|
|
|
721
858
|
const availableSlots = maxConcurrent - currentRunning;
|
|
722
859
|
if (availableSlots <= 0) return;
|
|
723
860
|
const allTasks = await this.cachedTaskStore.list();
|
|
861
|
+
const taskMap = new Map(allTasks.map((t) => [t.id, t]));
|
|
724
862
|
const candidates = allTasks.filter(
|
|
725
|
-
(t) => isDispatchable(t.status) && !isBlocked(t,
|
|
863
|
+
(t) => isDispatchable(t.status) && !isBlocked(t, taskMap) && !state.running[t.id] && !state.claimed.has(t.id)
|
|
726
864
|
).sort((a, b) => {
|
|
727
865
|
const bTime = b.updated_at ?? "";
|
|
728
866
|
const aTime = a.updated_at ?? "";
|
|
@@ -730,31 +868,38 @@ Agent role: ${role}` : `Autonomous work cycle. Agent role: ${role}`;
|
|
|
730
868
|
}).slice(0, availableSlots);
|
|
731
869
|
const blockedIds = /* @__PURE__ */ new Set();
|
|
732
870
|
const inProgressScoped = allTasks.filter((t) => t.status === "in_progress" && t.scope?.length);
|
|
733
|
-
|
|
734
|
-
|
|
871
|
+
const scopeIndex = new ScopeIndex(inProgressScoped.map((t) => t.scope));
|
|
872
|
+
for (const candidate of candidates) {
|
|
735
873
|
if (!candidate.scope?.length) continue;
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
overlapping = true;
|
|
748
|
-
break;
|
|
749
|
-
}
|
|
874
|
+
if (scopeIndex.overlapsAny(candidate.scope)) {
|
|
875
|
+
const overlapper = inProgressScoped.find((t) => scopesOverlap(candidate.scope, t.scope));
|
|
876
|
+
this.deps.eventBus.emit({
|
|
877
|
+
type: "task:scope_overlap",
|
|
878
|
+
taskId: candidate.id,
|
|
879
|
+
overlappingTaskId: overlapper?.id ?? candidate.id,
|
|
880
|
+
patterns: candidate.scope
|
|
881
|
+
});
|
|
882
|
+
blockedIds.add(candidate.id);
|
|
883
|
+
} else {
|
|
884
|
+
scopeIndex.add(candidate.scope);
|
|
750
885
|
}
|
|
751
|
-
if (overlapping) blockedIds.add(candidate.id);
|
|
752
886
|
}
|
|
753
887
|
for (const task of candidates) {
|
|
754
888
|
if (blockedIds.has(task.id)) continue;
|
|
755
889
|
try {
|
|
756
890
|
await this.dispatchTask(task.id);
|
|
757
891
|
} catch (err) {
|
|
892
|
+
if (err instanceof WorkspaceError) {
|
|
893
|
+
try {
|
|
894
|
+
const t = await this.deps.taskStore.get(task.id);
|
|
895
|
+
if (t && !isTerminal(t.status)) {
|
|
896
|
+
t.status = "failed";
|
|
897
|
+
t.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
898
|
+
await this.deps.taskStore.save(t);
|
|
899
|
+
}
|
|
900
|
+
} catch {
|
|
901
|
+
}
|
|
902
|
+
}
|
|
758
903
|
this.deps.eventBus.emit({
|
|
759
904
|
type: "orchestrator:error",
|
|
760
905
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -774,13 +919,13 @@ Agent role: ${role}` : `Autonomous work cycle. Agent role: ${role}`;
|
|
|
774
919
|
throw new TaskAlreadyRunningError(taskId, entry.run_id, entry.agent_id);
|
|
775
920
|
}
|
|
776
921
|
const task = await this.deps.taskService.get(taskId);
|
|
777
|
-
state.claimed.
|
|
922
|
+
state.claimed.add(taskId);
|
|
778
923
|
await this.saveState();
|
|
779
924
|
try {
|
|
925
|
+
const allAgents = await this.cachedAgentStore.list();
|
|
780
926
|
const agent = await this.deps.agentService.findBestAgent(task);
|
|
781
927
|
if (!agent) {
|
|
782
|
-
|
|
783
|
-
if (allAgents2.length === 0) {
|
|
928
|
+
if (allAgents.length === 0) {
|
|
784
929
|
throw new NoAgentsError();
|
|
785
930
|
}
|
|
786
931
|
this.unclaim(taskId);
|
|
@@ -793,7 +938,6 @@ Agent role: ${role}` : `Autonomous work cycle. Agent role: ${role}`;
|
|
|
793
938
|
this.deps.config
|
|
794
939
|
);
|
|
795
940
|
const template = this.deps.config.prompt?.template ?? DEFAULT_PROMPT_TEMPLATE;
|
|
796
|
-
const allAgents = await this.cachedAgentStore.list();
|
|
797
941
|
const attempt = task.attempts + 1;
|
|
798
942
|
let retryContext;
|
|
799
943
|
if (attempt > 1) {
|
|
@@ -805,15 +949,35 @@ Agent role: ${role}` : `Autonomous work cycle. Agent role: ${role}`;
|
|
|
805
949
|
};
|
|
806
950
|
}
|
|
807
951
|
}
|
|
808
|
-
const
|
|
809
|
-
const pendingMessages =
|
|
952
|
+
const goalId = task.goalId;
|
|
953
|
+
const [sharedContext, pendingMessages, goalRaw] = await Promise.all([
|
|
954
|
+
this.deps.contextStore?.getAll(),
|
|
955
|
+
this.deps.messageService ? this.deps.messageService.drainMailbox(agent.id, task.id) : [],
|
|
956
|
+
goalId && this.cachedGoalStore ? this.cachedGoalStore.get(goalId).catch(() => null) : null
|
|
957
|
+
]);
|
|
958
|
+
let goalContext;
|
|
959
|
+
if (goalRaw) {
|
|
960
|
+
const allTasks = await this.cachedTaskStore.list();
|
|
961
|
+
const goalTasks = allTasks.filter((t) => t.goalId === goalId);
|
|
962
|
+
const progressEntry = await this.deps.contextStore?.get(`${goalId}-progress`);
|
|
963
|
+
const MAX_GOAL_TASK_NAMES = 30;
|
|
964
|
+
const taskNames = goalTasks.map((t) => `[${t.status}] ${t.title}`);
|
|
965
|
+
goalContext = {
|
|
966
|
+
id: goalRaw.id,
|
|
967
|
+
title: goalRaw.title,
|
|
968
|
+
description: goalRaw.description,
|
|
969
|
+
status: goalRaw.status,
|
|
970
|
+
task_names: taskNames.length > MAX_GOAL_TASK_NAMES ? [...taskNames.slice(0, MAX_GOAL_TASK_NAMES), `... and ${taskNames.length - MAX_GOAL_TASK_NAMES} more`] : taskNames,
|
|
971
|
+
progress: progressEntry?.value
|
|
972
|
+
};
|
|
973
|
+
}
|
|
810
974
|
const context = buildPromptContext(
|
|
811
975
|
task,
|
|
812
976
|
agent,
|
|
813
977
|
attempt,
|
|
814
978
|
workspacePath,
|
|
815
979
|
this.deps.config,
|
|
816
|
-
{ allAgents, retryContext, sharedContext, feedback: task.feedback, messages: pendingMessages.length ? pendingMessages : void 0 }
|
|
980
|
+
{ allAgents, retryContext, sharedContext, feedback: task.feedback, messages: pendingMessages.length ? pendingMessages : void 0, goal: goalContext }
|
|
817
981
|
);
|
|
818
982
|
const prompt = await this.deps.templateEngine.render(template, context);
|
|
819
983
|
const run = await this.deps.runService.create({
|
|
@@ -990,7 +1154,7 @@ Agent role: ${role}` : `Autonomous work cycle. Agent role: ${role}`;
|
|
|
990
1154
|
const agent = await this.deps.agentStore.get(agentId);
|
|
991
1155
|
const isAutonomousTask = task.labels?.includes(AUTONOMOUS_LABEL);
|
|
992
1156
|
const autoApprove = isAutonomousTask || agent?.config.approval_policy === "auto";
|
|
993
|
-
const newStatus = resolveCompletionStatus(
|
|
1157
|
+
const newStatus = resolveCompletionStatus();
|
|
994
1158
|
await this.deps.runService.finish(runId, "succeeded", tokens);
|
|
995
1159
|
const runningEntry = state.running[taskId];
|
|
996
1160
|
const successRuntimeMs = runningEntry ? Date.now() - new Date(runningEntry.started_at).getTime() : 0;
|
|
@@ -1223,8 +1387,7 @@ ${task.proof?.agent_summary ?? ""}`.slice(0, 2e3),
|
|
|
1223
1387
|
await this.saveState();
|
|
1224
1388
|
}
|
|
1225
1389
|
unclaim(taskId) {
|
|
1226
|
-
|
|
1227
|
-
if (idx !== -1) this.state.claimed.splice(idx, 1);
|
|
1390
|
+
this.state.claimed.delete(taskId);
|
|
1228
1391
|
}
|
|
1229
1392
|
/**
|
|
1230
1393
|
* Throw if this instance doesn't own the lock (read-only session).
|
|
@@ -1289,4 +1452,6 @@ function serializeEventData(data, maxLen) {
|
|
|
1289
1452
|
return str.length > maxLen ? str.slice(0, maxLen) + "\u2026" : str;
|
|
1290
1453
|
}
|
|
1291
1454
|
|
|
1292
|
-
export { Orchestrator };
|
|
1455
|
+
export { Orchestrator, canTransition, isBlocked, isDispatchable, isTerminal, resolveFailureStatus };
|
|
1456
|
+
//# sourceMappingURL=chunk-2UC4SVJB.js.map
|
|
1457
|
+
//# sourceMappingURL=chunk-2UC4SVJB.js.map
|