@chatcode/chatcode-cli 3.0.4-rc.3 → 3.0.4-rc.4

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,7 +1,7 @@
1
1
  {
2
2
  "name": "@chatcode/chatcode-cli",
3
3
  "description": "ChatCode CLI: TUI-first coding agent, Web launcher, profiles, and legacy DSH-compatible plugins",
4
- "version": "3.0.4-rc.3",
4
+ "version": "3.0.4-rc.4",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -97,44 +97,44 @@
97
97
  "execa": "^10.0.0",
98
98
  "ws": "8.21.0",
99
99
  "@deepseek-ai/dsh-acp": "0.1.7-rc.2",
100
- "@deepseek-ai/dsh-agent": "0.1.7-rc.2",
101
- "@deepseek-ai/dsh-agent-loop": "0.1.7-rc.2",
102
100
  "@deepseek-ai/dsh-agent-loop-testkit": "0.1.7-rc.2",
103
- "@deepseek-ai/dsh-credentials-local": "0.1.7-rc.2",
101
+ "@deepseek-ai/dsh-agent-loop": "0.1.7-rc.2",
104
102
  "@deepseek-ai/dsh-bash-local": "0.1.7-rc.2",
103
+ "@deepseek-ai/dsh-attachment-local": "0.1.7-rc.2",
104
+ "@deepseek-ai/dsh-credentials-local": "0.1.7-rc.2",
105
105
  "@deepseek-ai/dsh-deepseek-llm-api-extensions": "0.1.7-rc.2",
106
106
  "@deepseek-ai/dsh-experimental-agent-team": "0.1.7-rc.2",
107
+ "@deepseek-ai/dsh-agent": "0.1.7-rc.2",
107
108
  "@deepseek-ai/dsh-experimental-ptc-runtime-python": "0.1.7-rc.2",
108
- "@deepseek-ai/dsh-fs-sandbox": "0.1.7-rc.2",
109
- "@deepseek-ai/dsh-host-webserver": "0.1.7-rc.2",
110
- "@deepseek-ai/dsh-llm": "0.1.7-rc.2",
111
109
  "@deepseek-ai/dsh-experimental-tool-agent-team": "0.1.7-rc.2",
112
110
  "@deepseek-ai/dsh-fs-observation-policy": "0.1.7-rc.2",
113
- "@deepseek-ai/dsh-attachment-local": "0.1.7-rc.2",
111
+ "@deepseek-ai/dsh-host-webserver": "0.1.7-rc.2",
112
+ "@deepseek-ai/dsh-fs-sandbox": "0.1.7-rc.2",
113
+ "@deepseek-ai/dsh-llm": "0.1.7-rc.2",
114
114
  "@deepseek-ai/dsh-host-frontend-static": "0.1.7-rc.2",
115
115
  "@deepseek-ai/dsh-llm-deepseek": "0.1.7-rc.2",
116
+ "@deepseek-ai/dsh-llm-deepseek-account": "0.1.7-rc.2",
117
+ "@deepseek-ai/dsh-llm-mock-server": "0.1.7-rc.2",
116
118
  "@deepseek-ai/dsh-llm-pi-ai": "0.1.7-rc.2",
119
+ "@deepseek-ai/dsh-llm-replay": "0.1.7-rc.2",
117
120
  "@deepseek-ai/dsh-llm-deepseek-api-key": "0.1.7-rc.2",
118
- "@deepseek-ai/dsh-llm-mock-server": "0.1.7-rc.2",
119
- "@deepseek-ai/dsh-llm-deepseek-account": "0.1.7-rc.2",
120
121
  "@deepseek-ai/dsh-plugin-package-inventory-deepseek": "0.1.7-rc.2",
121
- "@deepseek-ai/dsh-llm-replay": "0.1.7-rc.2",
122
- "@deepseek-ai/dsh-sandbox-policy": "0.1.7-rc.2",
123
122
  "@deepseek-ai/dsh-sandbox-local": "0.1.7-rc.2",
124
123
  "@deepseek-ai/dsh-loader-smoke": "0.1.7-rc.2",
124
+ "@deepseek-ai/dsh-sandbox-policy": "0.1.7-rc.2",
125
125
  "@deepseek-ai/dsh-session": "0.1.7-rc.2",
126
- "@deepseek-ai/dsh-sdk-client": "0.1.7-rc.2",
127
- "@deepseek-ai/dsh-session-checkpoint-policy": "0.1.7-rc.2",
128
126
  "@deepseek-ai/dsh-session-log-deepseek": "0.1.7-rc.2",
129
- "@deepseek-ai/dsh-subagent": "0.1.7-rc.2",
130
- "@deepseek-ai/dsh-session-persistence-jsonl": "0.1.7-rc.2",
127
+ "@deepseek-ai/dsh-session-checkpoint-policy": "0.1.7-rc.2",
131
128
  "@deepseek-ai/dsh-session-query": "0.1.7-rc.2",
132
- "@deepseek-ai/dsh-subagent-spawn-in-process": "0.1.7-rc.2",
129
+ "@deepseek-ai/dsh-session-persistence-jsonl": "0.1.7-rc.2",
133
130
  "@deepseek-ai/dsh-shell-env": "0.1.7-rc.2",
134
- "@deepseek-ai/dsh-subprocess-local": "0.1.7-rc.2",
135
- "@deepseek-ai/dsh-tools": "0.1.7-rc.2",
131
+ "@deepseek-ai/dsh-sdk-client": "0.1.7-rc.2",
132
+ "@deepseek-ai/dsh-subagent": "0.1.7-rc.2",
136
133
  "@deepseek-ai/dsh-subagent-fork-in-process": "0.1.7-rc.2",
137
134
  "@deepseek-ai/dsh-settings": "0.1.7-rc.2",
135
+ "@deepseek-ai/dsh-subagent-spawn-in-process": "0.1.7-rc.2",
136
+ "@deepseek-ai/dsh-subprocess-local": "0.1.7-rc.2",
137
+ "@deepseek-ai/dsh-tools": "0.1.7-rc.2",
138
138
  "@deepseek-ai/dsh-user-approval": "0.1.7-rc.2",
139
139
  "@deepseek-ai/dsh-system-prompt": "0.1.7-rc.2"
140
140
  },
@@ -282,6 +282,6 @@
282
282
  "@deepseek-ai/node-addon-system",
283
283
  "@deepseek-ai/schemastery"
284
284
  ],
285
- "fileCount": 3610,
286
- "byteCount": 68762823
285
+ "fileCount": 3611,
286
+ "byteCount": 68767974
287
287
  }
@@ -1,8 +1,10 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
- import { link, lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
2
+ import { link, lstat, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
3
3
  import { dirname, win32 } from "node:path";
4
4
  import { execFile } from "node:child_process";
5
5
  import { promisify } from "node:util";
6
+ import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync } from "node:fs";
7
+ import { constants as constants$1 } from "node:os";
6
8
  //#region lib/types/windows-lock.js
7
9
  /** Exclusive Windows inspection for an empty or zero-filled lock left by a failed creator. */
8
10
  const execute = promisify(execFile);
@@ -46,6 +48,68 @@ async function readUnopenedWindowsLock(path) {
46
48
  }
47
49
  }
48
50
  //#endregion
51
+ //#region lib/types/linux-native.js
52
+ /** Lazy Linux file-lease inspection for incomplete legacy writer records. */
53
+ let bindings;
54
+ const F_SETOWN = 8;
55
+ const F_SETSIG = 10;
56
+ const F_SETLEASE = 1024;
57
+ const F_GETLEASE = 1025;
58
+ const F_RDLCK = 0;
59
+ const SC_CLK_TCK = 2;
60
+ async function linux() {
61
+ return bindings ??= (async () => {
62
+ const libc = (await import("koffi")).default.load(null);
63
+ const fcntl = libc.func("fcntl", "int", [
64
+ "int",
65
+ "int",
66
+ "int"
67
+ ]);
68
+ const sysconf = libc.func("sysconf", "long", ["int"]);
69
+ return {
70
+ fcntl,
71
+ clockTicks: () => sysconf(SC_CLK_TCK)
72
+ };
73
+ })();
74
+ }
75
+ /**
76
+ * Read the Linux userspace clock-tick rate used by /proc process start times.
77
+ * @returns ticks per second, or undefined when libc cannot supply the rate.
78
+ */
79
+ async function linuxClockTicks() {
80
+ try {
81
+ const value = (await linux()).clockTicks();
82
+ return Number.isSafeInteger(value) && value > 0 ? value : void 0;
83
+ } catch (error) {
84
+ return;
85
+ }
86
+ }
87
+ /**
88
+ * Read a small regular lock only after the kernel excludes open writers.
89
+ * @param path - existing lock path protected by the caller's takeover claim.
90
+ * @returns the unchanged inode's contents, or undefined when inspection is inconclusive.
91
+ */
92
+ async function readClosedLinuxLock(path) {
93
+ let fd;
94
+ try {
95
+ const api = await linux();
96
+ fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
97
+ const held = fstatSync(fd);
98
+ if (!held.isFile() || held.size > 128) return void 0;
99
+ if (api.fcntl(fd, F_SETSIG, constants$1.signals.SIGURG) !== 0) return void 0;
100
+ if (api.fcntl(fd, F_SETLEASE, F_RDLCK) !== 0) return void 0;
101
+ if (api.fcntl(fd, F_SETOWN, 0) !== 0) return void 0;
102
+ const record = readFileSync(fd, "utf8");
103
+ const current = lstatSync(path);
104
+ if (current.dev !== held.dev || current.ino !== held.ino || api.fcntl(fd, F_GETLEASE, 0) !== F_RDLCK) return void 0;
105
+ return record;
106
+ } catch (error) {
107
+ return;
108
+ } finally {
109
+ if (fd !== void 0) closeSync(fd);
110
+ }
111
+ }
112
+ //#endregion
49
113
  //#region lib/types/linux-lock.js
50
114
  /** Linux writer identity and complete-record publication; other platforms retain PID records. */
51
115
  const RECORD = /^(\d+)\r?\n(?:linux ([a-f0-9-]{36}) (\d+) ([a-f0-9]{24})\n)?$/u;
@@ -55,9 +119,14 @@ async function identity(pid) {
55
119
  try {
56
120
  const [boot, stat] = await Promise.all([readFile("/proc/sys/kernel/random/boot_id", "utf8"), readFile(`/proc/${pid}/stat`, "utf8")]);
57
121
  const end = stat.lastIndexOf(")");
58
- const start = end < 0 ? void 0 : stat.slice(end + 2).trim().split(/\s+/u)[19];
122
+ const fields = end < 0 ? [] : stat.slice(end + 2).trim().split(/\s+/u);
123
+ const start = fields[19];
59
124
  if (!/^[a-f0-9-]{36}$/u.test(boot.trim()) || start === void 0 || !/^\d+$/u.test(start)) return void 0;
60
- return `${boot.trim()} ${start}`;
125
+ return {
126
+ id: `${boot.trim()} ${start}`,
127
+ start: Number(start),
128
+ state: fields[0] ?? ""
129
+ };
61
130
  } catch (error) {
62
131
  return;
63
132
  }
@@ -69,7 +138,7 @@ async function identity(pid) {
69
138
  async function writerLockRecord() {
70
139
  const pid = `${process.pid}\n`;
71
140
  if (process.platform !== "linux") return pid;
72
- const current = await (ownIdentity ??= identity(process.pid));
141
+ const current = await (ownIdentity ??= identity(process.pid).then((value) => value?.id));
73
142
  return current === void 0 ? pid : `${pid}linux ${current} ${randomBytes(12).toString("hex")}\n`;
74
143
  }
75
144
  /**
@@ -86,7 +155,7 @@ async function createWriterLock(path, record) {
86
155
  });
87
156
  return;
88
157
  }
89
- const temp = `${path}.${nonce}.tmp`;
158
+ const temp = `${path}.${randomBytes(12).toString("hex")}.tmp`;
90
159
  try {
91
160
  await writeFile(temp, record, {
92
161
  mode: 384,
@@ -94,15 +163,18 @@ async function createWriterLock(path, record) {
94
163
  });
95
164
  await link(temp, path);
96
165
  } finally {
97
- await rm(temp, { force: true });
166
+ try {
167
+ await rm(temp, { force: true });
168
+ } catch (error) {}
98
169
  }
99
170
  }
100
171
  /**
101
172
  * Determine whether a recorded writer exited, including Linux PID reuse and a previous boot.
102
173
  * @param record - untrusted contents of an existing lock file.
103
- * @returns true only for ESRCH or a different observable Linux process identity.
174
+ * @param path - optional lock path for checking legacy PID records against process creation.
175
+ * @returns true for an exited process, changed Linux identity, or a legacy record predating its current process.
104
176
  */
105
- async function writerExited(record) {
177
+ async function writerExited(record, path) {
106
178
  const match = RECORD.exec(record);
107
179
  if (match === null) return false;
108
180
  const pid = Number(match[1]);
@@ -112,14 +184,29 @@ async function writerExited(record) {
112
184
  } catch (error) {
113
185
  return error.code === "ESRCH";
114
186
  }
115
- if (process.platform !== "linux" || match[2] === void 0) return false;
187
+ if (process.platform !== "linux") return false;
116
188
  const current = await identity(pid);
117
- return current !== void 0 && current !== `${match[2]} ${match[3]}`;
189
+ if (current === void 0) return false;
190
+ if (current.state === "Z" || current.state === "X") return true;
191
+ if (match[2] !== void 0) return current.id !== `${match[2]} ${match[3]}`;
192
+ if (path === void 0) return false;
193
+ try {
194
+ const [metadata, kernelStat, ticks] = await Promise.all([
195
+ stat(path),
196
+ readFile("/proc/stat", "utf8"),
197
+ linuxClockTicks()
198
+ ]);
199
+ const bootSeconds = /^btime (\d+)$/mu.exec(kernelStat)?.[1];
200
+ if (bootSeconds === void 0 || ticks === void 0) return false;
201
+ return Number(bootSeconds) * 1e3 + current.start / ticks * 1e3 > Math.max(metadata.mtimeMs, metadata.ctimeMs, metadata.birthtimeMs) + 2e3;
202
+ } catch (error) {
203
+ return false;
204
+ }
118
205
  }
119
206
  //#endregion
120
207
  //#region lib/types/index.js
121
208
  /**
122
- * Zero-dependency atomic file replacement and writer coordination.
209
+ * Atomic file replacement and writer coordination.
123
210
  * `writeFileAtomic` writes a random-suffix sibling with exclusive create and
124
211
  * the caller's permission bits, then renames it over the target, so readers
125
212
  * observe either the old or the new complete content and a replaced file ends
@@ -129,8 +216,9 @@ async function writerExited(record) {
129
216
  * replaced; readers stay lock-free because the rename commit is atomic. A lock
130
217
  * whose recorded holder process no longer exists is taken over. Windows also
131
218
  * recovers empty or zero-filled records after an exclusive read proves no
132
- * creator still has the lock file open. Linux records also distinguish
133
- * a previous boot or reused PID through kernel process identity.
219
+ * creator still has the lock file open. Linux uses a read lease to exclude
220
+ * unfinished writers, and kernel process identity to distinguish a previous
221
+ * boot, reused PID, or zombie process.
134
222
  * @module @deepseek-ai/dsh-atomic-write
135
223
  */
136
224
  const WINDOWS_TRANSIENT_FILE_ERRORS = new Set([
@@ -207,12 +295,12 @@ async function isLockContention(error, lockPath) {
207
295
  }
208
296
  }
209
297
  /** Whether the recorded writer is absent or has a different observable Linux identity. */
210
- async function holderExited(record) {
211
- return writerExited(record);
298
+ async function holderExited(record, path) {
299
+ return writerExited(record, path);
212
300
  }
213
301
  /** A crash can leave an empty or zero-filled record before the holder's PID reaches disk. */
214
- function damagedRecord(record) {
215
- return record.length <= 64 && /^\0*$/.test(record);
302
+ function damagedRecord(record, limit = 64) {
303
+ return record.length <= limit && /^\0*$/.test(record);
216
304
  }
217
305
  /** The lock file's content, or undefined when it cannot be read. */
218
306
  async function readLockRecord(lockPath) {
@@ -223,11 +311,11 @@ async function readLockRecord(lockPath) {
223
311
  }
224
312
  }
225
313
  /**
226
- * Remove an exited holder's lock or a closed, damaged Windows lock. Contenders that read the
314
+ * Remove an exited holder's lock or a closed, damaged Windows or Linux lock. Contenders that read the
227
315
  * same record serialize on a claim file named after it. Under the claim, the
228
316
  * claimant re-reads the lock and probes its PID again, and removes it only
229
- * when it still holds that record and that PID is still gone. A damaged Windows
230
- * record instead requires an exclusive read after claiming it. The record's
317
+ * when it still holds that record and that PID is still gone. A damaged
318
+ * record instead requires kernel-backed inspection after claiming it. The record's
231
319
  * holder can no longer release it, and no other contender can remove it
232
320
  * without the claim, so a removal never deletes a lock another contender
233
321
  * acquired after the dead holder's, including one whose holder reused the PID.
@@ -236,24 +324,25 @@ async function readLockRecord(lockPath) {
236
324
  async function takeOverExitedLock(lockPath) {
237
325
  const record = await readLockRecord(lockPath);
238
326
  if (record === void 0) return false;
239
- const damaged = process.platform === "win32" && damagedRecord(record);
240
- if (!damaged && !await holderExited(record)) return false;
327
+ const damaged = (process.platform === "win32" || process.platform === "linux") && damagedRecord(record, process.platform === "linux" ? 128 : 64);
328
+ const partialLinuxPid = process.platform === "linux" && /^\d{1,10}$/u.test(record);
329
+ if (!damaged && !partialLinuxPid && !await holderExited(record, lockPath)) return false;
241
330
  const claim = `${lockPath}.takeover-${createHash("sha256").update(record).digest("hex").slice(0, 16)}`;
242
331
  try {
243
- await writeFile(claim, `${process.pid}\n`, {
244
- mode: 384,
245
- flag: "wx"
246
- });
332
+ await createWriterLock(claim, await writerLockRecord());
247
333
  } catch (error) {
248
334
  const code = error.code;
249
- if (code === "EEXIST" || code === "EPERM") return false;
335
+ if (code === "EEXIST" || code === "EPERM") {
336
+ if (await takeOverExitedLock(claim)) return takeOverExitedLock(lockPath);
337
+ return false;
338
+ }
250
339
  throw error;
251
340
  }
252
341
  try {
253
342
  if (await readLockRecord(lockPath) !== record) return false;
254
- if (damaged) {
255
- if (await readUnopenedWindowsLock(lockPath) !== record) return false;
256
- } else if (!await holderExited(record)) return false;
343
+ if (damaged || partialLinuxPid) {
344
+ if ((process.platform === "win32" ? await readUnopenedWindowsLock(lockPath) : await readClosedLinuxLock(lockPath)) !== record) return false;
345
+ } else if (!await holderExited(record, lockPath)) return false;
257
346
  try {
258
347
  await rm(lockPath, { force: true });
259
348
  } catch (error) {
@@ -295,10 +384,13 @@ const DEFAULT_LOCK_WAIT_MS = 2e3;
295
384
  * user (`EPERM`) or whose record is incomplete, is waited for. On Windows,
296
385
  * empty or zero-filled records of at most 64 bytes can also be recovered after
297
386
  * exclusive inspection proves no creator still has the file open. A failed
298
- * inspection preserves the lock. Windows lock release retries transient file
299
- * interference for a bounded interval. Contention backs
387
+ * inspection preserves the lock. Linux also recovers empty or zero-filled
388
+ * records of at most 128 bytes and unfinished PID records after a read lease
389
+ * excludes open writers. Dead takeover claimants are recovered automatically.
390
+ * Windows lock release retries transient file interference for a bounded interval. Contention backs
300
391
  * off exponentially and times out after the deadline. Linux identity records
301
- * distinguish PID reuse and a previous boot; legacy PID-only records cannot.
392
+ * distinguish PID reuse, a previous boot, and zombies. Legacy PID-only records
393
+ * are recovered when the current process began after the lock's file timestamps.
302
394
  * Missing or inaccessible identity metadata never authorizes takeover. Takeover
303
395
  * proves only that the recorded process exited: an operation that starts other
304
396
  * writers must stop them with it or leave its successor a way to find them.
@@ -326,7 +418,12 @@ async function withFileLock(filename, operation, options) {
326
418
  retriedUnconfirmedPermissionError = true;
327
419
  } else if (await takeOverExitedLock(lockPath)) continue;
328
420
  }
329
- if (Date.now() >= deadline) throw new Error(`atomic-write: timed out waiting for the writer lock at ${lockPath}`);
421
+ if (Date.now() >= deadline) {
422
+ const held = await readLockRecord(lockPath);
423
+ const pid = held === void 0 ? void 0 : /^(\d+)\r?\n/u.exec(held)?.[1];
424
+ const state = held === void 0 ? "unreadable" : held === "" ? "empty" : /^\0+$/u.test(held) ? `zero-filled, ${String(held.length)} bytes` : pid === void 0 ? `incomplete or unrecognized, ${String(held.length)} bytes` : `PID ${pid}`;
425
+ throw new Error(`atomic-write: timed out waiting for the writer lock at ${lockPath} (record: ${state})`);
426
+ }
330
427
  await new Promise((resolve) => setTimeout(resolve, delay));
331
428
  delay = Math.min(delay * 2, LOCK_RETRY_MAX_MS);
332
429
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Zero-dependency atomic file replacement and writer coordination.
2
+ * Atomic file replacement and writer coordination.
3
3
  * `writeFileAtomic` writes a random-suffix sibling with exclusive create and
4
4
  * the caller's permission bits, then renames it over the target, so readers
5
5
  * observe either the old or the new complete content and a replaced file ends
@@ -9,8 +9,9 @@
9
9
  * replaced; readers stay lock-free because the rename commit is atomic. A lock
10
10
  * whose recorded holder process no longer exists is taken over. Windows also
11
11
  * recovers empty or zero-filled records after an exclusive read proves no
12
- * creator still has the lock file open. Linux records also distinguish
13
- * a previous boot or reused PID through kernel process identity.
12
+ * creator still has the lock file open. Linux uses a read lease to exclude
13
+ * unfinished writers, and kernel process identity to distinguish a previous
14
+ * boot, reused PID, or zombie process.
14
15
  * @module @deepseek-ai/dsh-atomic-write
15
16
  */
16
17
  /**
@@ -73,10 +74,13 @@ export interface FileLockOptions {
73
74
  * user (`EPERM`) or whose record is incomplete, is waited for. On Windows,
74
75
  * empty or zero-filled records of at most 64 bytes can also be recovered after
75
76
  * exclusive inspection proves no creator still has the file open. A failed
76
- * inspection preserves the lock. Windows lock release retries transient file
77
- * interference for a bounded interval. Contention backs
77
+ * inspection preserves the lock. Linux also recovers empty or zero-filled
78
+ * records of at most 128 bytes and unfinished PID records after a read lease
79
+ * excludes open writers. Dead takeover claimants are recovered automatically.
80
+ * Windows lock release retries transient file interference for a bounded interval. Contention backs
78
81
  * off exponentially and times out after the deadline. Linux identity records
79
- * distinguish PID reuse and a previous boot; legacy PID-only records cannot.
82
+ * distinguish PID reuse, a previous boot, and zombies. Legacy PID-only records
83
+ * are recovered when the current process began after the lock's file timestamps.
80
84
  * Missing or inaccessible identity metadata never authorizes takeover. Takeover
81
85
  * proves only that the recorded process exited: an operation that starts other
82
86
  * writers must stop them with it or leave its successor a way to find them.
@@ -12,7 +12,8 @@ export declare function createWriterLock(path: string, record: string): Promise<
12
12
  /**
13
13
  * Determine whether a recorded writer exited, including Linux PID reuse and a previous boot.
14
14
  * @param record - untrusted contents of an existing lock file.
15
- * @returns true only for ESRCH or a different observable Linux process identity.
15
+ * @param path - optional lock path for checking legacy PID records against process creation.
16
+ * @returns true for an exited process, changed Linux identity, or a legacy record predating its current process.
16
17
  */
17
- export declare function writerExited(record: string): Promise<boolean>;
18
+ export declare function writerExited(record: string, path?: string): Promise<boolean>;
18
19
  //# sourceMappingURL=linux-lock.d.ts.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Read the Linux userspace clock-tick rate used by /proc process start times.
3
+ * @returns ticks per second, or undefined when libc cannot supply the rate.
4
+ */
5
+ export declare function linuxClockTicks(): Promise<number | undefined>;
6
+ /**
7
+ * Read a small regular lock only after the kernel excludes open writers.
8
+ * @param path - existing lock path protected by the caller's takeover claim.
9
+ * @returns the unchanged inode's contents, or undefined when inspection is inconclusive.
10
+ */
11
+ export declare function readClosedLinuxLock(path: string): Promise<string | undefined>;
12
+ //# sourceMappingURL=linux-native.d.ts.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-atomic-write",
3
- "description": "Zero-dependency atomic file replacement: exclusive-create random-suffix temp + rename carrying the caller-stated permissions (writeFileAtomic)",
3
+ "description": "Atomic file replacement and cross-process writer coordination with automatic orphan recovery",
4
4
  "version": "0.1.7-rc.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -26,6 +26,9 @@
26
26
  "lib/types/**/*.d.ts"
27
27
  ],
28
28
  "license": "MIT",
29
+ "dependencies": {
30
+ "koffi": "^3.1.0"
31
+ },
29
32
  "peerDependencies": {
30
33
  "@deepseek-ai/cordis": "workspace:~"
31
34
  },
@@ -4125,7 +4125,7 @@ window.__ModuleLoader__.load({
4125
4125
  for (const listener of listeners) listener();
4126
4126
  };
4127
4127
  /** @returns the client identity for one account call, read at call time so it carries the language and zone in effect then. */
4128
- const client = () => accountClientMetadata(ctx.locale.getSnapshot().active, "3.0.4-rc.3");
4128
+ const client = () => accountClientMetadata(ctx.locale.getSnapshot().active, "3.0.4-rc.4");
4129
4129
  const notices = createBonusNoticeController({
4130
4130
  ackRetryDelayMs: config.bonusAckRetryDelayMs,
4131
4131
  ackRetryMaxDelayMs: config.bonusAckRetryMaxDelayMs,
@@ -4256,7 +4256,7 @@ window.__ModuleLoader__.load({
4256
4256
  refreshAccount,
4257
4257
  contactUs() {
4258
4258
  const url = contactUrl(config, {
4259
- version: "3.0.4-rc.3",
4259
+ version: "3.0.4-rc.4",
4260
4260
  locale: ctx.locale.getSnapshot().active === "zh" ? "zh-CN" : "en",
4261
4261
  width: window.screen.width,
4262
4262
  height: window.screen.height,
@@ -657,7 +657,7 @@ window.__ModuleLoader__.load({
657
657
  function CurrentVersionRow({ t }) {
658
658
  return (0, react_jsx_runtime.jsx)("div", {
659
659
  className: CurrentVersionRow_module_css_default.row,
660
- children: t("general.currentVersion", { version: "3.0.4-rc.3" })
660
+ children: t("general.currentVersion", { version: "3.0.4-rc.4" })
661
661
  });
662
662
  }
663
663
  //#endregion
@@ -167,7 +167,7 @@ window.__ModuleLoader__.load({
167
167
  const SCROLLBAR_LINGER_MS = 2e3;
168
168
  /** Format complete-build metadata for the local brand badge. */
169
169
  function localBuildVersion() {
170
- return `3.0.4-rc.3-4d610ce-dirty`;
170
+ return `3.0.4-rc.4-b93a39c-dirty`;
171
171
  }
172
172
  /** Each panel row subscribes only to its own selection state. */
173
173
  function PanelRow({ id, label, wide, usePanelInfo, selectPanel, renderSlot }) {