jinzd-ai-cli 0.4.112 → 0.4.114
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/dist/auth-VBV7HTLQ.js +10 -0
- package/dist/{batch-QQGRQ45S.js → batch-Q5NQCXKN.js} +2 -2
- package/dist/{chunk-5XHNTLRS.js → chunk-2WAF7FOX.js} +1 -1
- package/dist/{chunk-BYNY5JPB.js → chunk-5UPFMM2A.js} +72 -5
- package/dist/{chunk-FM5VYCXA.js → chunk-CP6PALA4.js} +1 -1
- package/dist/{chunk-BWZJGCO6.js → chunk-E24HT62E.js} +2 -2
- package/dist/{chunk-AWZ63EVH.js → chunk-UF62SHR7.js} +1 -1
- package/dist/{chunk-DEXCXFLP.js → chunk-W45U3KQE.js} +1 -1
- package/dist/{chunk-7XXZWPTN.js → chunk-XXKWSBRC.js} +51 -10
- package/dist/{constants-OCYK6U3N.js → constants-YEBRZDBP.js} +1 -1
- package/dist/electron-server.js +141 -15
- package/dist/{hub-7KYFVLHM.js → hub-DGJC2RRF.js} +1 -1
- package/dist/index.js +27 -14
- package/dist/{run-tests-JMKGA7XU.js → run-tests-7WN5Q7YV.js} +2 -2
- package/dist/{run-tests-V73IZCNW.js → run-tests-OB4CWKKX.js} +1 -1
- package/dist/{server-YY6CUP3K.js → server-AKG7HG36.js} +29 -7
- package/dist/{server-WWFOPWWJ.js → server-NQ5J6FAL.js} +32 -13
- package/dist/{task-orchestrator-RPYZT2WL.js → task-orchestrator-32YQ6HB2.js} +3 -3
- package/package.json +9 -5
- package/dist/auth-SC6KHHI3.js +0 -8
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ConfigManager
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-CP6PALA4.js";
|
|
5
5
|
import "./chunk-2ZD3YTVM.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-UF62SHR7.js";
|
|
7
7
|
import "./chunk-PDX44BCA.js";
|
|
8
8
|
|
|
9
9
|
// src/cli/batch.ts
|
|
@@ -5,8 +5,14 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, copyFi
|
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
7
7
|
var USERS_FILE = "users.json";
|
|
8
|
-
var TOKEN_EXPIRY_HOURS = 24
|
|
8
|
+
var TOKEN_EXPIRY_HOURS = 24;
|
|
9
9
|
var USERS_DIR = "users";
|
|
10
|
+
var LOGIN_MAX_FAILS = 5;
|
|
11
|
+
var LOGIN_LOCKOUT_MS = 15 * 60 * 1e3;
|
|
12
|
+
var loginAttempts = /* @__PURE__ */ new Map();
|
|
13
|
+
function __resetLoginAttemptsForTests() {
|
|
14
|
+
loginAttempts.clear();
|
|
15
|
+
}
|
|
10
16
|
var AuthManager = class {
|
|
11
17
|
usersFile;
|
|
12
18
|
baseDir;
|
|
@@ -57,16 +63,30 @@ var AuthManager = class {
|
|
|
57
63
|
this.save();
|
|
58
64
|
return null;
|
|
59
65
|
}
|
|
60
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Authenticate user. Returns JWT token or null on failure.
|
|
68
|
+
*
|
|
69
|
+
* Audit closure (5th audit, v0.4.114): integrates failed-login lockout
|
|
70
|
+
* (CWE-307). After {@link LOGIN_MAX_FAILS} consecutive failures within a
|
|
71
|
+
* lockout window, further attempts return null without checking the
|
|
72
|
+
* password (avoiding pbkdf2 work + leaking timing). Successful login
|
|
73
|
+
* resets the counter.
|
|
74
|
+
*/
|
|
61
75
|
login(username, password) {
|
|
62
76
|
username = username.trim().toLowerCase();
|
|
77
|
+
const lockState = this.getLockState(username);
|
|
78
|
+
if (lockState.locked) return null;
|
|
63
79
|
const user = this.db.users.find((u) => u.username === username);
|
|
64
|
-
if (!user)
|
|
80
|
+
if (!user) {
|
|
81
|
+
this.recordFailedLogin(username);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
65
84
|
const isLegacy = !user.hashVersion || user.hashVersion < 2;
|
|
66
85
|
const hash = isLegacy ? this.hashPasswordLegacy(password, user.salt) : this.hashPassword(password, user.salt);
|
|
67
86
|
const a = Buffer.from(hash, "utf-8");
|
|
68
87
|
const b = Buffer.from(user.passwordHash, "utf-8");
|
|
69
88
|
if (a.length !== b.length || !timingSafeEqual(a, b)) {
|
|
89
|
+
this.recordFailedLogin(username);
|
|
70
90
|
return null;
|
|
71
91
|
}
|
|
72
92
|
if (isLegacy) {
|
|
@@ -76,8 +96,34 @@ var AuthManager = class {
|
|
|
76
96
|
user.hashVersion = 2;
|
|
77
97
|
this.save();
|
|
78
98
|
}
|
|
99
|
+
loginAttempts.delete(username);
|
|
79
100
|
return this.createToken(username);
|
|
80
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Returns current lockout state for a username and lazily expires it.
|
|
104
|
+
* Exposed (read-only) for tests and the `aicli user` CLI status output.
|
|
105
|
+
*/
|
|
106
|
+
getLockState(username) {
|
|
107
|
+
username = username.trim().toLowerCase();
|
|
108
|
+
const state = loginAttempts.get(username);
|
|
109
|
+
if (!state) return { locked: false, remainingMs: 0, fails: 0 };
|
|
110
|
+
if (state.lockedUntil > 0 && Date.now() >= state.lockedUntil) {
|
|
111
|
+
loginAttempts.delete(username);
|
|
112
|
+
return { locked: false, remainingMs: 0, fails: 0 };
|
|
113
|
+
}
|
|
114
|
+
if (state.lockedUntil > 0) {
|
|
115
|
+
return { locked: true, remainingMs: state.lockedUntil - Date.now(), fails: state.fails };
|
|
116
|
+
}
|
|
117
|
+
return { locked: false, remainingMs: 0, fails: state.fails };
|
|
118
|
+
}
|
|
119
|
+
recordFailedLogin(username) {
|
|
120
|
+
const state = loginAttempts.get(username) ?? { fails: 0, lockedUntil: 0 };
|
|
121
|
+
state.fails += 1;
|
|
122
|
+
if (state.fails >= LOGIN_MAX_FAILS) {
|
|
123
|
+
state.lockedUntil = Date.now() + LOGIN_LOCKOUT_MS;
|
|
124
|
+
}
|
|
125
|
+
loginAttempts.set(username, state);
|
|
126
|
+
}
|
|
81
127
|
/** Verify a token. Returns username or null. */
|
|
82
128
|
verifyToken(token) {
|
|
83
129
|
try {
|
|
@@ -90,12 +136,29 @@ var AuthManager = class {
|
|
|
90
136
|
Buffer.from(payloadB64, "base64url").toString("utf-8")
|
|
91
137
|
);
|
|
92
138
|
if (Date.now() > payload.exp) return null;
|
|
93
|
-
|
|
139
|
+
const user = this.db.users.find((u) => u.username === payload.username);
|
|
140
|
+
if (!user) return null;
|
|
141
|
+
if (user.tokensRevokedBefore && (!payload.iat || payload.iat < user.tokensRevokedBefore)) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
94
144
|
return payload.username;
|
|
95
145
|
} catch {
|
|
96
146
|
return null;
|
|
97
147
|
}
|
|
98
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Revoke every outstanding token for the given user by bumping the
|
|
151
|
+
* `tokensRevokedBefore` watermark to now. Audit closure (5th audit,
|
|
152
|
+
* v0.4.114). Returns true if the user existed.
|
|
153
|
+
*/
|
|
154
|
+
logoutAll(username) {
|
|
155
|
+
username = username.trim().toLowerCase();
|
|
156
|
+
const user = this.db.users.find((u) => u.username === username);
|
|
157
|
+
if (!user) return false;
|
|
158
|
+
user.tokensRevokedBefore = Date.now() + 1;
|
|
159
|
+
this.save();
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
99
162
|
/** Get user's data directory (absolute path) */
|
|
100
163
|
getUserDataDir(username) {
|
|
101
164
|
const user = this.db.users.find((u) => u.username === username);
|
|
@@ -127,6 +190,7 @@ var AuthManager = class {
|
|
|
127
190
|
user.passwordHash = this.hashPassword(newPassword, salt);
|
|
128
191
|
user.salt = salt;
|
|
129
192
|
user.hashVersion = 2;
|
|
193
|
+
user.tokensRevokedBefore = Date.now() + 1;
|
|
130
194
|
this.save();
|
|
131
195
|
return null;
|
|
132
196
|
}
|
|
@@ -198,9 +262,11 @@ var AuthManager = class {
|
|
|
198
262
|
return pbkdf2Sync(password, salt, 1e5, 64, "sha512").toString("hex");
|
|
199
263
|
}
|
|
200
264
|
createToken(username) {
|
|
265
|
+
const now = Date.now();
|
|
201
266
|
const payload = {
|
|
202
267
|
username,
|
|
203
|
-
|
|
268
|
+
iat: now,
|
|
269
|
+
exp: now + TOKEN_EXPIRY_HOURS * 3600 * 1e3
|
|
204
270
|
};
|
|
205
271
|
const payloadB64 = Buffer.from(JSON.stringify(payload), "utf-8").toString("base64url");
|
|
206
272
|
const signature = this.sign(payloadB64);
|
|
@@ -212,5 +278,6 @@ var AuthManager = class {
|
|
|
212
278
|
};
|
|
213
279
|
|
|
214
280
|
export {
|
|
281
|
+
__resetLoginAttemptsForTests,
|
|
215
282
|
AuthManager
|
|
216
283
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
schemaToJsonSchema,
|
|
4
4
|
truncateForPersist
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-XXKWSBRC.js";
|
|
6
6
|
import {
|
|
7
7
|
AuthError,
|
|
8
8
|
ProviderError,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
MCP_PROTOCOL_VERSION,
|
|
19
19
|
MCP_TOOL_PREFIX,
|
|
20
20
|
VERSION
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-UF62SHR7.js";
|
|
22
22
|
import {
|
|
23
23
|
redactJson
|
|
24
24
|
} from "./chunk-7ZJN4KLV.js";
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-3BICTI5M.js";
|
|
6
6
|
import {
|
|
7
7
|
runTestsTool
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-2WAF7FOX.js";
|
|
9
9
|
import {
|
|
10
10
|
EnvLoader,
|
|
11
11
|
NetworkError,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
SUBAGENT_ALLOWED_TOOLS,
|
|
19
19
|
SUBAGENT_DEFAULT_MAX_ROUNDS,
|
|
20
20
|
SUBAGENT_MAX_ROUNDS_LIMIT
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-UF62SHR7.js";
|
|
22
22
|
import {
|
|
23
23
|
fileCheckpoints
|
|
24
24
|
} from "./chunk-4BKXL7SM.js";
|
|
@@ -229,10 +229,36 @@ function resetInterrupt() {
|
|
|
229
229
|
interrupted = false;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
// src/tools/session-context.ts
|
|
233
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
234
|
+
var als = new AsyncLocalStorage();
|
|
235
|
+
var DEFAULT_SESSION_KEY = "__default__";
|
|
236
|
+
function getCurrentSessionKey() {
|
|
237
|
+
const ctx = als.getStore();
|
|
238
|
+
if (!ctx || !ctx.sessionKey) return DEFAULT_SESSION_KEY;
|
|
239
|
+
return ctx.sessionKey;
|
|
240
|
+
}
|
|
241
|
+
function runWithSessionKey(sessionKey, fn) {
|
|
242
|
+
const key = sessionKey && sessionKey.length > 0 ? sessionKey : DEFAULT_SESSION_KEY;
|
|
243
|
+
return als.run({ sessionKey: key }, fn);
|
|
244
|
+
}
|
|
245
|
+
|
|
232
246
|
// src/tools/builtin/bash.ts
|
|
233
247
|
var IS_WINDOWS = platform() === "win32";
|
|
234
248
|
var SHELL = IS_WINDOWS ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
|
|
235
|
-
var
|
|
249
|
+
var cwdBySession = /* @__PURE__ */ new Map();
|
|
250
|
+
function getCwd() {
|
|
251
|
+
const key = getCurrentSessionKey();
|
|
252
|
+
let cwd = cwdBySession.get(key);
|
|
253
|
+
if (!cwd) {
|
|
254
|
+
cwd = process.cwd();
|
|
255
|
+
cwdBySession.set(key, cwd);
|
|
256
|
+
}
|
|
257
|
+
return cwd;
|
|
258
|
+
}
|
|
259
|
+
function setCwd(next) {
|
|
260
|
+
cwdBySession.set(getCurrentSessionKey(), next);
|
|
261
|
+
}
|
|
236
262
|
var bashTool = {
|
|
237
263
|
definition: {
|
|
238
264
|
name: "bash",
|
|
@@ -277,17 +303,19 @@ Important rules:
|
|
|
277
303
|
if (!command.trim()) {
|
|
278
304
|
throw new ToolError("bash", "command is required");
|
|
279
305
|
}
|
|
280
|
-
|
|
306
|
+
let currentCwd = getCwd();
|
|
307
|
+
if (!existsSync2(currentCwd)) {
|
|
281
308
|
const fallback = process.cwd();
|
|
282
309
|
process.stderr.write(
|
|
283
|
-
`[bash] Previous cwd "${
|
|
310
|
+
`[bash] Previous cwd "${currentCwd}" no longer exists, reset to "${fallback}"
|
|
284
311
|
`
|
|
285
312
|
);
|
|
286
|
-
|
|
313
|
+
currentCwd = fallback;
|
|
314
|
+
setCwd(fallback);
|
|
287
315
|
}
|
|
288
|
-
let effectiveCwd =
|
|
316
|
+
let effectiveCwd = currentCwd;
|
|
289
317
|
if (cwdArg) {
|
|
290
|
-
const resolved = resolve(
|
|
318
|
+
const resolved = resolve(currentCwd, cwdArg);
|
|
291
319
|
if (!existsSync2(resolved)) {
|
|
292
320
|
throw new ToolError(
|
|
293
321
|
"bash",
|
|
@@ -295,7 +323,7 @@ Important rules:
|
|
|
295
323
|
);
|
|
296
324
|
}
|
|
297
325
|
effectiveCwd = resolved;
|
|
298
|
-
|
|
326
|
+
setCwd(resolved);
|
|
299
327
|
}
|
|
300
328
|
let actualCommand;
|
|
301
329
|
if (IS_WINDOWS) {
|
|
@@ -551,7 +579,7 @@ function updateCwdFromCommand(command, baseCwd) {
|
|
|
551
579
|
try {
|
|
552
580
|
const newDir = resolve(baseCwd, target);
|
|
553
581
|
if (existsSync2(newDir)) {
|
|
554
|
-
|
|
582
|
+
setCwd(newDir);
|
|
555
583
|
}
|
|
556
584
|
} catch {
|
|
557
585
|
}
|
|
@@ -1411,6 +1439,16 @@ var ToolExecutor = class {
|
|
|
1411
1439
|
* 通过 /yolo 命令切换。destructive 操作仍会显示警告但不阻塞。
|
|
1412
1440
|
*/
|
|
1413
1441
|
sessionAutoApprove = false;
|
|
1442
|
+
/**
|
|
1443
|
+
* Logical session key used to scope per-session state in stateful tools
|
|
1444
|
+
* (currently only `bash`'s persistent cwd). Web mode sets this per-tab so
|
|
1445
|
+
* concurrent tabs don't share cwd. CLI/REPL leaves it undefined (the bash
|
|
1446
|
+
* tool falls back to a default key).
|
|
1447
|
+
*/
|
|
1448
|
+
sessionKey;
|
|
1449
|
+
setSessionKey(key) {
|
|
1450
|
+
this.sessionKey = key;
|
|
1451
|
+
}
|
|
1414
1452
|
/**
|
|
1415
1453
|
* 由外部(repl.ts SIGINT handler)调用,将当前 confirm() 等待视为用户按 N 取消。
|
|
1416
1454
|
* 若当前没有 confirm() 进行中,无操作。
|
|
@@ -1443,6 +1481,9 @@ var ToolExecutor = class {
|
|
|
1443
1481
|
if (opts.defaultPermission) this.defaultPermission = opts.defaultPermission;
|
|
1444
1482
|
}
|
|
1445
1483
|
async execute(call) {
|
|
1484
|
+
return runWithSessionKey(this.sessionKey, () => this.executeInner(call));
|
|
1485
|
+
}
|
|
1486
|
+
async executeInner(call) {
|
|
1446
1487
|
const tool = this.registry.get(call.name);
|
|
1447
1488
|
if (!tool) {
|
|
1448
1489
|
return {
|
package/dist/electron-server.js
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
VERSION,
|
|
37
37
|
buildUserIdentityPrompt,
|
|
38
38
|
runTestsTool
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-W45U3KQE.js";
|
|
40
40
|
import {
|
|
41
41
|
hasSemanticIndex,
|
|
42
42
|
semanticSearch
|
|
@@ -3742,10 +3742,36 @@ function currentAbortSignal() {
|
|
|
3742
3742
|
return controller.signal;
|
|
3743
3743
|
}
|
|
3744
3744
|
|
|
3745
|
+
// src/tools/session-context.ts
|
|
3746
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3747
|
+
var als = new AsyncLocalStorage();
|
|
3748
|
+
var DEFAULT_SESSION_KEY = "__default__";
|
|
3749
|
+
function getCurrentSessionKey() {
|
|
3750
|
+
const ctx = als.getStore();
|
|
3751
|
+
if (!ctx || !ctx.sessionKey) return DEFAULT_SESSION_KEY;
|
|
3752
|
+
return ctx.sessionKey;
|
|
3753
|
+
}
|
|
3754
|
+
function runWithSessionKey(sessionKey, fn) {
|
|
3755
|
+
const key = sessionKey && sessionKey.length > 0 ? sessionKey : DEFAULT_SESSION_KEY;
|
|
3756
|
+
return als.run({ sessionKey: key }, fn);
|
|
3757
|
+
}
|
|
3758
|
+
|
|
3745
3759
|
// src/tools/builtin/bash.ts
|
|
3746
3760
|
var IS_WINDOWS = platform() === "win32";
|
|
3747
3761
|
var SHELL = IS_WINDOWS ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
|
|
3748
|
-
var
|
|
3762
|
+
var cwdBySession = /* @__PURE__ */ new Map();
|
|
3763
|
+
function getCwd() {
|
|
3764
|
+
const key = getCurrentSessionKey();
|
|
3765
|
+
let cwd = cwdBySession.get(key);
|
|
3766
|
+
if (!cwd) {
|
|
3767
|
+
cwd = process.cwd();
|
|
3768
|
+
cwdBySession.set(key, cwd);
|
|
3769
|
+
}
|
|
3770
|
+
return cwd;
|
|
3771
|
+
}
|
|
3772
|
+
function setCwd(next) {
|
|
3773
|
+
cwdBySession.set(getCurrentSessionKey(), next);
|
|
3774
|
+
}
|
|
3749
3775
|
var bashTool = {
|
|
3750
3776
|
definition: {
|
|
3751
3777
|
name: "bash",
|
|
@@ -3790,17 +3816,19 @@ Important rules:
|
|
|
3790
3816
|
if (!command.trim()) {
|
|
3791
3817
|
throw new ToolError("bash", "command is required");
|
|
3792
3818
|
}
|
|
3793
|
-
|
|
3819
|
+
let currentCwd = getCwd();
|
|
3820
|
+
if (!existsSync4(currentCwd)) {
|
|
3794
3821
|
const fallback = process.cwd();
|
|
3795
3822
|
process.stderr.write(
|
|
3796
|
-
`[bash] Previous cwd "${
|
|
3823
|
+
`[bash] Previous cwd "${currentCwd}" no longer exists, reset to "${fallback}"
|
|
3797
3824
|
`
|
|
3798
3825
|
);
|
|
3799
|
-
|
|
3826
|
+
currentCwd = fallback;
|
|
3827
|
+
setCwd(fallback);
|
|
3800
3828
|
}
|
|
3801
|
-
let effectiveCwd =
|
|
3829
|
+
let effectiveCwd = currentCwd;
|
|
3802
3830
|
if (cwdArg) {
|
|
3803
|
-
const resolved = resolve(
|
|
3831
|
+
const resolved = resolve(currentCwd, cwdArg);
|
|
3804
3832
|
if (!existsSync4(resolved)) {
|
|
3805
3833
|
throw new ToolError(
|
|
3806
3834
|
"bash",
|
|
@@ -3808,7 +3836,7 @@ Important rules:
|
|
|
3808
3836
|
);
|
|
3809
3837
|
}
|
|
3810
3838
|
effectiveCwd = resolved;
|
|
3811
|
-
|
|
3839
|
+
setCwd(resolved);
|
|
3812
3840
|
}
|
|
3813
3841
|
let actualCommand;
|
|
3814
3842
|
if (IS_WINDOWS) {
|
|
@@ -4064,7 +4092,7 @@ function updateCwdFromCommand(command, baseCwd) {
|
|
|
4064
4092
|
try {
|
|
4065
4093
|
const newDir = resolve(baseCwd, target);
|
|
4066
4094
|
if (existsSync4(newDir)) {
|
|
4067
|
-
|
|
4095
|
+
setCwd(newDir);
|
|
4068
4096
|
}
|
|
4069
4097
|
} catch {
|
|
4070
4098
|
}
|
|
@@ -4885,6 +4913,16 @@ var ToolExecutor = class {
|
|
|
4885
4913
|
* 通过 /yolo 命令切换。destructive 操作仍会显示警告但不阻塞。
|
|
4886
4914
|
*/
|
|
4887
4915
|
sessionAutoApprove = false;
|
|
4916
|
+
/**
|
|
4917
|
+
* Logical session key used to scope per-session state in stateful tools
|
|
4918
|
+
* (currently only `bash`'s persistent cwd). Web mode sets this per-tab so
|
|
4919
|
+
* concurrent tabs don't share cwd. CLI/REPL leaves it undefined (the bash
|
|
4920
|
+
* tool falls back to a default key).
|
|
4921
|
+
*/
|
|
4922
|
+
sessionKey;
|
|
4923
|
+
setSessionKey(key) {
|
|
4924
|
+
this.sessionKey = key;
|
|
4925
|
+
}
|
|
4888
4926
|
/**
|
|
4889
4927
|
* 由外部(repl.ts SIGINT handler)调用,将当前 confirm() 等待视为用户按 N 取消。
|
|
4890
4928
|
* 若当前没有 confirm() 进行中,无操作。
|
|
@@ -4917,6 +4955,9 @@ var ToolExecutor = class {
|
|
|
4917
4955
|
if (opts.defaultPermission) this.defaultPermission = opts.defaultPermission;
|
|
4918
4956
|
}
|
|
4919
4957
|
async execute(call) {
|
|
4958
|
+
return runWithSessionKey(this.sessionKey, () => this.executeInner(call));
|
|
4959
|
+
}
|
|
4960
|
+
async executeInner(call) {
|
|
4920
4961
|
const tool = this.registry.get(call.name);
|
|
4921
4962
|
if (!tool) {
|
|
4922
4963
|
return {
|
|
@@ -10046,6 +10087,7 @@ var SessionHandler = class _SessionHandler {
|
|
|
10046
10087
|
this.mcpManager = shared.mcpManager;
|
|
10047
10088
|
this.skillManager = shared.skillManager;
|
|
10048
10089
|
this.toolExecutor = new ToolExecutorWeb(shared.toolRegistry, ws);
|
|
10090
|
+
this.toolExecutor.setSessionKey(`web-${Math.random().toString(36).slice(2)}-${Date.now()}`);
|
|
10049
10091
|
this.currentProvider = this.config.get("defaultProvider");
|
|
10050
10092
|
const allDefaultModels = this.config.get("defaultModels");
|
|
10051
10093
|
try {
|
|
@@ -11918,7 +11960,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
11918
11960
|
case "test": {
|
|
11919
11961
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
11920
11962
|
try {
|
|
11921
|
-
const { executeTests } = await import("./run-tests-
|
|
11963
|
+
const { executeTests } = await import("./run-tests-OB4CWKKX.js");
|
|
11922
11964
|
const argStr = args.join(" ").trim();
|
|
11923
11965
|
let testArgs = {};
|
|
11924
11966
|
if (argStr) {
|
|
@@ -12906,8 +12948,11 @@ import { existsSync as existsSync21, readFileSync as readFileSync14, writeFileSy
|
|
|
12906
12948
|
import { join as join14 } from "path";
|
|
12907
12949
|
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
12908
12950
|
var USERS_FILE = "users.json";
|
|
12909
|
-
var TOKEN_EXPIRY_HOURS = 24
|
|
12951
|
+
var TOKEN_EXPIRY_HOURS = 24;
|
|
12910
12952
|
var USERS_DIR = "users";
|
|
12953
|
+
var LOGIN_MAX_FAILS = 5;
|
|
12954
|
+
var LOGIN_LOCKOUT_MS = 15 * 60 * 1e3;
|
|
12955
|
+
var loginAttempts = /* @__PURE__ */ new Map();
|
|
12911
12956
|
var AuthManager = class {
|
|
12912
12957
|
usersFile;
|
|
12913
12958
|
baseDir;
|
|
@@ -12958,16 +13003,30 @@ var AuthManager = class {
|
|
|
12958
13003
|
this.save();
|
|
12959
13004
|
return null;
|
|
12960
13005
|
}
|
|
12961
|
-
/**
|
|
13006
|
+
/**
|
|
13007
|
+
* Authenticate user. Returns JWT token or null on failure.
|
|
13008
|
+
*
|
|
13009
|
+
* Audit closure (5th audit, v0.4.114): integrates failed-login lockout
|
|
13010
|
+
* (CWE-307). After {@link LOGIN_MAX_FAILS} consecutive failures within a
|
|
13011
|
+
* lockout window, further attempts return null without checking the
|
|
13012
|
+
* password (avoiding pbkdf2 work + leaking timing). Successful login
|
|
13013
|
+
* resets the counter.
|
|
13014
|
+
*/
|
|
12962
13015
|
login(username, password) {
|
|
12963
13016
|
username = username.trim().toLowerCase();
|
|
13017
|
+
const lockState = this.getLockState(username);
|
|
13018
|
+
if (lockState.locked) return null;
|
|
12964
13019
|
const user = this.db.users.find((u) => u.username === username);
|
|
12965
|
-
if (!user)
|
|
13020
|
+
if (!user) {
|
|
13021
|
+
this.recordFailedLogin(username);
|
|
13022
|
+
return null;
|
|
13023
|
+
}
|
|
12966
13024
|
const isLegacy = !user.hashVersion || user.hashVersion < 2;
|
|
12967
13025
|
const hash = isLegacy ? this.hashPasswordLegacy(password, user.salt) : this.hashPassword(password, user.salt);
|
|
12968
13026
|
const a = Buffer.from(hash, "utf-8");
|
|
12969
13027
|
const b = Buffer.from(user.passwordHash, "utf-8");
|
|
12970
13028
|
if (a.length !== b.length || !timingSafeEqual(a, b)) {
|
|
13029
|
+
this.recordFailedLogin(username);
|
|
12971
13030
|
return null;
|
|
12972
13031
|
}
|
|
12973
13032
|
if (isLegacy) {
|
|
@@ -12977,8 +13036,34 @@ var AuthManager = class {
|
|
|
12977
13036
|
user.hashVersion = 2;
|
|
12978
13037
|
this.save();
|
|
12979
13038
|
}
|
|
13039
|
+
loginAttempts.delete(username);
|
|
12980
13040
|
return this.createToken(username);
|
|
12981
13041
|
}
|
|
13042
|
+
/**
|
|
13043
|
+
* Returns current lockout state for a username and lazily expires it.
|
|
13044
|
+
* Exposed (read-only) for tests and the `aicli user` CLI status output.
|
|
13045
|
+
*/
|
|
13046
|
+
getLockState(username) {
|
|
13047
|
+
username = username.trim().toLowerCase();
|
|
13048
|
+
const state = loginAttempts.get(username);
|
|
13049
|
+
if (!state) return { locked: false, remainingMs: 0, fails: 0 };
|
|
13050
|
+
if (state.lockedUntil > 0 && Date.now() >= state.lockedUntil) {
|
|
13051
|
+
loginAttempts.delete(username);
|
|
13052
|
+
return { locked: false, remainingMs: 0, fails: 0 };
|
|
13053
|
+
}
|
|
13054
|
+
if (state.lockedUntil > 0) {
|
|
13055
|
+
return { locked: true, remainingMs: state.lockedUntil - Date.now(), fails: state.fails };
|
|
13056
|
+
}
|
|
13057
|
+
return { locked: false, remainingMs: 0, fails: state.fails };
|
|
13058
|
+
}
|
|
13059
|
+
recordFailedLogin(username) {
|
|
13060
|
+
const state = loginAttempts.get(username) ?? { fails: 0, lockedUntil: 0 };
|
|
13061
|
+
state.fails += 1;
|
|
13062
|
+
if (state.fails >= LOGIN_MAX_FAILS) {
|
|
13063
|
+
state.lockedUntil = Date.now() + LOGIN_LOCKOUT_MS;
|
|
13064
|
+
}
|
|
13065
|
+
loginAttempts.set(username, state);
|
|
13066
|
+
}
|
|
12982
13067
|
/** Verify a token. Returns username or null. */
|
|
12983
13068
|
verifyToken(token) {
|
|
12984
13069
|
try {
|
|
@@ -12991,12 +13076,29 @@ var AuthManager = class {
|
|
|
12991
13076
|
Buffer.from(payloadB64, "base64url").toString("utf-8")
|
|
12992
13077
|
);
|
|
12993
13078
|
if (Date.now() > payload.exp) return null;
|
|
12994
|
-
|
|
13079
|
+
const user = this.db.users.find((u) => u.username === payload.username);
|
|
13080
|
+
if (!user) return null;
|
|
13081
|
+
if (user.tokensRevokedBefore && (!payload.iat || payload.iat < user.tokensRevokedBefore)) {
|
|
13082
|
+
return null;
|
|
13083
|
+
}
|
|
12995
13084
|
return payload.username;
|
|
12996
13085
|
} catch {
|
|
12997
13086
|
return null;
|
|
12998
13087
|
}
|
|
12999
13088
|
}
|
|
13089
|
+
/**
|
|
13090
|
+
* Revoke every outstanding token for the given user by bumping the
|
|
13091
|
+
* `tokensRevokedBefore` watermark to now. Audit closure (5th audit,
|
|
13092
|
+
* v0.4.114). Returns true if the user existed.
|
|
13093
|
+
*/
|
|
13094
|
+
logoutAll(username) {
|
|
13095
|
+
username = username.trim().toLowerCase();
|
|
13096
|
+
const user = this.db.users.find((u) => u.username === username);
|
|
13097
|
+
if (!user) return false;
|
|
13098
|
+
user.tokensRevokedBefore = Date.now() + 1;
|
|
13099
|
+
this.save();
|
|
13100
|
+
return true;
|
|
13101
|
+
}
|
|
13000
13102
|
/** Get user's data directory (absolute path) */
|
|
13001
13103
|
getUserDataDir(username) {
|
|
13002
13104
|
const user = this.db.users.find((u) => u.username === username);
|
|
@@ -13028,6 +13130,7 @@ var AuthManager = class {
|
|
|
13028
13130
|
user.passwordHash = this.hashPassword(newPassword, salt);
|
|
13029
13131
|
user.salt = salt;
|
|
13030
13132
|
user.hashVersion = 2;
|
|
13133
|
+
user.tokensRevokedBefore = Date.now() + 1;
|
|
13031
13134
|
this.save();
|
|
13032
13135
|
return null;
|
|
13033
13136
|
}
|
|
@@ -13099,9 +13202,11 @@ var AuthManager = class {
|
|
|
13099
13202
|
return pbkdf2Sync(password, salt, 1e5, 64, "sha512").toString("hex");
|
|
13100
13203
|
}
|
|
13101
13204
|
createToken(username) {
|
|
13205
|
+
const now = Date.now();
|
|
13102
13206
|
const payload = {
|
|
13103
13207
|
username,
|
|
13104
|
-
|
|
13208
|
+
iat: now,
|
|
13209
|
+
exp: now + TOKEN_EXPIRY_HOURS * 3600 * 1e3
|
|
13105
13210
|
};
|
|
13106
13211
|
const payloadB64 = Buffer.from(JSON.stringify(payload), "utf-8").toString("base64url");
|
|
13107
13212
|
const signature = this.sign(payloadB64);
|
|
@@ -13196,6 +13301,27 @@ async function startWebServer(options = {}) {
|
|
|
13196
13301
|
skillManager
|
|
13197
13302
|
};
|
|
13198
13303
|
const app = express();
|
|
13304
|
+
app.use((_req, res, next) => {
|
|
13305
|
+
res.setHeader(
|
|
13306
|
+
"Content-Security-Policy",
|
|
13307
|
+
[
|
|
13308
|
+
"default-src 'self'",
|
|
13309
|
+
"script-src 'self'",
|
|
13310
|
+
"style-src 'self' 'unsafe-inline'",
|
|
13311
|
+
"img-src 'self' data: blob:",
|
|
13312
|
+
"font-src 'self' data:",
|
|
13313
|
+
"connect-src 'self' ws: wss:",
|
|
13314
|
+
"frame-ancestors 'none'",
|
|
13315
|
+
"base-uri 'self'",
|
|
13316
|
+
"form-action 'self'"
|
|
13317
|
+
].join("; ")
|
|
13318
|
+
);
|
|
13319
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
13320
|
+
res.setHeader("Referrer-Policy", "no-referrer");
|
|
13321
|
+
res.setHeader("X-Frame-Options", "DENY");
|
|
13322
|
+
res.setHeader("Permissions-Policy", "geolocation=(), microphone=(), camera=()");
|
|
13323
|
+
next();
|
|
13324
|
+
});
|
|
13199
13325
|
const server = createServer(app);
|
|
13200
13326
|
const WS_MAX_PAYLOAD = 1 * 1024 * 1024;
|
|
13201
13327
|
const WS_MSG_RATE_PER_SEC = 30;
|
|
@@ -386,7 +386,7 @@ ${content}`);
|
|
|
386
386
|
}
|
|
387
387
|
}
|
|
388
388
|
async function runTaskMode(config, providers, configManager, topic) {
|
|
389
|
-
const { TaskOrchestrator } = await import("./task-orchestrator-
|
|
389
|
+
const { TaskOrchestrator } = await import("./task-orchestrator-32YQ6HB2.js");
|
|
390
390
|
const orchestrator = new TaskOrchestrator(config, providers, configManager);
|
|
391
391
|
let interrupted = false;
|
|
392
392
|
const onSigint = () => {
|
package/dist/index.js
CHANGED
|
@@ -31,10 +31,10 @@ import {
|
|
|
31
31
|
setupProxy,
|
|
32
32
|
stripPseudoToolCalls,
|
|
33
33
|
stripToolCallReminder
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-E24HT62E.js";
|
|
35
35
|
import {
|
|
36
36
|
ConfigManager
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-CP6PALA4.js";
|
|
38
38
|
import {
|
|
39
39
|
ToolExecutor,
|
|
40
40
|
ToolRegistry,
|
|
@@ -53,10 +53,10 @@ import {
|
|
|
53
53
|
spawnAgentContext,
|
|
54
54
|
theme,
|
|
55
55
|
undoStack
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-XXKWSBRC.js";
|
|
57
57
|
import "./chunk-3BICTI5M.js";
|
|
58
58
|
import "./chunk-2DXY7UGF.js";
|
|
59
|
-
import "./chunk-
|
|
59
|
+
import "./chunk-2WAF7FOX.js";
|
|
60
60
|
import "./chunk-2ZD3YTVM.js";
|
|
61
61
|
import {
|
|
62
62
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
@@ -79,7 +79,7 @@ import {
|
|
|
79
79
|
SKILLS_DIR_NAME,
|
|
80
80
|
VERSION,
|
|
81
81
|
buildUserIdentityPrompt
|
|
82
|
-
} from "./chunk-
|
|
82
|
+
} from "./chunk-UF62SHR7.js";
|
|
83
83
|
import {
|
|
84
84
|
formatGitContextForPrompt,
|
|
85
85
|
getGitContext,
|
|
@@ -1600,7 +1600,7 @@ ${text}
|
|
|
1600
1600
|
const { join: join6 } = await import("path");
|
|
1601
1601
|
const { existsSync: existsSync6 } = await import("fs");
|
|
1602
1602
|
const { getGitRoot: getGitRoot2 } = await import("./git-context-7KIP4X2V.js");
|
|
1603
|
-
const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-
|
|
1603
|
+
const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-YEBRZDBP.js");
|
|
1604
1604
|
const { approveProject, hashMcpFile } = await import("./project-trust-IFM7FXEV.js");
|
|
1605
1605
|
const cwd = process.cwd();
|
|
1606
1606
|
const projectRoot = getGitRoot2(cwd) ?? cwd;
|
|
@@ -2650,7 +2650,7 @@ ${hint}` : "")
|
|
|
2650
2650
|
usage: "/test [command|filter]",
|
|
2651
2651
|
async execute(args, ctx) {
|
|
2652
2652
|
try {
|
|
2653
|
-
const { executeTests } = await import("./run-tests-
|
|
2653
|
+
const { executeTests } = await import("./run-tests-7WN5Q7YV.js");
|
|
2654
2654
|
const argStr = args.join(" ").trim();
|
|
2655
2655
|
let testArgs = {};
|
|
2656
2656
|
if (argStr) {
|
|
@@ -6882,11 +6882,11 @@ program.command("web").description("Start Web UI server with browser-based chat
|
|
|
6882
6882
|
console.error("Error: Invalid port number. Must be between 1 and 65535.");
|
|
6883
6883
|
process.exit(1);
|
|
6884
6884
|
}
|
|
6885
|
-
const { startWebServer } = await import("./server-
|
|
6885
|
+
const { startWebServer } = await import("./server-AKG7HG36.js");
|
|
6886
6886
|
await startWebServer({ port, host: options.host });
|
|
6887
6887
|
});
|
|
6888
|
-
program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
|
|
6889
|
-
const { AuthManager } = await import("./auth-
|
|
6888
|
+
program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | logout-all <name> | migrate <name>)").action(async (action, username) => {
|
|
6889
|
+
const { AuthManager } = await import("./auth-VBV7HTLQ.js");
|
|
6890
6890
|
const config = new ConfigManager();
|
|
6891
6891
|
const auth = new AuthManager(config.getConfigDir());
|
|
6892
6892
|
if (!action || action === "list") {
|
|
@@ -6961,6 +6961,19 @@ ${users.length} user(s) registered (auth enabled):
|
|
|
6961
6961
|
console.log(`\u2713 Password reset for '${username}'.`);
|
|
6962
6962
|
return;
|
|
6963
6963
|
}
|
|
6964
|
+
if (action === "logout-all") {
|
|
6965
|
+
if (!username) {
|
|
6966
|
+
console.error("Usage: aicli user logout-all <username>");
|
|
6967
|
+
process.exit(1);
|
|
6968
|
+
}
|
|
6969
|
+
const ok = auth.logoutAll(username);
|
|
6970
|
+
if (!ok) {
|
|
6971
|
+
console.error(`Error: User '${username}' not found.`);
|
|
6972
|
+
process.exit(1);
|
|
6973
|
+
}
|
|
6974
|
+
console.log(`\u2713 All outstanding sessions for '${username}' revoked.`);
|
|
6975
|
+
return;
|
|
6976
|
+
}
|
|
6964
6977
|
if (action === "migrate") {
|
|
6965
6978
|
if (!username) {
|
|
6966
6979
|
console.error("Usage: aicli user migrate <username>");
|
|
@@ -6980,7 +6993,7 @@ ${users.length} user(s) registered (auth enabled):
|
|
|
6980
6993
|
return;
|
|
6981
6994
|
}
|
|
6982
6995
|
console.error(`Unknown action: ${action}`);
|
|
6983
|
-
console.error("Available: list, create, delete, reset-password, migrate");
|
|
6996
|
+
console.error("Available: list, create, delete, reset-password, logout-all, migrate");
|
|
6984
6997
|
process.exit(1);
|
|
6985
6998
|
});
|
|
6986
6999
|
program.command("sessions").description("List recent conversation sessions").action(async () => {
|
|
@@ -7005,7 +7018,7 @@ program.command("sessions").description("List recent conversation sessions").act
|
|
|
7005
7018
|
});
|
|
7006
7019
|
program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
|
|
7007
7020
|
try {
|
|
7008
|
-
const batch = await import("./batch-
|
|
7021
|
+
const batch = await import("./batch-Q5NQCXKN.js");
|
|
7009
7022
|
switch (action) {
|
|
7010
7023
|
case "submit":
|
|
7011
7024
|
if (!arg) {
|
|
@@ -7048,7 +7061,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
|
|
|
7048
7061
|
}
|
|
7049
7062
|
});
|
|
7050
7063
|
program.command("mcp-serve").description("Start an MCP server over STDIO, exposing aicli's built-in tools to Claude Desktop / Cursor / other MCP clients").option("--allow-destructive", "Allow bash / run_interactive / task_create (always destructive in MCP mode)").option("--allow-outside-cwd", "Allow tool path arguments to escape the sandbox root \u2014 disabled by default").option("--tools <list>", "Comma-separated whitelist of tools to expose (default: all eligible tools)").option("--cwd <path>", "Working directory AND sandbox root (default: current directory)").action(async (options) => {
|
|
7051
|
-
const { startMcpServer } = await import("./server-
|
|
7064
|
+
const { startMcpServer } = await import("./server-NQ5J6FAL.js");
|
|
7052
7065
|
await startMcpServer({
|
|
7053
7066
|
allowDestructive: !!options.allowDestructive,
|
|
7054
7067
|
allowOutsideCwd: !!options.allowOutsideCwd,
|
|
@@ -7175,7 +7188,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
|
|
|
7175
7188
|
}),
|
|
7176
7189
|
config.get("customProviders")
|
|
7177
7190
|
);
|
|
7178
|
-
const { startHub } = await import("./hub-
|
|
7191
|
+
const { startHub } = await import("./hub-DGJC2RRF.js");
|
|
7179
7192
|
await startHub(
|
|
7180
7193
|
{
|
|
7181
7194
|
topic: topic ?? "",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
AuthManager
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5UPFMM2A.js";
|
|
5
5
|
import {
|
|
6
6
|
CONTENT_ONLY_STREAM_REMINDER,
|
|
7
7
|
HALLUCINATION_CORRECTION_MESSAGE,
|
|
@@ -24,10 +24,10 @@ import {
|
|
|
24
24
|
setupProxy,
|
|
25
25
|
stripPseudoToolCalls,
|
|
26
26
|
stripToolCallReminder
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-E24HT62E.js";
|
|
28
28
|
import {
|
|
29
29
|
ConfigManager
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-CP6PALA4.js";
|
|
31
31
|
import {
|
|
32
32
|
ToolExecutor,
|
|
33
33
|
ToolRegistry,
|
|
@@ -45,10 +45,10 @@ import {
|
|
|
45
45
|
spawnAgentContext,
|
|
46
46
|
truncateOutput,
|
|
47
47
|
undoStack
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-XXKWSBRC.js";
|
|
49
49
|
import "./chunk-3BICTI5M.js";
|
|
50
50
|
import "./chunk-2DXY7UGF.js";
|
|
51
|
-
import "./chunk-
|
|
51
|
+
import "./chunk-2WAF7FOX.js";
|
|
52
52
|
import "./chunk-2ZD3YTVM.js";
|
|
53
53
|
import {
|
|
54
54
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
SKILLS_DIR_NAME,
|
|
69
69
|
VERSION,
|
|
70
70
|
buildUserIdentityPrompt
|
|
71
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-UF62SHR7.js";
|
|
72
72
|
import {
|
|
73
73
|
formatGitContextForPrompt,
|
|
74
74
|
getGitContext,
|
|
@@ -539,6 +539,7 @@ var SessionHandler = class _SessionHandler {
|
|
|
539
539
|
this.mcpManager = shared.mcpManager;
|
|
540
540
|
this.skillManager = shared.skillManager;
|
|
541
541
|
this.toolExecutor = new ToolExecutorWeb(shared.toolRegistry, ws);
|
|
542
|
+
this.toolExecutor.setSessionKey(`web-${Math.random().toString(36).slice(2)}-${Date.now()}`);
|
|
542
543
|
this.currentProvider = this.config.get("defaultProvider");
|
|
543
544
|
const allDefaultModels = this.config.get("defaultModels");
|
|
544
545
|
try {
|
|
@@ -2411,7 +2412,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
2411
2412
|
case "test": {
|
|
2412
2413
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
2413
2414
|
try {
|
|
2414
|
-
const { executeTests } = await import("./run-tests-
|
|
2415
|
+
const { executeTests } = await import("./run-tests-7WN5Q7YV.js");
|
|
2415
2416
|
const argStr = args.join(" ").trim();
|
|
2416
2417
|
let testArgs = {};
|
|
2417
2418
|
if (argStr) {
|
|
@@ -3467,6 +3468,27 @@ async function startWebServer(options = {}) {
|
|
|
3467
3468
|
skillManager
|
|
3468
3469
|
};
|
|
3469
3470
|
const app = express();
|
|
3471
|
+
app.use((_req, res, next) => {
|
|
3472
|
+
res.setHeader(
|
|
3473
|
+
"Content-Security-Policy",
|
|
3474
|
+
[
|
|
3475
|
+
"default-src 'self'",
|
|
3476
|
+
"script-src 'self'",
|
|
3477
|
+
"style-src 'self' 'unsafe-inline'",
|
|
3478
|
+
"img-src 'self' data: blob:",
|
|
3479
|
+
"font-src 'self' data:",
|
|
3480
|
+
"connect-src 'self' ws: wss:",
|
|
3481
|
+
"frame-ancestors 'none'",
|
|
3482
|
+
"base-uri 'self'",
|
|
3483
|
+
"form-action 'self'"
|
|
3484
|
+
].join("; ")
|
|
3485
|
+
);
|
|
3486
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
3487
|
+
res.setHeader("Referrer-Policy", "no-referrer");
|
|
3488
|
+
res.setHeader("X-Frame-Options", "DENY");
|
|
3489
|
+
res.setHeader("Permissions-Policy", "geolocation=(), microphone=(), camera=()");
|
|
3490
|
+
next();
|
|
3491
|
+
});
|
|
3470
3492
|
const server = createServer(app);
|
|
3471
3493
|
const WS_MAX_PAYLOAD = 1 * 1024 * 1024;
|
|
3472
3494
|
const WS_MSG_RATE_PER_SEC = 30;
|
|
@@ -3,14 +3,14 @@ import {
|
|
|
3
3
|
ToolRegistry,
|
|
4
4
|
getDangerLevel,
|
|
5
5
|
schemaToJsonSchema
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-XXKWSBRC.js";
|
|
7
7
|
import "./chunk-3BICTI5M.js";
|
|
8
8
|
import "./chunk-2DXY7UGF.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-2WAF7FOX.js";
|
|
10
10
|
import "./chunk-2ZD3YTVM.js";
|
|
11
11
|
import {
|
|
12
12
|
VERSION
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-UF62SHR7.js";
|
|
14
14
|
import "./chunk-4BKXL7SM.js";
|
|
15
15
|
import "./chunk-7ZJN4KLV.js";
|
|
16
16
|
import "./chunk-KHYD3WXE.js";
|
|
@@ -23,6 +23,14 @@ import { createInterface } from "readline";
|
|
|
23
23
|
import { resolve } from "path";
|
|
24
24
|
import { realpathSync } from "fs";
|
|
25
25
|
var STDIN_TOOLS = /* @__PURE__ */ new Set(["ask_user", "spawn_agent"]);
|
|
26
|
+
function looksLikePath(s) {
|
|
27
|
+
if (s.startsWith("/")) return true;
|
|
28
|
+
if (s.startsWith("\\\\")) return true;
|
|
29
|
+
if (/^[a-zA-Z]:[\\/]/.test(s)) return true;
|
|
30
|
+
if (s.startsWith("../") || s.startsWith("..\\")) return true;
|
|
31
|
+
if (s === "..") return true;
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
26
34
|
var MCP_ALWAYS_DESTRUCTIVE = /* @__PURE__ */ new Set([
|
|
27
35
|
"bash",
|
|
28
36
|
"run_interactive",
|
|
@@ -237,19 +245,30 @@ var McpServer = class {
|
|
|
237
245
|
validatePathArgs(toolName, args) {
|
|
238
246
|
if (this.opts.allowOutsideCwd) return void 0;
|
|
239
247
|
const keys = TOOL_PATH_ARGS[toolName];
|
|
240
|
-
if (
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
if (keys) {
|
|
249
|
+
for (const key of keys) {
|
|
250
|
+
const value = args[key];
|
|
251
|
+
if (value === void 0 || value === null) continue;
|
|
252
|
+
const list = Array.isArray(value) ? value : [value];
|
|
253
|
+
for (const entry of list) {
|
|
254
|
+
if (typeof entry !== "string" || entry.length === 0) continue;
|
|
255
|
+
const abs = resolve(this.sandboxRoot, entry);
|
|
256
|
+
if (!this.isInsideSandbox(abs)) {
|
|
257
|
+
return `Path '${entry}' escapes sandbox root '${this.sandboxRoot}'. Pass --allow-outside-cwd to permit.`;
|
|
258
|
+
}
|
|
250
259
|
}
|
|
251
260
|
}
|
|
252
261
|
}
|
|
262
|
+
const knownKeys = new Set(keys ?? []);
|
|
263
|
+
for (const [k, v] of Object.entries(args)) {
|
|
264
|
+
if (knownKeys.has(k)) continue;
|
|
265
|
+
if (typeof v !== "string" || v.length === 0) continue;
|
|
266
|
+
if (!looksLikePath(v)) continue;
|
|
267
|
+
const abs = resolve(this.sandboxRoot, v);
|
|
268
|
+
if (!this.isInsideSandbox(abs)) {
|
|
269
|
+
return `Path-like argument '${k}=${v}' escapes sandbox root '${this.sandboxRoot}'. Pass --allow-outside-cwd to permit.`;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
253
272
|
return void 0;
|
|
254
273
|
}
|
|
255
274
|
isInsideSandbox(abs) {
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
getDangerLevel,
|
|
5
5
|
googleSearchContext,
|
|
6
6
|
truncateOutput
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-XXKWSBRC.js";
|
|
8
8
|
import "./chunk-3BICTI5M.js";
|
|
9
9
|
import "./chunk-2DXY7UGF.js";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-2WAF7FOX.js";
|
|
11
11
|
import "./chunk-2ZD3YTVM.js";
|
|
12
12
|
import {
|
|
13
13
|
SUBAGENT_ALLOWED_TOOLS
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-UF62SHR7.js";
|
|
15
15
|
import "./chunk-4BKXL7SM.js";
|
|
16
16
|
import "./chunk-7ZJN4KLV.js";
|
|
17
17
|
import "./chunk-KHYD3WXE.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jinzd-ai-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.114",
|
|
4
4
|
"description": "Cross-platform REPL-style AI CLI with multi-provider support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"dev": "tsx src/index.ts",
|
|
22
22
|
"test": "vitest run",
|
|
23
23
|
"test:watch": "vitest",
|
|
24
|
+
"test:coverage": "vitest run --coverage",
|
|
24
25
|
"lint": "eslint src",
|
|
25
26
|
"prepublishOnly": "npm run build",
|
|
26
27
|
"patch": "node scripts/patch-sqlite.mjs",
|
|
@@ -89,8 +90,8 @@
|
|
|
89
90
|
"tree-sitter-javascript": "^0.25.0",
|
|
90
91
|
"tree-sitter-python": "^0.25.0",
|
|
91
92
|
"tree-sitter-typescript": "^0.23.2",
|
|
92
|
-
"undici": "^7.
|
|
93
|
-
"uuid": "^
|
|
93
|
+
"undici": "^7.24.0",
|
|
94
|
+
"uuid": "^14.0.0",
|
|
94
95
|
"web-tree-sitter": "^0.26.8",
|
|
95
96
|
"ws": "^8.19.0",
|
|
96
97
|
"zod": "^3.24.1"
|
|
@@ -101,12 +102,15 @@
|
|
|
101
102
|
"@types/uuid": "^10.0.0",
|
|
102
103
|
"@types/ws": "^8.18.1",
|
|
103
104
|
"@yao-pkg/pkg": "^6.14.0",
|
|
104
|
-
"
|
|
105
|
+
"@typescript-eslint/eslint-plugin": "^8.13.0",
|
|
106
|
+
"@typescript-eslint/parser": "^8.13.0",
|
|
107
|
+
"electron": "^41.4.0",
|
|
105
108
|
"electron-builder": "^26.0.0",
|
|
109
|
+
"eslint": "^9.13.0",
|
|
106
110
|
"tsup": "^8.3.5",
|
|
107
111
|
"tsx": "^4.19.2",
|
|
108
112
|
"typescript": "^5.7.3",
|
|
109
|
-
"vitest": "^2.
|
|
113
|
+
"vitest": "^3.2.4"
|
|
110
114
|
},
|
|
111
115
|
"author": "jinzd",
|
|
112
116
|
"build": {
|