ework-daemon 0.4.45 → 0.4.46

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/opencode.ts +17 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-daemon",
3
- "version": "0.4.45",
3
+ "version": "0.4.46",
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/opencode.ts CHANGED
@@ -207,13 +207,20 @@ 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
+ // ssh without these can sleep forever: no TCP keepalive on a blackholed
211
+ // connection, and host-key/passphrase prompts block a headless daemon.
212
+ const sshCmd = process.env.WORK_GIT_SSH_COMMAND
213
+ ?? "ssh -o ConnectTimeout=10 -o ServerAliveInterval=15 -o ServerAliveCountMax=4 -o BatchMode=yes";
210
214
  let r: { exitCode: number | null; stderr?: Uint8Array | undefined };
211
215
  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);
216
+ // async spawn: a slow/hung remote must never block the daemon event
217
+ // loop — spawnSync froze healthz+heartbeats for the whole clone
218
+ // duration. Capped at 10 minutes.
219
+ const proc = Bun.spawn({
220
+ cmd: gitArgs, stdout: "ignore", stderr: "pipe",
221
+ env: { ...process.env, ...env, GIT_SSH_COMMAND: sshCmd },
222
+ });
223
+ const killTimer = setTimeout(() => { try { proc.kill("SIGKILL"); } catch { /* already dead */ } }, 10 * 60_000);
217
224
  const [exitCode, stderr] = await Promise.all([proc.exited, new Response(proc.stderr).arrayBuffer()]);
218
225
  clearTimeout(killTimer);
219
226
  r = { exitCode, stderr: new Uint8Array(stderr) };
@@ -795,11 +802,13 @@ export class Engine {
795
802
  }
796
803
 
797
804
  const k = this.sessionKey(session, issue);
798
- const workdir = await this.resolveWorkdir(session, issue);
799
- log.info(`engine: session "${session.name}" created for ${k}, workdir=${workdir}`);
800
805
 
801
- await tracker.createComment(ref, `[system] 🔄 **${session.name}** picked up this issue.\n> session: ${this.sessionRef(session)} | workdir: ${this.workdirLink(workdir)}`);
806
+ // Ack before resolving the workdir: a first-touch clone can take minutes
807
+ // (or hit the 10-min cap), and the user must see pickup feedback immediately.
808
+ await tracker.createComment(ref, `[system] 🔄 **${session.name}** picked up this issue — preparing workspace…\n> session: ${this.sessionRef(session)}`);
802
809
  void tracker.updateStatus(ref, "processing");
810
+ const workdir = await this.resolveWorkdir(session, issue);
811
+ log.info(`engine: session "${session.name}" created for ${k}, workdir=${workdir}`);
803
812
 
804
813
  const instructions = tracker.getTrackerInstructions(ref);
805
814
  const prompt = this.buildInitialPrompt(