wildcrane-runner 0.6.5 → 0.6.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.
@@ -1 +1 @@
1
- {"name":"wildcrane","version":"0.11.4","akbContract":2,"type":"module","private":true}
1
+ {"name":"wildcrane","version":"0.11.7","akbContract":3,"type":"module","private":true}
package/dist/launcher.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // wildcrane-runner 0.6.5 — built by runner/scripts/build.mjs. Do not edit.
1
+ // wildcrane-runner 0.6.8 — built by runner/scripts/build.mjs. Do not edit.
2
2
  import { createRequire as __ai4kanbanRequire } from 'node:module'
3
3
  const require = __ai4kanbanRequire(import.meta.url)
4
4
 
@@ -50,8 +50,8 @@ function cloudEndpoints() {
50
50
  }
51
51
 
52
52
  // src/i18n.ts
53
- var ZH_HOLDER = { "claude-code": "Claude Code", codex: "Codex", terminal: "\u672C\u673A\u7EC8\u7AEF", desktop: "WildCrane \u684C\u9762\u7AEF" };
54
- var EN_HOLDER = { "claude-code": "Claude Code", codex: "Codex", terminal: "the terminal", desktop: "the WildCrane desktop app" };
53
+ var ZH_HOLDER = { "claude-code": "Claude Code", codex: "Codex", terminal: "\u672C\u673A\u7EC8\u7AEF" };
54
+ var EN_HOLDER = { "claude-code": "Claude Code", codex: "Codex", terminal: "the terminal" };
55
55
  var ZH = {
56
56
  signIn: (url) => `\u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u767B\u5F55 WildCrane\uFF1A
57
57
  ${url}`,
@@ -70,13 +70,7 @@ ${url}`,
70
70
  const who = holder ? ZH_HOLDER[holder] : "\u672C\u673A\u53E6\u4E00\u4E2A\u7A0B\u5E8F";
71
71
  return `${who}\u5DF2\u5728\u8FD0\u884C\u4E00\u4E2A WildCrane \u5B9E\u4F8B\uFF0C\u8D1F\u8D23\u63A5\u6536\u6240\u6709\u8FD0\u884C\u4EFB\u52A1\u3002\u5F53\u524D\u5B9E\u4F8B\u5DF2\u6682\u505C\uFF0C${who}\u5173\u95ED\u540E\u5C06\u7531\u5F53\u524D\u5B9E\u4F8B\u63A5\u66FF\u3002`;
72
72
  },
73
- stopped: () => "\u5DF2\u505C\u6B62",
74
- reviewTitle: () => "\u5F85\u4F60\u5BA1\u6838",
75
- reviewBody: (task) => `\u300C${task}\u300D\u5F85\u5BA1\u6838`,
76
- doneTitle: () => "\u4EFB\u52A1\u5DF2\u5B8C\u6210",
77
- failedTitle: () => "\u4EFB\u52A1\u5931\u8D25",
78
- commentTitle: (task) => `\u300C${task}\u300D\u6709\u65B0\u8BC4\u8BBA`,
79
- commentBody: (who, text) => `${who}\uFF1A${text}`
73
+ stopped: () => "\u5DF2\u505C\u6B62"
80
74
  };
81
75
  var EN = {
82
76
  signIn: (url) => `Sign in to WildCrane in your browser:
@@ -96,13 +90,7 @@ ${url}`,
96
90
  const who = holder ? EN_HOLDER[holder] : "another app on this computer";
97
91
  return `${who[0].toUpperCase()}${who.slice(1)} is already running a WildCrane instance that takes all run tasks. This instance is paused and takes over when ${who} closes.`;
98
92
  },
99
- stopped: () => "Stopped",
100
- reviewTitle: () => "Needs your review",
101
- reviewBody: (task) => `"${task}" is ready for review`,
102
- doneTitle: () => "Task finished",
103
- failedTitle: () => "Task failed",
104
- commentTitle: (task) => `New comment on ${task}`,
105
- commentBody: (who, text) => `${who}: ${text}`
93
+ stopped: () => "Stopped"
106
94
  };
107
95
  function isChinese(env = process.env) {
108
96
  const lang = env.LC_ALL || env.LC_MESSAGES || env.LANG || Intl.DateTimeFormat().resolvedOptions().locale;
@@ -119,9 +107,10 @@ function status(line) {
119
107
  }
120
108
 
121
109
  // src/protocol.ts
122
- var VERSION = true ? "0.6.5" : "0.0.0";
110
+ var VERSION = true ? "0.6.8" : "0.0.0";
123
111
  var EXIT_REFUSED = 10;
124
112
  var EXIT_OUTDATED = 11;
113
+ var EXIT_UPDATE = 12;
125
114
 
126
115
  // src/update.ts
127
116
  import crypto2 from "node:crypto";
@@ -285,8 +274,8 @@ async function askTarget(machine) {
285
274
  signal: AbortSignal.timeout(ASK_MS)
286
275
  });
287
276
  if (!answer.ok) return null;
288
- const { version } = await answer.json();
289
- return typeof version === "string" && version ? version : null;
277
+ const { version, restartable } = await answer.json();
278
+ return typeof version === "string" && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version) ? { version, restartable: restartable === true } : null;
290
279
  } catch {
291
280
  return null;
292
281
  }
@@ -295,9 +284,8 @@ async function candidates(own) {
295
284
  const state = readState();
296
285
  const proven = state.good.find((v) => fs3.existsSync(entryOf(versionDir(v))));
297
286
  const current = proven && compare(proven, own.version) > 0 ? { version: proven, entry: entryOf(versionDir(proven)) } : own;
298
- const target = await askTarget(machineId());
287
+ const target = (await askTarget(machineId()))?.version;
299
288
  if (!target || target === current.version || state.bad.includes(target)) return [current];
300
- if (process.env.WILDCRANE_DESKTOP && compare(target, own.version) < 0) return [current];
301
289
  if (target === own.version) return [own, current];
302
290
  const got = await install(target);
303
291
  if (got.ok) {
@@ -307,16 +295,56 @@ async function candidates(own) {
307
295
  if (got.why === "node") status(t.nodeTooOld(got.detail));
308
296
  return [current];
309
297
  }
310
- function runOnce(candidate) {
298
+ function runOnce(candidate, options) {
311
299
  return new Promise((resolve) => {
312
300
  const child = spawn(process.execPath, [candidate.entry], {
313
- stdio: ["inherit", "inherit", "inherit", "ipc"],
314
- env: { ...process.env, WILDCRANE_RUNNER_VERSION: candidate.version }
301
+ stdio: ["ignore", options.quiet ? 2 : "inherit", "inherit", "ipc"],
302
+ env: { ...process.env, WILDCRANE_RUNNER_VERSION: candidate.version, WILDCRANE_RUNNER_KIND: options.kind ?? "terminal" }
315
303
  });
304
+ options.onSpawn?.((type) => {
305
+ if (child.connected) child.send({ type }, () => {
306
+ });
307
+ });
308
+ let update = null;
309
+ let checking = false;
310
+ const check = async () => {
311
+ if (checking || stopping || update) return;
312
+ checking = true;
313
+ try {
314
+ const desired = await askTarget(machineId());
315
+ if (!desired?.restartable) return;
316
+ const target = desired.version;
317
+ if (!target || target === candidate.version || readState().bad.includes(target)) return;
318
+ const got = await install(target);
319
+ if (!got.ok) {
320
+ status("Runner \u66F4\u65B0\u4E0B\u8F7D\u5931\u8D25\uFF0C\u7EE7\u7EED\u8FD0\u884C\u5F53\u524D\u7248\u672C\uFF0C\u7A0D\u540E\u91CD\u8BD5\u3002");
321
+ return;
322
+ }
323
+ if (stopping || child.exitCode !== null || child.signalCode !== null) return;
324
+ if ((await askTarget(machineId()))?.version !== target) return;
325
+ update = { version: target, entry: entryOf(got.dir), fresh: !readState().good.includes(target) };
326
+ child.send({ type: "update" }, () => {
327
+ });
328
+ } finally {
329
+ checking = false;
330
+ }
331
+ };
332
+ const poll = setInterval(() => {
333
+ if (connected) void check().catch(() => {
334
+ });
335
+ }, Number(process.env.WILDCRANE_UPDATE_MS) || 6e4);
316
336
  let connected = false;
317
337
  let timer = null;
318
338
  let timedOut = false;
319
339
  let stopping = false;
340
+ let forceKill;
341
+ if (candidate.fresh) {
342
+ timer = setTimeout(() => {
343
+ timedOut = true;
344
+ child.kill("SIGTERM");
345
+ forceKill = setTimeout(() => child.kill("SIGKILL"), 5e3);
346
+ }, Number(process.env.WILDCRANE_CONNECT_MS) || CONNECT_MS);
347
+ }
320
348
  const forward = (signal) => () => {
321
349
  stopping = true;
322
350
  child.kill(signal);
@@ -325,13 +353,11 @@ function runOnce(candidate) {
325
353
  const onTerm = forward("SIGTERM");
326
354
  process.on("SIGINT", onInt);
327
355
  process.on("SIGTERM", onTerm);
356
+ options.signal?.addEventListener("abort", onTerm, { once: true });
357
+ if (options.signal?.aborted) onTerm();
328
358
  child.on("message", (message) => {
329
- if (message.type === "started" && candidate.fresh && !connected) {
330
- timer = setTimeout(() => {
331
- timedOut = true;
332
- child.kill("SIGTERM");
333
- }, Number(process.env.WILDCRANE_CONNECT_MS) || CONNECT_MS);
334
- }
359
+ options.onMessage?.(message);
360
+ if (message.type === "update-deferred") update = null;
335
361
  if (message.type === "waiting" && timer) {
336
362
  clearTimeout(timer);
337
363
  timer = null;
@@ -347,9 +373,13 @@ function runOnce(candidate) {
347
373
  }
348
374
  });
349
375
  child.on("exit", (code, signal) => {
376
+ clearInterval(poll);
377
+ clearTimeout(forceKill);
350
378
  if (timer) clearTimeout(timer);
351
379
  process.off("SIGINT", onInt);
352
380
  process.off("SIGTERM", onTerm);
381
+ options.signal?.removeEventListener("abort", onTerm);
382
+ if (code === EXIT_UPDATE && update && !stopping) return resolve({ update });
353
383
  if (connected || !candidate.fresh || stopping) return resolve({ exit: code ?? (signal ? 1 : 0) });
354
384
  if (timedOut) return resolve({ failed: "timeout" });
355
385
  if (code === 0 || code === EXIT_REFUSED || code === EXIT_OUTDATED) return resolve({ exit: code });
@@ -357,13 +387,18 @@ function runOnce(candidate) {
357
387
  });
358
388
  });
359
389
  }
360
- async function launch(own) {
390
+ async function launch(own, options = {}) {
361
391
  const list = await candidates(own);
362
392
  for (let i = 0; i < list.length; i++) {
393
+ if (options.signal?.aborted) return 0;
363
394
  const candidate = list[i];
364
- const outcome = await runOnce(candidate);
395
+ const outcome = await runOnce(candidate, options);
365
396
  if ("exit" in outcome) return outcome.exit;
366
- if (outcome.failed === "crashed") markBad(candidate.version);
397
+ if ("update" in outcome) {
398
+ list.splice(i + 1, list.length, outcome.update, candidate);
399
+ continue;
400
+ }
401
+ markBad(candidate.version);
367
402
  const next = list[i + 1];
368
403
  if (next) status(t.rolledBack(next.version));
369
404
  }