ework-daemon 0.4.24 → 0.4.26
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/package.json +1 -1
- package/src/config.ts +4 -1
- package/src/op.ts +8 -0
- package/src/opencode.ts +32 -2
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -36,6 +36,7 @@ export const configSchema = z.object({
|
|
|
36
36
|
}),
|
|
37
37
|
work: z.object({
|
|
38
38
|
capacity: z.coerce.number().int().positive().default(4),
|
|
39
|
+
maxConcurrent: z.coerce.number().int().positive().default(4),
|
|
39
40
|
heartbeatMs: z.coerce.number().int().positive().default(10_000),
|
|
40
41
|
leaseTtlMs: z.coerce.number().int().positive().default(60_000),
|
|
41
42
|
}),
|
|
@@ -85,8 +86,10 @@ const TEST_DEFAULTS = {
|
|
|
85
86
|
};
|
|
86
87
|
|
|
87
88
|
function readWorkSection() {
|
|
89
|
+
const capacity = process.env.WORK_DAEMON_CAPACITY ? Number(process.env.WORK_DAEMON_CAPACITY) : 4;
|
|
88
90
|
return {
|
|
89
|
-
capacity
|
|
91
|
+
capacity,
|
|
92
|
+
maxConcurrent: process.env.WORK_MAX_CONCURRENT ? Number(process.env.WORK_MAX_CONCURRENT) : capacity,
|
|
90
93
|
heartbeatMs: process.env.WORK_DAEMON_HEARTBEAT_MS ? Number(process.env.WORK_DAEMON_HEARTBEAT_MS) : 10_000,
|
|
91
94
|
leaseTtlMs: process.env.WORK_DAEMON_LEASE_TTL_MS ? Number(process.env.WORK_DAEMON_LEASE_TTL_MS) : 60_000,
|
|
92
95
|
};
|
package/src/op.ts
CHANGED
|
@@ -299,6 +299,14 @@ export class Store {
|
|
|
299
299
|
return row ? rowToMessage(row) : undefined;
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
+
async getGlobalPendingMessages(limit: number): Promise<Message[]> {
|
|
303
|
+
const rows = await getDB().all<MessageRow>(
|
|
304
|
+
"SELECT * FROM {{messages}} WHERE status = 'pending' ORDER BY created_at ASC LIMIT ?",
|
|
305
|
+
[limit]
|
|
306
|
+
);
|
|
307
|
+
return rows.map(rowToMessage);
|
|
308
|
+
}
|
|
309
|
+
|
|
302
310
|
async updateMessageStatus(id: string, status: Message["status"], error?: string): Promise<void> {
|
|
303
311
|
const row = await getDB().get<MessageRow>("SELECT * FROM {{messages}} WHERE uid = ?", [id]);
|
|
304
312
|
const attempts = row ? row.attempts + (status === "failed" ? 1 : 0) : 0;
|
package/src/opencode.ts
CHANGED
|
@@ -889,6 +889,11 @@ export class Engine {
|
|
|
889
889
|
return;
|
|
890
890
|
}
|
|
891
891
|
|
|
892
|
+
if (this.running.size >= this.cfg.work.maxConcurrent) {
|
|
893
|
+
log.info(`engine: concurrency limit reached (${this.running.size}/${this.cfg.work.maxConcurrent}), message ${msg.id.slice(0, 8)} queued for ${k}`);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
892
897
|
// Not running — execute directly
|
|
893
898
|
await this.executeMessage(k, session, issue, msg);
|
|
894
899
|
}
|
|
@@ -1267,13 +1272,38 @@ export class Engine {
|
|
|
1267
1272
|
if (nextMsg) {
|
|
1268
1273
|
const current = await this.store.getSession(session.id);
|
|
1269
1274
|
if (current && current.state !== "idle") {
|
|
1270
|
-
|
|
1271
|
-
|
|
1275
|
+
if (this.running.size >= this.cfg.work.maxConcurrent) {
|
|
1276
|
+
log.info(`engine: concurrency limit (${this.running.size}/${this.cfg.work.maxConcurrent}), keeping msg ${nextMsg.id.slice(0, 8)} pending for ${k}`);
|
|
1277
|
+
} else {
|
|
1278
|
+
await this.dequeueOrIdle(k, current, issue, nextMsg);
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1272
1281
|
}
|
|
1273
1282
|
}
|
|
1274
1283
|
|
|
1275
1284
|
this.clearRuntimeState(k);
|
|
1276
1285
|
await this.store.updateSession(session.id, { state: "idle" });
|
|
1286
|
+
|
|
1287
|
+
void this.drainGlobalPending();
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
private async drainGlobalPending(): Promise<void> {
|
|
1291
|
+
const slotsAvailable = this.cfg.work.maxConcurrent - this.running.size;
|
|
1292
|
+
if (slotsAvailable <= 0) return;
|
|
1293
|
+
const pending = await this.store.getGlobalPendingMessages(slotsAvailable);
|
|
1294
|
+
for (const msg of pending) {
|
|
1295
|
+
if (this.running.size >= this.cfg.work.maxConcurrent) break;
|
|
1296
|
+
const session = await this.store.getSession(msg.sessionId);
|
|
1297
|
+
if (!session || session.state === "running") continue;
|
|
1298
|
+
const issue = await this.store.getIssue(session.issueId);
|
|
1299
|
+
if (!issue || issue.state === "closed") continue;
|
|
1300
|
+
const k = this.sessionKey(session, issue);
|
|
1301
|
+
if (this.running.has(k)) continue;
|
|
1302
|
+
const won = await this.store.claimMessage(msg.id);
|
|
1303
|
+
if (!won) continue;
|
|
1304
|
+
log.info(`engine: drainGlobalPending picked up msg ${msg.id.slice(0, 8)} for ${k}`);
|
|
1305
|
+
await this.dequeueOrIdle(k, session, issue, msg);
|
|
1306
|
+
}
|
|
1277
1307
|
}
|
|
1278
1308
|
|
|
1279
1309
|
private async dequeueOrIdle(k: string, session: OpSession, issue: Issue, msg: Message) {
|