ework-daemon 0.1.0 → 0.1.2

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.1.0",
3
+ "version": "0.1.2",
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/op.ts CHANGED
@@ -432,7 +432,7 @@ export class Store {
432
432
 
433
433
  // ─── Messages ───
434
434
 
435
- createMessage(sessionId: string, content: string, sourceCommentId?: string, reactionCommentId?: string): Message {
435
+ createMessage(sessionId: string, content: string, sourceCommentId?: string, reactionCommentId?: string, model?: string): Message {
436
436
  const now = new Date().toISOString();
437
437
  const id = crypto.randomUUID();
438
438
  this.msgStmts.insert.run(
@@ -444,6 +444,7 @@ export class Store {
444
444
  id, sessionId, content, sourceCommentId, reactionCommentId,
445
445
  status: "pending", attempts: 0,
446
446
  createdAt: new Date(now), updatedAt: new Date(now),
447
+ model,
447
448
  };
448
449
  }
449
450
 
package/src/opencode.ts CHANGED
@@ -165,9 +165,9 @@ export class Engine {
165
165
 
166
166
  switch (event.type) {
167
167
  case "issue_opened":
168
- return this.handleOpened(ref, scopeKey, issueData, tracker);
168
+ return this.handleOpened(ref, scopeKey, issueData, tracker, event.model);
169
169
  case "comment_created":
170
- return this.handleCommented(ref, scopeKey, issueData, event.comment!, tracker);
170
+ return this.handleCommented(ref, scopeKey, issueData, event.comment!, tracker, event.model);
171
171
  case "issue_closed":
172
172
  return this.handleClosed(ref, scopeKey, tracker);
173
173
  }
@@ -177,7 +177,8 @@ export class Engine {
177
177
  ref: TrackerRef,
178
178
  scopeKey: string,
179
179
  issueData: TrackerEvent["issue"],
180
- tracker: IssueTracker
180
+ tracker: IssueTracker,
181
+ model?: string,
181
182
  ) {
182
183
  // Create or find issue
183
184
  const issue = this.store.findOrCreateIssue(ref, scopeKey, issueData.title);
@@ -217,7 +218,7 @@ export class Engine {
217
218
  this.handleLargeContent(workdir, issueData.body, "issue-body.txt"),
218
219
  issueData.author, workdir, instructions
219
220
  );
220
- this.enqueueOrRun(session, issue, prompt);
221
+ this.enqueueOrRun(session, issue, prompt, undefined, model);
221
222
  }
222
223
 
223
224
  private async handleCommented(
@@ -225,7 +226,8 @@ export class Engine {
225
226
  scopeKey: string,
226
227
  issueData: TrackerEvent["issue"],
227
228
  comment: NonNullable<TrackerEvent["comment"]>,
228
- tracker: IssueTracker
229
+ tracker: IssueTracker,
230
+ model?: string,
229
231
  ) {
230
232
  if (!comment) return;
231
233
 
@@ -281,7 +283,7 @@ export class Engine {
281
283
  // Immediate ack
282
284
  await tracker.createComment(ref, `[system] ✓ Message forwarded to **${session.name}**${this.running.has(this.sessionKey(session, issue)) ? " (running)" : ""}.\n> session: \`${session.id}\` | workdir: \`${workdir}\``);
283
285
 
284
- this.enqueueOrRun(session, issue, prompt, comment.id);
286
+ this.enqueueOrRun(session, issue, prompt, comment.id, model);
285
287
  } else {
286
288
  // Create new session
287
289
  session = this.store.createSession(issue.id, mentionName);
@@ -299,7 +301,7 @@ export class Engine {
299
301
  this.handleLargeContent(workdir, issueData.body, "issue-body.txt"),
300
302
  issueData.author, workdir, instructions
301
303
  );
302
- this.enqueueOrRun(session, issue, prompt, comment.id);
304
+ this.enqueueOrRun(session, issue, prompt, comment.id, model);
303
305
  }
304
306
  } else {
305
307
  // No @mention → broadcast to all sessions on this issue
@@ -317,7 +319,7 @@ export class Engine {
317
319
  session.name, this.handleLargeContent(workdir, comment.body, `comment-${comment.id}.txt`),
318
320
  comment.author, issueData.title, workdir, instructions
319
321
  );
320
- this.enqueueOrRun(session, issue, prompt, comment.id);
322
+ this.enqueueOrRun(session, issue, prompt, comment.id, model);
321
323
  }
322
324
 
323
325
  // Immediate ack
@@ -382,13 +384,13 @@ export class Engine {
382
384
 
383
385
  // ─── Preemptive Scheduler ───
384
386
 
385
- private enqueueOrRun(session: OpSession, issue: Issue, prompt: string, sourceCommentId?: string) {
387
+ private enqueueOrRun(session: OpSession, issue: Issue, prompt: string, sourceCommentId?: string, model?: string) {
386
388
  const k = this.sessionKey(session, issue);
387
389
 
388
390
  this.stuckNudgeRounds.delete(k);
389
391
  this.processExitNudgeRounds.delete(k);
390
392
 
391
- const msg = this.store.createMessage(session.id, prompt, sourceCommentId);
393
+ const msg = this.store.createMessage(session.id, prompt, sourceCommentId, undefined, model);
392
394
 
393
395
  if (this.running.has(k)) {
394
396
  // PREEMPTIVE: Kill running process, new message takes priority
@@ -455,6 +457,12 @@ export class Engine {
455
457
  if (session.opencodeSessionId) {
456
458
  args.push("--session", session.opencodeSessionId);
457
459
  }
460
+ // Push --model BEFORE the message content. Defends against env-var-
461
+ // registered providers stealing the slot (the original bug). Empty/
462
+ // undefined = omit, let opencode pick per its own opencode.json.
463
+ if (msg.model) {
464
+ args.push("--model", msg.model);
465
+ }
458
466
  args.push(msg.content);
459
467
 
460
468
  // Set eyes reaction on the source comment
@@ -466,10 +474,21 @@ export class Engine {
466
474
 
467
475
  let exitCode: number | null = null;
468
476
 
477
+ // M-1: build the child env explicitly. When a model IS resolved we push
478
+ // --model above, so opencode ignores OPENCODE_MODEL anyway. When NO model
479
+ // is resolved we must not let a leaked OPENCODE_MODEL from our own env
480
+ // silently win — strip it so opencode falls back to its opencode.json
481
+ // (the operator's explicit config) instead of arbitrary env pollution.
482
+ // Provider keys / Gitea vars are preserved (opencode + its plugin need
483
+ // them); only the explicit model-override var is neutralized.
484
+ const childEnv = { ...process.env };
485
+ if (!msg.model) delete childEnv.OPENCODE_MODEL;
486
+
469
487
  try {
470
488
  const proc = spawn({
471
489
  cmd: args,
472
490
  cwd: workdir,
491
+ env: childEnv,
473
492
  stdout: "pipe",
474
493
  stderr: "pipe",
475
494
  stdin: "ignore",
@@ -119,6 +119,11 @@ export class GiteaTracker implements IssueTracker {
119
119
  const repoName = repository.name as string;
120
120
  if (!repoOwner || !repoName) return null;
121
121
 
122
+ // ework-web extension (non-Gitea field, ignored by strict Gitea consumers).
123
+ // Empty/missing = no model override; engine omits --model.
124
+ const modelRaw = repository.ework_model;
125
+ const model = typeof modelRaw === "string" && modelRaw.trim() ? modelRaw.trim() : undefined;
126
+
122
127
  const ref: TrackerRef = {
123
128
  trackerType: "gitea",
124
129
  scope: { owner: repoOwner, repo: repoName },
@@ -137,6 +142,7 @@ export class GiteaTracker implements IssueTracker {
137
142
  state: (issue.state as string) ?? "open",
138
143
  author: issueUser?.login ?? "",
139
144
  },
145
+ model,
140
146
  };
141
147
  }
142
148
 
@@ -156,6 +162,7 @@ export class GiteaTracker implements IssueTracker {
156
162
  body: comment.body as string,
157
163
  author: commentUser?.login ?? "",
158
164
  },
165
+ model,
159
166
  };
160
167
  }
161
168
 
@@ -169,6 +176,7 @@ export class GiteaTracker implements IssueTracker {
169
176
  state: "closed",
170
177
  author: issueUser?.login ?? "",
171
178
  },
179
+ model,
172
180
  };
173
181
  }
174
182
 
@@ -29,6 +29,10 @@ export interface TrackerEvent {
29
29
  body: string;
30
30
  author: string;
31
31
  };
32
+ // Resolved "provider/model" string from ework-web (project override or
33
+ // global default). Empty/undefined = no override; engine omits --model
34
+ // and lets opencode pick per its own opencode.json + env.
35
+ model?: string;
32
36
  }
33
37
 
34
38
  /** Tracker-agnostic comment */
@@ -94,6 +98,10 @@ export interface Message {
94
98
  error?: string;
95
99
  createdAt: Date;
96
100
  updatedAt: Date;
101
+ // Resolved "provider/model" string from ework-web. When set, engine pushes
102
+ // `--model <X>` on opencode spawn to override opencode.json + env vars.
103
+ // Undefined = no override (let opencode pick).
104
+ model?: string;
97
105
  }
98
106
 
99
107
  // ─── Adapter Interface ───