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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-daemon",
3
- "version": "0.4.11",
3
+ "version": "0.4.13",
4
4
  "description": "Issue-driven AI development daemon. Spawns opencode subprocesses to resolve Gitea issues.",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
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: \`${session.id}\` | workdir: \`${workdir}\``);
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: \`${session.id}\` | workdir: \`${workdir}\``);
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: \`${session.id}\` | workdir: \`${workdir}\``);
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`);