jinzd-ai-cli 0.4.307 → 0.4.309

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 (96) hide show
  1. package/README.md +2 -1
  2. package/README.zh-CN.md +1 -1
  3. package/THIRD_PARTY_NOTICES.md +3 -0
  4. package/dist/{agent-client-6GX6QQDU.js → agent-client-TIEAYZXV.js} +4 -1
  5. package/dist/{auth-PE3Z5OHS.js → auth-QTQNZ6IX.js} +2 -2
  6. package/dist/{batch-HMADXNOD.js → batch-H4WWSZQ3.js} +3 -3
  7. package/dist/{chat-index-2D5GK2DB.js → chat-index-7CLR4CBG.js} +4 -3
  8. package/dist/{chat-index-DGUOUUQW.js → chat-index-RLVLQRRC.js} +3 -2
  9. package/dist/{chunk-CIM2PV2F.js → chunk-2E5W2E64.js} +2 -2
  10. package/dist/chunk-2XMUCC42.js +67 -0
  11. package/dist/{chunk-VAV2IJ3R.js → chunk-3CNPJLVI.js} +1 -1
  12. package/dist/{chunk-KRCYR46A.js → chunk-3GEO2LPO.js} +264 -55
  13. package/dist/{chunk-OAXLMF3U.js → chunk-3YVNDYOY.js} +1 -1
  14. package/dist/{chunk-5GQL4TSZ.js → chunk-4BXTGXZ6.js} +2 -2
  15. package/dist/{chunk-RYQBBEMM.js → chunk-4PR5AVJ5.js} +115 -19
  16. package/dist/{chunk-GHWZVPUV.js → chunk-6JHACXB3.js} +7 -5
  17. package/dist/{chunk-IUVDZVZ4.js → chunk-AXTLYOA7.js} +24 -17
  18. package/dist/{chunk-XJOK5353.js → chunk-B3MPYI4E.js} +2 -2
  19. package/dist/chunk-BMLQPSHP.js +69 -0
  20. package/dist/{chunk-NRSYBKWA.js → chunk-C7CKDFJS.js} +1 -1
  21. package/dist/{chunk-SJFZPS65.js → chunk-EAVX3RLD.js} +1 -1
  22. package/dist/{chunk-VXEF2SWG.js → chunk-EHMMBIZT.js} +16 -12
  23. package/dist/chunk-FVXNEZEW.js +161 -0
  24. package/dist/{chunk-AHL6KOGM.js → chunk-HXINWG37.js} +2 -2
  25. package/dist/{chunk-3CKJOTVM.js → chunk-IW6Q66WS.js} +4 -4
  26. package/dist/chunk-JJFJWD35.js +162 -0
  27. package/dist/{chunk-NYNSYLFY.js → chunk-JL44HGZC.js} +1 -1
  28. package/dist/{chunk-3TBXHLSC.js → chunk-K4F7QEGW.js} +1 -1
  29. package/dist/{chunk-NUJTNH76.js → chunk-LBWKMTT3.js} +1 -1
  30. package/dist/{chunk-YKJO3SI4.js → chunk-LONB36SP.js} +16 -3
  31. package/dist/{chunk-WQ3OMEC4.js → chunk-M6HLYG33.js} +1 -1
  32. package/dist/{chunk-I4GQRMFV.js → chunk-M6YV5KSS.js} +6 -4
  33. package/dist/{chunk-KBDKKHXB.js → chunk-O6CGOVD4.js} +2 -2
  34. package/dist/{chunk-L2RJPVHV.js → chunk-O7MMYJAD.js} +266 -512
  35. package/dist/chunk-QMFMST2T.js +39 -0
  36. package/dist/{chunk-AQF6IXSR.js → chunk-RBCKX2FZ.js} +11 -6
  37. package/dist/{chunk-4W6VDVQU.js → chunk-RLERORUI.js} +246 -56
  38. package/dist/{chunk-MI4SFZGX.js → chunk-RMEJR3AW.js} +5 -2
  39. package/dist/{chunk-UFW4S3RU.js → chunk-RVMCY6DP.js} +1 -1
  40. package/dist/{chunk-LF67HWWR.js → chunk-RWRZVQCB.js} +1 -1
  41. package/dist/{chunk-NQJSPMY2.js → chunk-SCG2VBPB.js} +4 -4
  42. package/dist/chunk-SXIJLSVF.js +41 -0
  43. package/dist/{chunk-YFDQIUQ3.js → chunk-T42GXE7Y.js} +18 -18
  44. package/dist/chunk-TDQW2YHK.js +65 -0
  45. package/dist/{chunk-45UVJXFQ.js → chunk-W2UOVGRC.js} +1 -1
  46. package/dist/{ci-QD2Q7NWY.js → ci-H6GPHG4T.js} +7 -7
  47. package/dist/{ci-format-42XIZO5P.js → ci-format-U47GSGYU.js} +2 -2
  48. package/dist/{constants-5CMEFZQB.js → constants-GYJTB6KI.js} +1 -1
  49. package/dist/{constants-NMASHFDL.js → constants-SNJR6546.js} +1 -1
  50. package/dist/doctor-cli-J6LAQ5LU.js +18 -0
  51. package/dist/electron-server.js +922 -842
  52. package/dist/file-checkpoint-64A2AXHE.js +7 -0
  53. package/dist/{file-checkpoint-NKBHGC7L.js → file-checkpoint-VVPUJNCI.js} +2 -1
  54. package/dist/{hub-ZLF6GMQD.js → hub-G5FMMTVZ.js} +3 -3
  55. package/dist/{hub-server-CBKCP5VD.js → hub-server-JLC7CNP7.js} +55 -17
  56. package/dist/index.js +48 -42
  57. package/dist/{indexer-P3FPRTPJ.js → indexer-GKIWSSRP.js} +3 -3
  58. package/dist/indexer-VRXDZ74A.js +10 -0
  59. package/dist/{persist-S77WRVLD.js → persist-ZZXRWFBX.js} +4 -4
  60. package/dist/{persistent-memory-GJE7HKVK.js → persistent-memory-EXJBXL33.js} +3 -3
  61. package/dist/{persistent-memory-MBSBTBGY.js → persistent-memory-LEE454DW.js} +3 -3
  62. package/dist/{pr-SXRW24JG.js → pr-6PUHVJ6K.js} +7 -7
  63. package/dist/{project-trust-MUG325AW.js → project-trust-6CJB5ZMX.js} +1 -1
  64. package/dist/{project-trust-NKYHL3VZ.js → project-trust-KGG2BWKC.js} +1 -1
  65. package/dist/run-tests-2C7MWZBN.js +12 -0
  66. package/dist/run-tests-33ZVQE3Q.js +13 -0
  67. package/dist/{semantic-7GM5M4MU.js → semantic-N227CLSC.js} +4 -4
  68. package/dist/{semantic-QOBAXEQ4.js → semantic-TRQAZSRY.js} +4 -4
  69. package/dist/{server-GGEO7SA2.js → server-AJH5TBVO.js} +231 -103
  70. package/dist/{server-MO3EU2XD.js → server-CYAB56O3.js} +16 -14
  71. package/dist/{store-SIKHCUYJ.js → store-2N6PPPBP.js} +2 -2
  72. package/dist/{store-CMSSWZF7.js → store-76VVFDXM.js} +2 -2
  73. package/dist/{task-orchestrator-JGEEQ2OE.js → task-orchestrator-HNRV3YUD.js} +15 -14
  74. package/dist/{usage-B7RIAPPT.js → usage-ZN5DU6Z7.js} +4 -4
  75. package/dist/{vector-store-JAXWDZYW.js → vector-store-T2DVUMY4.js} +2 -2
  76. package/dist/{vector-store-PWIOQFTP.js → vector-store-V6UGEYWK.js} +2 -2
  77. package/dist/web/client/app.js +7 -0
  78. package/dist/web/client/app.js.br +0 -0
  79. package/dist/web/client/app.js.gz +0 -0
  80. package/dist/web/client/util.js +14 -23
  81. package/dist/web/client/util.js.br +0 -0
  82. package/dist/web/client/util.js.gz +0 -0
  83. package/dist/web/client/vendor/DOMPurify-LICENSE.txt +202 -0
  84. package/dist/web/client/vendor/purify.es.mjs +2722 -0
  85. package/dist/web/client/vendor/purify.es.mjs.br +0 -0
  86. package/dist/web/client/vendor/purify.es.mjs.gz +0 -0
  87. package/package.json +4 -2
  88. package/dist/chunk-4BKXL7SM.js +0 -98
  89. package/dist/chunk-6BUTA5VW.js +0 -38
  90. package/dist/chunk-IW3Q7AE5.js +0 -40
  91. package/dist/chunk-SKET65WZ.js +0 -96
  92. package/dist/doctor-cli-IOVSHBDI.js +0 -18
  93. package/dist/file-checkpoint-CGH6OJVI.js +0 -6
  94. package/dist/indexer-FPKY7LCT.js +0 -10
  95. package/dist/run-tests-EQCAMFQE.js +0 -10
  96. package/dist/run-tests-V3N7YV3P.js +0 -9
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  atomicWriteFileSync
4
- } from "./chunk-IW3Q7AE5.js";
4
+ } from "./chunk-BMLQPSHP.js";
5
5
 
6
6
  // src/web/auth.ts
7
- import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, copyFileSync } from "fs";
7
+ import { existsSync, readFileSync, mkdirSync, readdirSync, openSync, closeSync, unlinkSync } from "fs";
8
8
  import { isAbsolute, join, relative } from "path";
9
- import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
9
+ import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync, pbkdf2 } from "crypto";
10
10
  var USERS_FILE = "users.json";
11
11
  var TOKEN_EXPIRY_HOURS = 24;
12
12
  var TOKEN_EXPIRY_MS = TOKEN_EXPIRY_HOURS * 3600 * 1e3;
@@ -21,6 +21,7 @@ var AuthManager = class {
21
21
  usersFile;
22
22
  baseDir;
23
23
  db;
24
+ loadedRaw;
24
25
  constructor(baseDir) {
25
26
  this.baseDir = baseDir;
26
27
  this.usersFile = join(baseDir, USERS_FILE);
@@ -29,6 +30,7 @@ var AuthManager = class {
29
30
  // ── Public API ─────────────────────────────────────────────────
30
31
  /** Check if any users exist (first-run detection) */
31
32
  hasUsers() {
33
+ this.refresh();
32
34
  return this.db.users.length > 0;
33
35
  }
34
36
  /** Check if auth is enabled (has users.json with at least 1 user) */
@@ -37,6 +39,18 @@ var AuthManager = class {
37
39
  }
38
40
  /** Register a new user. Returns error string or null on success. */
39
41
  register(username, password) {
42
+ return this.registerPrepared(username, password);
43
+ }
44
+ async registerAsync(username, password) {
45
+ if (typeof username !== "string" || typeof password !== "string" || password.length > 1024) return "Invalid username or password";
46
+ if (!/^[a-z0-9_-]{2,32}$/i.test(username.trim()) || password.length < 4) return "Invalid username or password";
47
+ const salt = randomBytes(16).toString("hex");
48
+ const hash = await hashPasswordAsync(password, salt);
49
+ return this.registerPrepared(username, password, { salt, hash });
50
+ }
51
+ registerPrepared(username, password, prepared) {
52
+ this.refresh();
53
+ if (typeof username !== "string" || typeof password !== "string" || password.length > 1024) return "Invalid username or password";
40
54
  username = username.trim().toLowerCase();
41
55
  if (!username || username.length < 2 || username.length > 32) {
42
56
  return "Username must be 2-32 characters";
@@ -50,11 +64,12 @@ var AuthManager = class {
50
64
  if (this.db.users.find((u) => u.username === username)) {
51
65
  return "Username already exists";
52
66
  }
53
- const salt = randomBytes(16).toString("hex");
54
- const passwordHash = this.hashPassword(password, salt);
67
+ const salt = prepared?.salt ?? randomBytes(16).toString("hex");
68
+ const passwordHash = prepared?.hash ?? this.hashPassword(password, salt);
55
69
  const dataDir = join(USERS_DIR, username);
56
70
  const fullDataDir = join(this.baseDir, dataDir);
57
- mkdirSync(join(fullDataDir, "history"), { recursive: true });
71
+ mkdirSync(fullDataDir, { recursive: true, mode: 448 });
72
+ mkdirSync(join(fullDataDir, "history"), { recursive: true, mode: 448 });
58
73
  const user = {
59
74
  username,
60
75
  passwordHash,
@@ -77,6 +92,8 @@ var AuthManager = class {
77
92
  * resets the counter.
78
93
  */
79
94
  login(username, password) {
95
+ this.refresh();
96
+ if (!validCredentials(username, password)) return null;
80
97
  username = username.trim().toLowerCase();
81
98
  const lockState = this.getLockState(username);
82
99
  if (lockState.locked) return null;
@@ -100,19 +117,51 @@ var AuthManager = class {
100
117
  user.hashVersion = 2;
101
118
  this.save();
102
119
  }
103
- loginAttempts.delete(username);
120
+ loginAttempts.delete(this.attemptKey(username));
104
121
  return this.createToken(username);
105
122
  }
106
123
  /**
107
124
  * Returns current lockout state for a username and lazily expires it.
108
125
  * Exposed (read-only) for tests and the `aicli user` CLI status output.
109
126
  */
127
+ async loginAsync(username, password) {
128
+ this.refresh();
129
+ if (!validCredentials(username, password)) return null;
130
+ username = username.trim().toLowerCase();
131
+ if (this.getLockState(username).locked) return null;
132
+ const user = this.db.users.find((u) => u.username === username);
133
+ if (!user) {
134
+ this.recordFailedLogin(username);
135
+ return null;
136
+ }
137
+ const snapshot = JSON.stringify(user);
138
+ const legacy = !user.hashVersion || user.hashVersion < 2;
139
+ const hash = legacy ? this.hashPasswordLegacy(password, user.salt) : await hashPasswordAsync(password, user.salt);
140
+ const salt = legacy ? randomBytes(16).toString("hex") : user.salt;
141
+ const upgraded = legacy ? await hashPasswordAsync(password, salt) : hash;
142
+ this.refresh();
143
+ const current = this.db.users.find((u) => u.username === username);
144
+ if (!current || JSON.stringify(current) !== snapshot || this.getLockState(username).locked) return null;
145
+ const a = Buffer.from(hash), b = Buffer.from(current.passwordHash);
146
+ if (a.length !== b.length || !timingSafeEqual(a, b)) {
147
+ this.recordFailedLogin(username);
148
+ return null;
149
+ }
150
+ if (legacy) {
151
+ current.salt = salt;
152
+ current.passwordHash = upgraded;
153
+ current.hashVersion = 2;
154
+ this.save();
155
+ }
156
+ loginAttempts.delete(this.attemptKey(username));
157
+ return this.createToken(username);
158
+ }
110
159
  getLockState(username) {
111
160
  username = username.trim().toLowerCase();
112
- const state = loginAttempts.get(username);
161
+ const state = loginAttempts.get(this.attemptKey(username));
113
162
  if (!state) return { locked: false, remainingMs: 0, fails: 0 };
114
- if (state.lockedUntil > 0 && Date.now() >= state.lockedUntil) {
115
- loginAttempts.delete(username);
163
+ if (Date.now() >= state.expiresAt) {
164
+ loginAttempts.delete(this.attemptKey(username));
116
165
  return { locked: false, remainingMs: 0, fails: 0 };
117
166
  }
118
167
  if (state.lockedUntil > 0) {
@@ -120,8 +169,15 @@ var AuthManager = class {
120
169
  }
121
170
  return { locked: false, remainingMs: 0, fails: state.fails };
122
171
  }
172
+ attemptKey(username) {
173
+ return this.usersFile + "\0" + username;
174
+ }
123
175
  recordFailedLogin(username) {
124
- const state = loginAttempts.get(username) ?? { fails: 0, lockedUntil: 0 };
176
+ username = this.attemptKey(username);
177
+ const now = Date.now();
178
+ for (const [key, value] of loginAttempts) if (value.expiresAt <= now) loginAttempts.delete(key);
179
+ if (!loginAttempts.has(username) && loginAttempts.size >= 1e4) loginAttempts.delete(loginAttempts.keys().next().value);
180
+ const state = loginAttempts.get(username) ?? { fails: 0, lockedUntil: 0, expiresAt: now + LOGIN_LOCKOUT_MS };
125
181
  state.fails += 1;
126
182
  if (state.fails >= LOGIN_MAX_FAILS) {
127
183
  state.lockedUntil = Date.now() + LOGIN_LOCKOUT_MS;
@@ -131,6 +187,8 @@ var AuthManager = class {
131
187
  /** Verify a token. Returns username or null. */
132
188
  verifyToken(token) {
133
189
  try {
190
+ this.refresh();
191
+ if (typeof token !== "string" || token.length > 4096) return null;
134
192
  const parts = token.split(".");
135
193
  if (parts.length !== 2) return null;
136
194
  const [payloadB64, signature] = parts;
@@ -141,7 +199,7 @@ var AuthManager = class {
141
199
  const payload = JSON.parse(
142
200
  Buffer.from(payloadB64, "base64url").toString("utf-8")
143
201
  );
144
- if (Date.now() > payload.exp) return null;
202
+ if (!Number.isFinite(payload.exp) || Date.now() >= payload.exp) return null;
145
203
  const user = this.db.users.find((u) => u.username === payload.username);
146
204
  if (!user) return null;
147
205
  if (user.tokensRevokedBefore && (!payload.iat || payload.iat < user.tokensRevokedBefore)) {
@@ -158,6 +216,7 @@ var AuthManager = class {
158
216
  * v0.4.114). Returns true if the user existed.
159
217
  */
160
218
  logoutAll(username) {
219
+ this.refresh();
161
220
  username = username.trim().toLowerCase();
162
221
  const user = this.db.users.find((u) => u.username === username);
163
222
  if (!user) return false;
@@ -167,16 +226,19 @@ var AuthManager = class {
167
226
  }
168
227
  /** Get user's data directory (absolute path) */
169
228
  getUserDataDir(username) {
229
+ this.refresh();
170
230
  const user = this.db.users.find((u) => u.username === username);
171
231
  if (!user) throw new Error(`User not found: ${username}`);
172
232
  return join(this.baseDir, user.dataDir);
173
233
  }
174
234
  /** List all usernames */
175
235
  listUsers() {
236
+ this.refresh();
176
237
  return this.db.users.map((u) => u.username);
177
238
  }
178
239
  /** Delete a user. Returns error string or null on success. */
179
240
  deleteUser(username) {
241
+ this.refresh();
180
242
  username = username.trim().toLowerCase();
181
243
  const idx = this.db.users.findIndex((u) => u.username === username);
182
244
  if (idx === -1) return `User '${username}' not found`;
@@ -186,10 +248,11 @@ var AuthManager = class {
186
248
  }
187
249
  /** Reset a user's password. Returns error string or null on success. */
188
250
  resetPassword(username, newPassword) {
251
+ this.refresh();
189
252
  username = username.trim().toLowerCase();
190
253
  const user = this.db.users.find((u) => u.username === username);
191
254
  if (!user) return `User '${username}' not found`;
192
- if (!newPassword || newPassword.length < 4) {
255
+ if (typeof newPassword !== "string" || newPassword.length > 1024 || newPassword.length < 4) {
193
256
  return "Password must be at least 4 characters";
194
257
  }
195
258
  const salt = randomBytes(16).toString("hex");
@@ -209,7 +272,7 @@ var AuthManager = class {
209
272
  if (existsSync(globalConfig)) {
210
273
  try {
211
274
  const content = readFileSync(globalConfig, "utf-8");
212
- writeFileSync(join(userDir, "config.json"), content, "utf-8");
275
+ atomicWriteFileSync(join(userDir, "config.json"), content, { mode: 384 });
213
276
  } catch {
214
277
  }
215
278
  }
@@ -217,7 +280,7 @@ var AuthManager = class {
217
280
  if (existsSync(globalMemory)) {
218
281
  try {
219
282
  const content = readFileSync(globalMemory, "utf-8");
220
- writeFileSync(join(userDir, "memory.md"), content, "utf-8");
283
+ atomicWriteFileSync(join(userDir, "memory.md"), content, { mode: 384 });
221
284
  } catch {
222
285
  }
223
286
  }
@@ -228,7 +291,7 @@ var AuthManager = class {
228
291
  const userHistory = join(userDir, "history");
229
292
  for (const f of files) {
230
293
  try {
231
- copyFileSync(join(globalHistory, f), join(userHistory, f));
294
+ atomicWriteFileSync(join(userHistory, f), readFileSync(join(globalHistory, f), "utf8"), { mode: 384 });
232
295
  } catch {
233
296
  }
234
297
  }
@@ -238,13 +301,22 @@ var AuthManager = class {
238
301
  return null;
239
302
  }
240
303
  // ── Private methods ────────────────────────────────────────────
304
+ refresh() {
305
+ const raw = readFileSync(this.usersFile, "utf8");
306
+ const parsed = JSON.parse(raw);
307
+ if (!this.isValidUsersDB(parsed)) throw new Error("Authentication database is invalid; access refused");
308
+ this.db = parsed;
309
+ this.loadedRaw = raw;
310
+ }
241
311
  loadOrCreate() {
242
312
  if (existsSync(this.usersFile)) {
243
313
  try {
244
- const parsed = JSON.parse(readFileSync(this.usersFile, "utf-8"));
314
+ const raw = readFileSync(this.usersFile, "utf-8");
315
+ const parsed = JSON.parse(raw);
245
316
  if (!this.isValidUsersDB(parsed)) {
246
317
  throw new Error("database structure is invalid");
247
318
  }
319
+ this.loadedRaw = raw;
248
320
  return parsed;
249
321
  } catch (err) {
250
322
  const detail = err instanceof Error ? err.message : String(err);
@@ -287,8 +359,19 @@ var AuthManager = class {
287
359
  this.saveDB(this.db);
288
360
  }
289
361
  saveDB(db) {
290
- mkdirSync(this.baseDir, { recursive: true });
291
- atomicWriteFileSync(this.usersFile, JSON.stringify(db, null, 2));
362
+ mkdirSync(this.baseDir, { recursive: true, mode: 448 });
363
+ const lock = this.usersFile + ".lock";
364
+ const fd = openSync(lock, "wx", 384);
365
+ try {
366
+ const current = existsSync(this.usersFile) ? readFileSync(this.usersFile, "utf8") : void 0;
367
+ if (current !== this.loadedRaw) throw new Error("Authentication database changed concurrently; retry the operation");
368
+ const raw = JSON.stringify(db, null, 2);
369
+ atomicWriteFileSync(this.usersFile, raw, { mode: 384 });
370
+ this.loadedRaw = raw;
371
+ } finally {
372
+ closeSync(fd);
373
+ unlinkSync(lock);
374
+ }
292
375
  }
293
376
  /** Legacy hash — kept only for migrating old users (v0.2.x) */
294
377
  hashPasswordLegacy(password, salt) {
@@ -312,6 +395,19 @@ var AuthManager = class {
312
395
  return createHmac("sha256", this.db.secret).update(data).digest("base64url");
313
396
  }
314
397
  };
398
+ function validCredentials(username, password) {
399
+ return typeof username === "string" && /^[a-z0-9_-]{2,32}$/i.test(username.trim()) && typeof password === "string" && password.length > 0 && password.length <= 1024;
400
+ }
401
+ var activeHashes = 0;
402
+ async function hashPasswordAsync(password, salt) {
403
+ if (activeHashes >= 4) throw new Error("Authentication capacity reached; try again later");
404
+ activeHashes++;
405
+ try {
406
+ return await new Promise((resolve, reject) => pbkdf2(password, salt, 1e5, 64, "sha512", (error, hash) => error ? reject(error) : resolve(hash.toString("hex"))));
407
+ } finally {
408
+ activeHashes--;
409
+ }
410
+ }
315
411
 
316
412
  export {
317
413
  TOKEN_EXPIRY_MS,
@@ -6,18 +6,20 @@ import {
6
6
  } from "./chunk-KHYD3WXE.js";
7
7
  import {
8
8
  getIndexMtimeMap
9
- } from "./chunk-NYNSYLFY.js";
9
+ } from "./chunk-JL44HGZC.js";
10
10
  import {
11
11
  redactString
12
12
  } from "./chunk-3FEZSLGI.js";
13
13
  import {
14
14
  atomicWriteFileSync
15
- } from "./chunk-IW3Q7AE5.js";
15
+ } from "./chunk-BMLQPSHP.js";
16
+ import {
17
+ getExecutionConfigDir
18
+ } from "./chunk-SXIJLSVF.js";
16
19
 
17
20
  // src/memory/chat-index.ts
18
21
  import fs from "fs";
19
22
  import path from "path";
20
- import os from "os";
21
23
  import crypto from "crypto";
22
24
  var MEMORY_DIR_NAME = "memory-index";
23
25
  var CHUNKS_FILE = "chunks.json";
@@ -26,7 +28,7 @@ var VEC_MAGIC = 1094929750;
26
28
  var VEC_VERSION = 1;
27
29
  var VEC_HEADER_BYTES = 16;
28
30
  function memoryIndexDir() {
29
- return path.join(os.homedir(), ".aicli", MEMORY_DIR_NAME);
31
+ return path.join(getExecutionConfigDir(), MEMORY_DIR_NAME);
30
32
  }
31
33
  function chunksPath() {
32
34
  return path.join(memoryIndexDir(), CHUNKS_FILE);
@@ -35,7 +37,7 @@ function vectorsPath() {
35
37
  return path.join(memoryIndexDir(), VECTORS_FILE);
36
38
  }
37
39
  function historyDir() {
38
- return path.join(os.homedir(), ".aicli", "history");
40
+ return path.join(getExecutionConfigDir(), "history");
39
41
  }
40
42
  var MAX_CHUNK_CHARS = 1200;
41
43
  var MIN_CHUNK_CHARS = 40;
@@ -6,13 +6,13 @@ import {
6
6
  safeDate,
7
7
  searchCandidates,
8
8
  updateSessionInIndex
9
- } from "./chunk-NYNSYLFY.js";
9
+ } from "./chunk-JL44HGZC.js";
10
10
  import {
11
11
  redactJson
12
12
  } from "./chunk-3FEZSLGI.js";
13
13
  import {
14
- atomicWriteFileSync
15
- } from "./chunk-IW3Q7AE5.js";
14
+ atomicWriteFile
15
+ } from "./chunk-BMLQPSHP.js";
16
16
 
17
17
  // src/session/session-manager.ts
18
18
  import { readFileSync, existsSync, mkdirSync, readdirSync, unlinkSync, statSync } from "fs";
@@ -616,26 +616,33 @@ var SessionManager = class {
616
616
  }
617
617
  /** P1-PERF-03: `force` 跳过脏检查(用于进程退出、fork 等必须落盘场景) */
618
618
  async save(force = false) {
619
- if (!this._current) return;
620
- if (this._current.messages.length === 0) return;
621
- if (!force && !this._current.dirty) return;
619
+ const session = this._current;
620
+ if (!session) return;
621
+ if (session.messages.length === 0) return;
622
+ if (!force && !session.dirty) return;
622
623
  mkdirSync(this.historyDir, { recursive: true });
623
- const filePath = join(this.historyDir, `${this._current.id}.json`);
624
- const raw = this._current.toJSON();
624
+ const filePath = join(this.historyDir, `${session.id}.json`);
625
+ const raw = session.toJSON();
625
626
  const opts = this.redactOptionsForSave();
626
627
  const { value: payload, hits } = redactJson(raw, opts);
627
628
  this.lastRedactionHits = hits.length;
628
- atomicWriteFileSync(filePath, JSON.stringify(payload, null, 2));
629
- this._current.dirty = false;
629
+ const serialized = JSON.stringify(payload, null, 2);
630
+ session.dirty = false;
631
+ try {
632
+ await atomicWriteFile(filePath, serialized, { mode: 384 });
633
+ } catch (error) {
634
+ session.dirty = true;
635
+ throw error;
636
+ }
630
637
  try {
631
638
  updateSessionInIndex(this.historyDir, {
632
- id: this._current.id,
633
- provider: this._current.provider,
634
- model: this._current.model,
635
- title: this._current.title,
636
- messageCount: this._current.messages.length,
637
- created: this._current.created,
638
- updated: this._current.updated
639
+ id: session.id,
640
+ provider: session.provider,
641
+ model: session.model,
642
+ title: session.title,
643
+ messageCount: session.messages.length,
644
+ created: session.created,
645
+ updated: session.updated
639
646
  }, filePath);
640
647
  } catch {
641
648
  }
@@ -2,12 +2,12 @@
2
2
  import {
3
3
  loadIndex,
4
4
  symbolFingerprint
5
- } from "./chunk-WQ3OMEC4.js";
5
+ } from "./chunk-M6HLYG33.js";
6
6
  import {
7
7
  loadVectorStore,
8
8
  saveVectorStore,
9
9
  searchVectorStore
10
- } from "./chunk-NRSYBKWA.js";
10
+ } from "./chunk-C7CKDFJS.js";
11
11
  import {
12
12
  EMBEDDING_DIM,
13
13
  embed,
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/core/atomic-write.ts
4
+ import fs from "fs";
5
+ import { randomUUID } from "crypto";
6
+ import { setTimeout as delay } from "timers/promises";
7
+ function modeFor(target, options) {
8
+ if (options.mode !== void 0) return options.mode;
9
+ try {
10
+ return fs.statSync(target).mode & 511;
11
+ } catch {
12
+ return 384;
13
+ }
14
+ }
15
+ function atomicWriteFileSync(target, data, options = {}) {
16
+ const tmp = `${target}.${process.pid}.${randomUUID()}.tmp`;
17
+ try {
18
+ fs.writeFileSync(tmp, data, { flag: "wx", mode: modeFor(target, options) });
19
+ fs.renameSync(tmp, target);
20
+ } finally {
21
+ try {
22
+ fs.unlinkSync(tmp);
23
+ } catch {
24
+ }
25
+ }
26
+ }
27
+ var pending = /* @__PURE__ */ new Map();
28
+ async function atomicWriteFile(target, data, options = {}) {
29
+ const previous = pending.get(target) ?? Promise.resolve();
30
+ const operation = previous.catch(() => {
31
+ }).then(async () => {
32
+ const tmp = `${target}.${process.pid}.${randomUUID()}.tmp`;
33
+ try {
34
+ let mode = options.mode ?? 384;
35
+ if (options.mode === void 0) {
36
+ try {
37
+ mode = (await fs.promises.stat(target)).mode & 511;
38
+ } catch {
39
+ }
40
+ }
41
+ await fs.promises.writeFile(tmp, data, { flag: "wx", mode });
42
+ for (let attempt = 0; ; attempt++) {
43
+ try {
44
+ await fs.promises.rename(tmp, target);
45
+ break;
46
+ } catch (error) {
47
+ if (attempt >= 10 || !["EPERM", "EBUSY", "EACCES"].includes(error.code ?? "")) throw error;
48
+ await delay(5 * (attempt + 1));
49
+ }
50
+ }
51
+ } finally {
52
+ try {
53
+ await fs.promises.unlink(tmp);
54
+ } catch {
55
+ }
56
+ }
57
+ });
58
+ pending.set(target, operation);
59
+ try {
60
+ await operation;
61
+ } finally {
62
+ if (pending.get(target) === operation) pending.delete(target);
63
+ }
64
+ }
65
+
66
+ export {
67
+ atomicWriteFileSync,
68
+ atomicWriteFile
69
+ };
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-KHYD3WXE.js";
5
5
  import {
6
6
  atomicWriteFileSync
7
- } from "./chunk-IW3Q7AE5.js";
7
+ } from "./chunk-BMLQPSHP.js";
8
8
 
9
9
  // src/symbols/vector-store.ts
10
10
  import fs from "fs";
@@ -1,5 +1,5 @@
1
1
  // src/core/constants.ts
2
- var VERSION = "0.4.307";
2
+ var VERSION = "0.4.309";
3
3
  var APP_NAME = "ai-cli";
4
4
  var CONFIG_DIR_NAME = ".aicli";
5
5
  var CONFIG_FILE_NAME = "config.json";
@@ -3,7 +3,7 @@ import {
3
3
  detectsHallucinatedFileOp,
4
4
  parseToolCallArguments,
5
5
  schemaToJsonSchema
6
- } from "./chunk-MI4SFZGX.js";
6
+ } from "./chunk-RMEJR3AW.js";
7
7
  import {
8
8
  AuthError,
9
9
  ProviderError,
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-TU3L3PW7.js";
13
13
  import {
14
14
  atomicWriteFileSync
15
- } from "./chunk-IW3Q7AE5.js";
15
+ } from "./chunk-BMLQPSHP.js";
16
16
 
17
17
  // src/providers/claude.ts
18
18
  import Anthropic from "@anthropic-ai/sdk";
@@ -1670,30 +1670,34 @@ var DeepSeekProvider = class extends OpenAICompatibleProvider {
1670
1670
  info = {
1671
1671
  id: "deepseek",
1672
1672
  displayName: "DeepSeek",
1673
- defaultModel: "deepseek-v4-flash",
1673
+ defaultModel: "deepseek-flash",
1674
1674
  apiKeyEnvVar: "AICLI_API_KEY_DEEPSEEK",
1675
1675
  requiresApiKey: true,
1676
1676
  baseUrl: this.defaultBaseUrl,
1677
1677
  models: [
1678
- // ── V4 family (2026-04-23+):1M context,支持 Thinking / Non-Thinking 双模式 ──
1678
+ // ── V4.1 Flash (2026-09):1M context,原生多模态,Thinking / Non-Thinking 双模式 ──
1679
+ // model code 实测取自端点本身:`/v1/models` 只列 `deepseek-flash` 与 `deepseek-v4-pro`,
1680
+ // 传 `deepseek-v4.1-flash` 报 400 并把合法名字写在错误消息里。**不要**按发布公告里的
1681
+ // 版本号拼 code(v0.4.198 的 `glm-5.2[1m]` 就是这么栽的)。
1679
1682
  {
1680
- id: "deepseek-v4-pro",
1681
- displayName: "DeepSeek V4 Pro (0813, 1.6T MoE, 49B active)",
1683
+ id: "deepseek-flash",
1684
+ displayName: "DeepSeek V4.1 Flash (multimodal, 1M)",
1682
1685
  contextWindow: 1048576,
1683
1686
  supportsStreaming: true,
1684
1687
  supportsThinking: true
1685
1688
  },
1686
1689
  {
1687
- id: "deepseek-v4-flash",
1688
- displayName: "DeepSeek V4 Flash (0731, 284B MoE, 13B active)",
1690
+ id: "deepseek-v4-pro",
1691
+ displayName: "DeepSeek V4 Pro (text-only; routes to V4.1 Flash from 2026-09-14)",
1689
1692
  contextWindow: 1048576,
1690
1693
  supportsStreaming: true,
1691
1694
  supportsThinking: true
1692
1695
  }
1693
- // Legacy aliases (deepseek-chat / deepseek-reasoner) removed in v0.4.140 —
1694
- // they retire 2026-07-24 and DeepSeek already routes them to V4 Flash server-side.
1695
- // Pre-existing sessions were migrated to deepseek-v4-flash; pricing.ts keeps the
1696
- // old ids for any historical cost calculations.
1696
+ // Legacy aliases:`deepseek-v4-flash` / `deepseek-v4-flash-vision-exp` 的底层模型已退役,
1697
+ // 端点仍接受这两个名字并路由到 V4.1 Flash(实测传 `deepseek-v4-flash` 返回 200 且响应
1698
+ // `model` 字段回显 `deepseek-flash`)。它们不再列进推荐清单,config pin 过的会被
1699
+ // `migrateLegacyModels()` 自愈到 `deepseek-flash`;pricing.ts 保留旧 id 供历史成本回算。
1700
+ // `deepseek-chat` / `deepseek-reasoner` 早在 v0.4.140 移除(2026-07-24 停用)。
1697
1701
  ]
1698
1702
  };
1699
1703
  /**