ework-daemon 0.4.42 → 0.4.44

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.42",
3
+ "version": "0.4.44",
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/db.ts CHANGED
@@ -543,6 +543,15 @@ async function runMigrations(db: AsyncDatabase): Promise<void> {
543
543
  sqlite ? "generation INTEGER NOT NULL DEFAULT 0" : "generation INT NOT NULL DEFAULT 0"
544
544
  );
545
545
 
546
+ // messages.model — per-message model override from the webhook payload.
547
+ // Persisted so queued/nudged/recovered messages keep their model instead of
548
+ // silently falling back to the daemon default.
549
+ await ensureColumn(
550
+ tMessages,
551
+ "model",
552
+ sqlite ? "model TEXT" : "model VARCHAR(128)"
553
+ );
554
+
546
555
  // Index over owner_daemon_id — added after the column exists. SQLite tolerates
547
556
  // IF NOT EXISTS; MySQL lacks it, so we tolerate ER_DUP_KEYNAME (1061) on re-runs.
548
557
  if (sqlite) {
package/src/op.ts CHANGED
@@ -42,6 +42,7 @@ interface MessageRow {
42
42
  content: string;
43
43
  source_comment_id: string | null;
44
44
  reaction_comment_id: string | null;
45
+ model: string | null;
45
46
  status: string;
46
47
  attempts: number;
47
48
  error: string | null;
@@ -96,6 +97,7 @@ function rowToMessage(row: MessageRow): Message {
96
97
  content: row.content,
97
98
  sourceCommentId: row.source_comment_id ?? undefined,
98
99
  reactionCommentId: row.reaction_comment_id ?? undefined,
100
+ model: row.model ?? undefined,
99
101
  status: row.status as Message["status"],
100
102
  attempts: row.attempts,
101
103
  error: row.error ?? undefined,
@@ -287,8 +289,8 @@ export class Store {
287
289
  const now = new Date().toISOString();
288
290
  const id = crypto.randomUUID();
289
291
  await getDB().run(
290
- "INSERT OR IGNORE INTO {{messages}} (uid, session_id, content, source_comment_id, reaction_comment_id, status, attempts, error, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
291
- [id, sessionId, content, sourceCommentId ?? null, reactionCommentId ?? null, "pending", 0, null, now, now]
292
+ "INSERT OR IGNORE INTO {{messages}} (uid, session_id, content, source_comment_id, reaction_comment_id, model, status, attempts, error, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
293
+ [id, sessionId, content, sourceCommentId ?? null, reactionCommentId ?? null, model ?? null, "pending", 0, null, now, now]
292
294
  );
293
295
  return {
294
296
  id, sessionId, content, sourceCommentId, reactionCommentId,
package/src/opencode.ts CHANGED
@@ -207,15 +207,28 @@ export class RecloneStrategy implements TakeoverStrategy {
207
207
  const gitArgs = ["git"];
208
208
  if (credHelper) gitArgs.push("-c", `credential.helper=${credHelper}`);
209
209
  gitArgs.push("clone", url, dir);
210
- const r = Bun.spawnSync({ cmd: gitArgs, stdout: "ignore", stderr: "pipe", env: { ...process.env, ...env } });
211
- if (r.exitCode !== 0) {
210
+ let r: { exitCode: number | null; stderr?: Uint8Array | undefined };
211
+ try {
212
+ // async spawn: a slow/hung remote (e.g. SSH host-key prompt) must never
213
+ // block the daemon event loop — spawnSync froze healthz+heartbeats for
214
+ // the whole clone duration. Capped at 10 minutes.
215
+ const proc = Bun.spawn({ cmd: gitArgs, stdout: "ignore", stderr: "pipe", env: { ...process.env, ...env } });
216
+ const killTimer = setTimeout(() => proc.kill("SIGKILL"), 10 * 60_000);
217
+ const [exitCode, stderr] = await Promise.all([proc.exited, new Response(proc.stderr).arrayBuffer()]);
218
+ clearTimeout(killTimer);
219
+ r = { exitCode, stderr: new Uint8Array(stderr) };
220
+ } catch {
221
+ r = { exitCode: -1 };
222
+ }
223
+ const exitCode = r.exitCode ?? -1;
224
+ if (exitCode !== 0) {
212
225
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
213
226
  if (existsSync(dir) && readdirSync(dir).length === 0) {
214
227
  Bun.spawnSync({ cmd: ["git", "init", dir], stdout: "ignore", stderr: "ignore" });
215
228
  }
216
229
  const stderrBuf = r.stderr as Uint8Array | undefined;
217
230
  const stderrText = stderrBuf ? new TextDecoder().decode(stderrBuf).slice(0, 500) : "";
218
- log.warn(`acquireWorkdir: git clone failed (exit ${r.exitCode}) for ${url}${stderrText ? `: ${stderrText}` : ""}; fell back to empty workdir`);
231
+ log.warn(`acquireWorkdir: git clone failed (exit ${exitCode}) for ${url}${stderrText ? `: ${stderrText}` : ""}; fell back to empty workdir`);
219
232
  }
220
233
  }
221
234
  } catch {
@@ -49,6 +49,7 @@ CREATE TABLE IF NOT EXISTS {{messages}} (
49
49
  content LONGTEXT NOT NULL,
50
50
  source_comment_id VARCHAR(64),
51
51
  reaction_comment_id VARCHAR(64),
52
+ model VARCHAR(128),
52
53
  status VARCHAR(16) NOT NULL DEFAULT 'pending',
53
54
  attempts INT NOT NULL DEFAULT 0,
54
55
  error TEXT,
@@ -41,6 +41,7 @@ CREATE TABLE IF NOT EXISTS {{messages}} (
41
41
  content TEXT NOT NULL,
42
42
  source_comment_id TEXT,
43
43
  reaction_comment_id TEXT,
44
+ model TEXT,
44
45
  status TEXT NOT NULL DEFAULT 'pending',
45
46
  attempts INTEGER NOT NULL DEFAULT 0,
46
47
  error TEXT,