ework-daemon 0.4.7 → 0.4.8

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.7",
3
+ "version": "0.4.8",
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/config.ts CHANGED
@@ -32,6 +32,7 @@ export const configSchema = z.object({
32
32
  dbPath: z.string().default(
33
33
  `${process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share")}/opencode/opencode.db`
34
34
  ),
35
+ defaultModel: z.string().default(""),
35
36
  }),
36
37
  work: z.object({
37
38
  capacity: z.coerce.number().int().positive().default(4),
@@ -77,7 +78,7 @@ const TEST_DEFAULTS = {
77
78
  gitea: { url: "http://localhost:9999", token: "test-token", webhookSecret: "" },
78
79
  bot: { username: "ework-daemon-test", token: "test-bot-token" },
79
80
  daemon: { port: 3111, host: "0.0.0.0", endpoint: "" },
80
- opencode: { binary: "opencode", baseWorkdir: join(tmpdir(), "ework-daemon-test") },
81
+ opencode: { binary: "opencode", baseWorkdir: join(tmpdir(), "ework-daemon-test"), defaultModel: "" },
81
82
  work: { capacity: 4, heartbeatMs: 10_000, leaseTtlMs: 60_000 },
82
83
  db: { path: join(process.cwd(), "test", "ework-daemon-test.db") },
83
84
  };
@@ -131,6 +132,7 @@ export function loadConfig(): Config {
131
132
  binary: process.env.OPENCODE_BINARY ?? TEST_DEFAULTS.opencode.binary,
132
133
  baseWorkdir: process.env.OPENCODE_BASE_WORKDIR ?? TEST_DEFAULTS.opencode.baseWorkdir,
133
134
  dbPath: process.env.OPENCODE_DB_PATH ?? `${process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share")}/opencode/opencode.db`,
135
+ defaultModel: process.env.WORK_DEFAULT_MODEL ?? TEST_DEFAULTS.opencode.defaultModel,
134
136
  },
135
137
  work: readWorkSection(),
136
138
  db: readDbSection(TEST_DEFAULTS.db.path),
@@ -166,6 +168,7 @@ export function loadConfig(): Config {
166
168
  binary: process.env.OPENCODE_BINARY ?? "opencode",
167
169
  baseWorkdir: process.env.OPENCODE_BASE_WORKDIR,
168
170
  dbPath: process.env.OPENCODE_DB_PATH ?? `${process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share")}/opencode/opencode.db`,
171
+ defaultModel: process.env.WORK_DEFAULT_MODEL ?? "",
169
172
  },
170
173
  work: readWorkSection(),
171
174
  db: readDbSection(PRODUCTION_DB_DEFAULT),
package/src/db.ts CHANGED
@@ -210,7 +210,7 @@ class MysqlDriver implements AsyncDatabase {
210
210
  try {
211
211
  await pool.query(stmt);
212
212
  } catch (e) {
213
- if (e && typeof e === "object" && "errno" in e && (e as { errno: number }).errno === 1061) continue;
213
+ if (e && typeof e === "object" && "errno" in e && ((e as { errno: number }).errno === 1061 || (e as { errno: number }).errno === 30000)) continue;
214
214
  throw e;
215
215
  }
216
216
  }
@@ -512,7 +512,7 @@ async function runMigrations(db: AsyncDatabase): Promise<void> {
512
512
  try {
513
513
  await db.exec(`CREATE INDEX idx_issues_owner ON ${tIssues}(owner_daemon_id)`);
514
514
  } catch (e) {
515
- if (e && typeof e === "object" && "errno" in e && (e as { errno: number }).errno === 1061) {
515
+ if (e && typeof e === "object" && "errno" in e && ((e as { errno: number }).errno === 1061 || (e as { errno: number }).errno === 30000)) {
516
516
  // index already exists — expected on re-runs
517
517
  } else {
518
518
  throw e;
package/src/opencode.ts CHANGED
@@ -124,11 +124,27 @@ export class RecloneStrategy implements TakeoverStrategy {
124
124
  if (entries.length === 0) {
125
125
  const base = this.cfg.gitea.url.replace(/\/$/, "");
126
126
  const url = `${base}/${owner}/${repo}.git`;
127
- Bun.spawnSync({ cmd: ["git", "clone", url, dir], stdout: "ignore", stderr: "ignore" });
127
+ const r = Bun.spawnSync({ cmd: ["git", "clone", url, dir], stdout: "ignore", stderr: "ignore" });
128
+ // git clone deletes the target dir on failure (e.g. gitea shim
129
+ // without smart-http /info/refs 404). Re-create the workdir so
130
+ // Bun.spawn later doesn't throw a misleading ENOENT pointing at the
131
+ // opencode binary path instead of the missing cwd. Fall back to
132
+ // `git init` so the workdir is at least a valid git repo the agent
133
+ // can work in.
134
+ if (r.exitCode !== 0) {
135
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
136
+ if (existsSync(dir) && readdirSync(dir).length === 0) {
137
+ Bun.spawnSync({ cmd: ["git", "init", dir], stdout: "ignore", stderr: "ignore" });
138
+ }
139
+ log.warn(`acquireWorkdir: git clone failed (exit ${r.exitCode}) for ${url}; fell back to empty workdir`);
140
+ }
128
141
  }
129
142
  } catch {
130
143
  // directory access failed — leave it; the agent's own tools can clone
131
144
  }
145
+ // Final safety net: git clone may have deleted the dir. Without this
146
+ // Bun.spawn will throw ENOENT with the binary path, not the cwd path.
147
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
132
148
  return dir;
133
149
  }
134
150
 
@@ -777,11 +793,12 @@ export class Engine {
777
793
  if (resumeSessionId) {
778
794
  args.push("--session", resumeSessionId);
779
795
  }
780
- // Push --model BEFORE the message content. Defends against env-var-
781
- // registered providers stealing the slot (the original bug). Empty/
782
- // undefined = omit, let opencode pick per its own opencode.json.
783
- if (msg.model) {
784
- args.push("--model", msg.model);
796
+ // Resolve the model: explicit per-message > daemon default > omit.
797
+ // Without this, opencode's own default (influenced by plugin agent
798
+ // presets) can pick a non-existent model, causing ProviderModelNotFoundError.
799
+ const model = msg.model || this.cfg.opencode.defaultModel;
800
+ if (model) {
801
+ args.push("--model", model);
785
802
  }
786
803
  args.push(msg.content);
787
804
 
@@ -802,7 +819,7 @@ export class Engine {
802
819
  // Provider keys / Gitea vars are preserved (opencode + its plugin need
803
820
  // them); only the explicit model-override var is neutralized.
804
821
  const childEnv = { ...process.env };
805
- if (!msg.model) delete childEnv.OPENCODE_MODEL;
822
+ if (!model) delete childEnv.OPENCODE_MODEL;
806
823
 
807
824
  try {
808
825
  const proc = spawn({