ework-daemon 0.4.20 → 0.4.22
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 +3 -0
- package/src/opencode.ts +30 -3
- package/src/server.ts +9 -0
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -64,6 +64,7 @@ export const configSchema = z.object({
|
|
|
64
64
|
maxLines: z.coerce.number().int().positive().default(2000),
|
|
65
65
|
maxBytes: z.coerce.number().int().positive().default(524288),
|
|
66
66
|
}).default({ roots: [], maxLines: 2000, maxBytes: 524288 }),
|
|
67
|
+
childEnvDeny: z.array(z.string()).default([]),
|
|
67
68
|
});
|
|
68
69
|
|
|
69
70
|
export type Config = z.infer<typeof configSchema>;
|
|
@@ -145,6 +146,7 @@ export function loadConfig(): Config {
|
|
|
145
146
|
thresholdMs: Number(process.env.DAEMON_STUCK_THRESHOLD_MS) || 30 * 60 * 1000,
|
|
146
147
|
maxNudges: Number(process.env.DAEMON_MAX_STUCK_NUDGES) || 1,
|
|
147
148
|
} : undefined,
|
|
149
|
+
childEnvDeny: (process.env.WORK_CHILD_ENV_DENY ?? "").split(",").map((s) => s.trim()).filter(Boolean),
|
|
148
150
|
});
|
|
149
151
|
}
|
|
150
152
|
|
|
@@ -188,5 +190,6 @@ export function loadConfig(): Config {
|
|
|
188
190
|
maxLines: Number(process.env.WORK_FILE_MAX_LINES) || 2000,
|
|
189
191
|
maxBytes: Number(process.env.WORK_FILE_MAX_BYTES) || 524288,
|
|
190
192
|
},
|
|
193
|
+
childEnvDeny: (process.env.WORK_CHILD_ENV_DENY ?? "").split(",").map((s) => s.trim()).filter(Boolean),
|
|
191
194
|
});
|
|
192
195
|
}
|
package/src/opencode.ts
CHANGED
|
@@ -308,6 +308,7 @@ export class Engine {
|
|
|
308
308
|
private processExitNudgeRounds = new Map<string, number>();
|
|
309
309
|
private stuckNudgeRounds = new Map<string, number>();
|
|
310
310
|
private currentPrompt = new Map<string, string>();
|
|
311
|
+
private paused = false;
|
|
311
312
|
|
|
312
313
|
// Generation counter per session key — incremented on every execProcess call.
|
|
313
314
|
// finishRun captures the generation at start and checks it after each await.
|
|
@@ -372,6 +373,22 @@ export class Engine {
|
|
|
372
373
|
return this.daemonId;
|
|
373
374
|
}
|
|
374
375
|
|
|
376
|
+
async pause(): Promise<void> {
|
|
377
|
+
this.paused = true;
|
|
378
|
+
await this.store.markDaemonStatus(this.daemonId, "drained");
|
|
379
|
+
log.info(`engine: daemon ${this.daemonId} paused (drained) — new issues rejected, existing sessions continue`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async resume(): Promise<void> {
|
|
383
|
+
this.paused = false;
|
|
384
|
+
await this.store.markDaemonStatus(this.daemonId, "active");
|
|
385
|
+
log.info(`engine: daemon ${this.daemonId} resumed (active)`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
isPaused(): boolean {
|
|
389
|
+
return this.paused;
|
|
390
|
+
}
|
|
391
|
+
|
|
375
392
|
/**
|
|
376
393
|
* Ensure this engine owns the issue before doing work on it. Returns true
|
|
377
394
|
* if we own it (either already, or just claimed). Returns false if another
|
|
@@ -539,6 +556,11 @@ export class Engine {
|
|
|
539
556
|
const tracker = this.getTracker(ref.trackerType);
|
|
540
557
|
const scopeKey = tracker.formatScopeKey(ref.scope);
|
|
541
558
|
|
|
559
|
+
if (this.paused && event.type === "issue_opened") {
|
|
560
|
+
log.info(`engine: paused — skipping issue_opened for ${ref.trackerType}:${scopeKey}#${ref.issueId}`);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
|
|
542
564
|
const issueMapKey = `${ref.trackerType}:${scopeKey}#${ref.issueId}`;
|
|
543
565
|
if (groupConfig) {
|
|
544
566
|
this.groupConfigs.set(issueMapKey, groupConfig);
|
|
@@ -941,14 +963,13 @@ export class Engine {
|
|
|
941
963
|
// (the operator's explicit config) instead of arbitrary env pollution.
|
|
942
964
|
// Provider keys / Gitea vars are preserved (opencode + its plugin need
|
|
943
965
|
// them); only the explicit model-override var is neutralized.
|
|
944
|
-
const childEnv = { ...process.env };
|
|
966
|
+
const childEnv = { ...process.env, ...this.hookEnvFor(issue, session, workdir) };
|
|
945
967
|
if (!model) delete childEnv.OPENCODE_MODEL;
|
|
946
|
-
// Strip parent-session env vars so the child opencode reads its own
|
|
947
|
-
// opencode.json instead of inheriting a parent session's model/provider.
|
|
948
968
|
delete childEnv.OPENCODE;
|
|
949
969
|
delete childEnv.OPENCODE_PID;
|
|
950
970
|
delete childEnv.OPENCODE_RUN_ID;
|
|
951
971
|
delete childEnv.OPENCODE_PROCESS_ROLE;
|
|
972
|
+
for (const k of this.cfg.childEnvDeny) delete childEnv[k];
|
|
952
973
|
|
|
953
974
|
try {
|
|
954
975
|
const proc = spawn({
|
|
@@ -960,6 +981,12 @@ export class Engine {
|
|
|
960
981
|
stdin: "ignore",
|
|
961
982
|
});
|
|
962
983
|
|
|
984
|
+
if (this.generation.get(k) !== gen) {
|
|
985
|
+
log.warn(`engine: spawned pid=${proc.pid} but generation superseded — killing orphan for ${k}`);
|
|
986
|
+
try { this.killProcessTree(proc.pid); } catch { /* already dead */ }
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
|
|
963
990
|
this.processes.set(k, proc);
|
|
964
991
|
this.lastOutputAt.set(k, Date.now());
|
|
965
992
|
if (!this.startedAt.has(k)) {
|
package/src/server.ts
CHANGED
|
@@ -205,6 +205,15 @@ export function createServer(
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
if (pathname === "/api/admin/pause" && req.method === "POST") {
|
|
209
|
+
await engine.pause();
|
|
210
|
+
return json({ ok: true, paused: true });
|
|
211
|
+
}
|
|
212
|
+
if (pathname === "/api/admin/resume" && req.method === "POST") {
|
|
213
|
+
await engine.resume();
|
|
214
|
+
return json({ ok: true, paused: false });
|
|
215
|
+
}
|
|
216
|
+
|
|
208
217
|
return json({ error: "not found" }, 404);
|
|
209
218
|
}
|
|
210
219
|
|