jinzd-ai-cli 0.4.307 → 0.4.308
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/README.md +1 -0
- package/THIRD_PARTY_NOTICES.md +3 -0
- package/dist/{agent-client-6GX6QQDU.js → agent-client-TIEAYZXV.js} +4 -1
- package/dist/{auth-PE3Z5OHS.js → auth-QTQNZ6IX.js} +2 -2
- package/dist/{batch-HMADXNOD.js → batch-S7XLEEOH.js} +3 -3
- package/dist/{chat-index-2D5GK2DB.js → chat-index-7CLR4CBG.js} +4 -3
- package/dist/{chat-index-DGUOUUQW.js → chat-index-RLVLQRRC.js} +3 -2
- package/dist/{chunk-SJFZPS65.js → chunk-2DYQDLLF.js} +1 -1
- package/dist/{chunk-VAV2IJ3R.js → chunk-2MNUMLN4.js} +1 -1
- package/dist/chunk-2XMUCC42.js +67 -0
- package/dist/{chunk-5GQL4TSZ.js → chunk-3537CQ5M.js} +2 -2
- package/dist/{chunk-OAXLMF3U.js → chunk-3YVNDYOY.js} +1 -1
- package/dist/{chunk-RYQBBEMM.js → chunk-4PR5AVJ5.js} +115 -19
- package/dist/{chunk-GHWZVPUV.js → chunk-6JHACXB3.js} +7 -5
- package/dist/{chunk-CIM2PV2F.js → chunk-7OHXFTZO.js} +2 -2
- package/dist/{chunk-IUVDZVZ4.js → chunk-AXTLYOA7.js} +24 -17
- package/dist/{chunk-XJOK5353.js → chunk-B3MPYI4E.js} +2 -2
- package/dist/{chunk-3CKJOTVM.js → chunk-BGZN4K7Q.js} +4 -4
- package/dist/chunk-BMLQPSHP.js +69 -0
- package/dist/{chunk-NRSYBKWA.js → chunk-C7CKDFJS.js} +1 -1
- package/dist/{chunk-NQJSPMY2.js → chunk-E2PNXNKI.js} +4 -4
- package/dist/chunk-FVXNEZEW.js +161 -0
- package/dist/{chunk-YKJO3SI4.js → chunk-G6DVGQ4I.js} +1 -1
- package/dist/{chunk-YFDQIUQ3.js → chunk-HC33C4U3.js} +18 -18
- package/dist/chunk-JJFJWD35.js +162 -0
- package/dist/{chunk-KRCYR46A.js → chunk-JJNUASMJ.js} +264 -55
- package/dist/{chunk-NYNSYLFY.js → chunk-JL44HGZC.js} +1 -1
- package/dist/{chunk-NUJTNH76.js → chunk-LBWKMTT3.js} +1 -1
- package/dist/{chunk-AHL6KOGM.js → chunk-LR3TGZWQ.js} +2 -2
- package/dist/{chunk-WQ3OMEC4.js → chunk-M6HLYG33.js} +1 -1
- package/dist/{chunk-I4GQRMFV.js → chunk-M6YV5KSS.js} +6 -4
- package/dist/{chunk-3TBXHLSC.js → chunk-NLK45GUS.js} +1 -1
- package/dist/{chunk-KBDKKHXB.js → chunk-O6CGOVD4.js} +2 -2
- package/dist/chunk-QMFMST2T.js +39 -0
- package/dist/{chunk-MI4SFZGX.js → chunk-RMEJR3AW.js} +5 -2
- package/dist/{chunk-UFW4S3RU.js → chunk-RVMCY6DP.js} +1 -1
- package/dist/{chunk-LF67HWWR.js → chunk-RWRZVQCB.js} +1 -1
- package/dist/chunk-SXIJLSVF.js +41 -0
- package/dist/chunk-TDQW2YHK.js +65 -0
- package/dist/{chunk-L2RJPVHV.js → chunk-TPTFV3Y6.js} +266 -512
- package/dist/{chunk-45UVJXFQ.js → chunk-W2UOVGRC.js} +1 -1
- package/dist/{chunk-VXEF2SWG.js → chunk-WT252ZDV.js} +2 -2
- package/dist/{chunk-4W6VDVQU.js → chunk-WVEIUSXE.js} +246 -56
- package/dist/{chunk-AQF6IXSR.js → chunk-YBZM7PHI.js} +4 -4
- package/dist/{ci-QD2Q7NWY.js → ci-5HONSUAS.js} +7 -7
- package/dist/{ci-format-42XIZO5P.js → ci-format-URUH3ZZ5.js} +2 -2
- package/dist/{constants-NMASHFDL.js → constants-BLDBI7IG.js} +1 -1
- package/dist/{constants-5CMEFZQB.js → constants-FG27DQMS.js} +1 -1
- package/dist/doctor-cli-QQLMNJN7.js +18 -0
- package/dist/electron-server.js +886 -828
- package/dist/file-checkpoint-64A2AXHE.js +7 -0
- package/dist/{file-checkpoint-NKBHGC7L.js → file-checkpoint-VVPUJNCI.js} +2 -1
- package/dist/{hub-ZLF6GMQD.js → hub-GGFW3DCC.js} +3 -3
- package/dist/{hub-server-CBKCP5VD.js → hub-server-JLC7CNP7.js} +55 -17
- package/dist/index.js +42 -40
- package/dist/{indexer-P3FPRTPJ.js → indexer-GKIWSSRP.js} +3 -3
- package/dist/indexer-VRXDZ74A.js +10 -0
- package/dist/{persist-S77WRVLD.js → persist-ZZXRWFBX.js} +4 -4
- package/dist/{persistent-memory-GJE7HKVK.js → persistent-memory-UNLCXUWH.js} +3 -3
- package/dist/{persistent-memory-MBSBTBGY.js → persistent-memory-YYWIN6BG.js} +3 -3
- package/dist/{pr-SXRW24JG.js → pr-YCFGPFI7.js} +7 -7
- package/dist/{project-trust-MUG325AW.js → project-trust-6CJB5ZMX.js} +1 -1
- package/dist/{project-trust-NKYHL3VZ.js → project-trust-KGG2BWKC.js} +1 -1
- package/dist/run-tests-GHY4BYDE.js +13 -0
- package/dist/run-tests-KRNMFXAN.js +12 -0
- package/dist/{semantic-7GM5M4MU.js → semantic-N227CLSC.js} +4 -4
- package/dist/{semantic-QOBAXEQ4.js → semantic-TRQAZSRY.js} +4 -4
- package/dist/{server-MO3EU2XD.js → server-73UBHSFV.js} +16 -14
- package/dist/{server-GGEO7SA2.js → server-QRAWSG4Z.js} +231 -103
- package/dist/{store-SIKHCUYJ.js → store-2N6PPPBP.js} +2 -2
- package/dist/{store-CMSSWZF7.js → store-76VVFDXM.js} +2 -2
- package/dist/{task-orchestrator-JGEEQ2OE.js → task-orchestrator-GI3RGLZI.js} +15 -14
- package/dist/{usage-B7RIAPPT.js → usage-GWTIGZF5.js} +4 -4
- package/dist/{vector-store-JAXWDZYW.js → vector-store-T2DVUMY4.js} +2 -2
- package/dist/{vector-store-PWIOQFTP.js → vector-store-V6UGEYWK.js} +2 -2
- package/dist/web/client/app.js +7 -0
- package/dist/web/client/app.js.br +0 -0
- package/dist/web/client/app.js.gz +0 -0
- package/dist/web/client/util.js +14 -23
- package/dist/web/client/util.js.br +0 -0
- package/dist/web/client/util.js.gz +0 -0
- package/dist/web/client/vendor/DOMPurify-LICENSE.txt +202 -0
- package/dist/web/client/vendor/purify.es.mjs +2722 -0
- package/dist/web/client/vendor/purify.es.mjs.br +0 -0
- package/dist/web/client/vendor/purify.es.mjs.gz +0 -0
- package/package.json +4 -2
- package/dist/chunk-4BKXL7SM.js +0 -98
- package/dist/chunk-6BUTA5VW.js +0 -38
- package/dist/chunk-IW3Q7AE5.js +0 -40
- package/dist/chunk-SKET65WZ.js +0 -96
- package/dist/doctor-cli-IOVSHBDI.js +0 -18
- package/dist/file-checkpoint-CGH6OJVI.js +0 -6
- package/dist/indexer-FPKY7LCT.js +0 -10
- package/dist/run-tests-EQCAMFQE.js +0 -10
- package/dist/run-tests-V3N7YV3P.js +0 -9
package/README.md
CHANGED
|
@@ -578,6 +578,7 @@ Two groups: what **you** write by hand (the script never touches these, and Guar
|
|
|
578
578
|
| `CHANGELOG.md` | **By hand, before releasing** | A new `## [X.Y.Z] - YYYY-MM-DD` section with the full narrative: root cause / fix / tests. This is the only place detail lives |
|
|
579
579
|
| `CLAUDE.md` → `## 最近里程碑` | **By hand, before releasing** | **One** index line at the top, which must carry the full-width marker `(vX.Y.Z)` — this exact line is what Guard A checks |
|
|
580
580
|
| `package.json` | Script | `"version"` |
|
|
581
|
+
| `package-lock.json` | Script | Root `version` and `packages[""].version`, including rollback on failed verification |
|
|
581
582
|
| `src/core/constants.ts` | Script | The `VERSION` constant (what `aicli --version` and the help banner actually read — drift here ships a package that reports the wrong version) |
|
|
582
583
|
| `CLAUDE.md` → `**当前版本**` | Script | The version number (a *second*, separate spot from the hand-written milestone line) |
|
|
583
584
|
| `docs/USAGE.md`, `docs/USAGE.zh-CN.md`, `docs/ADVANCED*.md`, `docs/TUTORIAL*.md`, `docs/RECIPES*.md`, `SECURITY.md`, `docs/SECURITY*.md` | Script | Only the **document-header** baseline lines (`> **Version**: vX.Y.Z`, security baseline, `当前 vX.Y.Z`). Deliberately first-match-only — "feature introduced in v0.4.246+" markers in the body must keep their original versions |
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -8,6 +8,9 @@ ai-cli includes the following vendored browser assets. Their licenses apply to t
|
|
|
8
8
|
| daisyUI | `src/web/client/vendor/daisyui-full.min.css` | 4.12.23 | MIT | https://github.com/saadeghi/daisyui |
|
|
9
9
|
| highlight.js and GitHub themes | `highlight.min.js`, `github.min.css`, `github-dark.min.css` | 11.9.0 | BSD-3-Clause | https://github.com/highlightjs/highlight.js |
|
|
10
10
|
| marked | `src/web/client/vendor/marked.min.js` | 15.0.12 | MIT | https://github.com/markedjs/marked |
|
|
11
|
+
| DOMPurify | `src/web/client/vendor/purify.es.mjs` | 3.4.15 | Apache-2.0 OR MPL-2.0 | https://github.com/cure53/DOMPurify |
|
|
12
|
+
|
|
13
|
+
DOMPurify's full license is included in `src/web/client/vendor/DOMPurify-LICENSE.txt` and copied into the distributed Web client. The build copies the browser module from the installed, lockfile-pinned package.
|
|
11
14
|
|
|
12
15
|
When replacing a vendored asset, update this file and preserve its upstream license header.
|
|
13
16
|
|
|
@@ -40,7 +40,9 @@ async function joinHub(options, configManager, providers) {
|
|
|
40
40
|
console.log(chalk.dim(` Provider: ${defaultProvider} / ${defaultModel}`));
|
|
41
41
|
}
|
|
42
42
|
console.log();
|
|
43
|
-
const
|
|
43
|
+
const token = process.env.AICLI_HUB_TOKEN;
|
|
44
|
+
if (!token) throw new Error("Set AICLI_HUB_TOKEN to the credential printed by the hub server.");
|
|
45
|
+
const ws = new WebSocket(url, { headers: { Authorization: `Bearer ${token}` }, maxPayload: 1024 * 1024 });
|
|
44
46
|
let myRoleId = "";
|
|
45
47
|
let myRoleName = "";
|
|
46
48
|
let topic = "";
|
|
@@ -133,6 +135,7 @@ async function joinHub(options, configManager, providers) {
|
|
|
133
135
|
}
|
|
134
136
|
const reply = {
|
|
135
137
|
type: "response",
|
|
138
|
+
turnId: msg.turnId,
|
|
136
139
|
content: passed ? "" : response.trim(),
|
|
137
140
|
passed
|
|
138
141
|
};
|
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
} from "./chunk-RS7SKYTS.js";
|
|
5
5
|
import {
|
|
6
6
|
ConfigManager
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-YBZM7PHI.js";
|
|
8
8
|
import "./chunk-TU3L3PW7.js";
|
|
9
9
|
import {
|
|
10
10
|
atomicWriteFileSync
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-BMLQPSHP.js";
|
|
12
|
+
import "./chunk-2MNUMLN4.js";
|
|
13
13
|
|
|
14
14
|
// src/cli/batch.ts
|
|
15
15
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -6,11 +6,12 @@ import {
|
|
|
6
6
|
getChatIndexStatus,
|
|
7
7
|
loadChatIndex,
|
|
8
8
|
searchChatMemory
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-6JHACXB3.js";
|
|
10
10
|
import "./chunk-KHYD3WXE.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-JL44HGZC.js";
|
|
12
12
|
import "./chunk-3FEZSLGI.js";
|
|
13
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-BMLQPSHP.js";
|
|
14
|
+
import "./chunk-SXIJLSVF.js";
|
|
14
15
|
export {
|
|
15
16
|
buildChatIndex,
|
|
16
17
|
chunkSession,
|
|
@@ -5,10 +5,11 @@ import {
|
|
|
5
5
|
getChatIndexStatus,
|
|
6
6
|
loadChatIndex,
|
|
7
7
|
searchChatMemory
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-M6YV5KSS.js";
|
|
9
9
|
import "./chunk-JV5N65KN.js";
|
|
10
10
|
import "./chunk-FSC6KEWU.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-2XMUCC42.js";
|
|
12
|
+
import "./chunk-QMFMST2T.js";
|
|
12
13
|
export {
|
|
13
14
|
buildChatIndex,
|
|
14
15
|
chunkSession,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// src/core/atomic-write.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
import { setTimeout as delay } from "timers/promises";
|
|
5
|
+
function modeFor(target, options) {
|
|
6
|
+
if (options.mode !== void 0) return options.mode;
|
|
7
|
+
try {
|
|
8
|
+
return fs.statSync(target).mode & 511;
|
|
9
|
+
} catch {
|
|
10
|
+
return 384;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function atomicWriteFileSync(target, data, options = {}) {
|
|
14
|
+
const tmp = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
15
|
+
try {
|
|
16
|
+
fs.writeFileSync(tmp, data, { flag: "wx", mode: modeFor(target, options) });
|
|
17
|
+
fs.renameSync(tmp, target);
|
|
18
|
+
} finally {
|
|
19
|
+
try {
|
|
20
|
+
fs.unlinkSync(tmp);
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
var pending = /* @__PURE__ */ new Map();
|
|
26
|
+
async function atomicWriteFile(target, data, options = {}) {
|
|
27
|
+
const previous = pending.get(target) ?? Promise.resolve();
|
|
28
|
+
const operation = previous.catch(() => {
|
|
29
|
+
}).then(async () => {
|
|
30
|
+
const tmp = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
31
|
+
try {
|
|
32
|
+
let mode = options.mode ?? 384;
|
|
33
|
+
if (options.mode === void 0) {
|
|
34
|
+
try {
|
|
35
|
+
mode = (await fs.promises.stat(target)).mode & 511;
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
await fs.promises.writeFile(tmp, data, { flag: "wx", mode });
|
|
40
|
+
for (let attempt = 0; ; attempt++) {
|
|
41
|
+
try {
|
|
42
|
+
await fs.promises.rename(tmp, target);
|
|
43
|
+
break;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (attempt >= 10 || !["EPERM", "EBUSY", "EACCES"].includes(error.code ?? "")) throw error;
|
|
46
|
+
await delay(5 * (attempt + 1));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} finally {
|
|
50
|
+
try {
|
|
51
|
+
await fs.promises.unlink(tmp);
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
pending.set(target, operation);
|
|
57
|
+
try {
|
|
58
|
+
await operation;
|
|
59
|
+
} finally {
|
|
60
|
+
if (pending.get(target) === operation) pending.delete(target);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
atomicWriteFileSync,
|
|
66
|
+
atomicWriteFile
|
|
67
|
+
};
|
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
} from "./chunk-RS7SKYTS.js";
|
|
5
5
|
import {
|
|
6
6
|
atomicWriteFileSync
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BMLQPSHP.js";
|
|
8
8
|
import {
|
|
9
9
|
CONFIG_DIR_NAME,
|
|
10
10
|
PLUGINS_DIR_NAME
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-2MNUMLN4.js";
|
|
12
12
|
|
|
13
13
|
// src/diagnostics/tool-stats.ts
|
|
14
14
|
import { mkdirSync } from "fs";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
atomicWriteFileSync
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-BMLQPSHP.js";
|
|
5
5
|
|
|
6
6
|
// src/web/auth.ts
|
|
7
|
-
import { existsSync, readFileSync,
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
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()
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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(
|
|
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(
|
|
40
|
+
return path.join(getExecutionConfigDir(), "history");
|
|
39
41
|
}
|
|
40
42
|
var MAX_CHUNK_CHARS = 1200;
|
|
41
43
|
var MIN_CHUNK_CHARS = 40;
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-FSC6KEWU.js";
|
|
5
5
|
import {
|
|
6
6
|
atomicWriteFileSync
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-2XMUCC42.js";
|
|
8
8
|
import {
|
|
9
9
|
getGitRoot
|
|
10
10
|
} from "./chunk-CGLYJRFA.js";
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
MEMORY_FILE_NAME,
|
|
13
13
|
MEMORY_MAX_CHARS,
|
|
14
14
|
MEMORY_STORE_FILE_NAME
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-2DYQDLLF.js";
|
|
16
16
|
|
|
17
17
|
// src/memory/persistent-memory.ts
|
|
18
18
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync } from "fs";
|
|
@@ -6,13 +6,13 @@ import {
|
|
|
6
6
|
safeDate,
|
|
7
7
|
searchCandidates,
|
|
8
8
|
updateSessionInIndex
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-JL44HGZC.js";
|
|
10
10
|
import {
|
|
11
11
|
redactJson
|
|
12
12
|
} from "./chunk-3FEZSLGI.js";
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
} from "./chunk-
|
|
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
|
-
|
|
620
|
-
if (
|
|
621
|
-
if (
|
|
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, `${
|
|
624
|
-
const raw =
|
|
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
|
-
|
|
629
|
-
|
|
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:
|
|
633
|
-
provider:
|
|
634
|
-
model:
|
|
635
|
-
title:
|
|
636
|
-
messageCount:
|
|
637
|
-
created:
|
|
638
|
-
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-
|
|
5
|
+
} from "./chunk-M6HLYG33.js";
|
|
6
6
|
import {
|
|
7
7
|
loadVectorStore,
|
|
8
8
|
saveVectorStore,
|
|
9
9
|
searchVectorStore
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-C7CKDFJS.js";
|
|
11
11
|
import {
|
|
12
12
|
EMBEDDING_DIM,
|
|
13
13
|
embed,
|