ework-daemon 0.4.56 → 0.4.58
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 +5 -2
- package/src/opencode.ts +30 -1
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -72,6 +72,7 @@ export const configSchema = z.object({
|
|
|
72
72
|
stuck: z.object({
|
|
73
73
|
thresholdMs: z.coerce.number().positive(),
|
|
74
74
|
maxNudges: z.coerce.number().int().nonnegative(),
|
|
75
|
+
maxRuntimeMs: z.coerce.number().positive(),
|
|
75
76
|
}).optional(),
|
|
76
77
|
file: z.object({
|
|
77
78
|
roots: z.array(z.string()).default([]),
|
|
@@ -170,9 +171,10 @@ export function loadConfig(): Config {
|
|
|
170
171
|
baseURL: process.env.COMPLETION_CHECK_BASE_URL ?? "",
|
|
171
172
|
model: process.env.COMPLETION_CHECK_MODEL ?? "",
|
|
172
173
|
} : undefined,
|
|
173
|
-
stuck: process.env.DAEMON_STUCK_THRESHOLD_MS || process.env.DAEMON_MAX_STUCK_NUDGES ? {
|
|
174
|
+
stuck: process.env.DAEMON_STUCK_THRESHOLD_MS || process.env.DAEMON_MAX_STUCK_NUDGES || process.env.DAEMON_STUCK_MAX_RUNTIME_MS ? {
|
|
174
175
|
thresholdMs: Number(process.env.DAEMON_STUCK_THRESHOLD_MS) || 30 * 60 * 1000,
|
|
175
176
|
maxNudges: Number(process.env.DAEMON_MAX_STUCK_NUDGES) || 1,
|
|
177
|
+
maxRuntimeMs: Number(process.env.DAEMON_STUCK_MAX_RUNTIME_MS) || 3 * 60 * 60 * 1000,
|
|
176
178
|
} : undefined,
|
|
177
179
|
childEnvDeny: (process.env.WORK_CHILD_ENV_DENY ?? "").split(",").map((s) => s.trim()).filter(Boolean),
|
|
178
180
|
});
|
|
@@ -217,9 +219,10 @@ export function loadConfig(): Config {
|
|
|
217
219
|
baseURL: process.env.COMPLETION_CHECK_BASE_URL ?? "",
|
|
218
220
|
model: process.env.COMPLETION_CHECK_MODEL ?? "",
|
|
219
221
|
} : undefined,
|
|
220
|
-
stuck: process.env.DAEMON_STUCK_THRESHOLD_MS || process.env.DAEMON_MAX_STUCK_NUDGES ? {
|
|
222
|
+
stuck: process.env.DAEMON_STUCK_THRESHOLD_MS || process.env.DAEMON_MAX_STUCK_NUDGES || process.env.DAEMON_STUCK_MAX_RUNTIME_MS ? {
|
|
221
223
|
thresholdMs: Number(process.env.DAEMON_STUCK_THRESHOLD_MS) || 30 * 60 * 1000,
|
|
222
224
|
maxNudges: Number(process.env.DAEMON_MAX_STUCK_NUDGES) || 1,
|
|
225
|
+
maxRuntimeMs: Number(process.env.DAEMON_STUCK_MAX_RUNTIME_MS) || 3 * 60 * 60 * 1000,
|
|
223
226
|
} : undefined,
|
|
224
227
|
file: {
|
|
225
228
|
roots: (process.env.WORK_FILE_ROOTS ?? "").split(":").filter(Boolean).length > 0
|
package/src/opencode.ts
CHANGED
|
@@ -332,6 +332,7 @@ export class Engine {
|
|
|
332
332
|
private startedAt = new Map<string, number>();
|
|
333
333
|
private progressCommentId = new Map<string, string>();
|
|
334
334
|
private pickupCommentId = new Map<string, string>();
|
|
335
|
+
private forwardCommentId = new Map<string, string>();
|
|
335
336
|
|
|
336
337
|
private nudgeRounds = new Map<string, number>();
|
|
337
338
|
private emptyResponseRounds = new Map<string, number>();
|
|
@@ -358,6 +359,7 @@ export class Engine {
|
|
|
358
359
|
private static MAX_NUDGE_ROUNDS = 1;
|
|
359
360
|
private static MAX_EMPTY_RESPONSE_ROUNDS = 1;
|
|
360
361
|
private static MAX_STUCK_NUDGE_ROUNDS = 1;
|
|
362
|
+
private static MAX_RUNTIME_MS = 3 * 60 * 60 * 1000;
|
|
361
363
|
private static OBSERVER_INTERVAL_MS = 5 * 60 * 1000;
|
|
362
364
|
private static STUCK_THRESHOLD_MS = 30 * 60 * 1000;
|
|
363
365
|
private static MAX_PROCESS_EXIT_NUDGE_ROUNDS = 1;
|
|
@@ -556,6 +558,10 @@ export class Engine {
|
|
|
556
558
|
return this.cfg.stuck?.maxNudges ?? Engine.MAX_STUCK_NUDGE_ROUNDS;
|
|
557
559
|
}
|
|
558
560
|
|
|
561
|
+
private get maxRuntimeMs(): number {
|
|
562
|
+
return this.cfg.stuck?.maxRuntimeMs ?? Engine.MAX_RUNTIME_MS;
|
|
563
|
+
}
|
|
564
|
+
|
|
559
565
|
private getTracker(type: string): IssueTracker {
|
|
560
566
|
const tracker = this.trackers.get(type);
|
|
561
567
|
if (!tracker) throw new Error(`Unknown tracker type: ${type}`);
|
|
@@ -894,7 +900,10 @@ export class Engine {
|
|
|
894
900
|
);
|
|
895
901
|
|
|
896
902
|
// Immediate ack
|
|
897
|
-
|
|
903
|
+
{
|
|
904
|
+
const c = await tracker.createComment(ref, `[system] ✓ Message forwarded to **${session.name}**${this.running.has(this.sessionKey(session, issue)) ? " (running)" : ""}.\n> session: ${this.sessionRef(session)} | workdir: ${this.workdirLink(workdir)}`);
|
|
905
|
+
if (!session.opencodeSessionId) this.forwardCommentId.set(this.sessionKey(session, issue), c.id);
|
|
906
|
+
}
|
|
898
907
|
|
|
899
908
|
await this.enqueueOrRun(session, issue, prompt, comment.id, model);
|
|
900
909
|
} else {
|
|
@@ -1230,10 +1239,17 @@ export class Engine {
|
|
|
1230
1239
|
const pickupId = this.pickupCommentId.get(k);
|
|
1231
1240
|
if (pickupId) {
|
|
1232
1241
|
this.pickupCommentId.delete(k);
|
|
1242
|
+
this.forwardCommentId.delete(k);
|
|
1233
1243
|
await tracker
|
|
1234
1244
|
.editComment(ref, pickupId, `[system] 🔄 **${session.name}** picked up this issue.\n> session: ${this.sessionRef(session)} | workdir: ${this.workdirLink(workdir)}`)
|
|
1235
1245
|
.catch((err) => log.error(`engine: failed to rewrite pickup comment for ${k}:`, (err as Error).message));
|
|
1236
1246
|
}
|
|
1247
|
+
const forwardId = this.forwardCommentId.get(k);
|
|
1248
|
+
if (forwardId) {
|
|
1249
|
+
this.forwardCommentId.delete(k);
|
|
1250
|
+
const body = `[system] ✓ Message forwarded to **${session.name}**${this.running.has(k) ? " (running)" : ""}.\n> session: ${this.sessionRef(session)} | workdir: ${this.workdirLink(workdir)}`;
|
|
1251
|
+
await tracker.editComment(ref, forwardId, body).catch(() => { /* cosmetic rewrite */ });
|
|
1252
|
+
}
|
|
1237
1253
|
}
|
|
1238
1254
|
},
|
|
1239
1255
|
},
|
|
@@ -1738,6 +1754,19 @@ export class Engine {
|
|
|
1738
1754
|
continue;
|
|
1739
1755
|
}
|
|
1740
1756
|
|
|
1757
|
+
// Process alive — cap total run time. Output-silence detection cannot
|
|
1758
|
+
// catch loops that keep emitting (observed: 6h of failing compress calls
|
|
1759
|
+
// every ~5s), so any single run is hard-stopped after maxRuntimeMs.
|
|
1760
|
+
const started = this.startedAt.get(k);
|
|
1761
|
+
if (started && Date.now() - started >= this.maxRuntimeMs) {
|
|
1762
|
+
const hrs = (this.maxRuntimeMs / 3600000).toFixed(1);
|
|
1763
|
+
log.warn(`engine: run exceeded max runtime (${hrs}h) on ${k}, stopping`);
|
|
1764
|
+
await tracker.createComment(this.sessionToRef(session, issue), `[system] ⏹ **${session.name}** run exceeded ${hrs}h — stopped. Reply again on the issue to continue.`).catch(() => { /* best-effort */ });
|
|
1765
|
+
this.nudgeRounds.set(k, Engine.MAX_NUDGE_ROUNDS);
|
|
1766
|
+
this.forceStop(k);
|
|
1767
|
+
continue;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1741
1770
|
// Process alive — check stuck
|
|
1742
1771
|
if (lastTs && Date.now() - lastTs >= this.stuckThresholdMs) {
|
|
1743
1772
|
const minutes = Math.round((Date.now() - lastTs) / 60000);
|