jinzd-ai-cli 0.4.113 → 0.4.115
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-7KK5BOCA.js +12 -0
- package/dist/{batch-NF26OYWB.js → batch-SHNIUSW2.js} +2 -2
- package/dist/{chunk-TFYDLG7E.js → chunk-FMPWML3F.js} +63 -11
- package/dist/{chunk-2WAEM5B6.js → chunk-KQZU2VS5.js} +1 -1
- package/dist/{chunk-BYNY5JPB.js → chunk-O7NM4WTS.js} +86 -7
- package/dist/{chunk-SRU5SYZI.js → chunk-OHUHYWBR.js} +1 -1
- package/dist/{chunk-SIDKPVRD.js → chunk-PEMNYHIS.js} +1 -1
- package/dist/{chunk-3GUNDGUV.js → chunk-TJGRPTJS.js} +52 -11
- package/dist/{chunk-WLZ2PWQV.js → chunk-UZLNS3QG.js} +1 -1
- package/dist/{constants-2Z7YP252.js → constants-Y6LRE5TI.js} +1 -1
- package/dist/electron-server.js +247 -30
- package/dist/{hub-KRNH76Y3.js → hub-E3WMJGYK.js} +1 -1
- package/dist/index.js +123 -37
- package/dist/{run-tests-YOBAV24V.js → run-tests-7VYL7OVA.js} +1 -1
- package/dist/{run-tests-BUDXHVNF.js → run-tests-TWE7TJ4T.js} +2 -2
- package/dist/{server-ABNZXOV2.js → server-3P5BYK74.js} +32 -13
- package/dist/{server-MJGOR6FU.js → server-RODHACCH.js} +64 -11
- package/dist/{task-orchestrator-KZISH5DT.js → task-orchestrator-24IGVXYP.js} +3 -3
- package/dist/web/client/style.css +129 -129
- package/package.json +2 -1
- 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-UZLNS3QG.js";
|
|
5
5
|
import "./chunk-2ZD3YTVM.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-OHUHYWBR.js";
|
|
7
7
|
import "./chunk-PDX44BCA.js";
|
|
8
8
|
|
|
9
9
|
// src/cli/batch.ts
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
schemaToJsonSchema,
|
|
4
4
|
truncateForPersist
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-TJGRPTJS.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-OHUHYWBR.js";
|
|
22
22
|
import {
|
|
23
23
|
redactJson
|
|
24
24
|
} from "./chunk-7ZJN4KLV.js";
|
|
@@ -72,10 +72,20 @@ var ClaudeProvider = class extends BaseProvider {
|
|
|
72
72
|
]
|
|
73
73
|
};
|
|
74
74
|
async initialize(apiKey, options) {
|
|
75
|
-
|
|
75
|
+
const clientOptions = {
|
|
76
76
|
apiKey,
|
|
77
77
|
baseURL: options?.baseUrl
|
|
78
|
-
}
|
|
78
|
+
};
|
|
79
|
+
const proxyUrl = options?.proxy;
|
|
80
|
+
try {
|
|
81
|
+
const { Agent, ProxyAgent, fetch: undiciFetch } = await import("undici");
|
|
82
|
+
const STREAM_BODY_TIMEOUT = 30 * 60 * 1e3;
|
|
83
|
+
const STREAM_HEADERS_TIMEOUT = 5 * 60 * 1e3;
|
|
84
|
+
const dispatcher = proxyUrl ? new ProxyAgent({ uri: proxyUrl, bodyTimeout: STREAM_BODY_TIMEOUT, headersTimeout: STREAM_HEADERS_TIMEOUT }) : new Agent({ bodyTimeout: STREAM_BODY_TIMEOUT, headersTimeout: STREAM_HEADERS_TIMEOUT });
|
|
85
|
+
clientOptions.fetch = ((url, init) => undiciFetch(url, { ...init, dispatcher }));
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
this.client = new Anthropic(clientOptions);
|
|
79
89
|
}
|
|
80
90
|
/**
|
|
81
91
|
* 将内部 MessageContentPart[] 格式转换为 Anthropic SDK 期望的 ContentBlockParam[]。
|
|
@@ -932,13 +942,20 @@ var OpenAICompatibleProvider = class extends BaseProvider {
|
|
|
932
942
|
timeout: this.defaultTimeout
|
|
933
943
|
};
|
|
934
944
|
const proxyUrl = options?.proxy;
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
945
|
+
try {
|
|
946
|
+
const { Agent, ProxyAgent, fetch: undiciFetch } = await import("undici");
|
|
947
|
+
const STREAM_BODY_TIMEOUT = 30 * 60 * 1e3;
|
|
948
|
+
const STREAM_HEADERS_TIMEOUT = 5 * 60 * 1e3;
|
|
949
|
+
const dispatcher = proxyUrl ? new ProxyAgent({
|
|
950
|
+
uri: proxyUrl,
|
|
951
|
+
bodyTimeout: STREAM_BODY_TIMEOUT,
|
|
952
|
+
headersTimeout: STREAM_HEADERS_TIMEOUT
|
|
953
|
+
}) : new Agent({
|
|
954
|
+
bodyTimeout: STREAM_BODY_TIMEOUT,
|
|
955
|
+
headersTimeout: STREAM_HEADERS_TIMEOUT
|
|
956
|
+
});
|
|
957
|
+
clientOptions.fetch = ((url, init) => undiciFetch(url, { ...init, dispatcher }));
|
|
958
|
+
} catch {
|
|
942
959
|
}
|
|
943
960
|
this.client = new OpenAI(clientOptions);
|
|
944
961
|
}
|
|
@@ -1854,6 +1871,40 @@ function peelMetaNarration(content) {
|
|
|
1854
1871
|
}
|
|
1855
1872
|
return out.trim();
|
|
1856
1873
|
}
|
|
1874
|
+
var META_NARRATION_HARD_MARKERS = [
|
|
1875
|
+
/\[⚠️\s*CONTENT GENERATION MODE\]/,
|
|
1876
|
+
/CONTENT_ONLY_STREAM_REMINDER\b/,
|
|
1877
|
+
/<system-reminder>/i
|
|
1878
|
+
];
|
|
1879
|
+
var META_NARRATION_HEURISTICS = [
|
|
1880
|
+
/\bthe user (?:is asking me|wants me|is requesting|expects me)\b/i,
|
|
1881
|
+
/\blet me (?:re-?read|re-?consider|reconsider|think about|carefully (?:re-?read|consider))\b/i,
|
|
1882
|
+
/\bI'?m (?:in (?:a )?content-only|in CONTENT-ONLY|currently in)\b/i,
|
|
1883
|
+
/\bI think (?:there might be|I should|I cannot|the (?:user|best)|maybe)\b/i,
|
|
1884
|
+
/\bWait,?\s+let me\b/i,
|
|
1885
|
+
/\bActually,?\s+I\b/i,
|
|
1886
|
+
/\bI need to be honest with the user\b/i,
|
|
1887
|
+
/\bI(?:'m| am) in a special mode\b/i,
|
|
1888
|
+
/\bGiven that I cannot\b/i
|
|
1889
|
+
];
|
|
1890
|
+
function detectMetaNarration(content) {
|
|
1891
|
+
if (!content) return null;
|
|
1892
|
+
const head = content.slice(0, 2e3);
|
|
1893
|
+
for (const re of META_NARRATION_HARD_MARKERS) {
|
|
1894
|
+
if (re.test(head)) return re.source;
|
|
1895
|
+
}
|
|
1896
|
+
if (/^#{1,3}\s+\S/m.test(head)) return null;
|
|
1897
|
+
let hits = 0;
|
|
1898
|
+
let firstMatch = "";
|
|
1899
|
+
for (const re of META_NARRATION_HEURISTICS) {
|
|
1900
|
+
if (re.test(head)) {
|
|
1901
|
+
hits++;
|
|
1902
|
+
if (!firstMatch) firstMatch = re.source;
|
|
1903
|
+
if (hits >= 2) return `meta-narration:${firstMatch}`;
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
return null;
|
|
1907
|
+
}
|
|
1857
1908
|
function looksLikeDocumentBody(content) {
|
|
1858
1909
|
if (!content || content.length < 200) return false;
|
|
1859
1910
|
if (/^#{1,6}\s+\S/m.test(content)) return true;
|
|
@@ -4156,6 +4207,7 @@ export {
|
|
|
4156
4207
|
buildPhantomCorrectionMessage,
|
|
4157
4208
|
detectPseudoToolCalls,
|
|
4158
4209
|
stripPseudoToolCalls,
|
|
4210
|
+
detectMetaNarration,
|
|
4159
4211
|
looksLikeDocumentBody,
|
|
4160
4212
|
stripToolCallReminder,
|
|
4161
4213
|
TEE_FINAL_USER_NUDGE,
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/web/auth.ts
|
|
4
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, copyFileSync } from "fs";
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, copyFileSync, renameSync, unlinkSync } from "fs";
|
|
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
|
+
var TOKEN_EXPIRY_MS = TOKEN_EXPIRY_HOURS * 3600 * 1e3;
|
|
9
10
|
var USERS_DIR = "users";
|
|
11
|
+
var LOGIN_MAX_FAILS = 5;
|
|
12
|
+
var LOGIN_LOCKOUT_MS = 15 * 60 * 1e3;
|
|
13
|
+
var loginAttempts = /* @__PURE__ */ new Map();
|
|
14
|
+
function __resetLoginAttemptsForTests() {
|
|
15
|
+
loginAttempts.clear();
|
|
16
|
+
}
|
|
10
17
|
var AuthManager = class {
|
|
11
18
|
usersFile;
|
|
12
19
|
baseDir;
|
|
@@ -57,16 +64,30 @@ var AuthManager = class {
|
|
|
57
64
|
this.save();
|
|
58
65
|
return null;
|
|
59
66
|
}
|
|
60
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* Authenticate user. Returns JWT token or null on failure.
|
|
69
|
+
*
|
|
70
|
+
* Audit closure (5th audit, v0.4.114): integrates failed-login lockout
|
|
71
|
+
* (CWE-307). After {@link LOGIN_MAX_FAILS} consecutive failures within a
|
|
72
|
+
* lockout window, further attempts return null without checking the
|
|
73
|
+
* password (avoiding pbkdf2 work + leaking timing). Successful login
|
|
74
|
+
* resets the counter.
|
|
75
|
+
*/
|
|
61
76
|
login(username, password) {
|
|
62
77
|
username = username.trim().toLowerCase();
|
|
78
|
+
const lockState = this.getLockState(username);
|
|
79
|
+
if (lockState.locked) return null;
|
|
63
80
|
const user = this.db.users.find((u) => u.username === username);
|
|
64
|
-
if (!user)
|
|
81
|
+
if (!user) {
|
|
82
|
+
this.recordFailedLogin(username);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
65
85
|
const isLegacy = !user.hashVersion || user.hashVersion < 2;
|
|
66
86
|
const hash = isLegacy ? this.hashPasswordLegacy(password, user.salt) : this.hashPassword(password, user.salt);
|
|
67
87
|
const a = Buffer.from(hash, "utf-8");
|
|
68
88
|
const b = Buffer.from(user.passwordHash, "utf-8");
|
|
69
89
|
if (a.length !== b.length || !timingSafeEqual(a, b)) {
|
|
90
|
+
this.recordFailedLogin(username);
|
|
70
91
|
return null;
|
|
71
92
|
}
|
|
72
93
|
if (isLegacy) {
|
|
@@ -76,8 +97,34 @@ var AuthManager = class {
|
|
|
76
97
|
user.hashVersion = 2;
|
|
77
98
|
this.save();
|
|
78
99
|
}
|
|
100
|
+
loginAttempts.delete(username);
|
|
79
101
|
return this.createToken(username);
|
|
80
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Returns current lockout state for a username and lazily expires it.
|
|
105
|
+
* Exposed (read-only) for tests and the `aicli user` CLI status output.
|
|
106
|
+
*/
|
|
107
|
+
getLockState(username) {
|
|
108
|
+
username = username.trim().toLowerCase();
|
|
109
|
+
const state = loginAttempts.get(username);
|
|
110
|
+
if (!state) return { locked: false, remainingMs: 0, fails: 0 };
|
|
111
|
+
if (state.lockedUntil > 0 && Date.now() >= state.lockedUntil) {
|
|
112
|
+
loginAttempts.delete(username);
|
|
113
|
+
return { locked: false, remainingMs: 0, fails: 0 };
|
|
114
|
+
}
|
|
115
|
+
if (state.lockedUntil > 0) {
|
|
116
|
+
return { locked: true, remainingMs: state.lockedUntil - Date.now(), fails: state.fails };
|
|
117
|
+
}
|
|
118
|
+
return { locked: false, remainingMs: 0, fails: state.fails };
|
|
119
|
+
}
|
|
120
|
+
recordFailedLogin(username) {
|
|
121
|
+
const state = loginAttempts.get(username) ?? { fails: 0, lockedUntil: 0 };
|
|
122
|
+
state.fails += 1;
|
|
123
|
+
if (state.fails >= LOGIN_MAX_FAILS) {
|
|
124
|
+
state.lockedUntil = Date.now() + LOGIN_LOCKOUT_MS;
|
|
125
|
+
}
|
|
126
|
+
loginAttempts.set(username, state);
|
|
127
|
+
}
|
|
81
128
|
/** Verify a token. Returns username or null. */
|
|
82
129
|
verifyToken(token) {
|
|
83
130
|
try {
|
|
@@ -90,12 +137,29 @@ var AuthManager = class {
|
|
|
90
137
|
Buffer.from(payloadB64, "base64url").toString("utf-8")
|
|
91
138
|
);
|
|
92
139
|
if (Date.now() > payload.exp) return null;
|
|
93
|
-
|
|
140
|
+
const user = this.db.users.find((u) => u.username === payload.username);
|
|
141
|
+
if (!user) return null;
|
|
142
|
+
if (user.tokensRevokedBefore && (!payload.iat || payload.iat < user.tokensRevokedBefore)) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
94
145
|
return payload.username;
|
|
95
146
|
} catch {
|
|
96
147
|
return null;
|
|
97
148
|
}
|
|
98
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Revoke every outstanding token for the given user by bumping the
|
|
152
|
+
* `tokensRevokedBefore` watermark to now. Audit closure (5th audit,
|
|
153
|
+
* v0.4.114). Returns true if the user existed.
|
|
154
|
+
*/
|
|
155
|
+
logoutAll(username) {
|
|
156
|
+
username = username.trim().toLowerCase();
|
|
157
|
+
const user = this.db.users.find((u) => u.username === username);
|
|
158
|
+
if (!user) return false;
|
|
159
|
+
user.tokensRevokedBefore = Date.now() + 1;
|
|
160
|
+
this.save();
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
99
163
|
/** Get user's data directory (absolute path) */
|
|
100
164
|
getUserDataDir(username) {
|
|
101
165
|
const user = this.db.users.find((u) => u.username === username);
|
|
@@ -127,6 +191,7 @@ var AuthManager = class {
|
|
|
127
191
|
user.passwordHash = this.hashPassword(newPassword, salt);
|
|
128
192
|
user.salt = salt;
|
|
129
193
|
user.hashVersion = 2;
|
|
194
|
+
user.tokensRevokedBefore = Date.now() + 1;
|
|
130
195
|
this.save();
|
|
131
196
|
return null;
|
|
132
197
|
}
|
|
@@ -188,7 +253,17 @@ var AuthManager = class {
|
|
|
188
253
|
}
|
|
189
254
|
saveDB(db) {
|
|
190
255
|
mkdirSync(this.baseDir, { recursive: true });
|
|
191
|
-
|
|
256
|
+
const tmp = `${this.usersFile}.tmp`;
|
|
257
|
+
try {
|
|
258
|
+
writeFileSync(tmp, JSON.stringify(db, null, 2), "utf-8");
|
|
259
|
+
renameSync(tmp, this.usersFile);
|
|
260
|
+
} catch (err) {
|
|
261
|
+
try {
|
|
262
|
+
unlinkSync(tmp);
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
265
|
+
throw err;
|
|
266
|
+
}
|
|
192
267
|
}
|
|
193
268
|
/** Legacy hash — kept only for migrating old users (v0.2.x) */
|
|
194
269
|
hashPasswordLegacy(password, salt) {
|
|
@@ -198,9 +273,11 @@ var AuthManager = class {
|
|
|
198
273
|
return pbkdf2Sync(password, salt, 1e5, 64, "sha512").toString("hex");
|
|
199
274
|
}
|
|
200
275
|
createToken(username) {
|
|
276
|
+
const now = Date.now();
|
|
201
277
|
const payload = {
|
|
202
278
|
username,
|
|
203
|
-
|
|
279
|
+
iat: now,
|
|
280
|
+
exp: now + TOKEN_EXPIRY_HOURS * 3600 * 1e3
|
|
204
281
|
};
|
|
205
282
|
const payloadB64 = Buffer.from(JSON.stringify(payload), "utf-8").toString("base64url");
|
|
206
283
|
const signature = this.sign(payloadB64);
|
|
@@ -212,5 +289,7 @@ var AuthManager = class {
|
|
|
212
289
|
};
|
|
213
290
|
|
|
214
291
|
export {
|
|
292
|
+
TOKEN_EXPIRY_MS,
|
|
293
|
+
__resetLoginAttemptsForTests,
|
|
215
294
|
AuthManager
|
|
216
295
|
};
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-3BICTI5M.js";
|
|
6
6
|
import {
|
|
7
7
|
runTestsTool
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-PEMNYHIS.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-OHUHYWBR.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) {
|
|
@@ -344,7 +372,7 @@ Important rules:
|
|
|
344
372
|
}
|
|
345
373
|
updateCwdFromCommand(command, effectiveCwd);
|
|
346
374
|
pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
|
|
347
|
-
const result =
|
|
375
|
+
const result = Buffer.isBuffer(stdout) ? stdout.toString("utf-8") : String(stdout ?? "");
|
|
348
376
|
return result || "(command completed with no output)";
|
|
349
377
|
} catch (err) {
|
|
350
378
|
pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
|
|
@@ -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 {
|