ework-daemon 0.4.39 → 0.4.41
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/op.ts +1 -1
- package/src/opencode.ts +35 -5
package/package.json
CHANGED
package/src/op.ts
CHANGED
|
@@ -520,7 +520,7 @@ export class Store {
|
|
|
520
520
|
`SELECT m.* FROM {{messages}} m
|
|
521
521
|
INNER JOIN {{op_sessions}} s ON s.uid = m.session_id
|
|
522
522
|
INNER JOIN {{issues}} i ON i.uid = s.issue_id
|
|
523
|
-
WHERE i.owner_daemon_id = ? AND m.status IN ('pending', 'running')
|
|
523
|
+
WHERE i.owner_daemon_id = ? AND m.status IN ('pending', 'running', 'interrupted')
|
|
524
524
|
ORDER BY m.created_at ASC`,
|
|
525
525
|
[daemonId]
|
|
526
526
|
);
|
package/src/opencode.ts
CHANGED
|
@@ -283,6 +283,7 @@ export interface EngineOptions {
|
|
|
283
283
|
daemonId: number;
|
|
284
284
|
takeover?: TakeoverStrategy;
|
|
285
285
|
backend?: RuntimeBackend;
|
|
286
|
+
gateChecker?: (issue: Issue) => Promise<{ allowed: boolean; reason: string }>;
|
|
286
287
|
}
|
|
287
288
|
|
|
288
289
|
function createDefaultBackend(cfg: Config): RuntimeBackend {
|
|
@@ -299,6 +300,7 @@ export class Engine {
|
|
|
299
300
|
private readonly daemonId: number;
|
|
300
301
|
private readonly takeover: TakeoverStrategy;
|
|
301
302
|
private readonly backend: RuntimeBackend;
|
|
303
|
+
private gateChecker: (issue: Issue) => Promise<{ allowed: boolean; reason: string }>;
|
|
302
304
|
private heartbeatTimer?: ReturnType<typeof setInterval>;
|
|
303
305
|
private maxConcurrent: number;
|
|
304
306
|
private maxConcurrentExplicit: boolean;
|
|
@@ -350,6 +352,7 @@ export class Engine {
|
|
|
350
352
|
this.daemonId = opts.daemonId;
|
|
351
353
|
this.takeover = opts.takeover ?? new RecloneStrategy(cfg);
|
|
352
354
|
this.backend = opts.backend ?? createDefaultBackend(cfg);
|
|
355
|
+
this.gateChecker = opts.gateChecker ?? ((issue: Issue) => this.webGateAllows(issue));
|
|
353
356
|
this.maxConcurrent = cfg.work.maxConcurrent;
|
|
354
357
|
this.maxConcurrentExplicit = cfg.work.maxConcurrentExplicit;
|
|
355
358
|
this.startGlobalObserver();
|
|
@@ -1119,6 +1122,17 @@ export class Engine {
|
|
|
1119
1122
|
// ─── Process Manager ───
|
|
1120
1123
|
|
|
1121
1124
|
private async execProcess(k: string, session: OpSession, issue: Issue, msg: Message) {
|
|
1125
|
+
const gate = await this.gateChecker(issue);
|
|
1126
|
+
if (!gate.allowed) {
|
|
1127
|
+
log.info(`engine: execProcess blocked by web gate (${gate.reason}) for ${k}`);
|
|
1128
|
+
await this.store.updateMessageStatus(msg.id, "failed", `gate: ${gate.reason}`);
|
|
1129
|
+
this.running.delete(k);
|
|
1130
|
+
this.currentMessage.delete(k);
|
|
1131
|
+
this.currentModel.delete(k);
|
|
1132
|
+
void this.dequeueAfterGate(k, session, issue);
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1122
1136
|
const gen = (this.generation.get(k) ?? 0) + 1;
|
|
1123
1137
|
this.generation.set(k, gen);
|
|
1124
1138
|
|
|
@@ -1392,6 +1406,12 @@ export class Engine {
|
|
|
1392
1406
|
void this.drainGlobalPending();
|
|
1393
1407
|
}
|
|
1394
1408
|
|
|
1409
|
+
private async dequeueAfterGate(k: string, session: OpSession, issue: Issue): Promise<void> {
|
|
1410
|
+
const next = await this.store.getNextPendingMessage(session.id);
|
|
1411
|
+
if (!next) return;
|
|
1412
|
+
await this.dequeueOrIdle(k, session, issue, next);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1395
1415
|
private async drainGlobalPending(): Promise<void> {
|
|
1396
1416
|
const slotsAvailable = this.maxConcurrent - this.running.size;
|
|
1397
1417
|
if (slotsAvailable <= 0) return;
|
|
@@ -1574,7 +1594,7 @@ export class Engine {
|
|
|
1574
1594
|
|
|
1575
1595
|
for (const issue of ownedIssues) {
|
|
1576
1596
|
try {
|
|
1577
|
-
|
|
1597
|
+
const gate = await this.gateChecker(issue);
|
|
1578
1598
|
if (!gate.allowed) {
|
|
1579
1599
|
log.info(`engine: observer — web gate blocked ${issue.trackerScopeKey}#${issue.trackerIssueId} (${gate.reason})`);
|
|
1580
1600
|
continue;
|
|
@@ -1793,7 +1813,7 @@ export class Engine {
|
|
|
1793
1813
|
// Reset running messages to pending
|
|
1794
1814
|
for (const msg of stuck) {
|
|
1795
1815
|
if (msg.status === "running") {
|
|
1796
|
-
await this.store.updateMessageStatus(msg.id, "
|
|
1816
|
+
await this.store.updateMessageStatus(msg.id, "interrupted");
|
|
1797
1817
|
}
|
|
1798
1818
|
}
|
|
1799
1819
|
|
|
@@ -1811,16 +1831,26 @@ export class Engine {
|
|
|
1811
1831
|
const issue = await this.store.getIssue(session.issueId);
|
|
1812
1832
|
if (!issue || issue.state === "closed") continue;
|
|
1813
1833
|
|
|
1814
|
-
const gate = await this.
|
|
1834
|
+
const gate = await this.gateChecker(issue);
|
|
1815
1835
|
if (!gate.allowed) {
|
|
1816
|
-
log.info(`engine: recover — web gate blocked ${issue.trackerScopeKey}#${issue.trackerIssueId} (${gate.reason}),
|
|
1817
|
-
for (const m of msgs)
|
|
1836
|
+
log.info(`engine: recover — web gate blocked ${issue.trackerScopeKey}#${issue.trackerIssueId} (${gate.reason}), discarding queued (pending) messages`);
|
|
1837
|
+
for (const m of msgs) {
|
|
1838
|
+
if (m.status === "pending") {
|
|
1839
|
+
await this.store.updateMessageStatus(m.id, "failed", `web gate: ${gate.reason}`);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1818
1842
|
continue;
|
|
1819
1843
|
}
|
|
1820
1844
|
|
|
1821
1845
|
const k = this.sessionKey(session, issue);
|
|
1822
1846
|
if (this.running.has(k)) continue;
|
|
1823
1847
|
|
|
1848
|
+
for (const m of msgs) {
|
|
1849
|
+
if (m.status === "running") {
|
|
1850
|
+
await this.store.updateMessageStatus(m.id, "pending");
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1824
1854
|
const first = msgs.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())[0]!;
|
|
1825
1855
|
|
|
1826
1856
|
// Check if AI already replied before the crash — skip re-running if so
|