ework-daemon 0.4.11 → 0.4.13
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/index.ts +22 -0
- package/src/opencode.ts +28 -6
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { GiteaTracker } from "./trackers/gitea-tracker";
|
|
|
8
8
|
import type { IssueTracker } from "./trackers/types";
|
|
9
9
|
import { initDB } from "./db";
|
|
10
10
|
import { hostname } from "os";
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
11
13
|
|
|
12
14
|
const config = loadConfig();
|
|
13
15
|
const isTest = config.env === "test";
|
|
@@ -20,6 +22,26 @@ log.info(` workdir: ${config.opencode.baseWorkdir}`);
|
|
|
20
22
|
log.info(` db: ${config.db.driver === "mysql" ? `${config.db.user}@${config.db.host}:${config.db.port}/${config.db.name}` : config.db.path}${config.db.prefix ? ` (prefix=${config.db.prefix})` : ""}`);
|
|
21
23
|
log.info(` work: capacity=${config.work.capacity} heartbeat=${config.work.heartbeatMs}ms leaseTtl=${config.work.leaseTtlMs}ms`);
|
|
22
24
|
|
|
25
|
+
function probePlugin() {
|
|
26
|
+
const configDir = process.env.XDG_CONFIG_HOME || path.join(process.env.HOME || "/tmp", ".config");
|
|
27
|
+
const cfgPath = path.join(configDir, "opencode", "opencode.json");
|
|
28
|
+
try {
|
|
29
|
+
const raw = fs.readFileSync(cfgPath, "utf8");
|
|
30
|
+
const parsed = JSON.parse(raw);
|
|
31
|
+
const plugin = parsed.plugin ?? parsed.plugins;
|
|
32
|
+
const plugins = Array.isArray(plugin) ? plugin : plugin ? [plugin] : [];
|
|
33
|
+
const hasEwork = plugins.some((p: unknown) =>
|
|
34
|
+
typeof p === "string" ? p.includes("opencode-ework") : false,
|
|
35
|
+
);
|
|
36
|
+
if (!hasEwork) {
|
|
37
|
+
log.warn(" ⚠️ opencode-ework plugin not found in opencode.json — the AI will lack the 'reply' tool and all nudges will fail. Install it: npm install -g opencode-ework");
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
log.warn(` ⚠️ could not read opencode config at ${cfgPath} — cannot verify opencode-ework plugin is installed`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
probePlugin();
|
|
44
|
+
|
|
23
45
|
const giteaClient = new GiteaClient(config.gitea, config.bot.token);
|
|
24
46
|
const giteaTracker = new GiteaTracker(
|
|
25
47
|
giteaClient,
|
package/src/opencode.ts
CHANGED
|
@@ -221,6 +221,7 @@ export class Engine {
|
|
|
221
221
|
private stopping = new Set<string>();
|
|
222
222
|
private processingComments = new Set<string>();
|
|
223
223
|
private currentMessage = new Map<string, string>();
|
|
224
|
+
private currentModel = new Map<string, string | undefined>();
|
|
224
225
|
private lastOutputAt = new Map<string, number>();
|
|
225
226
|
private startedAt = new Map<string, number>();
|
|
226
227
|
private progressCommentId = new Map<string, string>();
|
|
@@ -258,6 +259,22 @@ export class Engine {
|
|
|
258
259
|
void this.recover();
|
|
259
260
|
}
|
|
260
261
|
|
|
262
|
+
private daemonEndpoint(): string {
|
|
263
|
+
return this.cfg.daemon.endpoint || `${this.cfg.daemon.host}:${this.cfg.daemon.port}`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private workdirLink(workdir: string): string {
|
|
267
|
+
const ep = encodeURIComponent(this.daemonEndpoint());
|
|
268
|
+
const p = encodeURIComponent(workdir);
|
|
269
|
+
return `[${workdir}](/file?path=${p}&daemon=${ep})`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private sessionRef(session: { id: string; opencodeSessionId?: string | null }): string {
|
|
273
|
+
const ses = session.opencodeSessionId || session.id;
|
|
274
|
+
const ep = encodeURIComponent(this.daemonEndpoint());
|
|
275
|
+
return `[\`${ses}\`](/sessions/${encodeURIComponent(ses)}?daemon=${ep})`;
|
|
276
|
+
}
|
|
277
|
+
|
|
261
278
|
/** Start the lease heartbeat. Must be called once after registerDaemon. */
|
|
262
279
|
startHeartbeat(intervalMs: number): void {
|
|
263
280
|
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
@@ -497,7 +514,7 @@ export class Engine {
|
|
|
497
514
|
const workdir = await this.resolveWorkdir(session, issue);
|
|
498
515
|
log.info(`engine: session "${session.name}" created for ${k}, workdir=${workdir}`);
|
|
499
516
|
|
|
500
|
-
await tracker.createComment(ref, `[system] 🔄 **${session.name}** picked up this issue.\n> session:
|
|
517
|
+
await tracker.createComment(ref, `[system] 🔄 **${session.name}** picked up this issue.\n> session: ${this.sessionRef(session)} | workdir: ${this.workdirLink(workdir)}`);
|
|
501
518
|
|
|
502
519
|
const instructions = tracker.getTrackerInstructions(ref);
|
|
503
520
|
const prompt = this.buildInitialPrompt(
|
|
@@ -579,7 +596,7 @@ export class Engine {
|
|
|
579
596
|
);
|
|
580
597
|
|
|
581
598
|
// Immediate ack
|
|
582
|
-
await tracker.createComment(ref, `[system] ✓ Message forwarded to **${session.name}**${this.running.has(this.sessionKey(session, issue)) ? " (running)" : ""}.\n> session:
|
|
599
|
+
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)}`);
|
|
583
600
|
|
|
584
601
|
await this.enqueueOrRun(session, issue, prompt, comment.id, model);
|
|
585
602
|
} else {
|
|
@@ -626,7 +643,7 @@ export class Engine {
|
|
|
626
643
|
session.name, this.handleLargeContent(workdir, comment.body, `comment-${comment.id}.txt`),
|
|
627
644
|
comment.author, issueData.title, workdir, instructions
|
|
628
645
|
);
|
|
629
|
-
await tracker.createComment(ref, `[system] ✓ Message forwarded to **${session.name}**${this.running.has(this.sessionKey(session, issue)) ? " (running)" : ""}.\n> session:
|
|
646
|
+
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)}`);
|
|
630
647
|
await this.enqueueOrRun(session, issue, prompt, comment.id, model);
|
|
631
648
|
}
|
|
632
649
|
} finally {
|
|
@@ -687,6 +704,7 @@ export class Engine {
|
|
|
687
704
|
this.running.delete(k);
|
|
688
705
|
this.stopping.delete(k);
|
|
689
706
|
this.currentMessage.delete(k);
|
|
707
|
+
this.currentModel.delete(k);
|
|
690
708
|
this.lastOutputAt.delete(k);
|
|
691
709
|
this.startedAt.delete(k);
|
|
692
710
|
this.progressCommentId.delete(k);
|
|
@@ -738,6 +756,7 @@ export class Engine {
|
|
|
738
756
|
// Don't clear stopping — let old execProcess detect preemption via process reference mismatch
|
|
739
757
|
this.running.delete(k);
|
|
740
758
|
this.currentMessage.delete(k);
|
|
759
|
+
this.currentModel.delete(k);
|
|
741
760
|
this.startedAt.delete(k);
|
|
742
761
|
|
|
743
762
|
// Execute new message
|
|
@@ -797,6 +816,7 @@ export class Engine {
|
|
|
797
816
|
// Without this, opencode's own default (influenced by plugin agent
|
|
798
817
|
// presets) can pick a non-existent model, causing ProviderModelNotFoundError.
|
|
799
818
|
const model = msg.model || this.cfg.opencode.defaultModel;
|
|
819
|
+
this.currentModel.set(k, model);
|
|
800
820
|
if (model) {
|
|
801
821
|
args.push("--model", model);
|
|
802
822
|
}
|
|
@@ -935,6 +955,7 @@ export class Engine {
|
|
|
935
955
|
|
|
936
956
|
// running.delete deferred to dequeuePending
|
|
937
957
|
this.currentMessage.delete(k);
|
|
958
|
+
this.currentModel.delete(k);
|
|
938
959
|
|
|
939
960
|
const started = this.startedAt.get(k);
|
|
940
961
|
this.startedAt.delete(k);
|
|
@@ -1006,7 +1027,7 @@ export class Engine {
|
|
|
1006
1027
|
|
|
1007
1028
|
const instructions = tracker.getTrackerInstructions(ref);
|
|
1008
1029
|
const nudgePrompt = this.buildNudgePrompt(session, issue, instructions);
|
|
1009
|
-
const nudgeMsg = await this.store.createMessage(session.id, nudgePrompt);
|
|
1030
|
+
const nudgeMsg = await this.store.createMessage(session.id, nudgePrompt, undefined, undefined, this.currentModel.get(k));
|
|
1010
1031
|
await this.dequeueOrIdle(k, session, issue, nudgeMsg);
|
|
1011
1032
|
return;
|
|
1012
1033
|
}
|
|
@@ -1250,6 +1271,7 @@ export class Engine {
|
|
|
1250
1271
|
if (msgId) {
|
|
1251
1272
|
await this.store.updateMessageStatus(msgId, "failed", "process died unexpectedly");
|
|
1252
1273
|
this.currentMessage.delete(k);
|
|
1274
|
+
this.currentModel.delete(k);
|
|
1253
1275
|
}
|
|
1254
1276
|
|
|
1255
1277
|
const ref = this.sessionToRef(session, issue);
|
|
@@ -1268,7 +1290,7 @@ export class Engine {
|
|
|
1268
1290
|
|
|
1269
1291
|
const instructions = tracker.getTrackerInstructions(ref);
|
|
1270
1292
|
const nudgePrompt = this.buildProcessExitNudgePrompt(session, issue, instructions);
|
|
1271
|
-
const nudgeMsg = await this.store.createMessage(session.id, nudgePrompt);
|
|
1293
|
+
const nudgeMsg = await this.store.createMessage(session.id, nudgePrompt, undefined, undefined, this.currentModel.get(k));
|
|
1272
1294
|
await this.dequeueOrIdle(k, session, issue, nudgeMsg);
|
|
1273
1295
|
continue;
|
|
1274
1296
|
} else {
|
|
@@ -1312,7 +1334,7 @@ export class Engine {
|
|
|
1312
1334
|
|
|
1313
1335
|
const instructions = tracker.getTrackerInstructions(ref);
|
|
1314
1336
|
const nudgePrompt = this.buildStuckNudgePrompt(session, issue, instructions, minutes);
|
|
1315
|
-
const nudgeMsg = await this.store.createMessage(session.id, nudgePrompt);
|
|
1337
|
+
const nudgeMsg = await this.store.createMessage(session.id, nudgePrompt, undefined, undefined, this.currentModel.get(k));
|
|
1316
1338
|
await this.dequeueOrIdle(k, session, issue, nudgeMsg);
|
|
1317
1339
|
} else {
|
|
1318
1340
|
log.warn(`engine: stuck for ${minutes}min on ${k}, stuck nudge exhausted (${stuckNudgeRound}/${this.maxStuckNudges}), giving up`);
|