jinzd-ai-cli 0.4.306 → 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-JLGWQ7HU.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-JDT6UG6R.js → chunk-2DYQDLLF.js} +1 -1
- package/dist/{chunk-T6WQEQI4.js → chunk-2MNUMLN4.js} +1 -1
- package/dist/chunk-2XMUCC42.js +67 -0
- package/dist/{chunk-7SSCSSC6.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-WZWRA7R2.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-3RDJEYF7.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-JFNBJHR6.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-AYLK4EGA.js → chunk-HC33C4U3.js} +18 -18
- package/dist/chunk-JJFJWD35.js +162 -0
- package/dist/{chunk-W6PLV3EY.js → chunk-JJNUASMJ.js} +263 -55
- package/dist/{chunk-NYNSYLFY.js → chunk-JL44HGZC.js} +1 -1
- package/dist/{chunk-NUJTNH76.js → chunk-LBWKMTT3.js} +1 -1
- package/dist/{chunk-LAP7TPPK.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-K5SDDTN7.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-VLDB2SVN.js → chunk-TPTFV3Y6.js} +383 -513
- package/dist/{chunk-45UVJXFQ.js → chunk-W2UOVGRC.js} +1 -1
- package/dist/{chunk-VXEF2SWG.js → chunk-WT252ZDV.js} +2 -2
- package/dist/{chunk-NLB7JZSO.js → chunk-WVEIUSXE.js} +246 -55
- package/dist/{chunk-OC2XHG5L.js → chunk-YBZM7PHI.js} +4 -4
- package/dist/{ci-6PHEL3C7.js → ci-5HONSUAS.js} +7 -7
- package/dist/{ci-format-AZYUP6BL.js → ci-format-URUH3ZZ5.js} +2 -2
- package/dist/{constants-CPNWIUGZ.js → constants-BLDBI7IG.js} +1 -1
- package/dist/{constants-AXBD2EEB.js → constants-FG27DQMS.js} +1 -1
- package/dist/doctor-cli-QQLMNJN7.js +18 -0
- package/dist/electron-server.js +1003 -829
- package/dist/file-checkpoint-64A2AXHE.js +7 -0
- package/dist/{file-checkpoint-NKBHGC7L.js → file-checkpoint-VVPUJNCI.js} +2 -1
- package/dist/{hub-DDYAWLF2.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-XZISXRWT.js → persistent-memory-UNLCXUWH.js} +3 -3
- package/dist/{persistent-memory-6XYYOMSC.js → persistent-memory-YYWIN6BG.js} +3 -3
- package/dist/{pr-UY4ZCZNI.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-6IC33CJK.js → server-73UBHSFV.js} +16 -14
- package/dist/{server-DBHLVQVP.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-RDDETDW2.js → task-orchestrator-GI3RGLZI.js} +15 -14
- package/dist/{usage-D473ZVIP.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-DDHHDMMJ.js +0 -18
- package/dist/file-checkpoint-CGH6OJVI.js +0 -6
- package/dist/indexer-FPKY7LCT.js +0 -10
- package/dist/run-tests-BAMLVLO6.js +0 -10
- package/dist/run-tests-UEE3OMLS.js +0 -9
package/dist/electron-server.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hasSemanticIndex,
|
|
3
3
|
semanticSearch
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-O6CGOVD4.js";
|
|
5
|
+
import "./chunk-3YVNDYOY.js";
|
|
6
6
|
import {
|
|
7
7
|
buildChatIndex,
|
|
8
8
|
clearChatIndex,
|
|
@@ -15,17 +15,22 @@ import {
|
|
|
15
15
|
searchCandidates,
|
|
16
16
|
searchChatMemory,
|
|
17
17
|
updateSessionInIndex
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-M6YV5KSS.js";
|
|
19
19
|
import "./chunk-JV5N65KN.js";
|
|
20
20
|
import {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
MAX_SNAPSHOT_TOTAL_BYTES,
|
|
22
|
+
assertReadablePath,
|
|
23
|
+
canonicalPath,
|
|
24
|
+
fileCheckpoints,
|
|
25
|
+
isCredentialVault,
|
|
26
|
+
readSnapshot
|
|
27
|
+
} from "./chunk-FVXNEZEW.js";
|
|
23
28
|
import {
|
|
24
29
|
indexProject
|
|
25
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-RVMCY6DP.js";
|
|
26
31
|
import {
|
|
27
32
|
loadIndex
|
|
28
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-W2UOVGRC.js";
|
|
29
34
|
import {
|
|
30
35
|
addMemoryEntry,
|
|
31
36
|
backupMemoryStore,
|
|
@@ -50,23 +55,39 @@ import {
|
|
|
50
55
|
touchMemoryReferences,
|
|
51
56
|
updateMemoryApproval,
|
|
52
57
|
updateMemoryEntry
|
|
53
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-7OHXFTZO.js";
|
|
54
59
|
import {
|
|
55
60
|
DEFAULT_PATTERNS,
|
|
56
61
|
redactJson,
|
|
57
62
|
scanString
|
|
58
63
|
} from "./chunk-FSC6KEWU.js";
|
|
59
64
|
import {
|
|
65
|
+
atomicWriteFile,
|
|
60
66
|
atomicWriteFileSync
|
|
61
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-2XMUCC42.js";
|
|
62
68
|
import {
|
|
63
69
|
formatGitContextForPrompt,
|
|
64
70
|
getGitContext,
|
|
65
71
|
getGitRoot
|
|
66
72
|
} from "./chunk-CGLYJRFA.js";
|
|
67
73
|
import {
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
IS_WINDOWS,
|
|
75
|
+
WIN_UTF8_PREAMBLE,
|
|
76
|
+
currentAbortSignal,
|
|
77
|
+
decodeWindowsBuffer,
|
|
78
|
+
killProcessTree,
|
|
79
|
+
looksLikeGbkMojibake,
|
|
80
|
+
recoverGbkMojibake,
|
|
81
|
+
runTestsTool,
|
|
82
|
+
shellInvocation
|
|
83
|
+
} from "./chunk-WVEIUSXE.js";
|
|
84
|
+
import {
|
|
85
|
+
getCurrentSessionKey,
|
|
86
|
+
getExecutionConfigDir,
|
|
87
|
+
runWithExecutionContext,
|
|
88
|
+
runWithSessionKey,
|
|
89
|
+
scopedState
|
|
90
|
+
} from "./chunk-QMFMST2T.js";
|
|
70
91
|
import {
|
|
71
92
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
72
93
|
APP_NAME,
|
|
@@ -101,7 +122,65 @@ import {
|
|
|
101
122
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
102
123
|
VERSION,
|
|
103
124
|
buildUserIdentityPrompt
|
|
104
|
-
} from "./chunk-
|
|
125
|
+
} from "./chunk-2DYQDLLF.js";
|
|
126
|
+
|
|
127
|
+
// src/web/access.ts
|
|
128
|
+
import { randomBytes, timingSafeEqual } from "crypto";
|
|
129
|
+
import { networkInterfaces } from "os";
|
|
130
|
+
function equalToken(a, b) {
|
|
131
|
+
if (typeof a !== "string" || a.length > 4096) return false;
|
|
132
|
+
const left = Buffer.from(a), right = Buffer.from(b);
|
|
133
|
+
return left.length === right.length && timingSafeEqual(left, right);
|
|
134
|
+
}
|
|
135
|
+
var WebAccess = class {
|
|
136
|
+
constructor(host, externalOrigin) {
|
|
137
|
+
this.externalOrigin = externalOrigin;
|
|
138
|
+
if (!["0.0.0.0", "::"].includes(host)) this.hosts.add(host.includes(":") ? `[${host}]` : host.toLowerCase());
|
|
139
|
+
else for (const interfaces of Object.values(networkInterfaces())) for (const entry of interfaces ?? []) {
|
|
140
|
+
this.hosts.add(entry.address.includes(":") ? `[${entry.address}]` : entry.address);
|
|
141
|
+
}
|
|
142
|
+
if (externalOrigin && new URL(externalOrigin).origin !== externalOrigin) throw new Error("External origin must be an exact http(s) origin");
|
|
143
|
+
if (externalOrigin && !/^https?:\/\//.test(externalOrigin)) throw new Error("External origin must use HTTP(S)");
|
|
144
|
+
}
|
|
145
|
+
externalOrigin;
|
|
146
|
+
localToken = randomBytes(32).toString("base64url");
|
|
147
|
+
hosts = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
148
|
+
accepts(req) {
|
|
149
|
+
const host = req.headers.host;
|
|
150
|
+
if (!host) return false;
|
|
151
|
+
try {
|
|
152
|
+
const url = new URL(`http://${host}`);
|
|
153
|
+
const internal = this.hosts.has(url.hostname.toLowerCase()) && Number(url.port || 80) === req.socket.localPort;
|
|
154
|
+
const external = this.externalOrigin && new URL(this.externalOrigin).host === host;
|
|
155
|
+
if (!internal && !external) return false;
|
|
156
|
+
const origin = req.headers.origin;
|
|
157
|
+
return !origin || origin === (external ? this.externalOrigin : `http://${host}`);
|
|
158
|
+
} catch {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
var AuthRateLimiter = class {
|
|
164
|
+
ips = /* @__PURE__ */ new Map();
|
|
165
|
+
start = 0;
|
|
166
|
+
count = 0;
|
|
167
|
+
take(ip) {
|
|
168
|
+
const now = Date.now();
|
|
169
|
+
if (now - this.start >= 6e4) {
|
|
170
|
+
this.start = now;
|
|
171
|
+
this.count = 0;
|
|
172
|
+
}
|
|
173
|
+
if (++this.count > 120) return false;
|
|
174
|
+
for (const [key, value] of this.ips) if (now - value.start >= 6e4) this.ips.delete(key);
|
|
175
|
+
let bucket = this.ips.get(ip);
|
|
176
|
+
if (!bucket) {
|
|
177
|
+
if (this.ips.size >= 2048) return false;
|
|
178
|
+
bucket = { start: now, count: 0 };
|
|
179
|
+
this.ips.set(ip, bucket);
|
|
180
|
+
}
|
|
181
|
+
return ++bucket.count <= 30;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
105
184
|
|
|
106
185
|
// src/web/server.ts
|
|
107
186
|
import express from "express";
|
|
@@ -125,9 +204,9 @@ function imageValidationError(images) {
|
|
|
125
204
|
}
|
|
126
205
|
|
|
127
206
|
// src/web/server.ts
|
|
128
|
-
import { join as
|
|
129
|
-
import { existsSync as
|
|
130
|
-
import { networkInterfaces } from "os";
|
|
207
|
+
import { join as join25, dirname as dirname11, resolve as resolve12, relative as relative6, sep as sep5, extname as extname3, resolve as resolvePath } from "path";
|
|
208
|
+
import { existsSync as existsSync31, readFileSync as readFileSync22, readdirSync as readdirSync15, statSync as statSync16, realpathSync as realpathSync3, createReadStream } from "fs";
|
|
209
|
+
import { networkInterfaces as networkInterfaces2 } from "os";
|
|
131
210
|
|
|
132
211
|
// src/config/config-manager.ts
|
|
133
212
|
import { readFileSync, existsSync, mkdirSync } from "fs";
|
|
@@ -650,8 +729,8 @@ ${err}`
|
|
|
650
729
|
return changed;
|
|
651
730
|
}
|
|
652
731
|
save() {
|
|
653
|
-
mkdirSync(this.configDir, { recursive: true });
|
|
654
|
-
atomicWriteFileSync(this.configPath, JSON.stringify(this.config, null, 2));
|
|
732
|
+
mkdirSync(this.configDir, { recursive: true, mode: 448 });
|
|
733
|
+
atomicWriteFileSync(this.configPath, JSON.stringify(this.config, null, 2), { mode: 384 });
|
|
655
734
|
}
|
|
656
735
|
getApiKey(providerId) {
|
|
657
736
|
const envKey = EnvLoader.getApiKey(providerId);
|
|
@@ -1293,6 +1372,7 @@ function getDangerLevel(toolName, args) {
|
|
|
1293
1372
|
}
|
|
1294
1373
|
if (toolName === "bash") {
|
|
1295
1374
|
const cmd = String(args["command"] ?? "");
|
|
1375
|
+
if (/\bgit\s+(?:clean\b|reset\s+--hard\b|push\b[^\n]*(?:--force\b|\s-f\b))/i.test(cmd)) return "destructive";
|
|
1296
1376
|
if (/\brm\s+[^\n]*(?:-\w*[rRfF]\w*|--recursive|--force)\b/.test(cmd)) return "destructive";
|
|
1297
1377
|
if (/\brm\s+\S/.test(cmd)) return "destructive";
|
|
1298
1378
|
if (/\brmdir\b|\bformat\b|\bmkfs\b|\bdd\s+if=|\bshred\b|\bfdisk\b|\bparted\b/.test(cmd)) return "destructive";
|
|
@@ -1308,8 +1388,10 @@ function getDangerLevel(toolName, args) {
|
|
|
1308
1388
|
if (/\bSet-Content\b|\bOut-File\b|\bAdd-Content\b|\bCopy-Item\b|\bMove-Item\b|\bSet-ItemProperty\b|\bNew-ItemProperty\b/i.test(cmd)) return "write";
|
|
1309
1389
|
if (/\b(python3?|node|deno|bun|perl|ruby|php|bash|sh|zsh|pwsh|powershell)(\.exe)?\b[^\n]*?\s-(?:Command|EncodedCommand|c|e|p|r)\b/i.test(cmd)) return "write";
|
|
1310
1390
|
if (/\bcmd(\.exe)?\s+\/[ck]\b/i.test(cmd)) return "write";
|
|
1311
|
-
return "safe";
|
|
1391
|
+
if (!/[;&|`$<>\n\r(){}]/.test(cmd) && (/^(?:pwd|ls|cat|head|tail|wc|Get-ChildItem|Get-Location|Get-Content)(?:\s+[\w ./\\:*?=,@'"-]+)?\s*$/i.test(cmd.trim()) || /^echo(?:\s+[\w .,'"-]+)?\s*$/.test(cmd.trim()) || /^(?:node|python3?)\s+(?:-v|--version)\s*$/.test(cmd.trim()) || /^git\s+(?:status|log)(?:\s+(?:--short|--porcelain|--oneline|-\d+))*\s*$/.test(cmd.trim()))) return "safe";
|
|
1392
|
+
return "write";
|
|
1312
1393
|
}
|
|
1394
|
+
if (toolName === "run_tests") return "write";
|
|
1313
1395
|
if (toolName === "write_file") return "write";
|
|
1314
1396
|
if (toolName === "edit_file") return "write";
|
|
1315
1397
|
if (toolName === "save_last_response") return "write";
|
|
@@ -1324,7 +1406,7 @@ function getDangerLevel(toolName, args) {
|
|
|
1324
1406
|
if (toolName === "git_commit") return "write";
|
|
1325
1407
|
if (toolName === "git_status" || toolName === "git_diff" || toolName === "git_log") return "safe";
|
|
1326
1408
|
if (toolName === "notebook_edit") return "write";
|
|
1327
|
-
if (toolName === "read_file" || toolName === "list_dir" || toolName === "grep_files" || toolName === "glob_files" || toolName === "web_fetch" || toolName === "save_memory" || toolName === "ask_user" || toolName === "write_todos" || toolName === "google_search" || toolName === "spawn_agent"
|
|
1409
|
+
if (toolName === "read_file" || toolName === "list_dir" || toolName === "grep_files" || toolName === "glob_files" || toolName === "web_fetch" || toolName === "save_memory" || toolName === "ask_user" || toolName === "write_todos" || toolName === "google_search" || toolName === "spawn_agent") return "safe";
|
|
1328
1410
|
return "write";
|
|
1329
1411
|
}
|
|
1330
1412
|
function schemaToJsonSchema(schema) {
|
|
@@ -4678,7 +4760,7 @@ function clearModelCache(configDir, providerId) {
|
|
|
4678
4760
|
delete cache.providers[providerId];
|
|
4679
4761
|
saveModelCache(configDir, cache);
|
|
4680
4762
|
}
|
|
4681
|
-
function normalizeLiveModels(models, fetchedAt,
|
|
4763
|
+
function normalizeLiveModels(models, fetchedAt, source2 = "live") {
|
|
4682
4764
|
const seen = /* @__PURE__ */ new Set();
|
|
4683
4765
|
const normalized = [];
|
|
4684
4766
|
for (const model of models) {
|
|
@@ -4690,7 +4772,7 @@ function normalizeLiveModels(models, fetchedAt, source = "live") {
|
|
|
4690
4772
|
contextWindow: model.contextWindow || LIVE_DEFAULT_CONTEXT,
|
|
4691
4773
|
supportsStreaming: model.supportsStreaming ?? true,
|
|
4692
4774
|
...model.supportsThinking !== void 0 ? { supportsThinking: model.supportsThinking } : {},
|
|
4693
|
-
source,
|
|
4775
|
+
source: source2,
|
|
4694
4776
|
available: true,
|
|
4695
4777
|
metadataUnknown: model.metadataUnknown ?? !model.contextWindow,
|
|
4696
4778
|
...fetchedAt ? { fetchedAt } : {}
|
|
@@ -5463,26 +5545,33 @@ var SessionManager = class {
|
|
|
5463
5545
|
}
|
|
5464
5546
|
/** P1-PERF-03: `force` 跳过脏检查(用于进程退出、fork 等必须落盘场景) */
|
|
5465
5547
|
async save(force = false) {
|
|
5466
|
-
|
|
5467
|
-
if (
|
|
5468
|
-
if (
|
|
5548
|
+
const session = this._current;
|
|
5549
|
+
if (!session) return;
|
|
5550
|
+
if (session.messages.length === 0) return;
|
|
5551
|
+
if (!force && !session.dirty) return;
|
|
5469
5552
|
mkdirSync3(this.historyDir, { recursive: true });
|
|
5470
|
-
const filePath = join4(this.historyDir, `${
|
|
5471
|
-
const raw =
|
|
5553
|
+
const filePath = join4(this.historyDir, `${session.id}.json`);
|
|
5554
|
+
const raw = session.toJSON();
|
|
5472
5555
|
const opts = this.redactOptionsForSave();
|
|
5473
5556
|
const { value: payload, hits } = redactJson(raw, opts);
|
|
5474
5557
|
this.lastRedactionHits = hits.length;
|
|
5475
|
-
|
|
5476
|
-
|
|
5558
|
+
const serialized = JSON.stringify(payload, null, 2);
|
|
5559
|
+
session.dirty = false;
|
|
5560
|
+
try {
|
|
5561
|
+
await atomicWriteFile(filePath, serialized, { mode: 384 });
|
|
5562
|
+
} catch (error) {
|
|
5563
|
+
session.dirty = true;
|
|
5564
|
+
throw error;
|
|
5565
|
+
}
|
|
5477
5566
|
try {
|
|
5478
5567
|
updateSessionInIndex(this.historyDir, {
|
|
5479
|
-
id:
|
|
5480
|
-
provider:
|
|
5481
|
-
model:
|
|
5482
|
-
title:
|
|
5483
|
-
messageCount:
|
|
5484
|
-
created:
|
|
5485
|
-
updated:
|
|
5568
|
+
id: session.id,
|
|
5569
|
+
provider: session.provider,
|
|
5570
|
+
model: session.model,
|
|
5571
|
+
title: session.title,
|
|
5572
|
+
messageCount: session.messages.length,
|
|
5573
|
+
created: session.created,
|
|
5574
|
+
updated: session.updated
|
|
5486
5575
|
}, filePath);
|
|
5487
5576
|
} catch {
|
|
5488
5577
|
}
|
|
@@ -5633,16 +5722,22 @@ var SessionManager = class {
|
|
|
5633
5722
|
};
|
|
5634
5723
|
|
|
5635
5724
|
// src/tools/builtin/bash.ts
|
|
5636
|
-
import { spawn as
|
|
5725
|
+
import { spawn as spawn2 } from "child_process";
|
|
5637
5726
|
import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
5638
|
-
import { platform as
|
|
5727
|
+
import { platform as platform2 } from "os";
|
|
5639
5728
|
import { resolve } from "path";
|
|
5640
5729
|
|
|
5641
5730
|
// src/tools/undo-stack.ts
|
|
5642
|
-
import {
|
|
5731
|
+
import { writeFileSync, unlinkSync as unlinkSync3, rmdirSync, existsSync as existsSync5 } from "fs";
|
|
5643
5732
|
var MAX_UNDO_DEPTH = 20;
|
|
5644
5733
|
var UndoStack = class {
|
|
5645
|
-
|
|
5734
|
+
stateKey = /* @__PURE__ */ Symbol("stack");
|
|
5735
|
+
get stack() {
|
|
5736
|
+
return scopedState(this.stateKey, () => ({ items: [] })).items;
|
|
5737
|
+
}
|
|
5738
|
+
set stack(items) {
|
|
5739
|
+
scopedState(this.stateKey, () => ({ items: [] })).items = items;
|
|
5740
|
+
}
|
|
5646
5741
|
/**
|
|
5647
5742
|
* 在执行文件写入操作之前调用,保存当前状态。
|
|
5648
5743
|
* @param filePath 将要被写入的文件路径
|
|
@@ -5651,11 +5746,7 @@ var UndoStack = class {
|
|
|
5651
5746
|
push(filePath, description) {
|
|
5652
5747
|
let previousContent = null;
|
|
5653
5748
|
if (existsSync5(filePath)) {
|
|
5654
|
-
|
|
5655
|
-
previousContent = readFileSync5(filePath, "utf-8");
|
|
5656
|
-
} catch {
|
|
5657
|
-
return;
|
|
5658
|
-
}
|
|
5749
|
+
previousContent = readSnapshot(filePath);
|
|
5659
5750
|
}
|
|
5660
5751
|
this.pushEntry({
|
|
5661
5752
|
filePath,
|
|
@@ -5692,7 +5783,7 @@ var UndoStack = class {
|
|
|
5692
5783
|
/** 内部统一入栈方法,含溢出裁剪 */
|
|
5693
5784
|
pushEntry(entry) {
|
|
5694
5785
|
this.stack.push(entry);
|
|
5695
|
-
|
|
5786
|
+
while (this.stack.length > MAX_UNDO_DEPTH || this.stack.reduce((n, e) => n + Buffer.byteLength(e.previousContent ?? ""), 0) > MAX_SNAPSHOT_TOTAL_BYTES) {
|
|
5696
5787
|
this.stack.shift();
|
|
5697
5788
|
}
|
|
5698
5789
|
}
|
|
@@ -5753,186 +5844,17 @@ var UndoStack = class {
|
|
|
5753
5844
|
};
|
|
5754
5845
|
var undoStack = new UndoStack();
|
|
5755
5846
|
|
|
5756
|
-
// src/core/interrupt.ts
|
|
5757
|
-
var controller = new AbortController();
|
|
5758
|
-
function currentAbortSignal() {
|
|
5759
|
-
return controller.signal;
|
|
5760
|
-
}
|
|
5761
|
-
|
|
5762
|
-
// src/tools/session-context.ts
|
|
5763
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
5764
|
-
var als = new AsyncLocalStorage();
|
|
5765
|
-
var DEFAULT_SESSION_KEY = "__default__";
|
|
5766
|
-
function getCurrentSessionKey() {
|
|
5767
|
-
const ctx = als.getStore();
|
|
5768
|
-
if (!ctx || !ctx.sessionKey) return DEFAULT_SESSION_KEY;
|
|
5769
|
-
return ctx.sessionKey;
|
|
5770
|
-
}
|
|
5771
|
-
function runWithSessionKey(sessionKey, fn) {
|
|
5772
|
-
const key = sessionKey && sessionKey.length > 0 ? sessionKey : DEFAULT_SESSION_KEY;
|
|
5773
|
-
return als.run({ sessionKey: key }, fn);
|
|
5774
|
-
}
|
|
5775
|
-
|
|
5776
|
-
// src/tools/win-shell.ts
|
|
5777
|
-
import { platform } from "os";
|
|
5778
|
-
import { spawn } from "child_process";
|
|
5779
|
-
var IS_WINDOWS = platform() === "win32";
|
|
5780
|
-
var WIN_UTF8_PREAMBLE = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $OutputEncoding = [System.Text.Encoding]::UTF8; ";
|
|
5781
|
-
function shellInvocation(command) {
|
|
5782
|
-
if (IS_WINDOWS) {
|
|
5783
|
-
return {
|
|
5784
|
-
shell: "powershell.exe",
|
|
5785
|
-
args: ["-NoProfile", "-NonInteractive", "-Command", WIN_UTF8_PREAMBLE + command]
|
|
5786
|
-
};
|
|
5787
|
-
}
|
|
5788
|
-
return { shell: process.env["SHELL"] ?? "/bin/bash", args: ["-c", command] };
|
|
5789
|
-
}
|
|
5790
|
-
function decodeWindowsBuffer(buf) {
|
|
5791
|
-
const utf8 = buf.toString("utf-8");
|
|
5792
|
-
if (!utf8.includes("\uFFFD")) return utf8;
|
|
5793
|
-
try {
|
|
5794
|
-
return new TextDecoder("gbk").decode(buf);
|
|
5795
|
-
} catch {
|
|
5796
|
-
return utf8;
|
|
5797
|
-
}
|
|
5798
|
-
}
|
|
5799
|
-
var GBK_MOJIBAKE_FINGERPRINTS = [
|
|
5800
|
-
"\uFFFD",
|
|
5801
|
-
"\u9215",
|
|
5802
|
-
"\u951B",
|
|
5803
|
-
"\u9983",
|
|
5804
|
-
"\u9241",
|
|
5805
|
-
"\u6D93",
|
|
5806
|
-
"\u935B",
|
|
5807
|
-
"\u9428",
|
|
5808
|
-
"\u7EFE",
|
|
5809
|
-
"\u95AB",
|
|
5810
|
-
"\u9593",
|
|
5811
|
-
"\u95BF",
|
|
5812
|
-
"\u68E3",
|
|
5813
|
-
"\u95B4",
|
|
5814
|
-
"\u5A11",
|
|
5815
|
-
"\u95B8",
|
|
5816
|
-
"\u95BB",
|
|
5817
|
-
"\u7F01",
|
|
5818
|
-
"\u95C1"
|
|
5819
|
-
];
|
|
5820
|
-
var gbkReverse = null;
|
|
5821
|
-
function gbkReverseMap() {
|
|
5822
|
-
if (gbkReverse) return gbkReverse;
|
|
5823
|
-
const map = /* @__PURE__ */ new Map();
|
|
5824
|
-
try {
|
|
5825
|
-
const dec = new TextDecoder("gbk");
|
|
5826
|
-
for (let hi = 129; hi <= 254; hi++) {
|
|
5827
|
-
for (let lo = 64; lo <= 254; lo++) {
|
|
5828
|
-
if (lo === 127) continue;
|
|
5829
|
-
const ch = dec.decode(new Uint8Array([hi, lo]));
|
|
5830
|
-
if (ch.length === 1 && ch !== "\uFFFD" && !map.has(ch)) map.set(ch, [hi, lo]);
|
|
5831
|
-
}
|
|
5832
|
-
}
|
|
5833
|
-
} catch {
|
|
5834
|
-
}
|
|
5835
|
-
gbkReverse = map;
|
|
5836
|
-
return map;
|
|
5837
|
-
}
|
|
5838
|
-
function decodeRunAsChinese(bytes) {
|
|
5839
|
-
for (let start = 0; start <= 2 && start < bytes.length; start++) {
|
|
5840
|
-
for (let end = 0; end <= 2 && bytes.length - end > start; end++) {
|
|
5841
|
-
const slice = bytes.slice(start, bytes.length - end);
|
|
5842
|
-
if (slice.length < 6) continue;
|
|
5843
|
-
try {
|
|
5844
|
-
const out = new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(slice));
|
|
5845
|
-
if (/[一-鿿]/.test(out)) return out;
|
|
5846
|
-
} catch {
|
|
5847
|
-
}
|
|
5848
|
-
}
|
|
5849
|
-
}
|
|
5850
|
-
return null;
|
|
5851
|
-
}
|
|
5852
|
-
function recoverGbkMojibake(text) {
|
|
5853
|
-
if (!text) return null;
|
|
5854
|
-
const map = gbkReverseMap();
|
|
5855
|
-
if (map.size === 0) return null;
|
|
5856
|
-
let run = [];
|
|
5857
|
-
let best = null;
|
|
5858
|
-
const flush2 = () => {
|
|
5859
|
-
if (run.length >= 4) {
|
|
5860
|
-
const bytes = [];
|
|
5861
|
-
for (const p of run) bytes.push(p[0], p[1]);
|
|
5862
|
-
const out = decodeRunAsChinese(bytes);
|
|
5863
|
-
if (out && (!best || out.length > best.length)) best = out;
|
|
5864
|
-
}
|
|
5865
|
-
run = [];
|
|
5866
|
-
};
|
|
5867
|
-
for (const ch of text) {
|
|
5868
|
-
const pair = ch.codePointAt(0) < 128 ? void 0 : map.get(ch);
|
|
5869
|
-
if (pair) run.push(pair);
|
|
5870
|
-
else flush2();
|
|
5871
|
-
}
|
|
5872
|
-
flush2();
|
|
5873
|
-
return best;
|
|
5874
|
-
}
|
|
5875
|
-
function looksLikeGbkMojibake(text) {
|
|
5876
|
-
if (recoverGbkMojibake(text) !== null) return true;
|
|
5877
|
-
let hits = 0;
|
|
5878
|
-
for (const ch of GBK_MOJIBAKE_FINGERPRINTS) {
|
|
5879
|
-
if (text.includes(ch) && ++hits >= 2) return true;
|
|
5880
|
-
}
|
|
5881
|
-
return false;
|
|
5882
|
-
}
|
|
5883
|
-
function killProcessTree(proc) {
|
|
5884
|
-
if (proc.killed || proc.exitCode !== null) return;
|
|
5885
|
-
if (IS_WINDOWS && proc.pid) {
|
|
5886
|
-
try {
|
|
5887
|
-
const tk = spawn("taskkill", ["/PID", String(proc.pid), "/T", "/F"], {
|
|
5888
|
-
windowsHide: true,
|
|
5889
|
-
stdio: "ignore"
|
|
5890
|
-
});
|
|
5891
|
-
tk.on("exit", () => {
|
|
5892
|
-
try {
|
|
5893
|
-
proc.kill();
|
|
5894
|
-
} catch {
|
|
5895
|
-
}
|
|
5896
|
-
});
|
|
5897
|
-
tk.on("error", () => {
|
|
5898
|
-
try {
|
|
5899
|
-
proc.kill();
|
|
5900
|
-
} catch {
|
|
5901
|
-
}
|
|
5902
|
-
});
|
|
5903
|
-
} catch {
|
|
5904
|
-
try {
|
|
5905
|
-
proc.kill();
|
|
5906
|
-
} catch {
|
|
5907
|
-
}
|
|
5908
|
-
}
|
|
5909
|
-
return;
|
|
5910
|
-
}
|
|
5911
|
-
try {
|
|
5912
|
-
proc.kill("SIGTERM");
|
|
5913
|
-
} catch {
|
|
5914
|
-
}
|
|
5915
|
-
setTimeout(() => {
|
|
5916
|
-
if (!proc.killed && proc.exitCode === null) {
|
|
5917
|
-
try {
|
|
5918
|
-
proc.kill("SIGKILL");
|
|
5919
|
-
} catch {
|
|
5920
|
-
}
|
|
5921
|
-
}
|
|
5922
|
-
}, 500).unref();
|
|
5923
|
-
}
|
|
5924
|
-
|
|
5925
5847
|
// src/tools/shell-pool.ts
|
|
5926
|
-
import { spawn
|
|
5927
|
-
import { platform
|
|
5928
|
-
var IS_WINDOWS2 =
|
|
5848
|
+
import { spawn } from "child_process";
|
|
5849
|
+
import { platform } from "os";
|
|
5850
|
+
var IS_WINDOWS2 = platform() === "win32";
|
|
5929
5851
|
var SHELL = IS_WINDOWS2 ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
|
|
5930
5852
|
var pool = /* @__PURE__ */ new Map();
|
|
5931
5853
|
var IDLE_EVICT_MS = 5 * 6e4;
|
|
5932
5854
|
var evictionTimer = null;
|
|
5933
5855
|
function spawnShell(cwd) {
|
|
5934
5856
|
const shellArgs = IS_WINDOWS2 ? ["-NoProfile", "-NonInteractive", "-Command", "-"] : ["-s"];
|
|
5935
|
-
const proc =
|
|
5857
|
+
const proc = spawn(SHELL, shellArgs, {
|
|
5936
5858
|
cwd,
|
|
5937
5859
|
windowsHide: true,
|
|
5938
5860
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -6123,7 +6045,7 @@ function cleanupPoolSession(sessionKey) {
|
|
|
6123
6045
|
}
|
|
6124
6046
|
|
|
6125
6047
|
// src/tools/builtin/bash.ts
|
|
6126
|
-
var IS_WINDOWS3 =
|
|
6048
|
+
var IS_WINDOWS3 = platform2() === "win32";
|
|
6127
6049
|
var SHELL2 = IS_WINDOWS3 ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
|
|
6128
6050
|
var cwdBySession = /* @__PURE__ */ new Map();
|
|
6129
6051
|
function getCwd() {
|
|
@@ -6153,7 +6075,8 @@ Important rules:
|
|
|
6153
6075
|
4. To delete directories, use Remove-Item -Recurse (the system will automatically optimize to a more reliable method).
|
|
6154
6076
|
5. IMPORTANT: On Windows, "curl" is an alias for Invoke-WebRequest and does NOT support curl flags like -s, -X, -H. Use Invoke-RestMethod instead for HTTP requests. Example: Invoke-RestMethod -Uri "http://localhost:3000/api/health" -Method Get
|
|
6155
6077
|
6. NEVER run long-running / never-exiting commands through bash \u2014 they block until timeout. This includes starting an Android emulator (emulator -avd X), streaming logs (adb logcat), and dev servers (node server.js, npm run dev, metro / react-native start). Use the 'task_create' tool to run these in the background, then poll status with short bash calls (e.g. adb shell getprop sys.boot_completed).
|
|
6156
|
-
7. CRITICAL \u2014 this is PowerShell, NOT bash: \\ is NOT an escape character. Writing \\" inside a double-quoted string CLOSES the string and silently splits your command into fragments \u2014 it often still exits 0 with no output, so it looks like it worked when it did not. To escape a quote use \`" or "". For anything with nested quoting (especially ssh + psql -c "SELECT ..."), do NOT try to escape your way out: write the payload to a file with the 'write_file' tool, 'scp' it to the host, and run it with a single level of quoting (e.g. ssh host "psql -d db -f /tmp/x.sql")
|
|
6078
|
+
7. CRITICAL \u2014 this is PowerShell, NOT bash: \\ is NOT an escape character. Writing \\" inside a double-quoted string CLOSES the string and silently splits your command into fragments \u2014 it often still exits 0 with no output, so it looks like it worked when it did not. To escape a quote use \`" or "". For anything with nested quoting (especially ssh + psql -c "SELECT ..."), do NOT try to escape your way out: write the payload to a file with the 'write_file' tool, 'scp' it to the host, and run it with a single level of quoting (e.g. ssh host "psql -d db -f /tmp/x.sql").
|
|
6079
|
+
8. CRITICAL \u2014 running a program by its full path: a quoted string at the START of a statement is parsed as a string EXPRESSION, not a command. "C:\\path\\python.exe" -c "print(1)" is a SYNTAX ERROR. Put the call operator '&' in front: & "C:\\path\\python.exe" -c "print(1)" \u2014 or $py = "C:\\path\\python.exe"; & $py -c "print(1)". This applies to any quoted path used as a command, anywhere a statement starts (including after ';').` : `Execute commands in ${SHELL2}.
|
|
6157
6080
|
Important rules:
|
|
6158
6081
|
1. Each bash call runs in an independent subprocess; cd commands do not persist. To run in a specific directory, use the cwd parameter, or combine commands: e.g. "cd mydir && ls" or "mkdir -p mydir && touch mydir/file.txt".
|
|
6159
6082
|
2. If a command fails (returns an error or non-zero exit code), stop immediately, report the error to the user, and do not retry the same or similar commands.
|
|
@@ -6408,6 +6331,116 @@ function detectPowerShellBackslashQuote(command) {
|
|
|
6408
6331
|
}
|
|
6409
6332
|
return false;
|
|
6410
6333
|
}
|
|
6334
|
+
var PS_OPERATOR_WORDS = /* @__PURE__ */ new Set([
|
|
6335
|
+
"eq",
|
|
6336
|
+
"ne",
|
|
6337
|
+
"gt",
|
|
6338
|
+
"ge",
|
|
6339
|
+
"lt",
|
|
6340
|
+
"le",
|
|
6341
|
+
"ceq",
|
|
6342
|
+
"cne",
|
|
6343
|
+
"cgt",
|
|
6344
|
+
"cge",
|
|
6345
|
+
"clt",
|
|
6346
|
+
"cle",
|
|
6347
|
+
"ieq",
|
|
6348
|
+
"ine",
|
|
6349
|
+
"igt",
|
|
6350
|
+
"ige",
|
|
6351
|
+
"ilt",
|
|
6352
|
+
"ile",
|
|
6353
|
+
"like",
|
|
6354
|
+
"notlike",
|
|
6355
|
+
"match",
|
|
6356
|
+
"notmatch",
|
|
6357
|
+
"clike",
|
|
6358
|
+
"cnotlike",
|
|
6359
|
+
"cmatch",
|
|
6360
|
+
"cnotmatch",
|
|
6361
|
+
"ilike",
|
|
6362
|
+
"inotlike",
|
|
6363
|
+
"imatch",
|
|
6364
|
+
"inotmatch",
|
|
6365
|
+
"contains",
|
|
6366
|
+
"notcontains",
|
|
6367
|
+
"ccontains",
|
|
6368
|
+
"cnotcontains",
|
|
6369
|
+
"icontains",
|
|
6370
|
+
"inotcontains",
|
|
6371
|
+
"in",
|
|
6372
|
+
"notin",
|
|
6373
|
+
"cin",
|
|
6374
|
+
"cnotin",
|
|
6375
|
+
"iin",
|
|
6376
|
+
"inotin",
|
|
6377
|
+
"is",
|
|
6378
|
+
"isnot",
|
|
6379
|
+
"as",
|
|
6380
|
+
"and",
|
|
6381
|
+
"or",
|
|
6382
|
+
"not",
|
|
6383
|
+
"xor",
|
|
6384
|
+
"band",
|
|
6385
|
+
"bor",
|
|
6386
|
+
"bnot",
|
|
6387
|
+
"bxor",
|
|
6388
|
+
"shl",
|
|
6389
|
+
"shr",
|
|
6390
|
+
"join",
|
|
6391
|
+
"split",
|
|
6392
|
+
"replace",
|
|
6393
|
+
"csplit",
|
|
6394
|
+
"creplace",
|
|
6395
|
+
"isplit",
|
|
6396
|
+
"ireplace",
|
|
6397
|
+
"f"
|
|
6398
|
+
]);
|
|
6399
|
+
function detectMissingCallOperator(command) {
|
|
6400
|
+
if (!IS_WINDOWS3) return false;
|
|
6401
|
+
let atStatementStart = true;
|
|
6402
|
+
let afterCallOperator = false;
|
|
6403
|
+
for (let i = 0; i < command.length; i++) {
|
|
6404
|
+
const ch = command[i];
|
|
6405
|
+
if (ch === " " || ch === " ") continue;
|
|
6406
|
+
if (ch === ";" || ch === "\n" || ch === "{" || ch === "(" || ch === "|" || ch === "&") {
|
|
6407
|
+
if (ch === "&") {
|
|
6408
|
+
const next = command.slice(i + 1).replace(/^[ \t]+/, "");
|
|
6409
|
+
afterCallOperator = !next.startsWith("&");
|
|
6410
|
+
} else {
|
|
6411
|
+
afterCallOperator = false;
|
|
6412
|
+
}
|
|
6413
|
+
atStatementStart = true;
|
|
6414
|
+
continue;
|
|
6415
|
+
}
|
|
6416
|
+
if (ch === "." && atStatementStart) {
|
|
6417
|
+
afterCallOperator = true;
|
|
6418
|
+
continue;
|
|
6419
|
+
}
|
|
6420
|
+
if ((ch === '"' || ch === "'") && atStatementStart && !afterCallOperator) {
|
|
6421
|
+
const quote = ch;
|
|
6422
|
+
let j = i + 1;
|
|
6423
|
+
while (j < command.length && command[j] !== quote) j++;
|
|
6424
|
+
if (j >= command.length) return false;
|
|
6425
|
+
const rest = command.slice(j + 1);
|
|
6426
|
+
const m = /^[ \t]+(\S+)/.exec(rest);
|
|
6427
|
+
if (m) {
|
|
6428
|
+
const token = m[1];
|
|
6429
|
+
if (/^[-]/.test(token)) {
|
|
6430
|
+
const word = token.slice(1).toLowerCase();
|
|
6431
|
+
if (PS_OPERATOR_WORDS.has(word)) return false;
|
|
6432
|
+
return true;
|
|
6433
|
+
}
|
|
6434
|
+
if (/^[+*%=<>|;)}\]]/.test(token)) return false;
|
|
6435
|
+
return true;
|
|
6436
|
+
}
|
|
6437
|
+
return false;
|
|
6438
|
+
}
|
|
6439
|
+
atStatementStart = false;
|
|
6440
|
+
afterCallOperator = false;
|
|
6441
|
+
}
|
|
6442
|
+
return false;
|
|
6443
|
+
}
|
|
6411
6444
|
var BACKSLASH_QUOTE_SILENT_WARNING = `
|
|
6412
6445
|
|
|
6413
6446
|
\u26A0 WARNING: this command contains \\" inside a double-quoted string, but PowerShell does NOT treat \\ as an escape \u2014 that quote CLOSED the string and the command was split into fragments. It exited 0 with no output because the outer program (e.g. ssh) still ran, but it very likely did NOT run what you intended. Do NOT assume this succeeded.
|
|
@@ -6488,6 +6521,11 @@ function buildErrorHint(command, stderr) {
|
|
|
6488
6521
|
`Hint: PowerShell does NOT treat \\ as an escape character \u2014 the \\" in this command closed the double-quoted string and split the command into fragments. Escape quotes with \`" or "" instead. Better: avoid nested quoting entirely \u2014 write the payload to a file with 'write_file', 'scp' it over, and run it with one level of quoting (e.g. ssh host "psql -d <db> -f /tmp/x.sql").`
|
|
6489
6522
|
);
|
|
6490
6523
|
}
|
|
6524
|
+
if (detectMissingCallOperator(command) && /(ParserError|UnexpectedToken|Unexpected token|意外的标记|运算符后面提供|必须在)/i.test(stderr)) {
|
|
6525
|
+
hints.push(
|
|
6526
|
+
`Hint: in PowerShell a quoted string at the START of a statement is parsed as a string EXPRESSION, not as a command \u2014 so "C:\\path\\python.exe" -c "..." is a syntax error, not a quoting problem. Do NOT retry by removing the quotes, dropping the argument, or re-quoting it differently; all of those fail the same way. Fix: put the call operator '&' in front \u2014 \`& "C:\\path\\python.exe" -c "..."\` \u2014 or assign the path to a variable first (\`$py = "C:\\path\\python.exe"; & $py -c "..."\`).`
|
|
6527
|
+
);
|
|
6528
|
+
}
|
|
6491
6529
|
return hints.length > 0 ? hints.map((h) => `\u{1F4A1} ${h}`).join("\n\n") : null;
|
|
6492
6530
|
}
|
|
6493
6531
|
function detectBlockingCommand(command) {
|
|
@@ -6590,7 +6628,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
|
|
|
6590
6628
|
function runShell(command, opts) {
|
|
6591
6629
|
return new Promise((resolvePromise) => {
|
|
6592
6630
|
const shellArgs = IS_WINDOWS3 ? ["-NoProfile", "-NonInteractive", "-Command", WIN_UTF8_PREAMBLE + command] : ["-c", command];
|
|
6593
|
-
const child =
|
|
6631
|
+
const child = spawn2(SHELL2, shellArgs, {
|
|
6594
6632
|
cwd: opts.cwd,
|
|
6595
6633
|
env: opts.env,
|
|
6596
6634
|
windowsHide: true,
|
|
@@ -6669,16 +6707,16 @@ function updateCwdFromCommand(command, baseCwd) {
|
|
|
6669
6707
|
}
|
|
6670
6708
|
|
|
6671
6709
|
// src/tools/builtin/read-file.ts
|
|
6672
|
-
import { readFileSync as
|
|
6710
|
+
import { readFileSync as readFileSync5, existsSync as existsSync7, statSync as statSync3, readdirSync as readdirSync3 } from "fs";
|
|
6673
6711
|
import { execFileSync } from "child_process";
|
|
6674
6712
|
import { extname, resolve as resolve2, basename, sep, dirname as dirname2 } from "path";
|
|
6675
6713
|
import { homedir as homedir2 } from "os";
|
|
6676
6714
|
|
|
6677
6715
|
// src/tools/builtin/notebook-utils.ts
|
|
6678
|
-
function cellSourceToString(
|
|
6679
|
-
if (!
|
|
6680
|
-
if (Array.isArray(
|
|
6681
|
-
return
|
|
6716
|
+
function cellSourceToString(source2) {
|
|
6717
|
+
if (!source2) return "";
|
|
6718
|
+
if (Array.isArray(source2)) return source2.join("");
|
|
6719
|
+
return source2;
|
|
6682
6720
|
}
|
|
6683
6721
|
function stringToCellSource(content) {
|
|
6684
6722
|
if (content.length === 0) return [];
|
|
@@ -6733,16 +6771,16 @@ function renderNotebookAsText(nb, filePath) {
|
|
|
6733
6771
|
nb.cells.forEach((cell, idx) => {
|
|
6734
6772
|
const cellNum = idx + 1;
|
|
6735
6773
|
const type = cell.cell_type;
|
|
6736
|
-
const
|
|
6774
|
+
const source2 = cellSourceToString(cell.source);
|
|
6737
6775
|
if (type === "markdown") {
|
|
6738
6776
|
lines.push(`## Cell ${cellNum} [markdown]`);
|
|
6739
|
-
lines.push(
|
|
6777
|
+
lines.push(source2);
|
|
6740
6778
|
lines.push("");
|
|
6741
6779
|
} else if (type === "code") {
|
|
6742
6780
|
const execLabel = cell.execution_count != null ? ` (execution #${cell.execution_count})` : "";
|
|
6743
6781
|
lines.push(`## Cell ${cellNum} [code${execLabel}]`);
|
|
6744
6782
|
lines.push("```" + language);
|
|
6745
|
-
lines.push(
|
|
6783
|
+
lines.push(source2);
|
|
6746
6784
|
lines.push("```");
|
|
6747
6785
|
const outputText = extractOutputText(cell.outputs);
|
|
6748
6786
|
if (outputText) {
|
|
@@ -6754,7 +6792,7 @@ function renderNotebookAsText(nb, filePath) {
|
|
|
6754
6792
|
lines.push("");
|
|
6755
6793
|
} else if (type === "raw") {
|
|
6756
6794
|
lines.push(`## Cell ${cellNum} [raw]`);
|
|
6757
|
-
lines.push(
|
|
6795
|
+
lines.push(source2);
|
|
6758
6796
|
lines.push("");
|
|
6759
6797
|
}
|
|
6760
6798
|
});
|
|
@@ -6779,6 +6817,7 @@ function createCell(cellType, content) {
|
|
|
6779
6817
|
// src/tools/builtin/read-file.ts
|
|
6780
6818
|
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
6781
6819
|
function getHardRefusal(normalizedPath) {
|
|
6820
|
+
if (isCredentialVault(normalizedPath)) return "[Refused] Credential vault files cannot be read by tools.";
|
|
6782
6821
|
const home2 = homedir2();
|
|
6783
6822
|
const p = normalizedPath.toLowerCase();
|
|
6784
6823
|
const base = basename(normalizedPath).toLowerCase();
|
|
@@ -6953,7 +6992,7 @@ var readFileTool = {
|
|
|
6953
6992
|
const offsetLine = typeof rawOffset === "number" && Number.isFinite(rawOffset) && rawOffset > 0 ? Math.floor(rawOffset) : void 0;
|
|
6954
6993
|
const limitLines = typeof rawLimit === "number" && Number.isFinite(rawLimit) && rawLimit > 0 ? Math.floor(rawLimit) : void 0;
|
|
6955
6994
|
if (!filePath) throw new ToolError("read_file", "path is required");
|
|
6956
|
-
const normalizedPath =
|
|
6995
|
+
const normalizedPath = canonicalPath(filePath);
|
|
6957
6996
|
const refusal = getHardRefusal(normalizedPath);
|
|
6958
6997
|
if (refusal) return refusal;
|
|
6959
6998
|
if (!existsSync7(normalizedPath)) {
|
|
@@ -7020,7 +7059,7 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
|
|
|
7020
7059
|
}
|
|
7021
7060
|
if (ext === ".ipynb") {
|
|
7022
7061
|
try {
|
|
7023
|
-
const raw =
|
|
7062
|
+
const raw = readFileSync5(normalizedPath, "utf-8");
|
|
7024
7063
|
const nb = parseNotebook(raw);
|
|
7025
7064
|
return renderNotebookAsText(nb, filePath);
|
|
7026
7065
|
} catch (err) {
|
|
@@ -7038,8 +7077,8 @@ Do NOT claim you inspected this image \u2014 you have not seen it.`;
|
|
|
7038
7077
|
return `[Binary file: ${filePath} (${ext})]
|
|
7039
7078
|
This is a binary file and cannot be read as text. If there is a text version (.md / .txt) in the project, please read that instead.`;
|
|
7040
7079
|
}
|
|
7041
|
-
const { readFile
|
|
7042
|
-
const buf = size > 1048576 ? await
|
|
7080
|
+
const { readFile } = await import("fs/promises");
|
|
7081
|
+
const buf = size > 1048576 ? await readFile(normalizedPath) : readFileSync5(normalizedPath);
|
|
7043
7082
|
if (encoding === "base64") {
|
|
7044
7083
|
return `[File: ${filePath} | base64]
|
|
7045
7084
|
|
|
@@ -7077,12 +7116,12 @@ ${content}`;
|
|
|
7077
7116
|
};
|
|
7078
7117
|
|
|
7079
7118
|
// src/tools/builtin/write-file.ts
|
|
7080
|
-
import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as
|
|
7119
|
+
import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
|
|
7081
7120
|
import { dirname as dirname5 } from "path";
|
|
7082
7121
|
|
|
7083
7122
|
// src/tools/executor.ts
|
|
7084
7123
|
import chalk3 from "chalk";
|
|
7085
|
-
import { existsSync as
|
|
7124
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
|
|
7086
7125
|
import { tmpdir } from "os";
|
|
7087
7126
|
|
|
7088
7127
|
// src/core/readline-internal.ts
|
|
@@ -7322,7 +7361,7 @@ function simpleDiff(oldLines, newLines) {
|
|
|
7322
7361
|
// src/tools/hooks.ts
|
|
7323
7362
|
import { execSync } from "child_process";
|
|
7324
7363
|
import { createHash } from "crypto";
|
|
7325
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as
|
|
7364
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6 } from "fs";
|
|
7326
7365
|
import { join as join5 } from "path";
|
|
7327
7366
|
var TRUST_FILE = "hooks-trust.json";
|
|
7328
7367
|
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
@@ -7338,7 +7377,7 @@ function loadHookTrustStore(configDir) {
|
|
|
7338
7377
|
const file = hookTrustPath(configDir);
|
|
7339
7378
|
if (!existsSync8(file)) return { records: [] };
|
|
7340
7379
|
try {
|
|
7341
|
-
const parsed = JSON.parse(
|
|
7380
|
+
const parsed = JSON.parse(readFileSync6(file, "utf-8"));
|
|
7342
7381
|
return { records: Array.isArray(parsed.records) ? parsed.records : [] };
|
|
7343
7382
|
} catch {
|
|
7344
7383
|
return { records: [] };
|
|
@@ -7352,9 +7391,9 @@ function hashHook(event, command) {
|
|
|
7352
7391
|
return createHash("sha256").update(`${event}
|
|
7353
7392
|
${command}`, "utf-8").digest("hex");
|
|
7354
7393
|
}
|
|
7355
|
-
function isTrusted(
|
|
7356
|
-
if (
|
|
7357
|
-
return store.records.some((r) => r.source ===
|
|
7394
|
+
function isTrusted(source2, event, hash, store) {
|
|
7395
|
+
if (source2 === "user" || source2 === "managed") return true;
|
|
7396
|
+
return store.records.some((r) => r.source === source2 && r.event === event && r.hash === hash);
|
|
7358
7397
|
}
|
|
7359
7398
|
function listHooks(config, configDir) {
|
|
7360
7399
|
if (!config || config.enabled === false) return [];
|
|
@@ -7362,15 +7401,15 @@ function listHooks(config, configDir) {
|
|
|
7362
7401
|
const out = [];
|
|
7363
7402
|
const push = (event, hook, index) => {
|
|
7364
7403
|
if (!hook.command) return;
|
|
7365
|
-
const
|
|
7404
|
+
const source2 = hook.source ?? "user";
|
|
7366
7405
|
const hash = hashHook(event, hook.command);
|
|
7367
7406
|
out.push({
|
|
7368
7407
|
id: `${event}:${index}`,
|
|
7369
7408
|
event,
|
|
7370
7409
|
command: hook.command,
|
|
7371
|
-
source,
|
|
7410
|
+
source: source2,
|
|
7372
7411
|
hash,
|
|
7373
|
-
trusted: isTrusted(
|
|
7412
|
+
trusted: isTrusted(source2, event, hash, store),
|
|
7374
7413
|
disabled: hook.disabled === true,
|
|
7375
7414
|
description: hook.description
|
|
7376
7415
|
});
|
|
@@ -7446,15 +7485,15 @@ function runLifecycleHooks(config, event, payload, opts) {
|
|
|
7446
7485
|
for (let i = 0; i < hooks.length; i++) {
|
|
7447
7486
|
const hook = hooks[i];
|
|
7448
7487
|
if (!hook.command || hook.disabled) continue;
|
|
7449
|
-
const
|
|
7488
|
+
const source2 = hook.source ?? "user";
|
|
7450
7489
|
const hash = hashHook(event, hook.command);
|
|
7451
7490
|
const descriptor = {
|
|
7452
7491
|
id: `${event}:${i}`,
|
|
7453
7492
|
event,
|
|
7454
7493
|
command: hook.command,
|
|
7455
|
-
source,
|
|
7494
|
+
source: source2,
|
|
7456
7495
|
hash,
|
|
7457
|
-
trusted: isTrusted(
|
|
7496
|
+
trusted: isTrusted(source2, event, hash, store),
|
|
7458
7497
|
disabled: false,
|
|
7459
7498
|
description: hook.description
|
|
7460
7499
|
};
|
|
@@ -7603,7 +7642,8 @@ var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
|
|
|
7603
7642
|
"recall_memory"
|
|
7604
7643
|
]);
|
|
7605
7644
|
function normalizePathForPermission(value) {
|
|
7606
|
-
|
|
7645
|
+
const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
7646
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
7607
7647
|
}
|
|
7608
7648
|
function canonicalizeWithNearestParent(value) {
|
|
7609
7649
|
const missing = [];
|
|
@@ -7691,7 +7731,7 @@ function profileHardDecision(profileName, toolName, args, dangerLevel, ctx) {
|
|
|
7691
7731
|
return { action: "deny", reason: "file write target is outside workspace/temp roots", profileName, hardBoundary: true };
|
|
7692
7732
|
}
|
|
7693
7733
|
}
|
|
7694
|
-
if (toolName === "bash" || toolName === "run_interactive" || toolName === "task_create") {
|
|
7734
|
+
if (toolName === "bash" || toolName === "run_interactive" || toolName === "task_create" || toolName === "run_tests") {
|
|
7695
7735
|
return {
|
|
7696
7736
|
action: "confirm",
|
|
7697
7737
|
reason: "shell/process writes cannot be proven to stay inside workspace/temp roots",
|
|
@@ -7793,9 +7833,9 @@ function checkNetworkPolicy(toolName, args, policy) {
|
|
|
7793
7833
|
if (!policy?.enabled) return null;
|
|
7794
7834
|
let action = networkToolAction(toolName, policy);
|
|
7795
7835
|
let reason = "network access requires confirmation";
|
|
7796
|
-
if (
|
|
7836
|
+
if (["bash", "run_tests", "run_interactive", "task_create"].includes(toolName)) {
|
|
7797
7837
|
const command = String(args["command"] ?? "");
|
|
7798
|
-
if (!isShellNetworkCommand(command)) return null;
|
|
7838
|
+
if (toolName === "bash" && !isShellNetworkCommand(command) && dangerIsReadOnly(command)) return null;
|
|
7799
7839
|
action = policy.tools?.shell ?? policy.defaultAction ?? "confirm";
|
|
7800
7840
|
reason = "shell command may access the network";
|
|
7801
7841
|
}
|
|
@@ -7831,6 +7871,9 @@ function checkNetworkPolicy(toolName, args, policy) {
|
|
|
7831
7871
|
if (!permissionAction) return null;
|
|
7832
7872
|
return { action: permissionAction, reason, profileName: "networkPolicy" };
|
|
7833
7873
|
}
|
|
7874
|
+
function dangerIsReadOnly(command) {
|
|
7875
|
+
return /^(?:pwd|echo [\w .-]+|git status(?: --short)?)$/.test(command.trim());
|
|
7876
|
+
}
|
|
7834
7877
|
|
|
7835
7878
|
// src/tools/truncate.ts
|
|
7836
7879
|
var DEFAULT_MAX_TOOL_OUTPUT_CHARS = 12e3;
|
|
@@ -8057,8 +8100,7 @@ function getTopUsedTools(limit = 5) {
|
|
|
8057
8100
|
}
|
|
8058
8101
|
|
|
8059
8102
|
// src/tools/action-classifier.ts
|
|
8060
|
-
import {
|
|
8061
|
-
import { isAbsolute as isAbsolute2, join as join7, resolve as resolve4 } from "path";
|
|
8103
|
+
import { isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
|
|
8062
8104
|
var RECENT_DENIED_LIMIT = 50;
|
|
8063
8105
|
var recentlyDenied = [];
|
|
8064
8106
|
function recordRecentlyDeniedAutoAction(call, classification) {
|
|
@@ -8078,7 +8120,8 @@ function clearRecentlyDeniedAutoActions() {
|
|
|
8078
8120
|
recentlyDenied.splice(0, recentlyDenied.length);
|
|
8079
8121
|
}
|
|
8080
8122
|
function normalizePath(value) {
|
|
8081
|
-
|
|
8123
|
+
const normalized = canonicalPath(value).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
8124
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
8082
8125
|
}
|
|
8083
8126
|
function isInside(root, target) {
|
|
8084
8127
|
const absoluteRoot = resolve4(root);
|
|
@@ -8091,56 +8134,6 @@ function getPathArg(call) {
|
|
|
8091
8134
|
const raw = call.arguments.path ?? call.arguments.filePath ?? call.arguments.outputPath ?? call.arguments.file;
|
|
8092
8135
|
return typeof raw === "string" && raw.trim() ? raw.trim() : null;
|
|
8093
8136
|
}
|
|
8094
|
-
function splitShellWords(command) {
|
|
8095
|
-
const out = [];
|
|
8096
|
-
const re = /"([^"]*)"|'([^']*)'|([^\s]+)/g;
|
|
8097
|
-
let m;
|
|
8098
|
-
while (m = re.exec(command)) out.push(m[1] ?? m[2] ?? m[3] ?? "");
|
|
8099
|
-
return out.filter(Boolean);
|
|
8100
|
-
}
|
|
8101
|
-
function readPackageManifest(root) {
|
|
8102
|
-
const p = join7(root, "package.json");
|
|
8103
|
-
if (!existsSync10(p)) return null;
|
|
8104
|
-
try {
|
|
8105
|
-
return JSON.parse(readFileSync8(p, "utf-8"));
|
|
8106
|
-
} catch {
|
|
8107
|
-
return null;
|
|
8108
|
-
}
|
|
8109
|
-
}
|
|
8110
|
-
function manifestHasDependency(root, name) {
|
|
8111
|
-
const manifest = readPackageManifest(root);
|
|
8112
|
-
if (!manifest) return false;
|
|
8113
|
-
for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
8114
|
-
const deps = manifest[key];
|
|
8115
|
-
if (deps && typeof deps === "object" && Object.prototype.hasOwnProperty.call(deps, name)) return true;
|
|
8116
|
-
}
|
|
8117
|
-
return false;
|
|
8118
|
-
}
|
|
8119
|
-
function lockfileMentions(root, name) {
|
|
8120
|
-
for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
|
|
8121
|
-
const p = join7(root, file);
|
|
8122
|
-
if (!existsSync10(p)) continue;
|
|
8123
|
-
try {
|
|
8124
|
-
if (readFileSync8(p, "utf-8").includes(name)) return true;
|
|
8125
|
-
} catch {
|
|
8126
|
-
}
|
|
8127
|
-
}
|
|
8128
|
-
return false;
|
|
8129
|
-
}
|
|
8130
|
-
function getInstallPackages(command) {
|
|
8131
|
-
const words = splitShellWords(command);
|
|
8132
|
-
const cmd = words[0]?.toLowerCase();
|
|
8133
|
-
const sub = words[1]?.toLowerCase();
|
|
8134
|
-
if (!cmd || !sub) return [];
|
|
8135
|
-
const isInstall = cmd === "npm" && (sub === "install" || sub === "i" || sub === "add") || (cmd === "pnpm" || cmd === "yarn") && (sub === "add" || sub === "install");
|
|
8136
|
-
if (!isInstall) return [];
|
|
8137
|
-
return words.slice(2).filter((w) => !w.startsWith("-")).filter((w) => w !== "." && w !== "./").map((w) => w.replace(/^@([^/]+)\/([^@]+)(?:@.+)?$/, "@$1/$2").replace(/^([^@]+)@.+$/, "$1"));
|
|
8138
|
-
}
|
|
8139
|
-
function isPlainGitPush(command) {
|
|
8140
|
-
if (!/^\s*git\s+push\b/i.test(command)) return false;
|
|
8141
|
-
if (/--force|-f\b|\+[^\s]+/i.test(command)) return false;
|
|
8142
|
-
return !/[;&|`$<>\n\r]/.test(command);
|
|
8143
|
-
}
|
|
8144
8137
|
function hasSecretExfiltrationShape(command) {
|
|
8145
8138
|
return /(?:\.env|config\.json|id_rsa|id_ed25519|AWS_SECRET|AICLI_API_KEY|OPENAI_API_KEY|ANTHROPIC_API_KEY)/i.test(command) && /\b(curl|wget|scp|rsync|nc|netcat|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command);
|
|
8146
8139
|
}
|
|
@@ -8162,7 +8155,7 @@ var RuleBasedActionClassifier = class {
|
|
|
8162
8155
|
}
|
|
8163
8156
|
return { decision: "confirm", reason: "destructive actions require confirmation", ruleId: "auto.destructive-confirm", confidence: "high" };
|
|
8164
8157
|
}
|
|
8165
|
-
classifyShell(call, dangerLevel,
|
|
8158
|
+
classifyShell(call, dangerLevel, _ctx) {
|
|
8166
8159
|
const command = String(call.arguments.command ?? "");
|
|
8167
8160
|
if (/\bcurl\b[^\n|;&]*\|\s*(?:sudo\s+)?(?:bash|sh|zsh)\b/i.test(command)) {
|
|
8168
8161
|
return { decision: "deny", reason: "curl pipe shell execution is blocked in auto mode", ruleId: "deny.curl-pipe-shell", confidence: "high" };
|
|
@@ -8188,18 +8181,7 @@ var RuleBasedActionClassifier = class {
|
|
|
8188
8181
|
if (/\b(npx|pnpm\s+dlx|yarn\s+dlx)\s+(claude|codex|aider|cursor-agent|gemini)\b/i.test(command)) {
|
|
8189
8182
|
return { decision: "confirm", reason: "third-party agent loop requires confirmation", ruleId: "confirm.third-party-agent", confidence: "high" };
|
|
8190
8183
|
}
|
|
8191
|
-
if (
|
|
8192
|
-
return { decision: "auto-approve", reason: "plain git push without force", ruleId: "auto.git-push", confidence: "high" };
|
|
8193
|
-
}
|
|
8194
|
-
const pkgs = getInstallPackages(command);
|
|
8195
|
-
if (pkgs.length > 0) {
|
|
8196
|
-
const allDeclared = pkgs.every((p) => manifestHasDependency(ctx.workspaceRoot, p) || lockfileMentions(ctx.workspaceRoot, p));
|
|
8197
|
-
return allDeclared ? { decision: "auto-approve", reason: "installing dependencies already declared in manifest/lockfile", ruleId: "auto.declared-dependency-install", confidence: "medium" } : { decision: "confirm", reason: "dependency install includes undeclared packages", ruleId: "confirm.undeclared-dependency-install", confidence: "medium" };
|
|
8198
|
-
}
|
|
8199
|
-
if (/\b(curl|wget|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command) && !/[>|;&`$]/.test(command)) {
|
|
8200
|
-
return { decision: "auto-approve", reason: "read-only HTTP shell command", ruleId: "auto.readonly-http-shell", confidence: "medium" };
|
|
8201
|
-
}
|
|
8202
|
-
if (dangerLevel === "safe") {
|
|
8184
|
+
if (dangerLevel === "safe" && getDangerLevel(call.name, call.arguments) === "safe") {
|
|
8203
8185
|
return { decision: "auto-approve", reason: "safe shell action", ruleId: "auto.safe-shell", confidence: "medium" };
|
|
8204
8186
|
}
|
|
8205
8187
|
return { decision: "confirm", reason: "shell action is not proven low-risk", ruleId: "confirm.shell-default", confidence: "medium" };
|
|
@@ -8668,10 +8650,10 @@ var ToolExecutor = class {
|
|
|
8668
8650
|
if (call.name === "write_file") {
|
|
8669
8651
|
const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
|
|
8670
8652
|
if (!filePath) return;
|
|
8671
|
-
if (
|
|
8653
|
+
if (existsSync10(filePath)) {
|
|
8672
8654
|
let oldContent;
|
|
8673
8655
|
try {
|
|
8674
|
-
oldContent =
|
|
8656
|
+
oldContent = readFileSync7(filePath, "utf-8");
|
|
8675
8657
|
} catch {
|
|
8676
8658
|
return;
|
|
8677
8659
|
}
|
|
@@ -8695,7 +8677,7 @@ var ToolExecutor = class {
|
|
|
8695
8677
|
}
|
|
8696
8678
|
} else if (call.name === "edit_file") {
|
|
8697
8679
|
const filePath = String(call.arguments["path"] ?? "");
|
|
8698
|
-
if (!filePath || !
|
|
8680
|
+
if (!filePath || !existsSync10(filePath)) return;
|
|
8699
8681
|
const oldStr = call.arguments["old_str"];
|
|
8700
8682
|
const newStr = call.arguments["new_str"];
|
|
8701
8683
|
if (oldStr !== void 0) {
|
|
@@ -8722,7 +8704,7 @@ var ToolExecutor = class {
|
|
|
8722
8704
|
const to = Number(call.arguments["delete_to_line"] ?? from);
|
|
8723
8705
|
let fileContent;
|
|
8724
8706
|
try {
|
|
8725
|
-
fileContent =
|
|
8707
|
+
fileContent = readFileSync7(filePath, "utf-8");
|
|
8726
8708
|
} catch {
|
|
8727
8709
|
return;
|
|
8728
8710
|
}
|
|
@@ -8818,7 +8800,12 @@ import { resolve as resolve5, sep as sep2, basename as basename3 } from "path";
|
|
|
8818
8800
|
import { homedir as homedir4 } from "os";
|
|
8819
8801
|
var home = homedir4();
|
|
8820
8802
|
function norm(p) {
|
|
8821
|
-
|
|
8803
|
+
let abs;
|
|
8804
|
+
try {
|
|
8805
|
+
abs = canonicalPath(p);
|
|
8806
|
+
} catch {
|
|
8807
|
+
abs = resolve5(p);
|
|
8808
|
+
}
|
|
8822
8809
|
return process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
8823
8810
|
}
|
|
8824
8811
|
function homeRel(p) {
|
|
@@ -8832,6 +8819,7 @@ function homeRel(p) {
|
|
|
8832
8819
|
}
|
|
8833
8820
|
function classifyWritePath(path3) {
|
|
8834
8821
|
if (!path3) return { sensitive: false };
|
|
8822
|
+
if (isCredentialVault(path3)) return { sensitive: true, reason: "credential vault" };
|
|
8835
8823
|
const abs = norm(path3);
|
|
8836
8824
|
const base = basename3(abs);
|
|
8837
8825
|
const rel = homeRel(path3);
|
|
@@ -8914,7 +8902,7 @@ function countTextLines(text) {
|
|
|
8914
8902
|
|
|
8915
8903
|
// src/tools/builtin/write-file.ts
|
|
8916
8904
|
function countFileLines(filePath) {
|
|
8917
|
-
return countTextLines(
|
|
8905
|
+
return countTextLines(readFileSync8(filePath, "utf-8"));
|
|
8918
8906
|
}
|
|
8919
8907
|
var writeFileTool = {
|
|
8920
8908
|
definition: {
|
|
@@ -8978,7 +8966,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
8978
8966
|
const mode = appendMode ? "appended" : "written";
|
|
8979
8967
|
void (async () => {
|
|
8980
8968
|
try {
|
|
8981
|
-
const { updateFile } = await import("./indexer-
|
|
8969
|
+
const { updateFile } = await import("./indexer-VRXDZ74A.js");
|
|
8982
8970
|
await updateFile(process.cwd(), filePath);
|
|
8983
8971
|
} catch {
|
|
8984
8972
|
}
|
|
@@ -8997,7 +8985,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
8997
8985
|
};
|
|
8998
8986
|
|
|
8999
8987
|
// src/tools/builtin/edit-file.ts
|
|
9000
|
-
import { readFileSync as
|
|
8988
|
+
import { readFileSync as readFileSync9, existsSync as existsSync11 } from "fs";
|
|
9001
8989
|
|
|
9002
8990
|
// src/tools/builtin/patch-apply.ts
|
|
9003
8991
|
function parseUnifiedDiff(patch) {
|
|
@@ -9365,7 +9353,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
9365
9353
|
const filePath = String(args["path"] ?? args["file"] ?? "");
|
|
9366
9354
|
const encoding = args["encoding"] ?? "utf-8";
|
|
9367
9355
|
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
9368
|
-
if (!
|
|
9356
|
+
if (!existsSync11(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
9369
9357
|
const verdict = classifyWritePath(filePath);
|
|
9370
9358
|
if (verdict.sensitive && subAgentGuard.active) {
|
|
9371
9359
|
throw new ToolError(
|
|
@@ -9373,7 +9361,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
9373
9361
|
`Refused: sub-agents cannot edit sensitive paths \u2014 ${verdict.reason}. If this is genuinely needed, ask the parent agent to perform the edit so the user can approve it.`
|
|
9374
9362
|
);
|
|
9375
9363
|
}
|
|
9376
|
-
const original =
|
|
9364
|
+
const original = readFileSync9(filePath, encoding);
|
|
9377
9365
|
if (args["patch"] !== void 0) {
|
|
9378
9366
|
const patchText = String(args["patch"] ?? "");
|
|
9379
9367
|
const stopOnError = args["stop_on_error"] !== false;
|
|
@@ -9550,8 +9538,8 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
9550
9538
|
}
|
|
9551
9539
|
|
|
9552
9540
|
// src/tools/builtin/list-dir.ts
|
|
9553
|
-
import { readdirSync as readdirSync4, statSync as statSync5, existsSync as
|
|
9554
|
-
import { join as
|
|
9541
|
+
import { readdirSync as readdirSync4, statSync as statSync5, existsSync as existsSync12 } from "fs";
|
|
9542
|
+
import { join as join7, basename as basename4 } from "path";
|
|
9555
9543
|
var listDirTool = {
|
|
9556
9544
|
definition: {
|
|
9557
9545
|
name: "list_dir",
|
|
@@ -9573,7 +9561,7 @@ var listDirTool = {
|
|
|
9573
9561
|
async execute(args) {
|
|
9574
9562
|
const dirPath = String(args["path"] ?? process.cwd());
|
|
9575
9563
|
const recursive = Boolean(args["recursive"] ?? false);
|
|
9576
|
-
if (!
|
|
9564
|
+
if (!existsSync12(dirPath)) {
|
|
9577
9565
|
const targetName = basename4(dirPath).toLowerCase();
|
|
9578
9566
|
const cwd = process.cwd();
|
|
9579
9567
|
const suggestions = [];
|
|
@@ -9631,11 +9619,11 @@ function listRecursive(basePath, indent, recursive, lines) {
|
|
|
9631
9619
|
if (entry.isDirectory()) {
|
|
9632
9620
|
lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
|
|
9633
9621
|
if (recursive) {
|
|
9634
|
-
listRecursive(
|
|
9622
|
+
listRecursive(join7(basePath, entry.name), indent + " ", true, lines);
|
|
9635
9623
|
}
|
|
9636
9624
|
} else {
|
|
9637
9625
|
try {
|
|
9638
|
-
const stat = statSync5(
|
|
9626
|
+
const stat = statSync5(join7(basePath, entry.name));
|
|
9639
9627
|
const size = formatSize(stat.size);
|
|
9640
9628
|
lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
|
|
9641
9629
|
} catch {
|
|
@@ -9650,10 +9638,77 @@ function formatSize(bytes) {
|
|
|
9650
9638
|
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
9651
9639
|
}
|
|
9652
9640
|
|
|
9641
|
+
// src/tools/regex-search.ts
|
|
9642
|
+
import { Worker } from "worker_threads";
|
|
9643
|
+
var source = String.raw`
|
|
9644
|
+
const { parentPort, workerData: d } = require('node:worker_threads');
|
|
9645
|
+
const fs = require('node:fs');
|
|
9646
|
+
let re;
|
|
9647
|
+
try { re = new RegExp(d.pattern, d.ignoreCase ? 'i' : ''); }
|
|
9648
|
+
catch { re = new RegExp(d.pattern.replace(/[.*+?^$()|[\]\\]/g, '\\$&'), d.ignoreCase ? 'i' : ''); }
|
|
9649
|
+
const results = [];
|
|
9650
|
+
for (const f of d.files) {
|
|
9651
|
+
if (results.length >= d.maxResults) break;
|
|
9652
|
+
let fd;
|
|
9653
|
+
try {
|
|
9654
|
+
fd = fs.openSync(f.fullPath, 'r');
|
|
9655
|
+
const buf = Buffer.alloc(1000001);
|
|
9656
|
+
let size = 0;
|
|
9657
|
+
while (size < buf.length) { const n = fs.readSync(fd, buf, size, buf.length - size, null); if (!n) break; size += n; }
|
|
9658
|
+
if (size > 1000000 || buf.subarray(0, Math.min(size,512)).includes(0)) continue;
|
|
9659
|
+
const lines = buf.subarray(0,size).toString('utf8').split('\n');
|
|
9660
|
+
for (let i=0; i<lines.length && results.length<d.maxResults; i++) {
|
|
9661
|
+
if (!re.test(lines[i])) continue;
|
|
9662
|
+
const match = { file:f.relPath.replace(/\\/g,'/'), lineNumber:i+1, lineText:lines[i].slice(0,10000) };
|
|
9663
|
+
if (d.context) {
|
|
9664
|
+
match.contextBefore=[]; match.contextAfter=[];
|
|
9665
|
+
for(let j=Math.max(0,i-d.context);j<i;j++) match.contextBefore.push([j+1,lines[j].slice(0,10000)]);
|
|
9666
|
+
for(let j=i+1;j<=Math.min(lines.length-1,i+d.context);j++) match.contextAfter.push([j+1,lines[j].slice(0,10000)]);
|
|
9667
|
+
}
|
|
9668
|
+
results.push(match);
|
|
9669
|
+
}
|
|
9670
|
+
} catch {} finally { if (fd !== undefined) fs.closeSync(fd); }
|
|
9671
|
+
}
|
|
9672
|
+
parentPort.postMessage(results);
|
|
9673
|
+
`;
|
|
9674
|
+
var active = 0;
|
|
9675
|
+
async function searchInWorker(files, pattern, ignoreCase, context, maxResults) {
|
|
9676
|
+
const signal = currentAbortSignal();
|
|
9677
|
+
if (signal.aborted) throw new Error("Search cancelled");
|
|
9678
|
+
if (active >= 4) throw new Error("Search capacity reached; wait for another search to finish");
|
|
9679
|
+
active++;
|
|
9680
|
+
let worker;
|
|
9681
|
+
try {
|
|
9682
|
+
return await new Promise((resolve13, reject) => {
|
|
9683
|
+
worker = new Worker(source, { eval: true, workerData: { files, pattern, ignoreCase, context, maxResults }, resourceLimits: { maxOldGenerationSizeMb: 64 } });
|
|
9684
|
+
let settled = false;
|
|
9685
|
+
const finish = (error, results) => {
|
|
9686
|
+
if (settled) return;
|
|
9687
|
+
settled = true;
|
|
9688
|
+
clearTimeout(timer);
|
|
9689
|
+
signal.removeEventListener("abort", abort);
|
|
9690
|
+
if (error) reject(error);
|
|
9691
|
+
else resolve13(results ?? []);
|
|
9692
|
+
};
|
|
9693
|
+
const timer = setTimeout(() => finish(new Error("Search exceeded 2 second CPU/time budget; simplify the pattern or narrow the path")), 2e3);
|
|
9694
|
+
const abort = () => finish(new Error("Search cancelled"));
|
|
9695
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
9696
|
+
if (signal.aborted) abort();
|
|
9697
|
+
worker.once("message", (results) => finish(void 0, results));
|
|
9698
|
+
worker.once("error", (error) => finish(error));
|
|
9699
|
+
worker.once("exit", (code) => {
|
|
9700
|
+
if (!settled) finish(new Error(`Search worker exited (${code})`));
|
|
9701
|
+
});
|
|
9702
|
+
});
|
|
9703
|
+
} finally {
|
|
9704
|
+
if (worker) await worker.terminate();
|
|
9705
|
+
active--;
|
|
9706
|
+
}
|
|
9707
|
+
}
|
|
9708
|
+
|
|
9653
9709
|
// src/tools/builtin/grep-files.ts
|
|
9654
|
-
import { readdirSync as readdirSync5,
|
|
9655
|
-
import {
|
|
9656
|
-
import { join as join9, relative } from "path";
|
|
9710
|
+
import { readdirSync as readdirSync5, statSync as statSync6, existsSync as existsSync13 } from "fs";
|
|
9711
|
+
import { join as join8, relative } from "path";
|
|
9657
9712
|
var grepFilesTool = {
|
|
9658
9713
|
definition: {
|
|
9659
9714
|
name: "grep_files",
|
|
@@ -9701,43 +9756,24 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
9701
9756
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
9702
9757
|
const filePattern = args["file_pattern"] ? String(args["file_pattern"]) : void 0;
|
|
9703
9758
|
const ignoreCase = Boolean(args["ignore_case"] ?? false);
|
|
9704
|
-
const contextLines = Math.max(0, Number(args["context_lines"]
|
|
9705
|
-
const maxResults = Math.max(1, Number(args["max_results"]
|
|
9759
|
+
const contextLines = Math.min(10, Math.max(0, Number(args["context_lines"]) || 0));
|
|
9760
|
+
const maxResults = Math.min(200, Math.max(1, Number(args["max_results"]) || 50));
|
|
9706
9761
|
if (!pattern) throw new ToolError("grep_files", "pattern is required");
|
|
9707
|
-
if (!
|
|
9762
|
+
if (!existsSync13(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
|
|
9708
9763
|
const MAX_PATTERN_LENGTH = 1e3;
|
|
9709
|
-
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
9764
|
+
if (pattern.length > MAX_PATTERN_LENGTH || (filePattern?.length ?? 0) > MAX_PATTERN_LENGTH) {
|
|
9710
9765
|
throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
9711
9766
|
}
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
const results = [];
|
|
9720
|
-
const stat = statSync6(rootPath);
|
|
9721
|
-
if (stat.isFile()) {
|
|
9722
|
-
searchInFile(rootPath, rootPath, regex, contextLines, maxResults, results);
|
|
9723
|
-
} else {
|
|
9724
|
-
const filePaths = collectFilePaths(rootPath, filePattern, maxResults);
|
|
9725
|
-
const BATCH_SIZE = 16;
|
|
9726
|
-
for (let i = 0; i < filePaths.length && results.length < maxResults; i += BATCH_SIZE) {
|
|
9727
|
-
const batch = filePaths.slice(i, i + BATCH_SIZE);
|
|
9728
|
-
const batchResults = await Promise.all(
|
|
9729
|
-
batch.map(
|
|
9730
|
-
({ fullPath, relPath }) => searchInFileAsync(fullPath, relPath, regex, contextLines, maxResults - results.length)
|
|
9731
|
-
)
|
|
9732
|
-
);
|
|
9733
|
-
for (const br of batchResults) {
|
|
9734
|
-
for (const r of br) {
|
|
9735
|
-
if (results.length >= maxResults) break;
|
|
9736
|
-
results.push(r);
|
|
9737
|
-
}
|
|
9738
|
-
}
|
|
9767
|
+
assertReadablePath(rootPath);
|
|
9768
|
+
const paths = statSync6(rootPath).isFile() ? [{ fullPath: rootPath, relPath: rootPath }] : collectFilePaths(rootPath, filePattern, maxResults);
|
|
9769
|
+
const files = paths.filter((f) => {
|
|
9770
|
+
try {
|
|
9771
|
+
return !isCredentialVault(f.fullPath);
|
|
9772
|
+
} catch {
|
|
9773
|
+
return false;
|
|
9739
9774
|
}
|
|
9740
|
-
}
|
|
9775
|
+
});
|
|
9776
|
+
const results = await searchInWorker(files, pattern, ignoreCase, contextLines, maxResults);
|
|
9741
9777
|
if (results.length === 0) {
|
|
9742
9778
|
return `No matches found for pattern: ${pattern}
|
|
9743
9779
|
Searched in: ${rootPath}${filePattern ? `
|
|
@@ -9777,8 +9813,23 @@ File filter: ${filePattern}` : ""}`;
|
|
|
9777
9813
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", ".next", "__pycache__", ".cache", "coverage", ".nyc_output"]);
|
|
9778
9814
|
var BINARY_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".ico", ".woff", ".woff2", ".ttf", ".eot", ".otf", ".mp3", ".mp4", ".wav", ".zip", ".tar", ".gz", ".rar", ".7z", ".exe", ".dll", ".so", ".dylib", ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx"]);
|
|
9779
9815
|
function matchesFilePattern(filename, pattern) {
|
|
9780
|
-
|
|
9781
|
-
|
|
9816
|
+
filename = filename.toLowerCase();
|
|
9817
|
+
pattern = pattern.toLowerCase();
|
|
9818
|
+
let i = 0, j = 0, star = -1, restart = 0;
|
|
9819
|
+
while (i < filename.length) {
|
|
9820
|
+
if (pattern[j] === filename[i]) {
|
|
9821
|
+
i++;
|
|
9822
|
+
j++;
|
|
9823
|
+
} else if (pattern[j] === "*") {
|
|
9824
|
+
star = j++;
|
|
9825
|
+
restart = i;
|
|
9826
|
+
} else if (star >= 0) {
|
|
9827
|
+
j = star + 1;
|
|
9828
|
+
i = ++restart;
|
|
9829
|
+
} else return false;
|
|
9830
|
+
}
|
|
9831
|
+
while (pattern[j] === "*") j++;
|
|
9832
|
+
return j === pattern.length;
|
|
9782
9833
|
}
|
|
9783
9834
|
function isBinary(filename) {
|
|
9784
9835
|
const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
|
|
@@ -9786,8 +9837,9 @@ function isBinary(filename) {
|
|
|
9786
9837
|
}
|
|
9787
9838
|
function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
9788
9839
|
const paths = [];
|
|
9789
|
-
|
|
9790
|
-
|
|
9840
|
+
let visited = 0;
|
|
9841
|
+
function walk(dirPath, depth = 0) {
|
|
9842
|
+
if (paths.length >= maxFiles || depth > 20 || visited >= 2e4) return;
|
|
9791
9843
|
let entries;
|
|
9792
9844
|
try {
|
|
9793
9845
|
entries = readdirSync5(dirPath, { withFileTypes: true });
|
|
@@ -9795,14 +9847,14 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
9795
9847
|
return;
|
|
9796
9848
|
}
|
|
9797
9849
|
for (const entry of entries) {
|
|
9798
|
-
if (paths.length >= maxFiles) return;
|
|
9850
|
+
if (paths.length >= maxFiles || ++visited > 2e4) return;
|
|
9799
9851
|
if (entry.isDirectory()) {
|
|
9800
9852
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
9801
|
-
walk(
|
|
9853
|
+
walk(join8(dirPath, entry.name), depth + 1);
|
|
9802
9854
|
} else if (entry.isFile()) {
|
|
9803
9855
|
if (isBinary(entry.name)) continue;
|
|
9804
9856
|
if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
|
|
9805
|
-
const fullPath =
|
|
9857
|
+
const fullPath = join8(dirPath, entry.name);
|
|
9806
9858
|
try {
|
|
9807
9859
|
if (statSync6(fullPath).size > 1e6) continue;
|
|
9808
9860
|
} catch {
|
|
@@ -9815,81 +9867,10 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
9815
9867
|
walk(rootPath);
|
|
9816
9868
|
return paths;
|
|
9817
9869
|
}
|
|
9818
|
-
async function searchInFileAsync(fullPath, displayPath2, regex, contextLines, maxResults) {
|
|
9819
|
-
let content;
|
|
9820
|
-
try {
|
|
9821
|
-
content = await readFile(fullPath, "utf-8");
|
|
9822
|
-
} catch {
|
|
9823
|
-
return [];
|
|
9824
|
-
}
|
|
9825
|
-
const results = [];
|
|
9826
|
-
const lines = content.split("\n");
|
|
9827
|
-
regex.lastIndex = 0;
|
|
9828
|
-
for (let i = 0; i < lines.length && results.length < maxResults; i++) {
|
|
9829
|
-
regex.lastIndex = 0;
|
|
9830
|
-
if (regex.test(lines[i])) {
|
|
9831
|
-
const match = {
|
|
9832
|
-
file: displayPath2.replace(/\\/g, "/"),
|
|
9833
|
-
lineNumber: i + 1,
|
|
9834
|
-
lineText: lines[i]
|
|
9835
|
-
};
|
|
9836
|
-
if (contextLines > 0) {
|
|
9837
|
-
match.contextBefore = [];
|
|
9838
|
-
for (let j = Math.max(0, i - contextLines); j < i; j++) {
|
|
9839
|
-
match.contextBefore.push([j + 1, lines[j]]);
|
|
9840
|
-
}
|
|
9841
|
-
match.contextAfter = [];
|
|
9842
|
-
for (let j = i + 1; j <= Math.min(lines.length - 1, i + contextLines); j++) {
|
|
9843
|
-
match.contextAfter.push([j + 1, lines[j]]);
|
|
9844
|
-
}
|
|
9845
|
-
}
|
|
9846
|
-
results.push(match);
|
|
9847
|
-
}
|
|
9848
|
-
}
|
|
9849
|
-
return results;
|
|
9850
|
-
}
|
|
9851
|
-
function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, results) {
|
|
9852
|
-
try {
|
|
9853
|
-
const stat = statSync6(fullPath);
|
|
9854
|
-
if (stat.size > 1e6) return;
|
|
9855
|
-
} catch {
|
|
9856
|
-
return;
|
|
9857
|
-
}
|
|
9858
|
-
let content;
|
|
9859
|
-
try {
|
|
9860
|
-
content = readFileSync12(fullPath, "utf-8");
|
|
9861
|
-
} catch {
|
|
9862
|
-
return;
|
|
9863
|
-
}
|
|
9864
|
-
const lines = content.split("\n");
|
|
9865
|
-
regex.lastIndex = 0;
|
|
9866
|
-
for (let i = 0; i < lines.length; i++) {
|
|
9867
|
-
if (results.length >= maxResults) return;
|
|
9868
|
-
regex.lastIndex = 0;
|
|
9869
|
-
if (regex.test(lines[i])) {
|
|
9870
|
-
const result = {
|
|
9871
|
-
file: displayPath2,
|
|
9872
|
-
lineNumber: i + 1,
|
|
9873
|
-
lineText: lines[i].trimEnd()
|
|
9874
|
-
};
|
|
9875
|
-
if (contextLines > 0) {
|
|
9876
|
-
result.contextBefore = [];
|
|
9877
|
-
result.contextAfter = [];
|
|
9878
|
-
for (let c = Math.max(0, i - contextLines); c < i; c++) {
|
|
9879
|
-
result.contextBefore.push([c + 1, lines[c].trimEnd()]);
|
|
9880
|
-
}
|
|
9881
|
-
for (let c = i + 1; c <= Math.min(lines.length - 1, i + contextLines); c++) {
|
|
9882
|
-
result.contextAfter.push([c + 1, lines[c].trimEnd()]);
|
|
9883
|
-
}
|
|
9884
|
-
}
|
|
9885
|
-
results.push(result);
|
|
9886
|
-
}
|
|
9887
|
-
}
|
|
9888
|
-
}
|
|
9889
9870
|
|
|
9890
9871
|
// src/tools/builtin/glob-files.ts
|
|
9891
|
-
import { readdirSync as readdirSync6, statSync as statSync7, existsSync as
|
|
9892
|
-
import { join as
|
|
9872
|
+
import { readdirSync as readdirSync6, statSync as statSync7, existsSync as existsSync14 } from "fs";
|
|
9873
|
+
import { join as join9, relative as relative2, basename as basename5 } from "path";
|
|
9893
9874
|
var globFilesTool = {
|
|
9894
9875
|
definition: {
|
|
9895
9876
|
name: "glob_files",
|
|
@@ -9922,7 +9903,7 @@ Results sorted by most recent modification time. Automatically skips node_module
|
|
|
9922
9903
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
9923
9904
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
|
|
9924
9905
|
if (!pattern) throw new ToolError("glob_files", "pattern is required");
|
|
9925
|
-
if (!
|
|
9906
|
+
if (!existsSync14(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
|
|
9926
9907
|
const regex = globToRegex(pattern);
|
|
9927
9908
|
const matches2 = [];
|
|
9928
9909
|
collectMatchingFiles(rootPath, rootPath, regex, matches2, maxResults);
|
|
@@ -9999,7 +9980,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
9999
9980
|
}
|
|
10000
9981
|
for (const entry of entries) {
|
|
10001
9982
|
if (results.length >= maxResults) break;
|
|
10002
|
-
const fullPath =
|
|
9983
|
+
const fullPath = join9(dirPath, entry.name);
|
|
10003
9984
|
if (entry.isDirectory()) {
|
|
10004
9985
|
if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
10005
9986
|
collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
|
|
@@ -10018,9 +9999,9 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
10018
9999
|
}
|
|
10019
10000
|
|
|
10020
10001
|
// src/tools/builtin/run-interactive.ts
|
|
10021
|
-
import { spawn as
|
|
10022
|
-
import { platform as
|
|
10023
|
-
var IS_WINDOWS4 =
|
|
10002
|
+
import { spawn as spawn3 } from "child_process";
|
|
10003
|
+
import { platform as platform3 } from "os";
|
|
10004
|
+
var IS_WINDOWS4 = platform3() === "win32";
|
|
10024
10005
|
var runInteractiveTool = {
|
|
10025
10006
|
definition: {
|
|
10026
10007
|
name: "run_interactive",
|
|
@@ -10082,7 +10063,7 @@ var runInteractiveTool = {
|
|
|
10082
10063
|
};
|
|
10083
10064
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
10084
10065
|
return new Promise((resolve13) => {
|
|
10085
|
-
const child =
|
|
10066
|
+
const child = spawn3(executable, cmdArgs.map(String), {
|
|
10086
10067
|
cwd: process.cwd(),
|
|
10087
10068
|
env,
|
|
10088
10069
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10352,8 +10333,8 @@ var webFetchTool = {
|
|
|
10352
10333
|
if (e.message.startsWith("Blocked:")) throw e;
|
|
10353
10334
|
throw new NetworkError(`Invalid URL: "${url}"`);
|
|
10354
10335
|
}
|
|
10355
|
-
const
|
|
10356
|
-
const timeoutId = setTimeout(() =>
|
|
10336
|
+
const controller = new AbortController();
|
|
10337
|
+
const timeoutId = setTimeout(() => controller.abort(), 2e4);
|
|
10357
10338
|
let rawHtml;
|
|
10358
10339
|
let finalUrl;
|
|
10359
10340
|
let contentType;
|
|
@@ -10374,7 +10355,7 @@ var webFetchTool = {
|
|
|
10374
10355
|
}
|
|
10375
10356
|
await resolveAndCheck(parsedHop.hostname);
|
|
10376
10357
|
const r = await fetch(currentUrl, {
|
|
10377
|
-
signal:
|
|
10358
|
+
signal: controller.signal,
|
|
10378
10359
|
headers: FETCH_HEADERS,
|
|
10379
10360
|
redirect: "manual",
|
|
10380
10361
|
// 手动控制重定向
|
|
@@ -10737,8 +10718,8 @@ function parseGoogleResults(html, maxResults) {
|
|
|
10737
10718
|
return results;
|
|
10738
10719
|
}
|
|
10739
10720
|
async function fetchHtml(url) {
|
|
10740
|
-
const
|
|
10741
|
-
const timer = setTimeout(() =>
|
|
10721
|
+
const controller = new AbortController();
|
|
10722
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
10742
10723
|
try {
|
|
10743
10724
|
const res = await fetch(url, {
|
|
10744
10725
|
method: "GET",
|
|
@@ -10747,7 +10728,7 @@ async function fetchHtml(url) {
|
|
|
10747
10728
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9",
|
|
10748
10729
|
"Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8"
|
|
10749
10730
|
},
|
|
10750
|
-
signal:
|
|
10731
|
+
signal: controller.signal
|
|
10751
10732
|
});
|
|
10752
10733
|
if (!res.ok) {
|
|
10753
10734
|
throw new NetworkError(`HTTP ${res.status} ${res.statusText}`, res.status);
|
|
@@ -10841,10 +10822,8 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
10841
10822
|
};
|
|
10842
10823
|
|
|
10843
10824
|
// src/tools/builtin/save-memory.ts
|
|
10844
|
-
import { join as join11 } from "path";
|
|
10845
|
-
import { homedir as homedir5 } from "os";
|
|
10846
10825
|
function getConfigDir() {
|
|
10847
|
-
return
|
|
10826
|
+
return getExecutionConfigDir();
|
|
10848
10827
|
}
|
|
10849
10828
|
function parseSensitivity(value) {
|
|
10850
10829
|
return value === "low" || value === "medium" || value === "high" ? value : void 0;
|
|
@@ -11108,15 +11087,15 @@ var googleSearchTool = {
|
|
|
11108
11087
|
url.searchParams.set("cx", cx);
|
|
11109
11088
|
url.searchParams.set("q", query);
|
|
11110
11089
|
url.searchParams.set("num", String(numResults));
|
|
11111
|
-
const
|
|
11112
|
-
const timeout = setTimeout(() =>
|
|
11090
|
+
const controller = new AbortController();
|
|
11091
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
|
|
11113
11092
|
try {
|
|
11114
11093
|
const response = await fetch(url.toString(), {
|
|
11115
11094
|
method: "GET",
|
|
11116
11095
|
headers: {
|
|
11117
11096
|
"Accept": "application/json"
|
|
11118
11097
|
},
|
|
11119
|
-
signal:
|
|
11098
|
+
signal: controller.signal
|
|
11120
11099
|
});
|
|
11121
11100
|
if (!response.ok) {
|
|
11122
11101
|
const errorBody = await response.text().catch(() => "");
|
|
@@ -11200,13 +11179,13 @@ function isToolCapableProvider(provider) {
|
|
|
11200
11179
|
}
|
|
11201
11180
|
|
|
11202
11181
|
// src/agents/agent-config.ts
|
|
11203
|
-
import { existsSync as
|
|
11204
|
-
import { join as
|
|
11205
|
-
import { homedir as
|
|
11182
|
+
import { existsSync as existsSync16, readdirSync as readdirSync8, readFileSync as readFileSync11 } from "fs";
|
|
11183
|
+
import { join as join11 } from "path";
|
|
11184
|
+
import { homedir as homedir5 } from "os";
|
|
11206
11185
|
|
|
11207
11186
|
// src/plugins/plugin-manager.ts
|
|
11208
|
-
import { existsSync as
|
|
11209
|
-
import { basename as basename6, dirname as dirname7, join as
|
|
11187
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync10, realpathSync as realpathSync2, cpSync, rmSync, statSync as statSync8 } from "fs";
|
|
11188
|
+
import { basename as basename6, dirname as dirname7, join as join10, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
|
|
11210
11189
|
import { createHash as createHash2 } from "crypto";
|
|
11211
11190
|
import { z as z2 } from "zod";
|
|
11212
11191
|
var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
|
|
@@ -11266,19 +11245,19 @@ var PluginManifestSchema = z2.object({
|
|
|
11266
11245
|
permissionHints: z2.array(z2.string()).default([])
|
|
11267
11246
|
}).strict();
|
|
11268
11247
|
function pluginRoot(configDir) {
|
|
11269
|
-
return
|
|
11248
|
+
return join10(configDir, PLUGINS_DIR_NAME);
|
|
11270
11249
|
}
|
|
11271
11250
|
function pluginManifestPath(pluginDir) {
|
|
11272
|
-
return
|
|
11251
|
+
return join10(pluginDir, MANIFEST_RELATIVE);
|
|
11273
11252
|
}
|
|
11274
11253
|
function statePath(configDir) {
|
|
11275
|
-
return
|
|
11254
|
+
return join10(pluginRoot(configDir), STATE_FILE);
|
|
11276
11255
|
}
|
|
11277
11256
|
function loadState(configDir) {
|
|
11278
11257
|
const file = statePath(configDir);
|
|
11279
|
-
if (!
|
|
11258
|
+
if (!existsSync15(file)) return { version: 2, plugins: [] };
|
|
11280
11259
|
try {
|
|
11281
|
-
const parsed = JSON.parse(
|
|
11260
|
+
const parsed = JSON.parse(readFileSync10(file, "utf-8"));
|
|
11282
11261
|
const rawPlugins = Array.isArray(parsed.plugins) ? parsed.plugins : [];
|
|
11283
11262
|
return { version: 2, plugins: rawPlugins.map((p) => migrateEntry(p)) };
|
|
11284
11263
|
} catch {
|
|
@@ -11336,7 +11315,7 @@ function extractCommandPaths(command, pluginDir) {
|
|
|
11336
11315
|
try {
|
|
11337
11316
|
const abs = resolve6(resolvedDir, ref);
|
|
11338
11317
|
if (!abs.startsWith(resolvedDir + sep3)) continue;
|
|
11339
|
-
if (
|
|
11318
|
+
if (existsSync15(abs)) {
|
|
11340
11319
|
const real = realpathSync2(abs);
|
|
11341
11320
|
if (!real.startsWith(resolvedDir + sep3)) continue;
|
|
11342
11321
|
valid.push(relative3(resolvedDir, real));
|
|
@@ -11368,8 +11347,8 @@ function hashPluginContent(pluginDir, manifest) {
|
|
|
11368
11347
|
}
|
|
11369
11348
|
const sorted = [...assetRels].sort();
|
|
11370
11349
|
const hasher = createHash2("sha256");
|
|
11371
|
-
const manifestPath =
|
|
11372
|
-
hasher.update(normalizeForHash(
|
|
11350
|
+
const manifestPath = join10(resolvedDir, MANIFEST_RELATIVE);
|
|
11351
|
+
hasher.update(normalizeForHash(readFileSync10(manifestPath)));
|
|
11373
11352
|
for (const rel of sorted) {
|
|
11374
11353
|
const abs = resolve6(resolvedDir, rel);
|
|
11375
11354
|
if (!abs.startsWith(resolvedDir + sep3)) {
|
|
@@ -11389,7 +11368,7 @@ function hashPluginContent(pluginDir, manifest) {
|
|
|
11389
11368
|
if (!realPath.startsWith(resolvedDir + sep3)) {
|
|
11390
11369
|
throw new Error(`plugin asset symlink escapes plugin dir: ${rel} \u2192 ${realPath}`);
|
|
11391
11370
|
}
|
|
11392
|
-
hasher.update(normalizeForHash(
|
|
11371
|
+
hasher.update(normalizeForHash(readFileSync10(abs)));
|
|
11393
11372
|
}
|
|
11394
11373
|
return hasher.digest("hex");
|
|
11395
11374
|
}
|
|
@@ -11397,22 +11376,22 @@ function effectiveHash(entry) {
|
|
|
11397
11376
|
return entry.contentHash ?? entry.hash ?? "";
|
|
11398
11377
|
}
|
|
11399
11378
|
function readPluginManifest(manifestPath) {
|
|
11400
|
-
return PluginManifestSchema.parse(JSON.parse(
|
|
11379
|
+
return PluginManifestSchema.parse(JSON.parse(readFileSync10(manifestPath, "utf-8")));
|
|
11401
11380
|
}
|
|
11402
11381
|
function findPluginSourceDir(inputPath) {
|
|
11403
11382
|
const abs = resolve6(inputPath);
|
|
11404
11383
|
const direct = statSync8(abs).isDirectory() ? abs : dirname7(abs);
|
|
11405
|
-
if (
|
|
11406
|
-
const nested =
|
|
11407
|
-
if (
|
|
11384
|
+
if (existsSync15(pluginManifestPath(direct))) return direct;
|
|
11385
|
+
const nested = join10(direct, ".aicli-plugin");
|
|
11386
|
+
if (existsSync15(join10(nested, "plugin.json"))) return direct;
|
|
11408
11387
|
throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
|
|
11409
11388
|
}
|
|
11410
11389
|
function installPlugin(configDir, inputPath) {
|
|
11411
11390
|
const sourceDir = findPluginSourceDir(inputPath);
|
|
11412
11391
|
const manifest = readPluginManifest(pluginManifestPath(sourceDir));
|
|
11413
|
-
const targetDir =
|
|
11392
|
+
const targetDir = join10(pluginRoot(configDir), manifest.name);
|
|
11414
11393
|
mkdirSync8(pluginRoot(configDir), { recursive: true });
|
|
11415
|
-
if (
|
|
11394
|
+
if (existsSync15(targetDir)) rmSync(targetDir, { recursive: true, force: true });
|
|
11416
11395
|
cpSync(sourceDir, targetDir, { recursive: true });
|
|
11417
11396
|
const manifestPath = pluginManifestPath(targetDir);
|
|
11418
11397
|
const contentHash = hashPluginContent(targetDir, manifest);
|
|
@@ -11470,18 +11449,18 @@ function getInstalledPlugin(configDir, name) {
|
|
|
11470
11449
|
}
|
|
11471
11450
|
function listInstalledPlugins(configDir) {
|
|
11472
11451
|
const root = pluginRoot(configDir);
|
|
11473
|
-
if (!
|
|
11452
|
+
if (!existsSync15(root)) return [];
|
|
11474
11453
|
const state2 = loadState(configDir);
|
|
11475
11454
|
const out = [];
|
|
11476
11455
|
for (const entry of readdirSync7(root)) {
|
|
11477
|
-
const dir =
|
|
11456
|
+
const dir = join10(root, entry);
|
|
11478
11457
|
try {
|
|
11479
11458
|
if (!statSync8(dir).isDirectory()) continue;
|
|
11480
11459
|
} catch {
|
|
11481
11460
|
continue;
|
|
11482
11461
|
}
|
|
11483
11462
|
const manifestPath = pluginManifestPath(dir);
|
|
11484
|
-
if (!
|
|
11463
|
+
if (!existsSync15(manifestPath)) continue;
|
|
11485
11464
|
try {
|
|
11486
11465
|
const manifest = readPluginManifest(manifestPath);
|
|
11487
11466
|
const contentHash = hashPluginContent(dir, manifest);
|
|
@@ -11521,7 +11500,7 @@ function uniqueDirs(plugin, rels) {
|
|
|
11521
11500
|
for (const rel of rels) {
|
|
11522
11501
|
const abs = resolve6(plugin.dir, rel);
|
|
11523
11502
|
if (!abs.startsWith(resolve6(plugin.dir))) continue;
|
|
11524
|
-
if (
|
|
11503
|
+
if (existsSync15(abs)) dirs.add(statSync8(abs).isDirectory() ? abs : dirname7(abs));
|
|
11525
11504
|
}
|
|
11526
11505
|
return [...dirs];
|
|
11527
11506
|
}
|
|
@@ -11661,13 +11640,13 @@ function normalizeName(name) {
|
|
|
11661
11640
|
function isStringArray(value) {
|
|
11662
11641
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
11663
11642
|
}
|
|
11664
|
-
function parseAgentConfig(raw,
|
|
11643
|
+
function parseAgentConfig(raw, source2, path3) {
|
|
11665
11644
|
if (!raw || typeof raw !== "object") return null;
|
|
11666
11645
|
const obj = raw;
|
|
11667
11646
|
if (typeof obj.name !== "string" || obj.name.trim() === "") return null;
|
|
11668
11647
|
const cfg = {
|
|
11669
11648
|
name: obj.name.trim(),
|
|
11670
|
-
source,
|
|
11649
|
+
source: source2,
|
|
11671
11650
|
path: path3
|
|
11672
11651
|
};
|
|
11673
11652
|
if (typeof obj.description === "string") cfg.description = obj.description;
|
|
@@ -11683,15 +11662,15 @@ function parseAgentConfig(raw, source, path3) {
|
|
|
11683
11662
|
if (typeof obj.contextPolicy === "string") cfg.contextPolicy = obj.contextPolicy;
|
|
11684
11663
|
return cfg;
|
|
11685
11664
|
}
|
|
11686
|
-
function loadAgentDir(dir,
|
|
11687
|
-
if (!
|
|
11665
|
+
function loadAgentDir(dir, source2) {
|
|
11666
|
+
if (!existsSync16(dir)) return [];
|
|
11688
11667
|
const out = [];
|
|
11689
11668
|
for (const file of readdirSync8(dir)) {
|
|
11690
11669
|
if (!file.endsWith(".json")) continue;
|
|
11691
|
-
const path3 =
|
|
11670
|
+
const path3 = join11(dir, file);
|
|
11692
11671
|
try {
|
|
11693
|
-
const parsed = JSON.parse(
|
|
11694
|
-
const cfg = parseAgentConfig(parsed,
|
|
11672
|
+
const parsed = JSON.parse(readFileSync11(path3, "utf-8"));
|
|
11673
|
+
const cfg = parseAgentConfig(parsed, source2, path3);
|
|
11695
11674
|
if (cfg) out.push(cfg);
|
|
11696
11675
|
} catch {
|
|
11697
11676
|
}
|
|
@@ -11700,8 +11679,8 @@ function loadAgentDir(dir, source) {
|
|
|
11700
11679
|
}
|
|
11701
11680
|
function getAgentDirs(configDir, cwd = process.cwd()) {
|
|
11702
11681
|
return {
|
|
11703
|
-
user:
|
|
11704
|
-
project:
|
|
11682
|
+
user: join11(configDir ?? join11(homedir5(), CONFIG_DIR_NAME), "agents"),
|
|
11683
|
+
project: join11(cwd, CONFIG_DIR_NAME, "agents")
|
|
11705
11684
|
};
|
|
11706
11685
|
}
|
|
11707
11686
|
function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
@@ -12204,9 +12183,8 @@ var spawnAgentTool = {
|
|
|
12204
12183
|
};
|
|
12205
12184
|
|
|
12206
12185
|
// src/tools/builtin/task-manager.ts
|
|
12207
|
-
import { spawn as
|
|
12186
|
+
import { spawn as spawn4 } from "child_process";
|
|
12208
12187
|
import { randomUUID } from "crypto";
|
|
12209
|
-
import { platform as platform5 } from "os";
|
|
12210
12188
|
var MAX_OUTPUT_CHARS = 1e4;
|
|
12211
12189
|
var MAX_OUTPUT_BYTES = 64 * 1024;
|
|
12212
12190
|
var MAX_TASKS = 20;
|
|
@@ -12225,14 +12203,16 @@ function decodeField(chunks) {
|
|
|
12225
12203
|
}
|
|
12226
12204
|
function appendOutput(task, field, chunk) {
|
|
12227
12205
|
const chunks = field === "stdout" ? task._stdoutChunks : task._stderrChunks;
|
|
12228
|
-
chunks.push(chunk);
|
|
12206
|
+
chunks.push(chunk.length > MAX_OUTPUT_BYTES ? chunk.subarray(-MAX_OUTPUT_BYTES) : chunk);
|
|
12229
12207
|
capChunks(chunks);
|
|
12230
12208
|
task[field] = decodeField(chunks);
|
|
12231
12209
|
}
|
|
12232
12210
|
function createTask(command, description) {
|
|
12211
|
+
if ([...tasks.values()].filter((t) => t._active && t.owner === getExecutionConfigDir()).length >= 10) throw new Error("Background task limit reached (10 running per user).");
|
|
12212
|
+
if ([...tasks.values()].filter((t) => t._active).length >= MAX_TASKS) throw new Error("Background task limit reached (20 running). Stop a task before creating another.");
|
|
12233
12213
|
if (tasks.size >= MAX_TASKS) {
|
|
12234
12214
|
for (const [id2, t] of tasks) {
|
|
12235
|
-
if (t.
|
|
12215
|
+
if (!t._active) {
|
|
12236
12216
|
tasks.delete(id2);
|
|
12237
12217
|
break;
|
|
12238
12218
|
}
|
|
@@ -12240,16 +12220,19 @@ function createTask(command, description) {
|
|
|
12240
12220
|
}
|
|
12241
12221
|
const id = randomUUID().slice(0, 8);
|
|
12242
12222
|
const { shell, args } = shellInvocation(command);
|
|
12243
|
-
const proc =
|
|
12223
|
+
const proc = spawn4(shell, args, {
|
|
12244
12224
|
stdio: ["ignore", "pipe", "pipe"],
|
|
12245
|
-
detached:
|
|
12225
|
+
detached: process.platform !== "win32",
|
|
12226
|
+
windowsHide: true,
|
|
12246
12227
|
env: { ...process.env, PYTHONUTF8: "1", PYTHONIOENCODING: "utf-8" }
|
|
12247
12228
|
});
|
|
12248
12229
|
const task = {
|
|
12249
12230
|
id,
|
|
12231
|
+
owner: getExecutionConfigDir(),
|
|
12250
12232
|
command,
|
|
12251
12233
|
description,
|
|
12252
12234
|
process: proc,
|
|
12235
|
+
_active: true,
|
|
12253
12236
|
startTime: Date.now(),
|
|
12254
12237
|
endTime: null,
|
|
12255
12238
|
status: "running",
|
|
@@ -12262,11 +12245,13 @@ function createTask(command, description) {
|
|
|
12262
12245
|
proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk));
|
|
12263
12246
|
proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk));
|
|
12264
12247
|
proc.on("close", (code) => {
|
|
12248
|
+
task._active = false;
|
|
12265
12249
|
task.endTime = Date.now();
|
|
12266
12250
|
task.exitCode = code;
|
|
12267
|
-
task.status = code === 0 ? "completed" : "failed";
|
|
12251
|
+
if (task.status !== "stopped") task.status = code === 0 ? "completed" : "failed";
|
|
12268
12252
|
});
|
|
12269
12253
|
proc.on("error", (err) => {
|
|
12254
|
+
task._active = false;
|
|
12270
12255
|
task.endTime = Date.now();
|
|
12271
12256
|
task.status = "failed";
|
|
12272
12257
|
task.stderr += `
|
|
@@ -12276,43 +12261,20 @@ Process error: ${err.message}`;
|
|
|
12276
12261
|
return task;
|
|
12277
12262
|
}
|
|
12278
12263
|
function listTasks() {
|
|
12279
|
-
return [...tasks.values()].map(
|
|
12280
|
-
({ process: _p, _stdoutChunks: _o, _stderrChunks: _e, ...rest }) => rest
|
|
12264
|
+
return [...tasks.values()].filter((t) => t.owner === getExecutionConfigDir()).map(
|
|
12265
|
+
({ process: _p, _stdoutChunks: _o, _stderrChunks: _e, _active: _a, ...rest }) => rest
|
|
12281
12266
|
);
|
|
12282
12267
|
}
|
|
12283
12268
|
function getTaskOutput(id) {
|
|
12284
12269
|
const task = tasks.get(id);
|
|
12285
|
-
if (!task) return null;
|
|
12270
|
+
if (!task || task.owner !== getExecutionConfigDir()) return null;
|
|
12286
12271
|
return { stdout: task.stdout, stderr: task.stderr, status: task.status };
|
|
12287
12272
|
}
|
|
12288
12273
|
function stopTask(id) {
|
|
12289
12274
|
const task = tasks.get(id);
|
|
12290
|
-
if (!task || task.status !== "running") return false;
|
|
12275
|
+
if (!task || task.owner !== getExecutionConfigDir() || task.status !== "running") return false;
|
|
12291
12276
|
try {
|
|
12292
|
-
|
|
12293
|
-
if (platform5() === "win32" && proc.pid) {
|
|
12294
|
-
try {
|
|
12295
|
-
spawn5("taskkill", ["/PID", String(proc.pid), "/T", "/F"], {
|
|
12296
|
-
windowsHide: true,
|
|
12297
|
-
stdio: "ignore"
|
|
12298
|
-
});
|
|
12299
|
-
} catch {
|
|
12300
|
-
}
|
|
12301
|
-
try {
|
|
12302
|
-
proc.kill();
|
|
12303
|
-
} catch {
|
|
12304
|
-
}
|
|
12305
|
-
} else {
|
|
12306
|
-
proc.kill("SIGTERM");
|
|
12307
|
-
setTimeout(() => {
|
|
12308
|
-
if (proc.exitCode === null && proc.signalCode === null) {
|
|
12309
|
-
try {
|
|
12310
|
-
proc.kill("SIGKILL");
|
|
12311
|
-
} catch {
|
|
12312
|
-
}
|
|
12313
|
-
}
|
|
12314
|
-
}, 3e3).unref();
|
|
12315
|
-
}
|
|
12277
|
+
killProcessTree(task.process, true);
|
|
12316
12278
|
task.status = "stopped";
|
|
12317
12279
|
task.endTime = Date.now();
|
|
12318
12280
|
return true;
|
|
@@ -12420,14 +12382,14 @@ var taskStopTool = {
|
|
|
12420
12382
|
|
|
12421
12383
|
// src/tools/builtin/git-tools.ts
|
|
12422
12384
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
12423
|
-
import { existsSync as
|
|
12424
|
-
import { join as
|
|
12385
|
+
import { existsSync as existsSync17 } from "fs";
|
|
12386
|
+
import { join as join12 } from "path";
|
|
12425
12387
|
function assertGitRepo(cwd) {
|
|
12426
12388
|
let dir = cwd;
|
|
12427
12389
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
12428
12390
|
while (dir && dir !== root) {
|
|
12429
|
-
if (
|
|
12430
|
-
const parent =
|
|
12391
|
+
if (existsSync17(join12(dir, ".git"))) return;
|
|
12392
|
+
const parent = join12(dir, "..");
|
|
12431
12393
|
if (parent === dir) break;
|
|
12432
12394
|
dir = parent;
|
|
12433
12395
|
}
|
|
@@ -12692,7 +12654,7 @@ ${commitOutput.trim()}`;
|
|
|
12692
12654
|
};
|
|
12693
12655
|
|
|
12694
12656
|
// src/tools/builtin/notebook-edit.ts
|
|
12695
|
-
import { readFileSync as
|
|
12657
|
+
import { readFileSync as readFileSync12, existsSync as existsSync18 } from "fs";
|
|
12696
12658
|
import { writeFile } from "fs/promises";
|
|
12697
12659
|
import { resolve as resolve7, extname as extname2 } from "path";
|
|
12698
12660
|
var notebookEditTool = {
|
|
@@ -12747,10 +12709,10 @@ var notebookEditTool = {
|
|
|
12747
12709
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
12748
12710
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
12749
12711
|
}
|
|
12750
|
-
if (!
|
|
12712
|
+
if (!existsSync18(absPath)) {
|
|
12751
12713
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
12752
12714
|
}
|
|
12753
|
-
const raw =
|
|
12715
|
+
const raw = readFileSync12(absPath, "utf-8");
|
|
12754
12716
|
const nb = parseNotebook(raw);
|
|
12755
12717
|
const cellIdx0 = cellIndexRaw - 1;
|
|
12756
12718
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -13076,8 +13038,6 @@ ${lines.join("\n")}`;
|
|
|
13076
13038
|
};
|
|
13077
13039
|
|
|
13078
13040
|
// src/tools/builtin/recall-memory.ts
|
|
13079
|
-
import { join as join15 } from "path";
|
|
13080
|
-
import { homedir as homedir7 } from "os";
|
|
13081
13041
|
function formatHit(h, i) {
|
|
13082
13042
|
const ts = h.chunk.timestamp.slice(0, 16).replace("T", " ");
|
|
13083
13043
|
const title = h.chunk.sessionTitle ? ` \xB7 ${h.chunk.sessionTitle}` : "";
|
|
@@ -13127,7 +13087,7 @@ var recallMemoryTool = {
|
|
|
13127
13087
|
const excludeCurrent = Boolean(args["excludeCurrentSession"]);
|
|
13128
13088
|
const currentId = args["currentSessionId"] ? String(args["currentSessionId"]) : void 0;
|
|
13129
13089
|
const minScore = args["minScore"] !== void 0 ? Number(args["minScore"]) : 0.25;
|
|
13130
|
-
const configDir =
|
|
13090
|
+
const configDir = getExecutionConfigDir();
|
|
13131
13091
|
let persistentSection = "";
|
|
13132
13092
|
try {
|
|
13133
13093
|
const persistentHits = searchPersistentMemories(configDir, query, process.cwd(), 3);
|
|
@@ -13181,8 +13141,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
13181
13141
|
|
|
13182
13142
|
// src/tools/registry.ts
|
|
13183
13143
|
import { pathToFileURL } from "url";
|
|
13184
|
-
import { existsSync as
|
|
13185
|
-
import { join as
|
|
13144
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
|
|
13145
|
+
import { join as join13 } from "path";
|
|
13186
13146
|
var ToolRegistry = class {
|
|
13187
13147
|
tools = /* @__PURE__ */ new Map();
|
|
13188
13148
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -13343,7 +13303,7 @@ var ToolRegistry = class {
|
|
|
13343
13303
|
* Returns the number of successfully loaded plugins.
|
|
13344
13304
|
*/
|
|
13345
13305
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
13346
|
-
if (!
|
|
13306
|
+
if (!existsSync19(pluginsDir)) {
|
|
13347
13307
|
try {
|
|
13348
13308
|
mkdirSync9(pluginsDir, { recursive: true });
|
|
13349
13309
|
} catch {
|
|
@@ -13369,12 +13329,12 @@ var ToolRegistry = class {
|
|
|
13369
13329
|
process.stderr.write(
|
|
13370
13330
|
`
|
|
13371
13331
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
13372
|
-
` + files.map((f) => ` + ${
|
|
13332
|
+
` + files.map((f) => ` + ${join13(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
13373
13333
|
);
|
|
13374
13334
|
let loaded = 0;
|
|
13375
13335
|
for (const file of files) {
|
|
13376
13336
|
try {
|
|
13377
|
-
const fileUrl = pathToFileURL(
|
|
13337
|
+
const fileUrl = pathToFileURL(join13(pluginsDir, file)).href;
|
|
13378
13338
|
const mod = await import(fileUrl);
|
|
13379
13339
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
13380
13340
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -13402,7 +13362,7 @@ var ToolRegistry = class {
|
|
|
13402
13362
|
};
|
|
13403
13363
|
|
|
13404
13364
|
// src/mcp/client.ts
|
|
13405
|
-
import { spawn as
|
|
13365
|
+
import { spawn as spawn5 } from "child_process";
|
|
13406
13366
|
var McpClient = class {
|
|
13407
13367
|
serverId;
|
|
13408
13368
|
config;
|
|
@@ -13440,7 +13400,7 @@ var McpClient = class {
|
|
|
13440
13400
|
async connect() {
|
|
13441
13401
|
const timeout = this.config.timeout ?? MCP_CONNECT_TIMEOUT;
|
|
13442
13402
|
try {
|
|
13443
|
-
this.process =
|
|
13403
|
+
this.process = spawn5(this.config.command, this.config.args ?? [], {
|
|
13444
13404
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13445
13405
|
env: { ...process.env, ...this.config.env },
|
|
13446
13406
|
// Windows 上 npx 等是 .cmd 脚本,需要 shell 模式
|
|
@@ -13919,11 +13879,11 @@ var McpManager = class {
|
|
|
13919
13879
|
};
|
|
13920
13880
|
|
|
13921
13881
|
// src/skills/manager.ts
|
|
13922
|
-
import { existsSync as
|
|
13923
|
-
import { join as
|
|
13882
|
+
import { existsSync as existsSync20, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync9 } from "fs";
|
|
13883
|
+
import { join as join14 } from "path";
|
|
13924
13884
|
|
|
13925
13885
|
// src/skills/types.ts
|
|
13926
|
-
import { readFileSync as
|
|
13886
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
13927
13887
|
import { basename as basename7 } from "path";
|
|
13928
13888
|
function parseSimpleYaml(yaml) {
|
|
13929
13889
|
const result = {};
|
|
@@ -13945,7 +13905,7 @@ function parseYamlArray(value) {
|
|
|
13945
13905
|
function parseSkillFile(filePath) {
|
|
13946
13906
|
let raw;
|
|
13947
13907
|
try {
|
|
13948
|
-
raw =
|
|
13908
|
+
raw = readFileSync13(filePath, "utf-8");
|
|
13949
13909
|
} catch {
|
|
13950
13910
|
return null;
|
|
13951
13911
|
}
|
|
@@ -13993,7 +13953,7 @@ var SkillManager = class {
|
|
|
13993
13953
|
return this.skills.size;
|
|
13994
13954
|
}
|
|
13995
13955
|
loadSkillDir(dir, createIfMissing) {
|
|
13996
|
-
if (!
|
|
13956
|
+
if (!existsSync20(dir)) {
|
|
13997
13957
|
if (createIfMissing) {
|
|
13998
13958
|
try {
|
|
13999
13959
|
mkdirSync10(dir, { recursive: true });
|
|
@@ -14010,14 +13970,14 @@ var SkillManager = class {
|
|
|
14010
13970
|
}
|
|
14011
13971
|
for (const entry of entries) {
|
|
14012
13972
|
let filePath;
|
|
14013
|
-
const fullPath =
|
|
13973
|
+
const fullPath = join14(dir, entry);
|
|
14014
13974
|
if (entry.endsWith(".md")) {
|
|
14015
13975
|
filePath = fullPath;
|
|
14016
13976
|
} else {
|
|
14017
13977
|
try {
|
|
14018
13978
|
if (statSync9(fullPath).isDirectory()) {
|
|
14019
|
-
const skillMd =
|
|
14020
|
-
if (
|
|
13979
|
+
const skillMd = join14(fullPath, "SKILL.md");
|
|
13980
|
+
if (existsSync20(skillMd)) filePath = skillMd;
|
|
14021
13981
|
else continue;
|
|
14022
13982
|
} else {
|
|
14023
13983
|
continue;
|
|
@@ -14130,7 +14090,7 @@ function sendSocketMessage(ws, message) {
|
|
|
14130
14090
|
}
|
|
14131
14091
|
|
|
14132
14092
|
// src/web/tool-executor-web.ts
|
|
14133
|
-
import { existsSync as
|
|
14093
|
+
import { existsSync as existsSync21, readFileSync as readFileSync14 } from "fs";
|
|
14134
14094
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
14135
14095
|
constructor(registry, ws) {
|
|
14136
14096
|
this.registry = registry;
|
|
@@ -14277,9 +14237,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
14277
14237
|
getDiffPreview(call) {
|
|
14278
14238
|
if (call.name === "write_file") {
|
|
14279
14239
|
const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
|
|
14280
|
-
if (filePath &&
|
|
14240
|
+
if (filePath && existsSync21(filePath)) {
|
|
14281
14241
|
try {
|
|
14282
|
-
const old =
|
|
14242
|
+
const old = readFileSync14(filePath, "utf-8");
|
|
14283
14243
|
return renderDiff(old, append ? old + chunk : chunk, { filePath });
|
|
14284
14244
|
} catch {
|
|
14285
14245
|
}
|
|
@@ -14515,16 +14475,16 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
14515
14475
|
};
|
|
14516
14476
|
|
|
14517
14477
|
// src/repl/dev-state.ts
|
|
14518
|
-
import { existsSync as
|
|
14519
|
-
import { join as
|
|
14520
|
-
import { homedir as
|
|
14478
|
+
import { existsSync as existsSync22, readFileSync as readFileSync15, unlinkSync as unlinkSync5, mkdirSync as mkdirSync11 } from "fs";
|
|
14479
|
+
import { join as join15 } from "path";
|
|
14480
|
+
import { homedir as homedir6 } from "os";
|
|
14521
14481
|
function getDevStatePath() {
|
|
14522
|
-
return
|
|
14482
|
+
return join15(homedir6(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
14523
14483
|
}
|
|
14524
14484
|
function loadDevState() {
|
|
14525
14485
|
const path3 = getDevStatePath();
|
|
14526
|
-
if (!
|
|
14527
|
-
const content =
|
|
14486
|
+
if (!existsSync22(path3)) return null;
|
|
14487
|
+
const content = readFileSync15(path3, "utf-8").trim();
|
|
14528
14488
|
return content || null;
|
|
14529
14489
|
}
|
|
14530
14490
|
|
|
@@ -14894,8 +14854,8 @@ var checkpointDeletedMessage = (name) => `\u2713 Deleted checkpoint "${name}"`;
|
|
|
14894
14854
|
var checkpointNotFoundMessage = (name) => `Checkpoint "${name}" not found.`;
|
|
14895
14855
|
|
|
14896
14856
|
// src/core/context-files.ts
|
|
14897
|
-
import { existsSync as
|
|
14898
|
-
import { join as
|
|
14857
|
+
import { existsSync as existsSync23, readFileSync as readFileSync16 } from "fs";
|
|
14858
|
+
import { join as join16, relative as relative4, resolve as resolve8 } from "path";
|
|
14899
14859
|
function uniqueNonEmpty(values) {
|
|
14900
14860
|
const seen = /* @__PURE__ */ new Set();
|
|
14901
14861
|
const out = [];
|
|
@@ -14925,7 +14885,7 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
14925
14885
|
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
14926
14886
|
const shown = displayPath(filePath, cwd);
|
|
14927
14887
|
try {
|
|
14928
|
-
const raw =
|
|
14888
|
+
const raw = readFileSync16(filePath);
|
|
14929
14889
|
const byteCount = raw.byteLength;
|
|
14930
14890
|
if (byteCount === 0) {
|
|
14931
14891
|
return {
|
|
@@ -14972,8 +14932,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
14972
14932
|
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
14973
14933
|
const skipped = [];
|
|
14974
14934
|
for (const candidate of candidates) {
|
|
14975
|
-
const filePath =
|
|
14976
|
-
if (!
|
|
14935
|
+
const filePath = join16(dir, candidate);
|
|
14936
|
+
if (!existsSync23(filePath)) continue;
|
|
14977
14937
|
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
14978
14938
|
if (result.skipped) skipped.push(result.skipped);
|
|
14979
14939
|
if (result.layer) return { layer: result.layer, skipped };
|
|
@@ -15000,7 +14960,7 @@ function loadContextFiles(options) {
|
|
|
15000
14960
|
});
|
|
15001
14961
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
15002
14962
|
}
|
|
15003
|
-
if (!
|
|
14963
|
+
if (!existsSync23(filePath)) {
|
|
15004
14964
|
skipped.push({
|
|
15005
14965
|
level: "single",
|
|
15006
14966
|
filePath,
|
|
@@ -15113,8 +15073,8 @@ import { mkdirSync as mkdirSync15, createWriteStream as createWriteStream2 } fro
|
|
|
15113
15073
|
import { dirname as dirname10 } from "path";
|
|
15114
15074
|
|
|
15115
15075
|
// src/repl/commands/project-init.ts
|
|
15116
|
-
import { existsSync as
|
|
15117
|
-
import { join as
|
|
15076
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17, readdirSync as readdirSync11, statSync as statSync11 } from "fs";
|
|
15077
|
+
import { join as join17 } from "path";
|
|
15118
15078
|
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
15119
15079
|
"node_modules",
|
|
15120
15080
|
".git",
|
|
@@ -15158,11 +15118,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
15158
15118
|
const sorted = filtered.sort((a, b) => {
|
|
15159
15119
|
let aIsDir = false, bIsDir = false;
|
|
15160
15120
|
try {
|
|
15161
|
-
aIsDir = statSync11(
|
|
15121
|
+
aIsDir = statSync11(join17(d, a)).isDirectory();
|
|
15162
15122
|
} catch {
|
|
15163
15123
|
}
|
|
15164
15124
|
try {
|
|
15165
|
-
bIsDir = statSync11(
|
|
15125
|
+
bIsDir = statSync11(join17(d, b)).isDirectory();
|
|
15166
15126
|
} catch {
|
|
15167
15127
|
}
|
|
15168
15128
|
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
@@ -15170,7 +15130,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
15170
15130
|
});
|
|
15171
15131
|
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
15172
15132
|
const name = sorted[i];
|
|
15173
|
-
const fullPath =
|
|
15133
|
+
const fullPath = join17(d, name);
|
|
15174
15134
|
const isLast = i === sorted.length - 1;
|
|
15175
15135
|
const connector = isLast ? "+-- " : "|-- ";
|
|
15176
15136
|
let isDir;
|
|
@@ -15197,7 +15157,7 @@ function scanProject(cwd) {
|
|
|
15197
15157
|
configFiles: [],
|
|
15198
15158
|
directoryStructure: ""
|
|
15199
15159
|
};
|
|
15200
|
-
const check = (file) =>
|
|
15160
|
+
const check = (file) => existsSync24(join17(cwd, file));
|
|
15201
15161
|
const configCandidates = [
|
|
15202
15162
|
"package.json",
|
|
15203
15163
|
"tsconfig.json",
|
|
@@ -15224,7 +15184,7 @@ function scanProject(cwd) {
|
|
|
15224
15184
|
info.type = "node";
|
|
15225
15185
|
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
15226
15186
|
try {
|
|
15227
|
-
const pkg = JSON.parse(
|
|
15187
|
+
const pkg = JSON.parse(readFileSync17(join17(cwd, "package.json"), "utf-8"));
|
|
15228
15188
|
const scripts = pkg.scripts ?? {};
|
|
15229
15189
|
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
15230
15190
|
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
@@ -15992,7 +15952,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
|
|
|
15992
15952
|
}
|
|
15993
15953
|
|
|
15994
15954
|
// src/hub/persist.ts
|
|
15995
|
-
import { join as
|
|
15955
|
+
import { join as join18 } from "path";
|
|
15996
15956
|
function discussionToMessages(state2) {
|
|
15997
15957
|
const out = [];
|
|
15998
15958
|
const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -16030,7 +15990,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
|
|
|
16030
15990
|
session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
|
|
16031
15991
|
session.titleAiGenerated = true;
|
|
16032
15992
|
await sm.save();
|
|
16033
|
-
return { id: session.id, path:
|
|
15993
|
+
return { id: session.id, path: join18(config.getHistoryDir(), `${session.id}.json`) };
|
|
16034
15994
|
}
|
|
16035
15995
|
|
|
16036
15996
|
// src/core/think-command.ts
|
|
@@ -16151,10 +16111,10 @@ var PLAN_MODE_TOOL_HINT = [
|
|
|
16151
16111
|
"read_file \xB7 list_dir \xB7 grep_files \xB7 glob_files",
|
|
16152
16112
|
"web_fetch \xB7 google_search \xB7 ask_user \xB7 write_todos"
|
|
16153
16113
|
];
|
|
16154
|
-
function applyPlanCommand(sub,
|
|
16155
|
-
const key = (sub ?? "enter").toLowerCase() === "toggle" ?
|
|
16114
|
+
function applyPlanCommand(sub, active2) {
|
|
16115
|
+
const key = (sub ?? "enter").toLowerCase() === "toggle" ? active2 ? "exit" : "enter" : (sub ?? "enter").toLowerCase();
|
|
16156
16116
|
if (key === "enter") {
|
|
16157
|
-
if (
|
|
16117
|
+
if (active2) {
|
|
16158
16118
|
return {
|
|
16159
16119
|
planMode: true,
|
|
16160
16120
|
changed: false,
|
|
@@ -16177,7 +16137,7 @@ function applyPlanCommand(sub, active) {
|
|
|
16177
16137
|
};
|
|
16178
16138
|
}
|
|
16179
16139
|
if (key === "execute") {
|
|
16180
|
-
if (!
|
|
16140
|
+
if (!active2) {
|
|
16181
16141
|
return { planMode: false, changed: false, status: "not-active", summary: "Not in Plan Mode. Enter first with /plan", details: [] };
|
|
16182
16142
|
}
|
|
16183
16143
|
return {
|
|
@@ -16189,22 +16149,22 @@ function applyPlanCommand(sub, active) {
|
|
|
16189
16149
|
};
|
|
16190
16150
|
}
|
|
16191
16151
|
if (key === "exit" || key === "cancel") {
|
|
16192
|
-
if (!
|
|
16152
|
+
if (!active2) {
|
|
16193
16153
|
return { planMode: false, changed: false, status: "not-active", summary: "Not in Plan Mode.", details: [] };
|
|
16194
16154
|
}
|
|
16195
16155
|
return { planMode: false, changed: true, status: "cancelled", summary: "Plan Mode cancelled. Returning to normal mode.", details: [] };
|
|
16196
16156
|
}
|
|
16197
16157
|
if (key === "status" || key === "show") {
|
|
16198
16158
|
return {
|
|
16199
|
-
planMode:
|
|
16159
|
+
planMode: active2,
|
|
16200
16160
|
changed: false,
|
|
16201
16161
|
status: "status",
|
|
16202
|
-
summary: `Plan Mode: ${
|
|
16203
|
-
details:
|
|
16162
|
+
summary: `Plan Mode: ${active2 ? "ACTIVE \u{1F4CB}" : "inactive"}`,
|
|
16163
|
+
details: active2 ? ["Tools restricted to read-only set.", "Use /plan execute to start executing, or /plan exit to cancel."] : []
|
|
16204
16164
|
};
|
|
16205
16165
|
}
|
|
16206
16166
|
return {
|
|
16207
|
-
planMode:
|
|
16167
|
+
planMode: active2,
|
|
16208
16168
|
changed: false,
|
|
16209
16169
|
status: "unknown",
|
|
16210
16170
|
summary: `Unknown /plan option "${sub}" \u2014 expected execute | exit | status`,
|
|
@@ -16476,8 +16436,8 @@ function buildSessionCostReport(input) {
|
|
|
16476
16436
|
const cacheHitPct = usage.cacheReadTokens > 0 ? Math.round(usage.cacheReadTokens / (usage.cacheReadTokens + usage.inputTokens) * 100) : null;
|
|
16477
16437
|
return { hasUsage: true, totalTokens, cacheHitPct, cost, costSource, savings };
|
|
16478
16438
|
}
|
|
16479
|
-
function costSourceNote(
|
|
16480
|
-
switch (
|
|
16439
|
+
function costSourceNote(source2) {
|
|
16440
|
+
switch (source2) {
|
|
16481
16441
|
case "accrued-partial":
|
|
16482
16442
|
return "(partial \u2014 some turns had unknown pricing)";
|
|
16483
16443
|
case "estimated":
|
|
@@ -16488,8 +16448,8 @@ function costSourceNote(source) {
|
|
|
16488
16448
|
}
|
|
16489
16449
|
|
|
16490
16450
|
// src/web/commands/_shared.ts
|
|
16491
|
-
import { existsSync as
|
|
16492
|
-
import { join as
|
|
16451
|
+
import { existsSync as existsSync27, readFileSync as readFileSync19, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, statSync as statSync14, createWriteStream } from "fs";
|
|
16452
|
+
import { join as join21, resolve as resolve9, dirname as dirname8 } from "path";
|
|
16493
16453
|
|
|
16494
16454
|
// src/cli/review-prompts.ts
|
|
16495
16455
|
function buildReviewPrompt(diff, gitContextStr, detailed) {
|
|
@@ -16561,34 +16521,34 @@ function readGitDiff(options = {}) {
|
|
|
16561
16521
|
}
|
|
16562
16522
|
|
|
16563
16523
|
// src/diagnostics/doctor-report.ts
|
|
16564
|
-
import { existsSync as
|
|
16565
|
-
import { join as
|
|
16566
|
-
import { arch as arch2, platform as
|
|
16524
|
+
import { existsSync as existsSync26, statSync as statSync13 } from "fs";
|
|
16525
|
+
import { join as join20 } from "path";
|
|
16526
|
+
import { arch as arch2, platform as platform5, release as release2 } from "os";
|
|
16567
16527
|
|
|
16568
16528
|
// src/diagnostics/crash-log.ts
|
|
16569
16529
|
import {
|
|
16570
|
-
existsSync as
|
|
16530
|
+
existsSync as existsSync25,
|
|
16571
16531
|
mkdirSync as mkdirSync12,
|
|
16572
16532
|
readdirSync as readdirSync12,
|
|
16573
|
-
readFileSync as
|
|
16533
|
+
readFileSync as readFileSync18,
|
|
16574
16534
|
statSync as statSync12,
|
|
16575
16535
|
unlinkSync as unlinkSync6,
|
|
16576
16536
|
writeFileSync as writeFileSync2
|
|
16577
16537
|
} from "fs";
|
|
16578
|
-
import { join as
|
|
16579
|
-
import { homedir as
|
|
16538
|
+
import { join as join19 } from "path";
|
|
16539
|
+
import { homedir as homedir7, platform as platform4, release, arch } from "os";
|
|
16580
16540
|
var LOGS_DIR_NAME = "logs";
|
|
16581
16541
|
var CRASH_LOG_PREFIX = "crash-";
|
|
16582
16542
|
function getLogsDir(configDir) {
|
|
16583
|
-
const base = configDir ??
|
|
16584
|
-
return
|
|
16543
|
+
const base = configDir ?? join19(homedir7(), CONFIG_DIR_NAME);
|
|
16544
|
+
return join19(base, LOGS_DIR_NAME);
|
|
16585
16545
|
}
|
|
16586
16546
|
function truncate(s, n) {
|
|
16587
16547
|
return s.length <= n ? s : s.slice(0, n) + "\u2026";
|
|
16588
16548
|
}
|
|
16589
16549
|
function listRecentCrashes(limit = 10, configDir) {
|
|
16590
16550
|
const dir = getLogsDir(configDir);
|
|
16591
|
-
if (!
|
|
16551
|
+
if (!existsSync25(dir)) return [];
|
|
16592
16552
|
const files = [];
|
|
16593
16553
|
let names;
|
|
16594
16554
|
try {
|
|
@@ -16598,10 +16558,10 @@ function listRecentCrashes(limit = 10, configDir) {
|
|
|
16598
16558
|
}
|
|
16599
16559
|
for (const name of names) {
|
|
16600
16560
|
if (!name.startsWith(CRASH_LOG_PREFIX) || !name.endsWith(".log")) continue;
|
|
16601
|
-
const full =
|
|
16561
|
+
const full = join19(dir, name);
|
|
16602
16562
|
try {
|
|
16603
16563
|
const st = statSync12(full);
|
|
16604
|
-
const head =
|
|
16564
|
+
const head = readFileSync18(full, "utf-8").split("\n").slice(0, 20);
|
|
16605
16565
|
const tsLine = head.find((l) => l.startsWith("timestamp:"));
|
|
16606
16566
|
const kindLine = head.find((l) => l.startsWith("kind:"));
|
|
16607
16567
|
const msgIdx = head.findIndex((l) => l.trim() === "## message");
|
|
@@ -16620,8 +16580,8 @@ function listRecentCrashes(limit = 10, configDir) {
|
|
|
16620
16580
|
return files.slice(0, limit);
|
|
16621
16581
|
}
|
|
16622
16582
|
function getConfigDirUsage(configDir) {
|
|
16623
|
-
const base = configDir ??
|
|
16624
|
-
if (!
|
|
16583
|
+
const base = configDir ?? join19(homedir7(), CONFIG_DIR_NAME);
|
|
16584
|
+
if (!existsSync25(base)) return { totalBytes: 0, entries: [] };
|
|
16625
16585
|
const entries = [];
|
|
16626
16586
|
let total = 0;
|
|
16627
16587
|
let names;
|
|
@@ -16631,7 +16591,7 @@ function getConfigDirUsage(configDir) {
|
|
|
16631
16591
|
return { totalBytes: 0, entries: [] };
|
|
16632
16592
|
}
|
|
16633
16593
|
for (const name of names) {
|
|
16634
|
-
const full =
|
|
16594
|
+
const full = join19(base, name);
|
|
16635
16595
|
const bytes = dirSize(full);
|
|
16636
16596
|
entries.push({ name, bytes });
|
|
16637
16597
|
total += bytes;
|
|
@@ -16646,7 +16606,7 @@ function dirSize(path3) {
|
|
|
16646
16606
|
if (!st.isDirectory()) return 0;
|
|
16647
16607
|
let sum = 0;
|
|
16648
16608
|
for (const name of readdirSync12(path3)) {
|
|
16649
|
-
sum += dirSize(
|
|
16609
|
+
sum += dirSize(join19(path3, name));
|
|
16650
16610
|
}
|
|
16651
16611
|
return sum;
|
|
16652
16612
|
} catch {
|
|
@@ -16656,7 +16616,7 @@ function dirSize(path3) {
|
|
|
16656
16616
|
|
|
16657
16617
|
// src/diagnostics/doctor-report.ts
|
|
16658
16618
|
function checkFile(label, path3) {
|
|
16659
|
-
const exists =
|
|
16619
|
+
const exists = existsSync26(path3);
|
|
16660
16620
|
let sizeBytes = null;
|
|
16661
16621
|
if (exists) {
|
|
16662
16622
|
try {
|
|
@@ -16693,18 +16653,18 @@ function buildDoctorReport(options) {
|
|
|
16693
16653
|
version: VERSION,
|
|
16694
16654
|
npmCheck: options.npmCheck,
|
|
16695
16655
|
node: process.version,
|
|
16696
|
-
platform: `${
|
|
16656
|
+
platform: `${platform5()} ${release2()} (${arch2()})`,
|
|
16697
16657
|
cwd,
|
|
16698
16658
|
projectRoot,
|
|
16699
16659
|
configDir,
|
|
16700
16660
|
providers: options.providers.listAll().map((p) => ({ id: p.id, displayName: p.displayName, configured: p.configured })),
|
|
16701
16661
|
files: [
|
|
16702
|
-
checkFile("config.json",
|
|
16703
|
-
checkFile("memory.md",
|
|
16704
|
-
checkFile("memory.jsonl",
|
|
16705
|
-
checkFile("dev-state.md",
|
|
16706
|
-
checkFile("hooks-trust.json",
|
|
16707
|
-
checkFile("plugin-state.json",
|
|
16662
|
+
checkFile("config.json", join20(configDir, "config.json")),
|
|
16663
|
+
checkFile("memory.md", join20(configDir, MEMORY_FILE_NAME)),
|
|
16664
|
+
checkFile("memory.jsonl", join20(configDir, "memory.jsonl")),
|
|
16665
|
+
checkFile("dev-state.md", join20(configDir, DEV_STATE_FILE_NAME)),
|
|
16666
|
+
checkFile("hooks-trust.json", join20(configDir, "hooks-trust.json")),
|
|
16667
|
+
checkFile("plugin-state.json", join20(configDir, "plugins", "plugin-state.json"))
|
|
16708
16668
|
],
|
|
16709
16669
|
context: {
|
|
16710
16670
|
enabled: contextSetting !== false,
|
|
@@ -16739,7 +16699,7 @@ function buildDoctorReport(options) {
|
|
|
16739
16699
|
disabled: hooks.filter((h) => h.disabled).length
|
|
16740
16700
|
},
|
|
16741
16701
|
plugins: {
|
|
16742
|
-
root:
|
|
16702
|
+
root: join20(configDir, "plugins"),
|
|
16743
16703
|
installed: plugins.length,
|
|
16744
16704
|
active: activeAssets.plugins.length,
|
|
16745
16705
|
invalid: plugins.filter((p) => !p.valid).length,
|
|
@@ -16754,7 +16714,7 @@ function buildDoctorReport(options) {
|
|
|
16754
16714
|
mcp: {
|
|
16755
16715
|
enabled: config.get("mcpEnabled"),
|
|
16756
16716
|
configured: Object.keys(mcpServers).length,
|
|
16757
|
-
projectConfigExists:
|
|
16717
|
+
projectConfigExists: existsSync26(join20(projectRoot, MCP_PROJECT_CONFIG_NAME)),
|
|
16758
16718
|
statuses: options.mcpStatuses ?? []
|
|
16759
16719
|
},
|
|
16760
16720
|
recentCrashes: listRecentCrashes(5, configDir),
|
|
@@ -17276,7 +17236,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
|
|
|
17276
17236
|
Cost: ${formatCost(cost)}` : "";
|
|
17277
17237
|
let memoryLine = "";
|
|
17278
17238
|
try {
|
|
17279
|
-
const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-
|
|
17239
|
+
const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-UNLCXUWH.js");
|
|
17280
17240
|
const pending = countPendingMemories2(ctx.config.getConfigDir());
|
|
17281
17241
|
if (pending > 0) memoryLine = `
|
|
17282
17242
|
Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
|
|
@@ -17899,7 +17859,7 @@ async function handleRewind(args, ctx) {
|
|
|
17899
17859
|
ctx.send({ type: "error", message: intent.message });
|
|
17900
17860
|
return;
|
|
17901
17861
|
}
|
|
17902
|
-
const { fileCheckpoints: fileCheckpoints2 } = await import("./file-checkpoint-
|
|
17862
|
+
const { fileCheckpoints: fileCheckpoints2 } = await import("./file-checkpoint-64A2AXHE.js");
|
|
17903
17863
|
if (intent.kind === "list") {
|
|
17904
17864
|
const rows = buildRewindList(session.messages, fileCheckpoints2.getMessageIndices());
|
|
17905
17865
|
ctx.send({
|
|
@@ -17956,9 +17916,9 @@ function parseAgentCommand(args) {
|
|
|
17956
17916
|
function formatAgentList(agents, preferred) {
|
|
17957
17917
|
const lines = [`Agents (${agents.length})`, ""];
|
|
17958
17918
|
for (const agent of agents) {
|
|
17959
|
-
const
|
|
17919
|
+
const active2 = agent.name === preferred ? " *" : " ";
|
|
17960
17920
|
const model = [agent.provider, agent.model].filter(Boolean).join("/") || "inherit";
|
|
17961
|
-
lines.push(`${
|
|
17921
|
+
lines.push(`${active2} ${agent.name} [${agent.source ?? "builtin"}] \u2014 ${agent.description ?? ""}`);
|
|
17962
17922
|
lines.push(
|
|
17963
17923
|
` model: ${model} \xB7 permission: ${agent.permissionProfile ?? "workspace-write"} \xB7 maxRounds: ${agent.maxToolRounds ?? "inherit"}`
|
|
17964
17924
|
);
|
|
@@ -18017,7 +17977,7 @@ async function handlePlugin(args, ctx) {
|
|
|
18017
17977
|
lines.push("");
|
|
18018
17978
|
}
|
|
18019
17979
|
if (pluginTools.length > 0) {
|
|
18020
|
-
const legacyDir =
|
|
17980
|
+
const legacyDir = join21(configDir, PLUGINS_DIR_NAME);
|
|
18021
17981
|
lines.push(`**Legacy JS tools** (${pluginTools.length}):`);
|
|
18022
17982
|
lines.push(`Dir: ${legacyDir}`);
|
|
18023
17983
|
for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
|
|
@@ -18099,14 +18059,14 @@ async function handleSkill(args, ctx) {
|
|
|
18099
18059
|
return;
|
|
18100
18060
|
}
|
|
18101
18061
|
const intent = parseSkillCommand(args);
|
|
18102
|
-
const
|
|
18062
|
+
const active2 = ctx.skillManager.getActive();
|
|
18103
18063
|
if (intent.kind === "list") {
|
|
18104
18064
|
const skills = ctx.skillManager.listSkills();
|
|
18105
18065
|
if (skills.length === 0) {
|
|
18106
18066
|
ctx.send({ type: "info", message: "No skills available. Add .md files to ~/.aicli/skills/" });
|
|
18107
18067
|
} else {
|
|
18108
18068
|
const lines = skills.map((s) => {
|
|
18109
|
-
const isActive =
|
|
18069
|
+
const isActive = active2?.meta.name === s.meta.name;
|
|
18110
18070
|
const marker = isActive ? " \u2705 (active)" : "";
|
|
18111
18071
|
return ` ${s.meta.name}${marker} \u2014 ${s.meta.description || "(no description)"}`;
|
|
18112
18072
|
});
|
|
@@ -18122,12 +18082,12 @@ ${lines.join("\n")}` });
|
|
|
18122
18082
|
return;
|
|
18123
18083
|
}
|
|
18124
18084
|
const note = skillHistoryNote(
|
|
18125
|
-
|
|
18085
|
+
active2?.meta.name ?? "(none)",
|
|
18126
18086
|
intent.kind === "deactivate" ? "(none)" : intent.name,
|
|
18127
18087
|
ctx.sessions.current?.messages.length ?? 0
|
|
18128
18088
|
);
|
|
18129
18089
|
if (intent.kind === "deactivate") {
|
|
18130
|
-
if (!
|
|
18090
|
+
if (!active2) {
|
|
18131
18091
|
ctx.send({ type: "info", message: "No skill is currently active." });
|
|
18132
18092
|
return;
|
|
18133
18093
|
}
|
|
@@ -18138,7 +18098,7 @@ ${note}` : "\u{1F50C} Skill deactivated." });
|
|
|
18138
18098
|
ctx.sendStatus();
|
|
18139
18099
|
return;
|
|
18140
18100
|
}
|
|
18141
|
-
if (
|
|
18101
|
+
if (active2?.meta.name === intent.name) {
|
|
18142
18102
|
ctx.send({ type: "info", message: `Skill '${intent.name}' is already active.` });
|
|
18143
18103
|
return;
|
|
18144
18104
|
}
|
|
@@ -18157,8 +18117,8 @@ ${note}` : head });
|
|
|
18157
18117
|
}
|
|
18158
18118
|
async function handleCommands(_args, ctx) {
|
|
18159
18119
|
const configDir = ctx.config.getConfigDir();
|
|
18160
|
-
const commandsDir =
|
|
18161
|
-
if (!
|
|
18120
|
+
const commandsDir = join21(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
18121
|
+
if (!existsSync27(commandsDir)) {
|
|
18162
18122
|
ctx.send({ type: "info", message: `No custom commands directory.
|
|
18163
18123
|
Create: ${commandsDir}/ with .md files.` });
|
|
18164
18124
|
return;
|
|
@@ -18264,7 +18224,7 @@ async function handleTest(args, ctx) {
|
|
|
18264
18224
|
const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
|
|
18265
18225
|
testArgs = isCommand ? { command: argStr } : { filter: argStr };
|
|
18266
18226
|
}
|
|
18267
|
-
const runTests = ctx.runTests ?? (await import("./run-tests-
|
|
18227
|
+
const runTests = ctx.runTests ?? (await import("./run-tests-KRNMFXAN.js")).executeTests;
|
|
18268
18228
|
const report = await runTests(testArgs);
|
|
18269
18229
|
ctx.send({ type: "info", message: report });
|
|
18270
18230
|
} catch (err) {
|
|
@@ -18288,9 +18248,9 @@ async function handleDiff(args, ctx) {
|
|
|
18288
18248
|
let modifiedFiles = 0;
|
|
18289
18249
|
const diffLines2 = [];
|
|
18290
18250
|
for (const [filePath, { earliest }] of fileMap) {
|
|
18291
|
-
const currentContent =
|
|
18251
|
+
const currentContent = existsSync27(filePath) ? (() => {
|
|
18292
18252
|
try {
|
|
18293
|
-
return
|
|
18253
|
+
return readFileSync19(filePath, "utf-8");
|
|
18294
18254
|
} catch {
|
|
18295
18255
|
return null;
|
|
18296
18256
|
}
|
|
@@ -18325,9 +18285,9 @@ async function handleDiff(args, ctx) {
|
|
|
18325
18285
|
}
|
|
18326
18286
|
async function handleInit(args, ctx) {
|
|
18327
18287
|
const cwd = process.cwd();
|
|
18328
|
-
const targetPath =
|
|
18288
|
+
const targetPath = join21(cwd, "AICLI.md");
|
|
18329
18289
|
const force = args.includes("--force");
|
|
18330
|
-
if (
|
|
18290
|
+
if (existsSync27(targetPath) && !force) {
|
|
18331
18291
|
ctx.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
18332
18292
|
Use /init --force to overwrite.` });
|
|
18333
18293
|
return;
|
|
@@ -18556,9 +18516,9 @@ async function handleContext(_args, ctx) {
|
|
|
18556
18516
|
async function handleIndex(args, ctx) {
|
|
18557
18517
|
const sub = (args[0] ?? "status").toLowerCase();
|
|
18558
18518
|
const root = process.cwd();
|
|
18559
|
-
const { loadIndex: loadIndex2, clearIndex } = await import("./store-
|
|
18560
|
-
const { indexProject: indexProject2 } = await import("./indexer-
|
|
18561
|
-
const { loadVectorStore, clearVectorStore } = await import("./vector-store-
|
|
18519
|
+
const { loadIndex: loadIndex2, clearIndex } = await import("./store-2N6PPPBP.js");
|
|
18520
|
+
const { indexProject: indexProject2 } = await import("./indexer-VRXDZ74A.js");
|
|
18521
|
+
const { loadVectorStore, clearVectorStore } = await import("./vector-store-T2DVUMY4.js");
|
|
18562
18522
|
if (sub === "status") {
|
|
18563
18523
|
const idx = loadIndex2(root);
|
|
18564
18524
|
const vec = loadVectorStore(root);
|
|
@@ -18608,7 +18568,7 @@ async function handleIndex(args, ctx) {
|
|
|
18608
18568
|
message: `Building semantic index for ${idx.symbolCount} symbols\u2026 (first run downloads ~117 MB model)`
|
|
18609
18569
|
});
|
|
18610
18570
|
try {
|
|
18611
|
-
const { rebuildSemanticIndex } = await import("./semantic-
|
|
18571
|
+
const { rebuildSemanticIndex } = await import("./semantic-TRQAZSRY.js");
|
|
18612
18572
|
const stats = await rebuildSemanticIndex(root);
|
|
18613
18573
|
const first = stats.modelFirstLoadMs ? ` (model load+first batch ${stats.modelFirstLoadMs}ms)` : "";
|
|
18614
18574
|
ctx.send({
|
|
@@ -18639,7 +18599,7 @@ async function handleMemory(args, ctx) {
|
|
|
18639
18599
|
if (intent.kind === "rebuild") {
|
|
18640
18600
|
ctx.send({ type: "info", message: rebuildStartMessage(intent.full) });
|
|
18641
18601
|
try {
|
|
18642
|
-
const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-
|
|
18602
|
+
const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
|
|
18643
18603
|
const stats = await buildChatIndex2({ full: intent.full });
|
|
18644
18604
|
ctx.send({ type: "info", message: rebuildDoneMessage(stats) });
|
|
18645
18605
|
} catch (err) {
|
|
@@ -18648,14 +18608,14 @@ async function handleMemory(args, ctx) {
|
|
|
18648
18608
|
return;
|
|
18649
18609
|
}
|
|
18650
18610
|
if (intent.kind === "index-clear") {
|
|
18651
|
-
const { clearChatIndex: clearChatIndex2 } = await import("./chat-index-
|
|
18611
|
+
const { clearChatIndex: clearChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
|
|
18652
18612
|
clearChatIndex2();
|
|
18653
18613
|
ctx.send({ type: "info", message: "Chat memory index cleared (persistent memory untouched)." });
|
|
18654
18614
|
return;
|
|
18655
18615
|
}
|
|
18656
18616
|
if (intent.kind === "status") {
|
|
18657
|
-
const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-
|
|
18658
|
-
const { listMemoryEntries: listMemoryEntries2, isMemoryExpired: isMemoryExpired2, formatMemoryForPrompt: formatMemoryForPrompt2 } = await import("./persistent-memory-
|
|
18617
|
+
const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-RLVLQRRC.js");
|
|
18618
|
+
const { listMemoryEntries: listMemoryEntries2, isMemoryExpired: isMemoryExpired2, formatMemoryForPrompt: formatMemoryForPrompt2 } = await import("./persistent-memory-UNLCXUWH.js");
|
|
18659
18619
|
const memories = listMemoryEntries2(configDir, { includeExpired: true, includeRejected: true });
|
|
18660
18620
|
const injected = formatMemoryForPrompt2(configDir, process.cwd(), ctx.config.get("memory")?.maxChars);
|
|
18661
18621
|
const view = buildMemoryStatusView({
|
|
@@ -18678,8 +18638,8 @@ async function handleMemory(args, ctx) {
|
|
|
18678
18638
|
return;
|
|
18679
18639
|
}
|
|
18680
18640
|
if (intent.kind === "recall") {
|
|
18681
|
-
const { searchPersistentMemories: searchPersistentMemories2, touchMemoryReferences: touchMemoryReferences2 } = await import("./persistent-memory-
|
|
18682
|
-
const { searchChatMemory: searchChatMemory2 } = await import("./chat-index-
|
|
18641
|
+
const { searchPersistentMemories: searchPersistentMemories2, touchMemoryReferences: touchMemoryReferences2 } = await import("./persistent-memory-UNLCXUWH.js");
|
|
18642
|
+
const { searchChatMemory: searchChatMemory2 } = await import("./chat-index-RLVLQRRC.js");
|
|
18683
18643
|
const persistentHits = searchPersistentMemories2(configDir, intent.query, process.cwd(), 3);
|
|
18684
18644
|
if (persistentHits.length > 0) {
|
|
18685
18645
|
touchMemoryReferences2(configDir, persistentHits.map((h) => h.entry.id));
|
|
@@ -18799,7 +18759,7 @@ async function handleMemory(args, ctx) {
|
|
|
18799
18759
|
ctx.handleMemoryManage(intent.action, args[1], intent.action === "expire" ? args[2] : void 0);
|
|
18800
18760
|
} else if (intent.kind === "export") {
|
|
18801
18761
|
if (args[1] === "md") {
|
|
18802
|
-
const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-
|
|
18762
|
+
const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-UNLCXUWH.js");
|
|
18803
18763
|
ctx.send({
|
|
18804
18764
|
type: "export_data",
|
|
18805
18765
|
format: "md",
|
|
@@ -18837,7 +18797,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
18837
18797
|
return;
|
|
18838
18798
|
}
|
|
18839
18799
|
const dirPath = resolve9(sub);
|
|
18840
|
-
if (!
|
|
18800
|
+
if (!existsSync27(dirPath)) {
|
|
18841
18801
|
ctx.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
18842
18802
|
return;
|
|
18843
18803
|
}
|
|
@@ -19201,7 +19161,7 @@ async function handleAgent(args, ctx) {
|
|
|
19201
19161
|
|
|
19202
19162
|
// src/repl/commands/utils.ts
|
|
19203
19163
|
import { execSync as execSync2 } from "child_process";
|
|
19204
|
-
import { platform as
|
|
19164
|
+
import { platform as platform6 } from "os";
|
|
19205
19165
|
|
|
19206
19166
|
// src/repl/commands/constants.ts
|
|
19207
19167
|
var SESSION_PAGE_SIZE = 20;
|
|
@@ -19214,7 +19174,7 @@ function fmtCtx(tokens) {
|
|
|
19214
19174
|
return `${tokens}`;
|
|
19215
19175
|
}
|
|
19216
19176
|
function copyToClipboard(text) {
|
|
19217
|
-
const plat =
|
|
19177
|
+
const plat = platform6();
|
|
19218
19178
|
if (plat === "win32") {
|
|
19219
19179
|
execSync2("clip", { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
19220
19180
|
} else if (plat === "darwin") {
|
|
@@ -19801,16 +19761,16 @@ No tools match "${filter}".
|
|
|
19801
19761
|
usage: "/mcp [reconnect [serverId] | trust-project]",
|
|
19802
19762
|
async execute(args, ctx) {
|
|
19803
19763
|
if (args[0] === "trust-project") {
|
|
19804
|
-
const { join:
|
|
19805
|
-
const { existsSync:
|
|
19764
|
+
const { join: join26 } = await import("path");
|
|
19765
|
+
const { existsSync: existsSync32 } = await import("fs");
|
|
19806
19766
|
const { getGitRoot: getGitRoot2 } = await import("./git-context-VNCA2GU7.js");
|
|
19807
|
-
const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-
|
|
19808
|
-
const { approveProject, hashMcpFile } = await import("./project-trust-
|
|
19767
|
+
const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-FG27DQMS.js");
|
|
19768
|
+
const { approveProject, hashMcpFile } = await import("./project-trust-6CJB5ZMX.js");
|
|
19809
19769
|
const cwd = process.cwd();
|
|
19810
19770
|
const projectRoot = getGitRoot2(cwd) ?? cwd;
|
|
19811
|
-
const mcpPath =
|
|
19771
|
+
const mcpPath = join26(projectRoot, MCP_PROJECT_CONFIG_NAME2);
|
|
19812
19772
|
console.log();
|
|
19813
|
-
if (!
|
|
19773
|
+
if (!existsSync32(mcpPath)) {
|
|
19814
19774
|
console.log(theme.dim(` No .mcp.json in ${projectRoot}.`));
|
|
19815
19775
|
console.log();
|
|
19816
19776
|
return;
|
|
@@ -19883,8 +19843,8 @@ No tools match "${filter}".
|
|
|
19883
19843
|
const errorStr = s.error ? theme.error(`
|
|
19884
19844
|
Error: ${s.error}`) : "";
|
|
19885
19845
|
const serverLabel = s.serverName !== s.serverId ? ` (${s.serverName})` : "";
|
|
19886
|
-
const
|
|
19887
|
-
const sourceTag =
|
|
19846
|
+
const source2 = ctx.getMcpServerSource(s.serverId);
|
|
19847
|
+
const sourceTag = source2 === "project" ? theme.accent(" [project]") : theme.dim(" [global]");
|
|
19888
19848
|
console.log(` ${theme.accent(s.serverId)}${theme.dim(serverLabel)}${sourceTag} [${statusBadge}]${toolCountStr}${errorStr}`);
|
|
19889
19849
|
if (s.connected) {
|
|
19890
19850
|
const mcpTools = ctx.tools.listMcpTools().filter(
|
|
@@ -19912,7 +19872,7 @@ No tools match "${filter}".
|
|
|
19912
19872
|
}
|
|
19913
19873
|
if (!sub || sub === "list") {
|
|
19914
19874
|
const skills = manager.listSkills();
|
|
19915
|
-
const
|
|
19875
|
+
const active2 = manager.getActive();
|
|
19916
19876
|
console.log();
|
|
19917
19877
|
console.log(theme.heading(" \u{1F3AF} Agent Skills"));
|
|
19918
19878
|
console.log(theme.dim(" " + "\u2500".repeat(50)));
|
|
@@ -19922,7 +19882,7 @@ No tools match "${filter}".
|
|
|
19922
19882
|
console.log(theme.dim(" Format: YAML frontmatter (name/description/tools) + prompt body"));
|
|
19923
19883
|
} else {
|
|
19924
19884
|
for (const s of skills) {
|
|
19925
|
-
const isActive =
|
|
19885
|
+
const isActive = active2?.meta.name === s.meta.name;
|
|
19926
19886
|
const badge = isActive ? theme.success(" \u2713 active") : "";
|
|
19927
19887
|
const toolInfo = s.meta.tools ? theme.dim(` [${s.meta.tools.join(", ")}]`) : "";
|
|
19928
19888
|
console.log(
|
|
@@ -20010,7 +19970,7 @@ No tools match "${filter}".
|
|
|
20010
19970
|
}
|
|
20011
19971
|
|
|
20012
19972
|
// src/repl/commands/security-commands.ts
|
|
20013
|
-
import { platform as
|
|
19973
|
+
import { platform as platform7 } from "os";
|
|
20014
19974
|
import chalk6 from "chalk";
|
|
20015
19975
|
function createSecurityCommands() {
|
|
20016
19976
|
return [
|
|
@@ -20091,7 +20051,7 @@ ${outcome.summary}
|
|
|
20091
20051
|
usage: "/bug [--copy]",
|
|
20092
20052
|
execute(args, ctx) {
|
|
20093
20053
|
const doCopy = args.includes("--copy");
|
|
20094
|
-
const os =
|
|
20054
|
+
const os = platform7();
|
|
20095
20055
|
const nodeVer = process.version;
|
|
20096
20056
|
const provider = ctx.getCurrentProvider();
|
|
20097
20057
|
const model = ctx.getCurrentModel();
|
|
@@ -20245,12 +20205,12 @@ ${outcome.summary}
|
|
|
20245
20205
|
if (scanAll) {
|
|
20246
20206
|
const metas = ctx.sessions.listSessions();
|
|
20247
20207
|
console.log(theme.info(` Scanning ${metas.length} session(s)\u2026`));
|
|
20248
|
-
const { readFileSync:
|
|
20249
|
-
const { join:
|
|
20208
|
+
const { readFileSync: readFileSync23 } = await import("fs");
|
|
20209
|
+
const { join: join26 } = await import("path");
|
|
20250
20210
|
const historyDir = ctx.config.getHistoryDir();
|
|
20251
20211
|
for (const m of metas) {
|
|
20252
20212
|
try {
|
|
20253
|
-
const content =
|
|
20213
|
+
const content = readFileSync23(join26(historyDir, `${m.id}.json`), "utf-8");
|
|
20254
20214
|
const hits2 = scanString(content, opts);
|
|
20255
20215
|
if (hits2.length) {
|
|
20256
20216
|
filesWithHits++;
|
|
@@ -20297,8 +20257,8 @@ ${outcome.summary}
|
|
|
20297
20257
|
}
|
|
20298
20258
|
|
|
20299
20259
|
// src/repl/commands/review-commands.ts
|
|
20300
|
-
import { existsSync as
|
|
20301
|
-
import { join as
|
|
20260
|
+
import { existsSync as existsSync28, readFileSync as readFileSync20, writeFileSync as writeFileSync4 } from "fs";
|
|
20261
|
+
import { join as join22 } from "path";
|
|
20302
20262
|
function createReviewCommands() {
|
|
20303
20263
|
return [
|
|
20304
20264
|
{
|
|
@@ -20472,11 +20432,11 @@ function createReviewCommands() {
|
|
|
20472
20432
|
Branches (${branches.length}):
|
|
20473
20433
|
`));
|
|
20474
20434
|
for (const b of branches) {
|
|
20475
|
-
const
|
|
20435
|
+
const active2 = b.id === session.activeBranchId ? theme.success("\u25CF ") : " ";
|
|
20476
20436
|
const parent = b.parentBranchId ? theme.dim(` \u2190 ${b.parentBranchId}@${b.parentMessageIndex}`) : "";
|
|
20477
20437
|
const count = b.id === session.activeBranchId ? session.messages.length : session.getBranchMessages(b.id)?.length ?? 0;
|
|
20478
20438
|
console.log(
|
|
20479
|
-
` ${
|
|
20439
|
+
` ${active2}${theme.accent(b.id.padEnd(10))} ${b.title.padEnd(20)} ` + theme.dim(`(${count} msgs)`) + parent
|
|
20480
20440
|
);
|
|
20481
20441
|
}
|
|
20482
20442
|
console.log();
|
|
@@ -20589,9 +20549,9 @@ function createReviewCommands() {
|
|
|
20589
20549
|
ctx.renderer.renderError(`Branch "${id}" not found.`);
|
|
20590
20550
|
return;
|
|
20591
20551
|
}
|
|
20592
|
-
const
|
|
20552
|
+
const active2 = session.activeBranchId;
|
|
20593
20553
|
console.log(`
|
|
20594
|
-
${theme.info("diff")} ${id} (source) \u2194 ${
|
|
20554
|
+
${theme.info("diff")} ${id} (source) \u2194 ${active2} (active)`);
|
|
20595
20555
|
console.log(theme.dim(` Common prefix: ${d.commonPrefix} message(s)`));
|
|
20596
20556
|
if (d.sourceOnly.length === 0 && d.targetOnly.length === 0) {
|
|
20597
20557
|
console.log(theme.success(" Branches are identical.\n"));
|
|
@@ -20610,7 +20570,7 @@ function createReviewCommands() {
|
|
|
20610
20570
|
}
|
|
20611
20571
|
if (d.targetOnly.length > 0) {
|
|
20612
20572
|
console.log(theme.dim(`
|
|
20613
|
-
Only in ${
|
|
20573
|
+
Only in ${active2} (active) (${d.targetOnly.length}):`));
|
|
20614
20574
|
d.targetOnly.forEach((m, i) => {
|
|
20615
20575
|
console.log(` ${theme.dim(`[${d.commonPrefix + i}]`)} ${m.role}: ${preview(m)}`);
|
|
20616
20576
|
});
|
|
@@ -20657,9 +20617,9 @@ function createReviewCommands() {
|
|
|
20657
20617
|
usage: "/init [--force]",
|
|
20658
20618
|
async execute(args, ctx) {
|
|
20659
20619
|
const cwd = process.cwd();
|
|
20660
|
-
const targetPath =
|
|
20620
|
+
const targetPath = join22(cwd, "AICLI.md");
|
|
20661
20621
|
const force = args.includes("--force");
|
|
20662
|
-
if (
|
|
20622
|
+
if (existsSync28(targetPath) && !force) {
|
|
20663
20623
|
ctx.renderer.printInfo(`AICLI.md already exists at ${targetPath}`);
|
|
20664
20624
|
ctx.renderer.printInfo("Use /init --force to overwrite, or edit it manually.");
|
|
20665
20625
|
return;
|
|
@@ -20723,7 +20683,7 @@ function createReviewCommands() {
|
|
|
20723
20683
|
usage: "/test [command|filter]",
|
|
20724
20684
|
async execute(args, ctx) {
|
|
20725
20685
|
try {
|
|
20726
|
-
const { executeTests } = await import("./run-tests-
|
|
20686
|
+
const { executeTests } = await import("./run-tests-KRNMFXAN.js");
|
|
20727
20687
|
const argStr = args.join(" ").trim();
|
|
20728
20688
|
let testArgs = {};
|
|
20729
20689
|
if (argStr) {
|
|
@@ -20758,9 +20718,9 @@ function createReviewCommands() {
|
|
|
20758
20718
|
let newFiles = 0;
|
|
20759
20719
|
let modifiedFiles = 0;
|
|
20760
20720
|
for (const [filePath, { earliest }] of fileMap) {
|
|
20761
|
-
const currentContent =
|
|
20721
|
+
const currentContent = existsSync28(filePath) ? (() => {
|
|
20762
20722
|
try {
|
|
20763
|
-
return
|
|
20723
|
+
return readFileSync20(filePath, "utf-8");
|
|
20764
20724
|
} catch {
|
|
20765
20725
|
return null;
|
|
20766
20726
|
}
|
|
@@ -20974,9 +20934,9 @@ function createMemoryCommands() {
|
|
|
20974
20934
|
async execute(args, ctx) {
|
|
20975
20935
|
const sub = (args[0] ?? "status").toLowerCase();
|
|
20976
20936
|
const root = process.cwd();
|
|
20977
|
-
const { loadIndex: loadIndex2, clearIndex } = await import("./store-
|
|
20978
|
-
const { indexProject: indexProject2 } = await import("./indexer-
|
|
20979
|
-
const { loadVectorStore, clearVectorStore } = await import("./vector-store-
|
|
20937
|
+
const { loadIndex: loadIndex2, clearIndex } = await import("./store-2N6PPPBP.js");
|
|
20938
|
+
const { indexProject: indexProject2 } = await import("./indexer-VRXDZ74A.js");
|
|
20939
|
+
const { loadVectorStore, clearVectorStore } = await import("./vector-store-T2DVUMY4.js");
|
|
20980
20940
|
if (sub === "status") {
|
|
20981
20941
|
const idx = loadIndex2(root);
|
|
20982
20942
|
const vec = loadVectorStore(root);
|
|
@@ -21036,7 +20996,7 @@ function createMemoryCommands() {
|
|
|
21036
20996
|
}
|
|
21037
20997
|
console.log(theme.dim(` Building semantic index for ${idx.symbolCount} symbols\u2026`));
|
|
21038
20998
|
console.log(theme.dim(" (First run downloads ~117 MB embedding model to ~/.aicli/models/)"));
|
|
21039
|
-
const { rebuildSemanticIndex } = await import("./semantic-
|
|
20999
|
+
const { rebuildSemanticIndex } = await import("./semantic-TRQAZSRY.js");
|
|
21040
21000
|
try {
|
|
21041
21001
|
const stats = await rebuildSemanticIndex(root, {
|
|
21042
21002
|
onProgress: (done, total) => {
|
|
@@ -21235,14 +21195,14 @@ ${view.chatHeading}
|
|
|
21235
21195
|
console.log(theme.heading("\n\u{1F4DD} Governed Persistent Memory:") + filterNote + "\n" + theme.dim("\u2500".repeat(72)));
|
|
21236
21196
|
for (const entry of entries) {
|
|
21237
21197
|
const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
|
|
21238
|
-
const
|
|
21198
|
+
const source2 = [
|
|
21239
21199
|
entry.source ?? "unknown",
|
|
21240
21200
|
entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : "",
|
|
21241
21201
|
entry.sourceProject ? `project ${entry.sourceProject}` : ""
|
|
21242
21202
|
].filter(Boolean).join(" \xB7 ");
|
|
21243
21203
|
console.log(theme.accent(` ${entry.id.slice(0, 8)} ${status} scope:${entry.scope} sensitivity:${entry.sensitivity}`));
|
|
21244
21204
|
const lastRef = entry.lastReferencedAt ? ` \xB7 last-referenced: ${entry.lastReferencedAt.slice(0, 10)}` : "";
|
|
21245
|
-
console.log(theme.dim(` source: ${
|
|
21205
|
+
console.log(theme.dim(` source: ${source2} \xB7 created: ${entry.createdAt.replace("T", " ").slice(0, 19)}${lastRef}${entry.expiresAt ? ` \xB7 expires: ${entry.expiresAt}` : ""}`));
|
|
21246
21206
|
console.log(" " + entry.content.replace(/\n/g, "\n "));
|
|
21247
21207
|
console.log();
|
|
21248
21208
|
}
|
|
@@ -21923,16 +21883,16 @@ function createSessionCommands() {
|
|
|
21923
21883
|
|
|
21924
21884
|
// src/repl/commands/misc-commands.ts
|
|
21925
21885
|
import { basename as basename9 } from "path";
|
|
21926
|
-
import { platform as
|
|
21886
|
+
import { platform as platform8 } from "os";
|
|
21927
21887
|
|
|
21928
21888
|
// src/repl/clipboard.ts
|
|
21929
21889
|
import { execSync as execSync3 } from "child_process";
|
|
21930
|
-
import { existsSync as
|
|
21890
|
+
import { existsSync as existsSync29, statSync as statSync15, unlinkSync as unlinkSync7 } from "fs";
|
|
21931
21891
|
import { tmpdir as tmpdir3 } from "os";
|
|
21932
|
-
import { join as
|
|
21892
|
+
import { join as join23 } from "path";
|
|
21933
21893
|
var CLIPBOARD_TIMEOUT = 5e3;
|
|
21934
21894
|
function readClipboardImage() {
|
|
21935
|
-
const outPath =
|
|
21895
|
+
const outPath = join23(tmpdir3(), `aicli-paste-${Date.now()}.png`);
|
|
21936
21896
|
try {
|
|
21937
21897
|
if (process.platform === "win32") {
|
|
21938
21898
|
_readWin32(outPath);
|
|
@@ -21945,7 +21905,7 @@ function readClipboardImage() {
|
|
|
21945
21905
|
_cleanup(outPath);
|
|
21946
21906
|
return null;
|
|
21947
21907
|
}
|
|
21948
|
-
if (!
|
|
21908
|
+
if (!existsSync29(outPath) || statSync15(outPath).size === 0) {
|
|
21949
21909
|
_cleanup(outPath);
|
|
21950
21910
|
return null;
|
|
21951
21911
|
}
|
|
@@ -21998,7 +21958,7 @@ function _readLinux(outPath) {
|
|
|
21998
21958
|
}
|
|
21999
21959
|
function _cleanup(path3) {
|
|
22000
21960
|
try {
|
|
22001
|
-
if (
|
|
21961
|
+
if (existsSync29(path3)) unlinkSync7(path3);
|
|
22002
21962
|
} catch {
|
|
22003
21963
|
}
|
|
22004
21964
|
}
|
|
@@ -22245,7 +22205,7 @@ No commands match "${filter}".
|
|
|
22245
22205
|
ctx.renderer.printSuccess(`Copied to clipboard (${lines} lines, ${chars} chars)`);
|
|
22246
22206
|
} catch (err) {
|
|
22247
22207
|
const msg = err instanceof Error ? err.message : String(err);
|
|
22248
|
-
if (
|
|
22208
|
+
if (platform8() === "linux" && msg.includes("not found")) {
|
|
22249
22209
|
ctx.renderer.renderError("Clipboard tool not found. Install xclip or xsel: sudo apt install xclip");
|
|
22250
22210
|
} else {
|
|
22251
22211
|
ctx.renderer.renderError(`Failed to copy: ${msg}`);
|
|
@@ -22840,7 +22800,18 @@ var SessionHandler = class _SessionHandler {
|
|
|
22840
22800
|
}))
|
|
22841
22801
|
});
|
|
22842
22802
|
}
|
|
22803
|
+
executionState = /* @__PURE__ */ new Map();
|
|
22804
|
+
connectionAbort = new AbortController();
|
|
22843
22805
|
async handleMessage(raw) {
|
|
22806
|
+
if (this.connectionAbort.signal.aborted && /"type"\s*:\s*"(?:chat|command|hub_start)"/.test(raw)) this.connectionAbort = new AbortController();
|
|
22807
|
+
return runWithExecutionContext({
|
|
22808
|
+
configDir: this.config.getConfigDir(),
|
|
22809
|
+
sessionKey: this._bashSessionKey,
|
|
22810
|
+
state: this.executionState,
|
|
22811
|
+
signal: this.connectionAbort.signal
|
|
22812
|
+
}, () => this.handleMessageScoped(raw));
|
|
22813
|
+
}
|
|
22814
|
+
async handleMessageScoped(raw) {
|
|
22844
22815
|
if (this.disconnected) return;
|
|
22845
22816
|
let msg;
|
|
22846
22817
|
try {
|
|
@@ -22851,6 +22822,7 @@ var SessionHandler = class _SessionHandler {
|
|
|
22851
22822
|
}
|
|
22852
22823
|
switch (msg.type) {
|
|
22853
22824
|
case "chat": {
|
|
22825
|
+
if (this.connectionAbort.signal.aborted) this.connectionAbort = new AbortController();
|
|
22854
22826
|
if (msg.messageId) {
|
|
22855
22827
|
if (typeof msg.messageId !== "string" || msg.messageId.length > 200) throw new Error("Invalid message ID");
|
|
22856
22828
|
const scope = this.config.getConfigDir();
|
|
@@ -22911,6 +22883,7 @@ var SessionHandler = class _SessionHandler {
|
|
|
22911
22883
|
return;
|
|
22912
22884
|
}
|
|
22913
22885
|
case "abort":
|
|
22886
|
+
this.connectionAbort.abort();
|
|
22914
22887
|
if (this.abortController) {
|
|
22915
22888
|
this.abortController.abort();
|
|
22916
22889
|
}
|
|
@@ -22934,8 +22907,16 @@ var SessionHandler = class _SessionHandler {
|
|
|
22934
22907
|
}
|
|
22935
22908
|
}
|
|
22936
22909
|
onDisconnect(closeSocket = true) {
|
|
22910
|
+
runWithExecutionContext({
|
|
22911
|
+
configDir: this.config.getConfigDir(),
|
|
22912
|
+
sessionKey: this._bashSessionKey,
|
|
22913
|
+
state: this.executionState
|
|
22914
|
+
}, () => this.disconnectScoped(closeSocket));
|
|
22915
|
+
}
|
|
22916
|
+
disconnectScoped(closeSocket) {
|
|
22937
22917
|
if (this.disconnected) return;
|
|
22938
22918
|
this.disconnected = true;
|
|
22919
|
+
this.connectionAbort.abort();
|
|
22939
22920
|
if (closeSocket && this.ws.readyState === this.ws.OPEN) this.ws.close(1001, "Connection closed; reconnect to restore session");
|
|
22940
22921
|
runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
|
|
22941
22922
|
this.toolExecutor.cancelAll();
|
|
@@ -23914,20 +23895,20 @@ ${summaryContent}`,
|
|
|
23914
23895
|
this.send({ type: "error", message: "Usage: /memory show --stale <days> (positive number)" });
|
|
23915
23896
|
return;
|
|
23916
23897
|
}
|
|
23917
|
-
const
|
|
23918
|
-
const { entries, applied } = filterMemoryEntries(
|
|
23898
|
+
const source2 = filters.orphaned ? listOrphanedProjectMemories(configDir) : listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
|
|
23899
|
+
const { entries, applied } = filterMemoryEntries(source2, filters);
|
|
23919
23900
|
const filterNote = applied.length > 0 ? ` (filtered: ${applied.join(", ")})` : "";
|
|
23920
23901
|
const content = entries.length === 0 ? applied.length > 0 ? `(no entries match [${applied.join(", ")}])` : "(empty \u2014 no persistent memory entries yet)" : `Governed persistent memory${filterNote}
|
|
23921
23902
|
|
|
23922
23903
|
` + entries.map((entry) => {
|
|
23923
23904
|
const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
|
|
23924
|
-
const
|
|
23905
|
+
const source3 = [
|
|
23925
23906
|
entry.source ?? "unknown",
|
|
23926
23907
|
entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : "",
|
|
23927
23908
|
entry.sourceProject ? `project ${entry.sourceProject}` : ""
|
|
23928
23909
|
].filter(Boolean).join(" \xB7 ");
|
|
23929
23910
|
return `## ${entry.id.slice(0, 8)} \xB7 ${status} \xB7 scope:${entry.scope} \xB7 sensitivity:${entry.sensitivity}
|
|
23930
|
-
Source: ${
|
|
23911
|
+
Source: ${source3}
|
|
23931
23912
|
${entry.content}`;
|
|
23932
23913
|
}).join("\n\n");
|
|
23933
23914
|
this.send({
|
|
@@ -24037,7 +24018,7 @@ Backup saved: ${result.backupPath}` : "";
|
|
|
24037
24018
|
return;
|
|
24038
24019
|
}
|
|
24039
24020
|
try {
|
|
24040
|
-
const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-
|
|
24021
|
+
const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
|
|
24041
24022
|
const loaded = loadChatIndex2();
|
|
24042
24023
|
if (!loaded || loaded.idx.chunks.length === 0) {
|
|
24043
24024
|
this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
|
|
@@ -24073,7 +24054,7 @@ Backup saved: ${result.backupPath}` : "";
|
|
|
24073
24054
|
}
|
|
24074
24055
|
async handleMemoryStatus() {
|
|
24075
24056
|
try {
|
|
24076
|
-
const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-
|
|
24057
|
+
const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-RLVLQRRC.js");
|
|
24077
24058
|
const s = getChatIndexStatus2();
|
|
24078
24059
|
this.send({
|
|
24079
24060
|
type: "memory_status",
|
|
@@ -24098,7 +24079,7 @@ Backup saved: ${result.backupPath}` : "";
|
|
|
24098
24079
|
type: "info",
|
|
24099
24080
|
message: full ? "\u{1F9E0} Rebuilding chat memory index (this may take a while on first run \u2014 ~117 MB embedder)." : "\u{1F9E0} Refreshing chat memory index (incremental)\u2026"
|
|
24100
24081
|
});
|
|
24101
|
-
const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-
|
|
24082
|
+
const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
|
|
24102
24083
|
const stats = await buildChatIndex2({
|
|
24103
24084
|
full,
|
|
24104
24085
|
onProgress: (p) => {
|
|
@@ -24256,9 +24237,9 @@ async function setupProxy(configProxy) {
|
|
|
24256
24237
|
}
|
|
24257
24238
|
|
|
24258
24239
|
// src/web/auth.ts
|
|
24259
|
-
import { existsSync as
|
|
24260
|
-
import { isAbsolute as isAbsolute3, join as
|
|
24261
|
-
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
24240
|
+
import { existsSync as existsSync30, readFileSync as readFileSync21, mkdirSync as mkdirSync16, readdirSync as readdirSync14, openSync, closeSync, unlinkSync as unlinkSync8 } from "fs";
|
|
24241
|
+
import { isAbsolute as isAbsolute3, join as join24, relative as relative5 } from "path";
|
|
24242
|
+
import { createHmac, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2, pbkdf2Sync, pbkdf2 } from "crypto";
|
|
24262
24243
|
var USERS_FILE = "users.json";
|
|
24263
24244
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
24264
24245
|
var TOKEN_EXPIRY_MS = TOKEN_EXPIRY_HOURS * 3600 * 1e3;
|
|
@@ -24270,14 +24251,16 @@ var AuthManager = class {
|
|
|
24270
24251
|
usersFile;
|
|
24271
24252
|
baseDir;
|
|
24272
24253
|
db;
|
|
24254
|
+
loadedRaw;
|
|
24273
24255
|
constructor(baseDir) {
|
|
24274
24256
|
this.baseDir = baseDir;
|
|
24275
|
-
this.usersFile =
|
|
24257
|
+
this.usersFile = join24(baseDir, USERS_FILE);
|
|
24276
24258
|
this.db = this.loadOrCreate();
|
|
24277
24259
|
}
|
|
24278
24260
|
// ── Public API ─────────────────────────────────────────────────
|
|
24279
24261
|
/** Check if any users exist (first-run detection) */
|
|
24280
24262
|
hasUsers() {
|
|
24263
|
+
this.refresh();
|
|
24281
24264
|
return this.db.users.length > 0;
|
|
24282
24265
|
}
|
|
24283
24266
|
/** Check if auth is enabled (has users.json with at least 1 user) */
|
|
@@ -24286,6 +24269,18 @@ var AuthManager = class {
|
|
|
24286
24269
|
}
|
|
24287
24270
|
/** Register a new user. Returns error string or null on success. */
|
|
24288
24271
|
register(username, password) {
|
|
24272
|
+
return this.registerPrepared(username, password);
|
|
24273
|
+
}
|
|
24274
|
+
async registerAsync(username, password) {
|
|
24275
|
+
if (typeof username !== "string" || typeof password !== "string" || password.length > 1024) return "Invalid username or password";
|
|
24276
|
+
if (!/^[a-z0-9_-]{2,32}$/i.test(username.trim()) || password.length < 4) return "Invalid username or password";
|
|
24277
|
+
const salt = randomBytes2(16).toString("hex");
|
|
24278
|
+
const hash = await hashPasswordAsync(password, salt);
|
|
24279
|
+
return this.registerPrepared(username, password, { salt, hash });
|
|
24280
|
+
}
|
|
24281
|
+
registerPrepared(username, password, prepared) {
|
|
24282
|
+
this.refresh();
|
|
24283
|
+
if (typeof username !== "string" || typeof password !== "string" || password.length > 1024) return "Invalid username or password";
|
|
24289
24284
|
username = username.trim().toLowerCase();
|
|
24290
24285
|
if (!username || username.length < 2 || username.length > 32) {
|
|
24291
24286
|
return "Username must be 2-32 characters";
|
|
@@ -24299,11 +24294,12 @@ var AuthManager = class {
|
|
|
24299
24294
|
if (this.db.users.find((u) => u.username === username)) {
|
|
24300
24295
|
return "Username already exists";
|
|
24301
24296
|
}
|
|
24302
|
-
const salt =
|
|
24303
|
-
const passwordHash = this.hashPassword(password, salt);
|
|
24304
|
-
const dataDir =
|
|
24305
|
-
const fullDataDir =
|
|
24306
|
-
mkdirSync16(
|
|
24297
|
+
const salt = prepared?.salt ?? randomBytes2(16).toString("hex");
|
|
24298
|
+
const passwordHash = prepared?.hash ?? this.hashPassword(password, salt);
|
|
24299
|
+
const dataDir = join24(USERS_DIR, username);
|
|
24300
|
+
const fullDataDir = join24(this.baseDir, dataDir);
|
|
24301
|
+
mkdirSync16(fullDataDir, { recursive: true, mode: 448 });
|
|
24302
|
+
mkdirSync16(join24(fullDataDir, "history"), { recursive: true, mode: 448 });
|
|
24307
24303
|
const user = {
|
|
24308
24304
|
username,
|
|
24309
24305
|
passwordHash,
|
|
@@ -24326,6 +24322,8 @@ var AuthManager = class {
|
|
|
24326
24322
|
* resets the counter.
|
|
24327
24323
|
*/
|
|
24328
24324
|
login(username, password) {
|
|
24325
|
+
this.refresh();
|
|
24326
|
+
if (!validCredentials(username, password)) return null;
|
|
24329
24327
|
username = username.trim().toLowerCase();
|
|
24330
24328
|
const lockState = this.getLockState(username);
|
|
24331
24329
|
if (lockState.locked) return null;
|
|
@@ -24338,30 +24336,62 @@ var AuthManager = class {
|
|
|
24338
24336
|
const hash = isLegacy ? this.hashPasswordLegacy(password, user.salt) : this.hashPassword(password, user.salt);
|
|
24339
24337
|
const a = Buffer.from(hash, "utf-8");
|
|
24340
24338
|
const b = Buffer.from(user.passwordHash, "utf-8");
|
|
24341
|
-
if (a.length !== b.length || !
|
|
24339
|
+
if (a.length !== b.length || !timingSafeEqual2(a, b)) {
|
|
24342
24340
|
this.recordFailedLogin(username);
|
|
24343
24341
|
return null;
|
|
24344
24342
|
}
|
|
24345
24343
|
if (isLegacy) {
|
|
24346
|
-
const newSalt =
|
|
24344
|
+
const newSalt = randomBytes2(16).toString("hex");
|
|
24347
24345
|
user.passwordHash = this.hashPassword(password, newSalt);
|
|
24348
24346
|
user.salt = newSalt;
|
|
24349
24347
|
user.hashVersion = 2;
|
|
24350
24348
|
this.save();
|
|
24351
24349
|
}
|
|
24352
|
-
loginAttempts.delete(username);
|
|
24350
|
+
loginAttempts.delete(this.attemptKey(username));
|
|
24353
24351
|
return this.createToken(username);
|
|
24354
24352
|
}
|
|
24355
24353
|
/**
|
|
24356
24354
|
* Returns current lockout state for a username and lazily expires it.
|
|
24357
24355
|
* Exposed (read-only) for tests and the `aicli user` CLI status output.
|
|
24358
24356
|
*/
|
|
24357
|
+
async loginAsync(username, password) {
|
|
24358
|
+
this.refresh();
|
|
24359
|
+
if (!validCredentials(username, password)) return null;
|
|
24360
|
+
username = username.trim().toLowerCase();
|
|
24361
|
+
if (this.getLockState(username).locked) return null;
|
|
24362
|
+
const user = this.db.users.find((u) => u.username === username);
|
|
24363
|
+
if (!user) {
|
|
24364
|
+
this.recordFailedLogin(username);
|
|
24365
|
+
return null;
|
|
24366
|
+
}
|
|
24367
|
+
const snapshot = JSON.stringify(user);
|
|
24368
|
+
const legacy = !user.hashVersion || user.hashVersion < 2;
|
|
24369
|
+
const hash = legacy ? this.hashPasswordLegacy(password, user.salt) : await hashPasswordAsync(password, user.salt);
|
|
24370
|
+
const salt = legacy ? randomBytes2(16).toString("hex") : user.salt;
|
|
24371
|
+
const upgraded = legacy ? await hashPasswordAsync(password, salt) : hash;
|
|
24372
|
+
this.refresh();
|
|
24373
|
+
const current = this.db.users.find((u) => u.username === username);
|
|
24374
|
+
if (!current || JSON.stringify(current) !== snapshot || this.getLockState(username).locked) return null;
|
|
24375
|
+
const a = Buffer.from(hash), b = Buffer.from(current.passwordHash);
|
|
24376
|
+
if (a.length !== b.length || !timingSafeEqual2(a, b)) {
|
|
24377
|
+
this.recordFailedLogin(username);
|
|
24378
|
+
return null;
|
|
24379
|
+
}
|
|
24380
|
+
if (legacy) {
|
|
24381
|
+
current.salt = salt;
|
|
24382
|
+
current.passwordHash = upgraded;
|
|
24383
|
+
current.hashVersion = 2;
|
|
24384
|
+
this.save();
|
|
24385
|
+
}
|
|
24386
|
+
loginAttempts.delete(this.attemptKey(username));
|
|
24387
|
+
return this.createToken(username);
|
|
24388
|
+
}
|
|
24359
24389
|
getLockState(username) {
|
|
24360
24390
|
username = username.trim().toLowerCase();
|
|
24361
|
-
const state2 = loginAttempts.get(username);
|
|
24391
|
+
const state2 = loginAttempts.get(this.attemptKey(username));
|
|
24362
24392
|
if (!state2) return { locked: false, remainingMs: 0, fails: 0 };
|
|
24363
|
-
if (
|
|
24364
|
-
loginAttempts.delete(username);
|
|
24393
|
+
if (Date.now() >= state2.expiresAt) {
|
|
24394
|
+
loginAttempts.delete(this.attemptKey(username));
|
|
24365
24395
|
return { locked: false, remainingMs: 0, fails: 0 };
|
|
24366
24396
|
}
|
|
24367
24397
|
if (state2.lockedUntil > 0) {
|
|
@@ -24369,8 +24399,15 @@ var AuthManager = class {
|
|
|
24369
24399
|
}
|
|
24370
24400
|
return { locked: false, remainingMs: 0, fails: state2.fails };
|
|
24371
24401
|
}
|
|
24402
|
+
attemptKey(username) {
|
|
24403
|
+
return this.usersFile + "\0" + username;
|
|
24404
|
+
}
|
|
24372
24405
|
recordFailedLogin(username) {
|
|
24373
|
-
|
|
24406
|
+
username = this.attemptKey(username);
|
|
24407
|
+
const now = Date.now();
|
|
24408
|
+
for (const [key, value] of loginAttempts) if (value.expiresAt <= now) loginAttempts.delete(key);
|
|
24409
|
+
if (!loginAttempts.has(username) && loginAttempts.size >= 1e4) loginAttempts.delete(loginAttempts.keys().next().value);
|
|
24410
|
+
const state2 = loginAttempts.get(username) ?? { fails: 0, lockedUntil: 0, expiresAt: now + LOGIN_LOCKOUT_MS };
|
|
24374
24411
|
state2.fails += 1;
|
|
24375
24412
|
if (state2.fails >= LOGIN_MAX_FAILS) {
|
|
24376
24413
|
state2.lockedUntil = Date.now() + LOGIN_LOCKOUT_MS;
|
|
@@ -24380,17 +24417,19 @@ var AuthManager = class {
|
|
|
24380
24417
|
/** Verify a token. Returns username or null. */
|
|
24381
24418
|
verifyToken(token) {
|
|
24382
24419
|
try {
|
|
24420
|
+
this.refresh();
|
|
24421
|
+
if (typeof token !== "string" || token.length > 4096) return null;
|
|
24383
24422
|
const parts = token.split(".");
|
|
24384
24423
|
if (parts.length !== 2) return null;
|
|
24385
24424
|
const [payloadB64, signature] = parts;
|
|
24386
24425
|
const expectedSig = this.sign(payloadB64);
|
|
24387
24426
|
const sigBuf = Buffer.from(signature ?? "", "utf-8");
|
|
24388
24427
|
const expBuf = Buffer.from(expectedSig, "utf-8");
|
|
24389
|
-
if (sigBuf.length !== expBuf.length || !
|
|
24428
|
+
if (sigBuf.length !== expBuf.length || !timingSafeEqual2(sigBuf, expBuf)) return null;
|
|
24390
24429
|
const payload = JSON.parse(
|
|
24391
24430
|
Buffer.from(payloadB64, "base64url").toString("utf-8")
|
|
24392
24431
|
);
|
|
24393
|
-
if (Date.now()
|
|
24432
|
+
if (!Number.isFinite(payload.exp) || Date.now() >= payload.exp) return null;
|
|
24394
24433
|
const user = this.db.users.find((u) => u.username === payload.username);
|
|
24395
24434
|
if (!user) return null;
|
|
24396
24435
|
if (user.tokensRevokedBefore && (!payload.iat || payload.iat < user.tokensRevokedBefore)) {
|
|
@@ -24407,6 +24446,7 @@ var AuthManager = class {
|
|
|
24407
24446
|
* v0.4.114). Returns true if the user existed.
|
|
24408
24447
|
*/
|
|
24409
24448
|
logoutAll(username) {
|
|
24449
|
+
this.refresh();
|
|
24410
24450
|
username = username.trim().toLowerCase();
|
|
24411
24451
|
const user = this.db.users.find((u) => u.username === username);
|
|
24412
24452
|
if (!user) return false;
|
|
@@ -24416,16 +24456,19 @@ var AuthManager = class {
|
|
|
24416
24456
|
}
|
|
24417
24457
|
/** Get user's data directory (absolute path) */
|
|
24418
24458
|
getUserDataDir(username) {
|
|
24459
|
+
this.refresh();
|
|
24419
24460
|
const user = this.db.users.find((u) => u.username === username);
|
|
24420
24461
|
if (!user) throw new Error(`User not found: ${username}`);
|
|
24421
|
-
return
|
|
24462
|
+
return join24(this.baseDir, user.dataDir);
|
|
24422
24463
|
}
|
|
24423
24464
|
/** List all usernames */
|
|
24424
24465
|
listUsers() {
|
|
24466
|
+
this.refresh();
|
|
24425
24467
|
return this.db.users.map((u) => u.username);
|
|
24426
24468
|
}
|
|
24427
24469
|
/** Delete a user. Returns error string or null on success. */
|
|
24428
24470
|
deleteUser(username) {
|
|
24471
|
+
this.refresh();
|
|
24429
24472
|
username = username.trim().toLowerCase();
|
|
24430
24473
|
const idx = this.db.users.findIndex((u) => u.username === username);
|
|
24431
24474
|
if (idx === -1) return `User '${username}' not found`;
|
|
@@ -24435,13 +24478,14 @@ var AuthManager = class {
|
|
|
24435
24478
|
}
|
|
24436
24479
|
/** Reset a user's password. Returns error string or null on success. */
|
|
24437
24480
|
resetPassword(username, newPassword) {
|
|
24481
|
+
this.refresh();
|
|
24438
24482
|
username = username.trim().toLowerCase();
|
|
24439
24483
|
const user = this.db.users.find((u) => u.username === username);
|
|
24440
24484
|
if (!user) return `User '${username}' not found`;
|
|
24441
|
-
if (
|
|
24485
|
+
if (typeof newPassword !== "string" || newPassword.length > 1024 || newPassword.length < 4) {
|
|
24442
24486
|
return "Password must be at least 4 characters";
|
|
24443
24487
|
}
|
|
24444
|
-
const salt =
|
|
24488
|
+
const salt = randomBytes2(16).toString("hex");
|
|
24445
24489
|
user.passwordHash = this.hashPassword(newPassword, salt);
|
|
24446
24490
|
user.salt = salt;
|
|
24447
24491
|
user.hashVersion = 2;
|
|
@@ -24454,30 +24498,30 @@ var AuthManager = class {
|
|
|
24454
24498
|
const err = this.register(username, password);
|
|
24455
24499
|
if (err) return err;
|
|
24456
24500
|
const userDir = this.getUserDataDir(username);
|
|
24457
|
-
const globalConfig =
|
|
24458
|
-
if (
|
|
24501
|
+
const globalConfig = join24(this.baseDir, "config.json");
|
|
24502
|
+
if (existsSync30(globalConfig)) {
|
|
24459
24503
|
try {
|
|
24460
|
-
const content =
|
|
24461
|
-
|
|
24504
|
+
const content = readFileSync21(globalConfig, "utf-8");
|
|
24505
|
+
atomicWriteFileSync(join24(userDir, "config.json"), content, { mode: 384 });
|
|
24462
24506
|
} catch {
|
|
24463
24507
|
}
|
|
24464
24508
|
}
|
|
24465
|
-
const globalMemory =
|
|
24466
|
-
if (
|
|
24509
|
+
const globalMemory = join24(this.baseDir, "memory.md");
|
|
24510
|
+
if (existsSync30(globalMemory)) {
|
|
24467
24511
|
try {
|
|
24468
|
-
const content =
|
|
24469
|
-
|
|
24512
|
+
const content = readFileSync21(globalMemory, "utf-8");
|
|
24513
|
+
atomicWriteFileSync(join24(userDir, "memory.md"), content, { mode: 384 });
|
|
24470
24514
|
} catch {
|
|
24471
24515
|
}
|
|
24472
24516
|
}
|
|
24473
|
-
const globalHistory =
|
|
24474
|
-
if (
|
|
24517
|
+
const globalHistory = join24(this.baseDir, "history");
|
|
24518
|
+
if (existsSync30(globalHistory)) {
|
|
24475
24519
|
try {
|
|
24476
24520
|
const files = readdirSync14(globalHistory).filter((f) => f.endsWith(".json"));
|
|
24477
|
-
const userHistory =
|
|
24521
|
+
const userHistory = join24(userDir, "history");
|
|
24478
24522
|
for (const f of files) {
|
|
24479
24523
|
try {
|
|
24480
|
-
|
|
24524
|
+
atomicWriteFileSync(join24(userHistory, f), readFileSync21(join24(globalHistory, f), "utf8"), { mode: 384 });
|
|
24481
24525
|
} catch {
|
|
24482
24526
|
}
|
|
24483
24527
|
}
|
|
@@ -24487,13 +24531,22 @@ var AuthManager = class {
|
|
|
24487
24531
|
return null;
|
|
24488
24532
|
}
|
|
24489
24533
|
// ── Private methods ────────────────────────────────────────────
|
|
24534
|
+
refresh() {
|
|
24535
|
+
const raw = readFileSync21(this.usersFile, "utf8");
|
|
24536
|
+
const parsed = JSON.parse(raw);
|
|
24537
|
+
if (!this.isValidUsersDB(parsed)) throw new Error("Authentication database is invalid; access refused");
|
|
24538
|
+
this.db = parsed;
|
|
24539
|
+
this.loadedRaw = raw;
|
|
24540
|
+
}
|
|
24490
24541
|
loadOrCreate() {
|
|
24491
|
-
if (
|
|
24542
|
+
if (existsSync30(this.usersFile)) {
|
|
24492
24543
|
try {
|
|
24493
|
-
const
|
|
24544
|
+
const raw = readFileSync21(this.usersFile, "utf-8");
|
|
24545
|
+
const parsed = JSON.parse(raw);
|
|
24494
24546
|
if (!this.isValidUsersDB(parsed)) {
|
|
24495
24547
|
throw new Error("database structure is invalid");
|
|
24496
24548
|
}
|
|
24549
|
+
this.loadedRaw = raw;
|
|
24497
24550
|
return parsed;
|
|
24498
24551
|
} catch (err) {
|
|
24499
24552
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -24505,7 +24558,7 @@ var AuthManager = class {
|
|
|
24505
24558
|
}
|
|
24506
24559
|
const db = {
|
|
24507
24560
|
version: 1,
|
|
24508
|
-
secret:
|
|
24561
|
+
secret: randomBytes2(32).toString("hex"),
|
|
24509
24562
|
users: []
|
|
24510
24563
|
};
|
|
24511
24564
|
this.saveDB(db);
|
|
@@ -24524,7 +24577,7 @@ var AuthManager = class {
|
|
|
24524
24577
|
if (typeof record.username !== "string" || !/^[a-z0-9_-]{2,32}$/.test(record.username) || usernames.has(record.username) || typeof record.passwordHash !== "string" || record.passwordHash.length === 0 || typeof record.salt !== "string" || record.salt.length === 0 || typeof record.createdAt !== "string" || typeof record.dataDir !== "string" || record.dataDir.length === 0 || isAbsolute3(record.dataDir)) {
|
|
24525
24578
|
return false;
|
|
24526
24579
|
}
|
|
24527
|
-
const dataDirFromBase = relative5(this.baseDir,
|
|
24580
|
+
const dataDirFromBase = relative5(this.baseDir, join24(this.baseDir, record.dataDir));
|
|
24528
24581
|
if (dataDirFromBase.startsWith("..") || isAbsolute3(dataDirFromBase)) return false;
|
|
24529
24582
|
if (record.hashVersion !== void 0 && record.hashVersion !== 1 && record.hashVersion !== 2) return false;
|
|
24530
24583
|
if (record.tokensRevokedBefore !== void 0 && !Number.isFinite(record.tokensRevokedBefore)) return false;
|
|
@@ -24536,8 +24589,19 @@ var AuthManager = class {
|
|
|
24536
24589
|
this.saveDB(this.db);
|
|
24537
24590
|
}
|
|
24538
24591
|
saveDB(db) {
|
|
24539
|
-
mkdirSync16(this.baseDir, { recursive: true });
|
|
24540
|
-
|
|
24592
|
+
mkdirSync16(this.baseDir, { recursive: true, mode: 448 });
|
|
24593
|
+
const lock = this.usersFile + ".lock";
|
|
24594
|
+
const fd = openSync(lock, "wx", 384);
|
|
24595
|
+
try {
|
|
24596
|
+
const current = existsSync30(this.usersFile) ? readFileSync21(this.usersFile, "utf8") : void 0;
|
|
24597
|
+
if (current !== this.loadedRaw) throw new Error("Authentication database changed concurrently; retry the operation");
|
|
24598
|
+
const raw = JSON.stringify(db, null, 2);
|
|
24599
|
+
atomicWriteFileSync(this.usersFile, raw, { mode: 384 });
|
|
24600
|
+
this.loadedRaw = raw;
|
|
24601
|
+
} finally {
|
|
24602
|
+
closeSync(fd);
|
|
24603
|
+
unlinkSync8(lock);
|
|
24604
|
+
}
|
|
24541
24605
|
}
|
|
24542
24606
|
/** Legacy hash — kept only for migrating old users (v0.2.x) */
|
|
24543
24607
|
hashPasswordLegacy(password, salt) {
|
|
@@ -24561,6 +24625,19 @@ var AuthManager = class {
|
|
|
24561
24625
|
return createHmac("sha256", this.db.secret).update(data).digest("base64url");
|
|
24562
24626
|
}
|
|
24563
24627
|
};
|
|
24628
|
+
function validCredentials(username, password) {
|
|
24629
|
+
return typeof username === "string" && /^[a-z0-9_-]{2,32}$/i.test(username.trim()) && typeof password === "string" && password.length > 0 && password.length <= 1024;
|
|
24630
|
+
}
|
|
24631
|
+
var activeHashes = 0;
|
|
24632
|
+
async function hashPasswordAsync(password, salt) {
|
|
24633
|
+
if (activeHashes >= 4) throw new Error("Authentication capacity reached; try again later");
|
|
24634
|
+
activeHashes++;
|
|
24635
|
+
try {
|
|
24636
|
+
return await new Promise((resolve13, reject) => pbkdf2(password, salt, 1e5, 64, "sha512", (error, hash) => error ? reject(error) : resolve13(hash.toString("hex"))));
|
|
24637
|
+
} finally {
|
|
24638
|
+
activeHashes--;
|
|
24639
|
+
}
|
|
24640
|
+
}
|
|
24564
24641
|
|
|
24565
24642
|
// src/web/server.ts
|
|
24566
24643
|
function getModuleDir() {
|
|
@@ -24580,6 +24657,10 @@ async function startWebServer(options = {}) {
|
|
|
24580
24657
|
console.log(`\u{1F916} ai-cli Web UI v${VERSION}`);
|
|
24581
24658
|
console.log(` Initializing...`);
|
|
24582
24659
|
const config = new ConfigManager();
|
|
24660
|
+
const authManager = new AuthManager(config.getConfigDir());
|
|
24661
|
+
if (!authManager.isEnabled() && !["localhost", "127.0.0.1", "::1"].includes(host)) {
|
|
24662
|
+
throw new Error("Network listening requires authentication. Create a user with aicli user create first.");
|
|
24663
|
+
}
|
|
24583
24664
|
await setupProxy(config.get("proxy"));
|
|
24584
24665
|
const providers = new ProviderRegistry();
|
|
24585
24666
|
const sessions = new SessionManager(config);
|
|
@@ -24613,7 +24694,7 @@ async function startWebServer(options = {}) {
|
|
|
24613
24694
|
const projectMcpResolved = mcpEnabled ? resolveProjectMcpPath() : null;
|
|
24614
24695
|
let projectMcpServers = {};
|
|
24615
24696
|
if (projectMcpResolved) {
|
|
24616
|
-
const { checkTrust } = await import("./project-trust-
|
|
24697
|
+
const { checkTrust } = await import("./project-trust-6CJB5ZMX.js");
|
|
24617
24698
|
const verdict = checkTrust(config.getConfigDir(), projectMcpResolved);
|
|
24618
24699
|
if (verdict.trusted) {
|
|
24619
24700
|
projectMcpServers = loadProjectMcpConfig() ?? {};
|
|
@@ -24640,8 +24721,8 @@ async function startWebServer(options = {}) {
|
|
|
24640
24721
|
}
|
|
24641
24722
|
}
|
|
24642
24723
|
let skillManager = null;
|
|
24643
|
-
const skillsDir =
|
|
24644
|
-
if (
|
|
24724
|
+
const skillsDir = join25(config.getConfigDir(), SKILLS_DIR_NAME);
|
|
24725
|
+
if (existsSync31(skillsDir)) {
|
|
24645
24726
|
skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
|
|
24646
24727
|
skillManager.loadSkills(pluginAssets.skillDirs);
|
|
24647
24728
|
const count = skillManager.listSkills().length;
|
|
@@ -24660,6 +24741,16 @@ async function startWebServer(options = {}) {
|
|
|
24660
24741
|
skillManager
|
|
24661
24742
|
};
|
|
24662
24743
|
const app = express();
|
|
24744
|
+
const access = new WebAccess(host, options.externalOrigin);
|
|
24745
|
+
const authLimits = new AuthRateLimiter();
|
|
24746
|
+
app.use((req, res, next) => {
|
|
24747
|
+
if (!access.accepts(req)) {
|
|
24748
|
+
res.status(403).json({ error: "Untrusted request origin or host" });
|
|
24749
|
+
return;
|
|
24750
|
+
}
|
|
24751
|
+
res.locals.externalSecure = !!options.externalOrigin?.startsWith("https://") && req.headers.host === new URL(options.externalOrigin).host;
|
|
24752
|
+
next();
|
|
24753
|
+
});
|
|
24663
24754
|
app.use((_req, res, next) => {
|
|
24664
24755
|
res.setHeader(
|
|
24665
24756
|
"Content-Security-Policy",
|
|
@@ -24688,19 +24779,23 @@ async function startWebServer(options = {}) {
|
|
|
24688
24779
|
const WS_MSG_RATE_PER_SEC = 30;
|
|
24689
24780
|
const WS_MSG_BURST = 60;
|
|
24690
24781
|
const WS_PREAUTH_TIMEOUT_MS = 3e4;
|
|
24691
|
-
const wss = new WebSocketServer({ server, maxPayload: WS_MAX_PAYLOAD })
|
|
24692
|
-
|
|
24782
|
+
const wss = new WebSocketServer({ server, maxPayload: WS_MAX_PAYLOAD, verifyClient: ({ req }) => {
|
|
24783
|
+
if (!access.accepts(req) || wss.clients.size >= 128) return false;
|
|
24784
|
+
if (req.headers.origin) return true;
|
|
24785
|
+
const cookie = parseCookie(req.headers.cookie ?? "");
|
|
24786
|
+
return equalToken(cookie.aicli_local, access.localToken) || !!authManager.verifyToken(cookie.aicli_token ?? "");
|
|
24787
|
+
} });
|
|
24693
24788
|
if (authManager.isEnabled()) {
|
|
24694
24789
|
console.log(` Auth: ${authManager.listUsers().length} user(s) registered`);
|
|
24695
24790
|
} else {
|
|
24696
|
-
console.log(
|
|
24697
|
-
if (host === "0.0.0.0" || host === "::") {
|
|
24698
|
-
console.log(" WARNING: Web UI is reachable from the network without authentication.");
|
|
24699
|
-
console.log(" Create a user first for LAN/team use: aicli user create <name>");
|
|
24700
|
-
}
|
|
24791
|
+
console.log(" Auth: local launch token (no users registered)");
|
|
24701
24792
|
}
|
|
24702
24793
|
const requireAuth = (req, res, next) => {
|
|
24703
|
-
if (!authManager.isEnabled())
|
|
24794
|
+
if (!authManager.isEnabled()) {
|
|
24795
|
+
if (equalToken(parseCookie(req.headers.cookie ?? "").aicli_local, access.localToken)) return next();
|
|
24796
|
+
res.status(401).json({ error: "Open the launch URL printed by aicli to authorize this browser" });
|
|
24797
|
+
return;
|
|
24798
|
+
}
|
|
24704
24799
|
const authHeader = req.headers.authorization;
|
|
24705
24800
|
const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
24706
24801
|
const cookieToken = parseCookie(req.headers.cookie ?? "")["aicli_token"];
|
|
@@ -24718,19 +24813,20 @@ async function startWebServer(options = {}) {
|
|
|
24718
24813
|
next();
|
|
24719
24814
|
};
|
|
24720
24815
|
const moduleDir = getModuleDir();
|
|
24721
|
-
let clientDir =
|
|
24722
|
-
if (!
|
|
24723
|
-
clientDir =
|
|
24816
|
+
let clientDir = join25(moduleDir, "web", "client");
|
|
24817
|
+
if (!existsSync31(clientDir)) {
|
|
24818
|
+
clientDir = join25(moduleDir, "client");
|
|
24724
24819
|
}
|
|
24725
|
-
if (!
|
|
24726
|
-
clientDir =
|
|
24820
|
+
if (!existsSync31(clientDir)) {
|
|
24821
|
+
clientDir = join25(moduleDir, "..", "..", "src", "web", "client");
|
|
24727
24822
|
}
|
|
24728
|
-
if (!
|
|
24729
|
-
clientDir =
|
|
24823
|
+
if (!existsSync31(clientDir)) {
|
|
24824
|
+
clientDir = join25(process.cwd(), "src", "web", "client");
|
|
24730
24825
|
}
|
|
24731
24826
|
console.log(` Static files: ${clientDir}`);
|
|
24732
24827
|
const PRECOMPRESSED_TYPES = {
|
|
24733
24828
|
".js": "text/javascript; charset=utf-8",
|
|
24829
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
24734
24830
|
".css": "text/css; charset=utf-8",
|
|
24735
24831
|
".html": "text/html; charset=utf-8",
|
|
24736
24832
|
".json": "application/json; charset=utf-8",
|
|
@@ -24745,14 +24841,14 @@ async function startWebServer(options = {}) {
|
|
|
24745
24841
|
const encoding = /\bbr\b/.test(accept) ? "br" : /\bgzip\b/.test(accept) ? "gzip" : null;
|
|
24746
24842
|
if (!encoding) return next();
|
|
24747
24843
|
const target = resolvePath(clientDir, `.${req.path}`) + (encoding === "br" ? ".br" : ".gz");
|
|
24748
|
-
if (!target.startsWith(clientDir) || !
|
|
24844
|
+
if (!target.startsWith(clientDir) || !existsSync31(target)) return next();
|
|
24749
24845
|
res.setHeader("Content-Type", contentType);
|
|
24750
24846
|
res.setHeader("Content-Encoding", encoding);
|
|
24751
24847
|
res.setHeader("Vary", "Accept-Encoding");
|
|
24752
24848
|
if (req.path.startsWith("/vendor/")) res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
24753
24849
|
createReadStream(target).pipe(res);
|
|
24754
24850
|
});
|
|
24755
|
-
app.use("/vendor", express.static(
|
|
24851
|
+
app.use("/vendor", express.static(join25(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
|
|
24756
24852
|
app.use(express.static(clientDir));
|
|
24757
24853
|
app.get("/api/status", (_req, res) => {
|
|
24758
24854
|
res.json({
|
|
@@ -24771,9 +24867,28 @@ async function startWebServer(options = {}) {
|
|
|
24771
24867
|
}
|
|
24772
24868
|
});
|
|
24773
24869
|
});
|
|
24774
|
-
app.use(express.json());
|
|
24775
|
-
app.post("/api/auth/
|
|
24870
|
+
app.use(express.json({ limit: "16kb" }));
|
|
24871
|
+
app.post("/api/auth/bootstrap", (req, res) => {
|
|
24872
|
+
if (!equalToken(req.body?.token, access.localToken)) {
|
|
24873
|
+
res.status(403).json({ error: "Invalid launch token" });
|
|
24874
|
+
return;
|
|
24875
|
+
}
|
|
24876
|
+
res.cookie("aicli_local", access.localToken, { httpOnly: true, sameSite: "strict", secure: isHttpsRequest(req) });
|
|
24877
|
+
res.json({ success: true });
|
|
24878
|
+
});
|
|
24879
|
+
app.use("/api/auth", (req, res, next) => {
|
|
24880
|
+
if (!authLimits.take(req.socket.remoteAddress ?? "unknown")) {
|
|
24881
|
+
res.status(429).json({ error: "Too many authentication attempts; try again later" });
|
|
24882
|
+
return;
|
|
24883
|
+
}
|
|
24884
|
+
next();
|
|
24885
|
+
});
|
|
24886
|
+
app.post("/api/auth/register", async (req, res) => {
|
|
24776
24887
|
const firstRun = !authManager.hasUsers();
|
|
24888
|
+
if (firstRun && !equalToken(parseCookie(req.headers.cookie ?? "").aicli_local, access.localToken)) {
|
|
24889
|
+
res.status(403).json({ error: "Open the launch URL before enabling authentication" });
|
|
24890
|
+
return;
|
|
24891
|
+
}
|
|
24777
24892
|
if (!firstRun) {
|
|
24778
24893
|
const authHeader = req.headers.authorization;
|
|
24779
24894
|
const token2 = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
@@ -24789,23 +24904,24 @@ async function startWebServer(options = {}) {
|
|
|
24789
24904
|
res.status(400).json({ error: "Username and password required" });
|
|
24790
24905
|
return;
|
|
24791
24906
|
}
|
|
24792
|
-
const err = authManager.
|
|
24907
|
+
const err = await authManager.registerAsync(username, password);
|
|
24793
24908
|
if (err) {
|
|
24794
24909
|
res.status(400).json({ error: err });
|
|
24795
24910
|
return;
|
|
24796
24911
|
}
|
|
24797
|
-
const token = authManager.
|
|
24912
|
+
const token = await authManager.loginAsync(username, password);
|
|
24913
|
+
invalidateConnections();
|
|
24798
24914
|
console.log(` \u2713 User registered via API: ${username}${firstRun ? " (first-run)" : ""}`);
|
|
24799
24915
|
res.cookie("aicli_token", token, authCookieOptions(req));
|
|
24800
24916
|
res.json({ success: true, username });
|
|
24801
24917
|
});
|
|
24802
|
-
app.post("/api/auth/login", (req, res) => {
|
|
24918
|
+
app.post("/api/auth/login", async (req, res) => {
|
|
24803
24919
|
const { username, password } = req.body ?? {};
|
|
24804
24920
|
if (!username || !password) {
|
|
24805
24921
|
res.status(400).json({ error: "Username and password required" });
|
|
24806
24922
|
return;
|
|
24807
24923
|
}
|
|
24808
|
-
const token = authManager.
|
|
24924
|
+
const token = await authManager.loginAsync(username, password);
|
|
24809
24925
|
if (!token) {
|
|
24810
24926
|
res.status(401).json({ error: "Invalid username or password" });
|
|
24811
24927
|
return;
|
|
@@ -24814,13 +24930,16 @@ async function startWebServer(options = {}) {
|
|
|
24814
24930
|
res.json({ success: true, username });
|
|
24815
24931
|
});
|
|
24816
24932
|
app.post("/api/auth/logout", (req, res) => {
|
|
24933
|
+
const user = authManager.verifyToken(parseCookie(req.headers.cookie ?? "").aicli_token ?? "");
|
|
24934
|
+
if (user) authManager.logoutAll(user);
|
|
24935
|
+
invalidateConnections();
|
|
24817
24936
|
res.clearCookie("aicli_token", clearAuthCookieOptions(req));
|
|
24818
24937
|
res.json({ success: true });
|
|
24819
24938
|
});
|
|
24820
24939
|
app.get("/api/files", requireAuth, (req, res) => {
|
|
24821
24940
|
const cwd = process.cwd();
|
|
24822
24941
|
const prefix = req.query.prefix || "";
|
|
24823
|
-
const targetDir =
|
|
24942
|
+
const targetDir = join25(cwd, prefix);
|
|
24824
24943
|
try {
|
|
24825
24944
|
const canonicalTarget = realpathSync3(resolve12(targetDir));
|
|
24826
24945
|
const canonicalCwd = realpathSync3(resolve12(cwd));
|
|
@@ -24837,7 +24956,7 @@ async function startWebServer(options = {}) {
|
|
|
24837
24956
|
const entries = readdirSync15(targetDir, { withFileTypes: true });
|
|
24838
24957
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
24839
24958
|
name: e.name,
|
|
24840
|
-
path: relative6(cwd,
|
|
24959
|
+
path: relative6(cwd, join25(targetDir, e.name)).replace(/\\/g, "/"),
|
|
24841
24960
|
isDir: e.isDirectory()
|
|
24842
24961
|
}));
|
|
24843
24962
|
res.json({ files });
|
|
@@ -24877,8 +24996,8 @@ async function startWebServer(options = {}) {
|
|
|
24877
24996
|
try {
|
|
24878
24997
|
const authUser = req._authUser;
|
|
24879
24998
|
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
24880
|
-
const filePath =
|
|
24881
|
-
if (!
|
|
24999
|
+
const filePath = join25(histDir, `${id}.json`);
|
|
25000
|
+
if (!existsSync31(filePath)) {
|
|
24882
25001
|
res.status(404).json({ error: "Session not found" });
|
|
24883
25002
|
return;
|
|
24884
25003
|
}
|
|
@@ -24893,7 +25012,7 @@ async function startWebServer(options = {}) {
|
|
|
24893
25012
|
res.status(404).json({ error: "Session not found" });
|
|
24894
25013
|
return;
|
|
24895
25014
|
}
|
|
24896
|
-
const data = JSON.parse(
|
|
25015
|
+
const data = JSON.parse(readFileSync22(filePath, "utf-8"));
|
|
24897
25016
|
res.json({ session: data });
|
|
24898
25017
|
} catch (err) {
|
|
24899
25018
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -24906,9 +25025,10 @@ async function startWebServer(options = {}) {
|
|
|
24906
25025
|
return;
|
|
24907
25026
|
}
|
|
24908
25027
|
const cwd = process.cwd();
|
|
24909
|
-
const fullPath = resolve12(
|
|
25028
|
+
const fullPath = resolve12(join25(cwd, filePath));
|
|
24910
25029
|
try {
|
|
24911
25030
|
const canonicalFull = realpathSync3(fullPath);
|
|
25031
|
+
assertReadablePath(canonicalFull);
|
|
24912
25032
|
const canonicalCwd = realpathSync3(resolve12(cwd));
|
|
24913
25033
|
if (!canonicalFull.startsWith(canonicalCwd + sep5) && canonicalFull !== canonicalCwd) {
|
|
24914
25034
|
res.json({ error: "Access denied" });
|
|
@@ -24924,7 +25044,7 @@ async function startWebServer(options = {}) {
|
|
|
24924
25044
|
res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
|
|
24925
25045
|
return;
|
|
24926
25046
|
}
|
|
24927
|
-
const content =
|
|
25047
|
+
const content = readFileSync22(fullPath, "utf-8");
|
|
24928
25048
|
res.json({ content, size: stat.size });
|
|
24929
25049
|
} catch {
|
|
24930
25050
|
res.json({ error: "Cannot read file" });
|
|
@@ -24936,6 +25056,9 @@ async function startWebServer(options = {}) {
|
|
|
24936
25056
|
if (cached) return cached;
|
|
24937
25057
|
const userDataDir = authManager.getUserDataDir(username);
|
|
24938
25058
|
const userConfig = new ConfigManager(userDataDir);
|
|
25059
|
+
for (const key of ["defaultPermissionProfile", "allowedPermissionProfiles", "permissionProfiles", "permissionRules", "defaultPermission", "networkPolicy"]) {
|
|
25060
|
+
userConfig.setTransient(key, structuredClone(config.get(key)));
|
|
25061
|
+
}
|
|
24939
25062
|
if (userConfig.isFirstRun()) {
|
|
24940
25063
|
userConfig.setTransient("defaultProvider", config.get("defaultProvider"));
|
|
24941
25064
|
userConfig.setTransient("defaultModels", config.get("defaultModels"));
|
|
@@ -24946,7 +25069,7 @@ async function startWebServer(options = {}) {
|
|
|
24946
25069
|
// 每用户各自的 config 目录 → 各自的 cost-history.json
|
|
24947
25070
|
costTracker: new CostTracker(userConfig.getConfigDir()),
|
|
24948
25071
|
providers,
|
|
24949
|
-
//
|
|
25072
|
+
// Host-owned service credentials; shared explicitly, never per-user keys.
|
|
24950
25073
|
sessions: userSessions,
|
|
24951
25074
|
toolRegistry,
|
|
24952
25075
|
// Shared
|
|
@@ -24959,6 +25082,21 @@ async function startWebServer(options = {}) {
|
|
|
24959
25082
|
return userShared;
|
|
24960
25083
|
}
|
|
24961
25084
|
const handlers = /* @__PURE__ */ new Map();
|
|
25085
|
+
const validators = /* @__PURE__ */ new Set();
|
|
25086
|
+
function invalidateConnections() {
|
|
25087
|
+
for (const validate of validators) validate();
|
|
25088
|
+
}
|
|
25089
|
+
let authRefreshTimer;
|
|
25090
|
+
server.on("listening", () => {
|
|
25091
|
+
if (!authRefreshTimer) {
|
|
25092
|
+
authRefreshTimer = setInterval(invalidateConnections, 1e3);
|
|
25093
|
+
authRefreshTimer.unref();
|
|
25094
|
+
}
|
|
25095
|
+
});
|
|
25096
|
+
server.on("close", () => {
|
|
25097
|
+
clearInterval(authRefreshTimer);
|
|
25098
|
+
authRefreshTimer = void 0;
|
|
25099
|
+
});
|
|
24962
25100
|
wss.on("connection", (ws, req) => {
|
|
24963
25101
|
const urlStr = req.url ?? "";
|
|
24964
25102
|
const qMark = urlStr.indexOf("?");
|
|
@@ -24966,6 +25104,8 @@ async function startWebServer(options = {}) {
|
|
|
24966
25104
|
const tabId = params.get("tabId") || `tab-${Date.now()}`;
|
|
24967
25105
|
const cookies = parseCookie(req.headers.cookie ?? "");
|
|
24968
25106
|
const token = cookies["aicli_token"] || "";
|
|
25107
|
+
let activeToken = token;
|
|
25108
|
+
const localAuthorized = equalToken(cookies.aicli_local, access.localToken);
|
|
24969
25109
|
let tokens = WS_MSG_BURST;
|
|
24970
25110
|
let lastRefill = Date.now();
|
|
24971
25111
|
const takeToken = () => {
|
|
@@ -25025,7 +25165,7 @@ async function startWebServer(options = {}) {
|
|
|
25025
25165
|
authenticatedUser = authManager.verifyToken(token);
|
|
25026
25166
|
}
|
|
25027
25167
|
const authRequired = authManager.isEnabled();
|
|
25028
|
-
if (!authRequired) {
|
|
25168
|
+
if (!authRequired && localAuthorized) {
|
|
25029
25169
|
console.log(` \u2713 Tab connected: ${tabId.slice(0, 12)} (no auth) (total: ${handlers.size + 1})`);
|
|
25030
25170
|
handler = attachHandler(shared);
|
|
25031
25171
|
clearPreAuthTimer();
|
|
@@ -25043,7 +25183,26 @@ async function startWebServer(options = {}) {
|
|
|
25043
25183
|
}));
|
|
25044
25184
|
}
|
|
25045
25185
|
}
|
|
25186
|
+
const validate = () => {
|
|
25187
|
+
if (!handler) return;
|
|
25188
|
+
let valid = false;
|
|
25189
|
+
try {
|
|
25190
|
+
valid = authManager.isEnabled() ? !!activeToken && authManager.verifyToken(activeToken) === authenticatedUser : localAuthorized && !authenticatedUser;
|
|
25191
|
+
} catch {
|
|
25192
|
+
}
|
|
25193
|
+
if (!valid) {
|
|
25194
|
+
handler.onDisconnect(false);
|
|
25195
|
+
if (handlers.get(handlerKey) === handler) handlers.delete(handlerKey);
|
|
25196
|
+
handler = null;
|
|
25197
|
+
ws.close(1008, "Authentication changed; reconnect");
|
|
25198
|
+
}
|
|
25199
|
+
};
|
|
25200
|
+
validators.add(validate);
|
|
25201
|
+
ws.on("close", () => validators.delete(validate));
|
|
25202
|
+
let authBusy = false;
|
|
25046
25203
|
ws.on("message", async (data) => {
|
|
25204
|
+
validate();
|
|
25205
|
+
if (ws.readyState !== ws.OPEN) return;
|
|
25047
25206
|
if (!takeToken()) {
|
|
25048
25207
|
if (ws.readyState === ws.OPEN) {
|
|
25049
25208
|
try {
|
|
@@ -25063,57 +25222,73 @@ async function startWebServer(options = {}) {
|
|
|
25063
25222
|
return;
|
|
25064
25223
|
}
|
|
25065
25224
|
if (parsed.type === "auth") {
|
|
25066
|
-
|
|
25067
|
-
|
|
25068
|
-
|
|
25069
|
-
|
|
25225
|
+
if (authBusy) return;
|
|
25226
|
+
authBusy = true;
|
|
25227
|
+
try {
|
|
25228
|
+
if (!authLimits.take(req.socket.remoteAddress ?? "unknown")) {
|
|
25229
|
+
ws.close(1008, "Authentication rate limit");
|
|
25070
25230
|
return;
|
|
25071
25231
|
}
|
|
25072
|
-
const
|
|
25073
|
-
|
|
25074
|
-
|
|
25232
|
+
const { action, password } = parsed;
|
|
25233
|
+
const username = typeof parsed.username === "string" ? parsed.username.trim().toLowerCase() : "";
|
|
25234
|
+
if (action === "register") {
|
|
25235
|
+
if (!authenticatedUser && (authManager.hasUsers() || !localAuthorized)) {
|
|
25236
|
+
ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Registration is closed. Use `aicli user create` on the host to add users." }));
|
|
25237
|
+
return;
|
|
25238
|
+
}
|
|
25239
|
+
const err = await authManager.registerAsync(username, password);
|
|
25240
|
+
if (err) {
|
|
25241
|
+
ws.send(JSON.stringify({ type: "auth_result", success: false, error: err }));
|
|
25242
|
+
return;
|
|
25243
|
+
}
|
|
25244
|
+
activeToken = await authManager.loginAsync(username, password) ?? "";
|
|
25245
|
+
if (ws.readyState !== ws.OPEN || authManager.verifyToken(activeToken) !== username) return;
|
|
25246
|
+
authenticatedUser = username;
|
|
25247
|
+
const userShared = getUserShared(username);
|
|
25248
|
+
handler = attachHandler(userShared);
|
|
25249
|
+
clearPreAuthTimer();
|
|
25250
|
+
invalidateConnections();
|
|
25251
|
+
console.log(` \u2713 User registered & connected: ${username} (tab: ${tabId.slice(0, 12)})`);
|
|
25252
|
+
ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
|
|
25075
25253
|
return;
|
|
25076
25254
|
}
|
|
25077
|
-
|
|
25078
|
-
|
|
25079
|
-
|
|
25080
|
-
|
|
25081
|
-
|
|
25082
|
-
|
|
25083
|
-
|
|
25084
|
-
|
|
25085
|
-
|
|
25086
|
-
|
|
25087
|
-
|
|
25088
|
-
|
|
25089
|
-
|
|
25090
|
-
ws.send(JSON.stringify({ type: "auth_result", success:
|
|
25255
|
+
if (action === "token") {
|
|
25256
|
+
const { token: clientToken } = parsed;
|
|
25257
|
+
const verifiedUser = authManager.verifyToken(clientToken);
|
|
25258
|
+
if (!verifiedUser) {
|
|
25259
|
+
ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Invalid or expired token" }));
|
|
25260
|
+
return;
|
|
25261
|
+
}
|
|
25262
|
+
activeToken = clientToken;
|
|
25263
|
+
authenticatedUser = verifiedUser;
|
|
25264
|
+
const userShared = getUserShared(verifiedUser);
|
|
25265
|
+
handler = attachHandler(userShared);
|
|
25266
|
+
clearPreAuthTimer();
|
|
25267
|
+
console.log(` \u2713 Token auth: ${verifiedUser} (tab: ${tabId.slice(0, 12)})`);
|
|
25268
|
+
ws.send(JSON.stringify({ type: "auth_result", success: true, username: verifiedUser }));
|
|
25091
25269
|
return;
|
|
25092
25270
|
}
|
|
25093
|
-
|
|
25094
|
-
|
|
25095
|
-
|
|
25096
|
-
|
|
25097
|
-
|
|
25098
|
-
|
|
25099
|
-
|
|
25100
|
-
|
|
25101
|
-
|
|
25102
|
-
|
|
25103
|
-
|
|
25104
|
-
|
|
25271
|
+
if (action === "login") {
|
|
25272
|
+
const loginToken = await authManager.loginAsync(username, password);
|
|
25273
|
+
if (!loginToken) {
|
|
25274
|
+
ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Invalid username or password" }));
|
|
25275
|
+
return;
|
|
25276
|
+
}
|
|
25277
|
+
if (ws.readyState !== ws.OPEN || authManager.verifyToken(loginToken) !== username) return;
|
|
25278
|
+
activeToken = loginToken;
|
|
25279
|
+
authenticatedUser = username;
|
|
25280
|
+
const userShared = getUserShared(username);
|
|
25281
|
+
handler = attachHandler(userShared);
|
|
25282
|
+
clearPreAuthTimer();
|
|
25283
|
+
console.log(` \u2713 User logged in: ${username} (tab: ${tabId.slice(0, 12)})`);
|
|
25284
|
+
ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
|
|
25105
25285
|
return;
|
|
25106
25286
|
}
|
|
25107
|
-
|
|
25108
|
-
const userShared = getUserShared(username);
|
|
25109
|
-
handler = attachHandler(userShared);
|
|
25110
|
-
clearPreAuthTimer();
|
|
25111
|
-
console.log(` \u2713 User logged in: ${username} (tab: ${tabId.slice(0, 12)})`);
|
|
25112
|
-
ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
|
|
25287
|
+
ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
|
|
25113
25288
|
return;
|
|
25289
|
+
} finally {
|
|
25290
|
+
authBusy = false;
|
|
25114
25291
|
}
|
|
25115
|
-
ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
|
|
25116
|
-
return;
|
|
25117
25292
|
}
|
|
25118
25293
|
if (!handler) {
|
|
25119
25294
|
if (ws.readyState === ws.OPEN) {
|
|
@@ -25159,13 +25334,15 @@ async function startWebServer(options = {}) {
|
|
|
25159
25334
|
}
|
|
25160
25335
|
});
|
|
25161
25336
|
server.listen(actualPort, host, () => {
|
|
25162
|
-
|
|
25337
|
+
actualPort = server.address().port;
|
|
25338
|
+
const address = host.includes(":") ? `[${host}]` : host === "0.0.0.0" ? "localhost" : host;
|
|
25339
|
+
const url = `http://${address}:${actualPort}` + (!authManager.isEnabled() ? `#token=${access.localToken}` : "");
|
|
25163
25340
|
console.log(`
|
|
25164
25341
|
\u{1F310} Web UI ready: ${url}`);
|
|
25165
25342
|
if (actualPort !== port) console.log(` (port ${port} was in use, using ${actualPort})`);
|
|
25166
25343
|
if (host === "0.0.0.0" || host === "::") {
|
|
25167
25344
|
try {
|
|
25168
|
-
const nets =
|
|
25345
|
+
const nets = networkInterfaces2();
|
|
25169
25346
|
for (const name of Object.keys(nets)) {
|
|
25170
25347
|
for (const net2 of nets[name] ?? []) {
|
|
25171
25348
|
if (net2.family === "IPv4" && !net2.internal) {
|
|
@@ -25198,27 +25375,24 @@ function resolveProjectMcpPath() {
|
|
|
25198
25375
|
const cwd = process.cwd();
|
|
25199
25376
|
const gitRoot = getGitRoot(cwd);
|
|
25200
25377
|
const projectRoot = gitRoot ?? cwd;
|
|
25201
|
-
const configPath =
|
|
25202
|
-
return
|
|
25378
|
+
const configPath = join25(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
25379
|
+
return existsSync31(configPath) ? configPath : null;
|
|
25203
25380
|
}
|
|
25204
25381
|
function loadProjectMcpConfig() {
|
|
25205
25382
|
const cwd = process.cwd();
|
|
25206
25383
|
const gitRoot = getGitRoot(cwd);
|
|
25207
25384
|
const projectRoot = gitRoot ?? cwd;
|
|
25208
|
-
const configPath =
|
|
25209
|
-
if (!
|
|
25385
|
+
const configPath = join25(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
25386
|
+
if (!existsSync31(configPath)) return null;
|
|
25210
25387
|
try {
|
|
25211
|
-
const raw = JSON.parse(
|
|
25388
|
+
const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
|
|
25212
25389
|
return raw.mcpServers ?? raw;
|
|
25213
25390
|
} catch {
|
|
25214
25391
|
return null;
|
|
25215
25392
|
}
|
|
25216
25393
|
}
|
|
25217
25394
|
function isHttpsRequest(req) {
|
|
25218
|
-
|
|
25219
|
-
const forwardedProto = req.headers["x-forwarded-proto"];
|
|
25220
|
-
const proto = Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto;
|
|
25221
|
-
return proto?.split(",")[0]?.trim().toLowerCase() === "https";
|
|
25395
|
+
return req.secure || req.res?.locals.externalSecure === true;
|
|
25222
25396
|
}
|
|
25223
25397
|
function authCookieOptions(req) {
|
|
25224
25398
|
return {
|
|
@@ -25245,13 +25419,13 @@ function parseCookie(cookie) {
|
|
|
25245
25419
|
}
|
|
25246
25420
|
async function openBrowser(url) {
|
|
25247
25421
|
try {
|
|
25248
|
-
const { spawn:
|
|
25422
|
+
const { spawn: spawn6 } = await import("child_process");
|
|
25249
25423
|
if (process.platform === "win32") {
|
|
25250
|
-
|
|
25424
|
+
spawn6("cmd", ["/c", "start", '""', url], { detached: true, stdio: "ignore" }).unref();
|
|
25251
25425
|
} else if (process.platform === "darwin") {
|
|
25252
|
-
|
|
25426
|
+
spawn6("open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
25253
25427
|
} else {
|
|
25254
|
-
|
|
25428
|
+
spawn6("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
25255
25429
|
}
|
|
25256
25430
|
} catch {
|
|
25257
25431
|
}
|