chatccc 0.2.260 → 0.2.261

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.
@@ -17,15 +17,21 @@ export const DEFAULT_ENGINE_ROOT = join(homedir(), ".chatccc", "engines");
17
17
  export function createCoalescedAsyncTask(task) {
18
18
  let requested = false;
19
19
  let active = null;
20
+ let failed = false;
21
+ let failure;
20
22
  const start = () => {
21
- if (active)
23
+ if (active || failed)
22
24
  return;
23
25
  active = (async () => {
24
26
  while (requested) {
25
27
  requested = false;
26
28
  await task();
27
29
  }
28
- })().finally(() => {
30
+ })().catch((error) => {
31
+ failed = true;
32
+ failure = error;
33
+ requested = false;
34
+ }).finally(() => {
29
35
  active = null;
30
36
  if (requested)
31
37
  start();
@@ -37,13 +43,32 @@ export function createCoalescedAsyncTask(task) {
37
43
  start();
38
44
  },
39
45
  async flush() {
40
- requested = true;
41
- start();
46
+ if (!failed) {
47
+ requested = true;
48
+ start();
49
+ }
42
50
  while (active)
43
51
  await active;
52
+ if (failed)
53
+ throw failure;
44
54
  },
45
55
  };
46
56
  }
57
+ async function renameWithTransientRetry(source, destination) {
58
+ const retriable = new Set(["EPERM", "EBUSY", "EACCES"]);
59
+ for (let attempt = 0;; attempt += 1) {
60
+ try {
61
+ await rename(source, destination);
62
+ return;
63
+ }
64
+ catch (error) {
65
+ const code = error.code ?? "";
66
+ if (!retriable.has(code) || attempt >= 5)
67
+ throw error;
68
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 20 * (2 ** attempt)));
69
+ }
70
+ }
71
+ }
47
72
  export function resolveNpmInvocation() {
48
73
  const candidates = [
49
74
  process.env.npm_execpath,
@@ -63,6 +88,7 @@ export class EngineManager {
63
88
  verifyRuntime;
64
89
  nodeVersion;
65
90
  activeInstalls = new Map();
91
+ persistTails = new Map();
66
92
  constructor(options) {
67
93
  this.rootDir = options.rootDir ?? DEFAULT_ENGINE_ROOT;
68
94
  for (const spec of options.specs)
@@ -208,7 +234,7 @@ export class EngineManager {
208
234
  const pointerPath = join(this.engineDir(spec), "current.json");
209
235
  const temporaryPointer = `${pointerPath}.${job.jobId}.tmp`;
210
236
  await writeFile(temporaryPointer, JSON.stringify(pointer, null, 2) + "\n", "utf8");
211
- await rename(temporaryPointer, pointerPath);
237
+ await renameWithTransientRetry(temporaryPointer, pointerPath);
212
238
  await update(100, `已切换到 v${spec.version}`);
213
239
  });
214
240
  await this.runStep(spec, job, "cleanup", async (update) => {
@@ -275,10 +301,27 @@ export class EngineManager {
275
301
  }
276
302
  async persistJob(spec, job) {
277
303
  const path = this.jobPath(spec);
278
- await mkdir(dirname(path), { recursive: true });
279
- const temporary = `${path}.${process.pid}.tmp`;
280
- await writeFile(temporary, JSON.stringify(job, null, 2) + "\n", "utf8");
281
- await rename(temporary, path);
304
+ const contents = JSON.stringify(job, null, 2) + "\n";
305
+ const previous = this.persistTails.get(spec.id) ?? Promise.resolve();
306
+ const operation = previous.catch(() => { }).then(async () => {
307
+ await mkdir(dirname(path), { recursive: true });
308
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
309
+ try {
310
+ await writeFile(temporary, contents, "utf8");
311
+ await renameWithTransientRetry(temporary, path);
312
+ }
313
+ finally {
314
+ await rm(temporary, { force: true }).catch(() => { });
315
+ }
316
+ });
317
+ this.persistTails.set(spec.id, operation);
318
+ try {
319
+ await operation;
320
+ }
321
+ finally {
322
+ if (this.persistTails.get(spec.id) === operation)
323
+ this.persistTails.delete(spec.id);
324
+ }
282
325
  }
283
326
  async readJob(spec) {
284
327
  try {
@@ -359,12 +402,12 @@ async function defaultInstallPackages(installationDir, spec, onProgress) {
359
402
  });
360
403
  child.once("close", (code) => {
361
404
  clearInterval(timer);
362
- void progressReporter.flush().catch(() => { }).finally(() => {
405
+ void progressReporter.flush().then(() => {
363
406
  if (code === 0)
364
407
  resolvePromise();
365
408
  else
366
409
  reject(new Error(`npm install 失败(退出码 ${String(code)}):${stderr.trim().split(/\r?\n/).slice(-6).join(" | ").slice(0, 1000)}`));
367
- });
410
+ }).catch(reject);
368
411
  });
369
412
  });
370
413
  }
@@ -2492,7 +2492,7 @@ function engineRefreshStatus(engineId) {
2492
2492
  renderEngineStatus(engineId, status);
2493
2493
  if (status.running || (status.job && status.job.state === 'running')) scheduleEnginePoll(engineId);
2494
2494
  return status;
2495
- });
2495
+ }).catch(function(){ scheduleEnginePoll(engineId); });
2496
2496
  }
2497
2497
 
2498
2498
  function installEngine(engineId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatccc",
3
- "version": "0.2.260",
3
+ "version": "0.2.261",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",