jinzd-ai-cli 0.4.307 → 0.4.308
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/THIRD_PARTY_NOTICES.md +3 -0
- package/dist/{agent-client-6GX6QQDU.js → agent-client-TIEAYZXV.js} +4 -1
- package/dist/{auth-PE3Z5OHS.js → auth-QTQNZ6IX.js} +2 -2
- package/dist/{batch-HMADXNOD.js → batch-S7XLEEOH.js} +3 -3
- package/dist/{chat-index-2D5GK2DB.js → chat-index-7CLR4CBG.js} +4 -3
- package/dist/{chat-index-DGUOUUQW.js → chat-index-RLVLQRRC.js} +3 -2
- package/dist/{chunk-SJFZPS65.js → chunk-2DYQDLLF.js} +1 -1
- package/dist/{chunk-VAV2IJ3R.js → chunk-2MNUMLN4.js} +1 -1
- package/dist/chunk-2XMUCC42.js +67 -0
- package/dist/{chunk-5GQL4TSZ.js → chunk-3537CQ5M.js} +2 -2
- package/dist/{chunk-OAXLMF3U.js → chunk-3YVNDYOY.js} +1 -1
- package/dist/{chunk-RYQBBEMM.js → chunk-4PR5AVJ5.js} +115 -19
- package/dist/{chunk-GHWZVPUV.js → chunk-6JHACXB3.js} +7 -5
- package/dist/{chunk-CIM2PV2F.js → chunk-7OHXFTZO.js} +2 -2
- package/dist/{chunk-IUVDZVZ4.js → chunk-AXTLYOA7.js} +24 -17
- package/dist/{chunk-XJOK5353.js → chunk-B3MPYI4E.js} +2 -2
- package/dist/{chunk-3CKJOTVM.js → chunk-BGZN4K7Q.js} +4 -4
- package/dist/chunk-BMLQPSHP.js +69 -0
- package/dist/{chunk-NRSYBKWA.js → chunk-C7CKDFJS.js} +1 -1
- package/dist/{chunk-NQJSPMY2.js → chunk-E2PNXNKI.js} +4 -4
- package/dist/chunk-FVXNEZEW.js +161 -0
- package/dist/{chunk-YKJO3SI4.js → chunk-G6DVGQ4I.js} +1 -1
- package/dist/{chunk-YFDQIUQ3.js → chunk-HC33C4U3.js} +18 -18
- package/dist/chunk-JJFJWD35.js +162 -0
- package/dist/{chunk-KRCYR46A.js → chunk-JJNUASMJ.js} +264 -55
- package/dist/{chunk-NYNSYLFY.js → chunk-JL44HGZC.js} +1 -1
- package/dist/{chunk-NUJTNH76.js → chunk-LBWKMTT3.js} +1 -1
- package/dist/{chunk-AHL6KOGM.js → chunk-LR3TGZWQ.js} +2 -2
- package/dist/{chunk-WQ3OMEC4.js → chunk-M6HLYG33.js} +1 -1
- package/dist/{chunk-I4GQRMFV.js → chunk-M6YV5KSS.js} +6 -4
- package/dist/{chunk-3TBXHLSC.js → chunk-NLK45GUS.js} +1 -1
- package/dist/{chunk-KBDKKHXB.js → chunk-O6CGOVD4.js} +2 -2
- package/dist/chunk-QMFMST2T.js +39 -0
- package/dist/{chunk-MI4SFZGX.js → chunk-RMEJR3AW.js} +5 -2
- package/dist/{chunk-UFW4S3RU.js → chunk-RVMCY6DP.js} +1 -1
- package/dist/{chunk-LF67HWWR.js → chunk-RWRZVQCB.js} +1 -1
- package/dist/chunk-SXIJLSVF.js +41 -0
- package/dist/chunk-TDQW2YHK.js +65 -0
- package/dist/{chunk-L2RJPVHV.js → chunk-TPTFV3Y6.js} +266 -512
- package/dist/{chunk-45UVJXFQ.js → chunk-W2UOVGRC.js} +1 -1
- package/dist/{chunk-VXEF2SWG.js → chunk-WT252ZDV.js} +2 -2
- package/dist/{chunk-4W6VDVQU.js → chunk-WVEIUSXE.js} +246 -56
- package/dist/{chunk-AQF6IXSR.js → chunk-YBZM7PHI.js} +4 -4
- package/dist/{ci-QD2Q7NWY.js → ci-5HONSUAS.js} +7 -7
- package/dist/{ci-format-42XIZO5P.js → ci-format-URUH3ZZ5.js} +2 -2
- package/dist/{constants-NMASHFDL.js → constants-BLDBI7IG.js} +1 -1
- package/dist/{constants-5CMEFZQB.js → constants-FG27DQMS.js} +1 -1
- package/dist/doctor-cli-QQLMNJN7.js +18 -0
- package/dist/electron-server.js +886 -828
- package/dist/file-checkpoint-64A2AXHE.js +7 -0
- package/dist/{file-checkpoint-NKBHGC7L.js → file-checkpoint-VVPUJNCI.js} +2 -1
- package/dist/{hub-ZLF6GMQD.js → hub-GGFW3DCC.js} +3 -3
- package/dist/{hub-server-CBKCP5VD.js → hub-server-JLC7CNP7.js} +55 -17
- package/dist/index.js +42 -40
- package/dist/{indexer-P3FPRTPJ.js → indexer-GKIWSSRP.js} +3 -3
- package/dist/indexer-VRXDZ74A.js +10 -0
- package/dist/{persist-S77WRVLD.js → persist-ZZXRWFBX.js} +4 -4
- package/dist/{persistent-memory-GJE7HKVK.js → persistent-memory-UNLCXUWH.js} +3 -3
- package/dist/{persistent-memory-MBSBTBGY.js → persistent-memory-YYWIN6BG.js} +3 -3
- package/dist/{pr-SXRW24JG.js → pr-YCFGPFI7.js} +7 -7
- package/dist/{project-trust-MUG325AW.js → project-trust-6CJB5ZMX.js} +1 -1
- package/dist/{project-trust-NKYHL3VZ.js → project-trust-KGG2BWKC.js} +1 -1
- package/dist/run-tests-GHY4BYDE.js +13 -0
- package/dist/run-tests-KRNMFXAN.js +12 -0
- package/dist/{semantic-7GM5M4MU.js → semantic-N227CLSC.js} +4 -4
- package/dist/{semantic-QOBAXEQ4.js → semantic-TRQAZSRY.js} +4 -4
- package/dist/{server-MO3EU2XD.js → server-73UBHSFV.js} +16 -14
- package/dist/{server-GGEO7SA2.js → server-QRAWSG4Z.js} +231 -103
- package/dist/{store-SIKHCUYJ.js → store-2N6PPPBP.js} +2 -2
- package/dist/{store-CMSSWZF7.js → store-76VVFDXM.js} +2 -2
- package/dist/{task-orchestrator-JGEEQ2OE.js → task-orchestrator-GI3RGLZI.js} +15 -14
- package/dist/{usage-B7RIAPPT.js → usage-GWTIGZF5.js} +4 -4
- package/dist/{vector-store-JAXWDZYW.js → vector-store-T2DVUMY4.js} +2 -2
- package/dist/{vector-store-PWIOQFTP.js → vector-store-V6UGEYWK.js} +2 -2
- package/dist/web/client/app.js +7 -0
- package/dist/web/client/app.js.br +0 -0
- package/dist/web/client/app.js.gz +0 -0
- package/dist/web/client/util.js +14 -23
- package/dist/web/client/util.js.br +0 -0
- package/dist/web/client/util.js.gz +0 -0
- package/dist/web/client/vendor/DOMPurify-LICENSE.txt +202 -0
- package/dist/web/client/vendor/purify.es.mjs +2722 -0
- package/dist/web/client/vendor/purify.es.mjs.br +0 -0
- package/dist/web/client/vendor/purify.es.mjs.gz +0 -0
- package/package.json +4 -2
- package/dist/chunk-4BKXL7SM.js +0 -98
- package/dist/chunk-6BUTA5VW.js +0 -38
- package/dist/chunk-IW3Q7AE5.js +0 -40
- package/dist/chunk-SKET65WZ.js +0 -96
- package/dist/doctor-cli-IOVSHBDI.js +0 -18
- package/dist/file-checkpoint-CGH6OJVI.js +0 -6
- package/dist/indexer-FPKY7LCT.js +0 -10
- package/dist/run-tests-EQCAMFQE.js +0 -10
- package/dist/run-tests-V3N7YV3P.js +0 -9
package/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() {
|
|
@@ -6706,7 +6628,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
|
|
|
6706
6628
|
function runShell(command, opts) {
|
|
6707
6629
|
return new Promise((resolvePromise) => {
|
|
6708
6630
|
const shellArgs = IS_WINDOWS3 ? ["-NoProfile", "-NonInteractive", "-Command", WIN_UTF8_PREAMBLE + command] : ["-c", command];
|
|
6709
|
-
const child =
|
|
6631
|
+
const child = spawn2(SHELL2, shellArgs, {
|
|
6710
6632
|
cwd: opts.cwd,
|
|
6711
6633
|
env: opts.env,
|
|
6712
6634
|
windowsHide: true,
|
|
@@ -6785,16 +6707,16 @@ function updateCwdFromCommand(command, baseCwd) {
|
|
|
6785
6707
|
}
|
|
6786
6708
|
|
|
6787
6709
|
// src/tools/builtin/read-file.ts
|
|
6788
|
-
import { readFileSync as
|
|
6710
|
+
import { readFileSync as readFileSync5, existsSync as existsSync7, statSync as statSync3, readdirSync as readdirSync3 } from "fs";
|
|
6789
6711
|
import { execFileSync } from "child_process";
|
|
6790
6712
|
import { extname, resolve as resolve2, basename, sep, dirname as dirname2 } from "path";
|
|
6791
6713
|
import { homedir as homedir2 } from "os";
|
|
6792
6714
|
|
|
6793
6715
|
// src/tools/builtin/notebook-utils.ts
|
|
6794
|
-
function cellSourceToString(
|
|
6795
|
-
if (!
|
|
6796
|
-
if (Array.isArray(
|
|
6797
|
-
return
|
|
6716
|
+
function cellSourceToString(source2) {
|
|
6717
|
+
if (!source2) return "";
|
|
6718
|
+
if (Array.isArray(source2)) return source2.join("");
|
|
6719
|
+
return source2;
|
|
6798
6720
|
}
|
|
6799
6721
|
function stringToCellSource(content) {
|
|
6800
6722
|
if (content.length === 0) return [];
|
|
@@ -6849,16 +6771,16 @@ function renderNotebookAsText(nb, filePath) {
|
|
|
6849
6771
|
nb.cells.forEach((cell, idx) => {
|
|
6850
6772
|
const cellNum = idx + 1;
|
|
6851
6773
|
const type = cell.cell_type;
|
|
6852
|
-
const
|
|
6774
|
+
const source2 = cellSourceToString(cell.source);
|
|
6853
6775
|
if (type === "markdown") {
|
|
6854
6776
|
lines.push(`## Cell ${cellNum} [markdown]`);
|
|
6855
|
-
lines.push(
|
|
6777
|
+
lines.push(source2);
|
|
6856
6778
|
lines.push("");
|
|
6857
6779
|
} else if (type === "code") {
|
|
6858
6780
|
const execLabel = cell.execution_count != null ? ` (execution #${cell.execution_count})` : "";
|
|
6859
6781
|
lines.push(`## Cell ${cellNum} [code${execLabel}]`);
|
|
6860
6782
|
lines.push("```" + language);
|
|
6861
|
-
lines.push(
|
|
6783
|
+
lines.push(source2);
|
|
6862
6784
|
lines.push("```");
|
|
6863
6785
|
const outputText = extractOutputText(cell.outputs);
|
|
6864
6786
|
if (outputText) {
|
|
@@ -6870,7 +6792,7 @@ function renderNotebookAsText(nb, filePath) {
|
|
|
6870
6792
|
lines.push("");
|
|
6871
6793
|
} else if (type === "raw") {
|
|
6872
6794
|
lines.push(`## Cell ${cellNum} [raw]`);
|
|
6873
|
-
lines.push(
|
|
6795
|
+
lines.push(source2);
|
|
6874
6796
|
lines.push("");
|
|
6875
6797
|
}
|
|
6876
6798
|
});
|
|
@@ -6895,6 +6817,7 @@ function createCell(cellType, content) {
|
|
|
6895
6817
|
// src/tools/builtin/read-file.ts
|
|
6896
6818
|
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
6897
6819
|
function getHardRefusal(normalizedPath) {
|
|
6820
|
+
if (isCredentialVault(normalizedPath)) return "[Refused] Credential vault files cannot be read by tools.";
|
|
6898
6821
|
const home2 = homedir2();
|
|
6899
6822
|
const p = normalizedPath.toLowerCase();
|
|
6900
6823
|
const base = basename(normalizedPath).toLowerCase();
|
|
@@ -7069,7 +6992,7 @@ var readFileTool = {
|
|
|
7069
6992
|
const offsetLine = typeof rawOffset === "number" && Number.isFinite(rawOffset) && rawOffset > 0 ? Math.floor(rawOffset) : void 0;
|
|
7070
6993
|
const limitLines = typeof rawLimit === "number" && Number.isFinite(rawLimit) && rawLimit > 0 ? Math.floor(rawLimit) : void 0;
|
|
7071
6994
|
if (!filePath) throw new ToolError("read_file", "path is required");
|
|
7072
|
-
const normalizedPath =
|
|
6995
|
+
const normalizedPath = canonicalPath(filePath);
|
|
7073
6996
|
const refusal = getHardRefusal(normalizedPath);
|
|
7074
6997
|
if (refusal) return refusal;
|
|
7075
6998
|
if (!existsSync7(normalizedPath)) {
|
|
@@ -7136,7 +7059,7 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
|
|
|
7136
7059
|
}
|
|
7137
7060
|
if (ext === ".ipynb") {
|
|
7138
7061
|
try {
|
|
7139
|
-
const raw =
|
|
7062
|
+
const raw = readFileSync5(normalizedPath, "utf-8");
|
|
7140
7063
|
const nb = parseNotebook(raw);
|
|
7141
7064
|
return renderNotebookAsText(nb, filePath);
|
|
7142
7065
|
} catch (err) {
|
|
@@ -7154,8 +7077,8 @@ Do NOT claim you inspected this image \u2014 you have not seen it.`;
|
|
|
7154
7077
|
return `[Binary file: ${filePath} (${ext})]
|
|
7155
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.`;
|
|
7156
7079
|
}
|
|
7157
|
-
const { readFile
|
|
7158
|
-
const buf = size > 1048576 ? await
|
|
7080
|
+
const { readFile } = await import("fs/promises");
|
|
7081
|
+
const buf = size > 1048576 ? await readFile(normalizedPath) : readFileSync5(normalizedPath);
|
|
7159
7082
|
if (encoding === "base64") {
|
|
7160
7083
|
return `[File: ${filePath} | base64]
|
|
7161
7084
|
|
|
@@ -7193,12 +7116,12 @@ ${content}`;
|
|
|
7193
7116
|
};
|
|
7194
7117
|
|
|
7195
7118
|
// src/tools/builtin/write-file.ts
|
|
7196
|
-
import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as
|
|
7119
|
+
import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
|
|
7197
7120
|
import { dirname as dirname5 } from "path";
|
|
7198
7121
|
|
|
7199
7122
|
// src/tools/executor.ts
|
|
7200
7123
|
import chalk3 from "chalk";
|
|
7201
|
-
import { existsSync as
|
|
7124
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
|
|
7202
7125
|
import { tmpdir } from "os";
|
|
7203
7126
|
|
|
7204
7127
|
// src/core/readline-internal.ts
|
|
@@ -7438,7 +7361,7 @@ function simpleDiff(oldLines, newLines) {
|
|
|
7438
7361
|
// src/tools/hooks.ts
|
|
7439
7362
|
import { execSync } from "child_process";
|
|
7440
7363
|
import { createHash } from "crypto";
|
|
7441
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as
|
|
7364
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6 } from "fs";
|
|
7442
7365
|
import { join as join5 } from "path";
|
|
7443
7366
|
var TRUST_FILE = "hooks-trust.json";
|
|
7444
7367
|
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
@@ -7454,7 +7377,7 @@ function loadHookTrustStore(configDir) {
|
|
|
7454
7377
|
const file = hookTrustPath(configDir);
|
|
7455
7378
|
if (!existsSync8(file)) return { records: [] };
|
|
7456
7379
|
try {
|
|
7457
|
-
const parsed = JSON.parse(
|
|
7380
|
+
const parsed = JSON.parse(readFileSync6(file, "utf-8"));
|
|
7458
7381
|
return { records: Array.isArray(parsed.records) ? parsed.records : [] };
|
|
7459
7382
|
} catch {
|
|
7460
7383
|
return { records: [] };
|
|
@@ -7468,9 +7391,9 @@ function hashHook(event, command) {
|
|
|
7468
7391
|
return createHash("sha256").update(`${event}
|
|
7469
7392
|
${command}`, "utf-8").digest("hex");
|
|
7470
7393
|
}
|
|
7471
|
-
function isTrusted(
|
|
7472
|
-
if (
|
|
7473
|
-
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);
|
|
7474
7397
|
}
|
|
7475
7398
|
function listHooks(config, configDir) {
|
|
7476
7399
|
if (!config || config.enabled === false) return [];
|
|
@@ -7478,15 +7401,15 @@ function listHooks(config, configDir) {
|
|
|
7478
7401
|
const out = [];
|
|
7479
7402
|
const push = (event, hook, index) => {
|
|
7480
7403
|
if (!hook.command) return;
|
|
7481
|
-
const
|
|
7404
|
+
const source2 = hook.source ?? "user";
|
|
7482
7405
|
const hash = hashHook(event, hook.command);
|
|
7483
7406
|
out.push({
|
|
7484
7407
|
id: `${event}:${index}`,
|
|
7485
7408
|
event,
|
|
7486
7409
|
command: hook.command,
|
|
7487
|
-
source,
|
|
7410
|
+
source: source2,
|
|
7488
7411
|
hash,
|
|
7489
|
-
trusted: isTrusted(
|
|
7412
|
+
trusted: isTrusted(source2, event, hash, store),
|
|
7490
7413
|
disabled: hook.disabled === true,
|
|
7491
7414
|
description: hook.description
|
|
7492
7415
|
});
|
|
@@ -7562,15 +7485,15 @@ function runLifecycleHooks(config, event, payload, opts) {
|
|
|
7562
7485
|
for (let i = 0; i < hooks.length; i++) {
|
|
7563
7486
|
const hook = hooks[i];
|
|
7564
7487
|
if (!hook.command || hook.disabled) continue;
|
|
7565
|
-
const
|
|
7488
|
+
const source2 = hook.source ?? "user";
|
|
7566
7489
|
const hash = hashHook(event, hook.command);
|
|
7567
7490
|
const descriptor = {
|
|
7568
7491
|
id: `${event}:${i}`,
|
|
7569
7492
|
event,
|
|
7570
7493
|
command: hook.command,
|
|
7571
|
-
source,
|
|
7494
|
+
source: source2,
|
|
7572
7495
|
hash,
|
|
7573
|
-
trusted: isTrusted(
|
|
7496
|
+
trusted: isTrusted(source2, event, hash, store),
|
|
7574
7497
|
disabled: false,
|
|
7575
7498
|
description: hook.description
|
|
7576
7499
|
};
|
|
@@ -7719,7 +7642,8 @@ var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
|
|
|
7719
7642
|
"recall_memory"
|
|
7720
7643
|
]);
|
|
7721
7644
|
function normalizePathForPermission(value) {
|
|
7722
|
-
|
|
7645
|
+
const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
7646
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
7723
7647
|
}
|
|
7724
7648
|
function canonicalizeWithNearestParent(value) {
|
|
7725
7649
|
const missing = [];
|
|
@@ -7807,7 +7731,7 @@ function profileHardDecision(profileName, toolName, args, dangerLevel, ctx) {
|
|
|
7807
7731
|
return { action: "deny", reason: "file write target is outside workspace/temp roots", profileName, hardBoundary: true };
|
|
7808
7732
|
}
|
|
7809
7733
|
}
|
|
7810
|
-
if (toolName === "bash" || toolName === "run_interactive" || toolName === "task_create") {
|
|
7734
|
+
if (toolName === "bash" || toolName === "run_interactive" || toolName === "task_create" || toolName === "run_tests") {
|
|
7811
7735
|
return {
|
|
7812
7736
|
action: "confirm",
|
|
7813
7737
|
reason: "shell/process writes cannot be proven to stay inside workspace/temp roots",
|
|
@@ -7909,9 +7833,9 @@ function checkNetworkPolicy(toolName, args, policy) {
|
|
|
7909
7833
|
if (!policy?.enabled) return null;
|
|
7910
7834
|
let action = networkToolAction(toolName, policy);
|
|
7911
7835
|
let reason = "network access requires confirmation";
|
|
7912
|
-
if (
|
|
7836
|
+
if (["bash", "run_tests", "run_interactive", "task_create"].includes(toolName)) {
|
|
7913
7837
|
const command = String(args["command"] ?? "");
|
|
7914
|
-
if (!isShellNetworkCommand(command)) return null;
|
|
7838
|
+
if (toolName === "bash" && !isShellNetworkCommand(command) && dangerIsReadOnly(command)) return null;
|
|
7915
7839
|
action = policy.tools?.shell ?? policy.defaultAction ?? "confirm";
|
|
7916
7840
|
reason = "shell command may access the network";
|
|
7917
7841
|
}
|
|
@@ -7947,6 +7871,9 @@ function checkNetworkPolicy(toolName, args, policy) {
|
|
|
7947
7871
|
if (!permissionAction) return null;
|
|
7948
7872
|
return { action: permissionAction, reason, profileName: "networkPolicy" };
|
|
7949
7873
|
}
|
|
7874
|
+
function dangerIsReadOnly(command) {
|
|
7875
|
+
return /^(?:pwd|echo [\w .-]+|git status(?: --short)?)$/.test(command.trim());
|
|
7876
|
+
}
|
|
7950
7877
|
|
|
7951
7878
|
// src/tools/truncate.ts
|
|
7952
7879
|
var DEFAULT_MAX_TOOL_OUTPUT_CHARS = 12e3;
|
|
@@ -8173,8 +8100,7 @@ function getTopUsedTools(limit = 5) {
|
|
|
8173
8100
|
}
|
|
8174
8101
|
|
|
8175
8102
|
// src/tools/action-classifier.ts
|
|
8176
|
-
import {
|
|
8177
|
-
import { isAbsolute as isAbsolute2, join as join7, resolve as resolve4 } from "path";
|
|
8103
|
+
import { isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
|
|
8178
8104
|
var RECENT_DENIED_LIMIT = 50;
|
|
8179
8105
|
var recentlyDenied = [];
|
|
8180
8106
|
function recordRecentlyDeniedAutoAction(call, classification) {
|
|
@@ -8194,7 +8120,8 @@ function clearRecentlyDeniedAutoActions() {
|
|
|
8194
8120
|
recentlyDenied.splice(0, recentlyDenied.length);
|
|
8195
8121
|
}
|
|
8196
8122
|
function normalizePath(value) {
|
|
8197
|
-
|
|
8123
|
+
const normalized = canonicalPath(value).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
8124
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
8198
8125
|
}
|
|
8199
8126
|
function isInside(root, target) {
|
|
8200
8127
|
const absoluteRoot = resolve4(root);
|
|
@@ -8207,56 +8134,6 @@ function getPathArg(call) {
|
|
|
8207
8134
|
const raw = call.arguments.path ?? call.arguments.filePath ?? call.arguments.outputPath ?? call.arguments.file;
|
|
8208
8135
|
return typeof raw === "string" && raw.trim() ? raw.trim() : null;
|
|
8209
8136
|
}
|
|
8210
|
-
function splitShellWords(command) {
|
|
8211
|
-
const out = [];
|
|
8212
|
-
const re = /"([^"]*)"|'([^']*)'|([^\s]+)/g;
|
|
8213
|
-
let m;
|
|
8214
|
-
while (m = re.exec(command)) out.push(m[1] ?? m[2] ?? m[3] ?? "");
|
|
8215
|
-
return out.filter(Boolean);
|
|
8216
|
-
}
|
|
8217
|
-
function readPackageManifest(root) {
|
|
8218
|
-
const p = join7(root, "package.json");
|
|
8219
|
-
if (!existsSync10(p)) return null;
|
|
8220
|
-
try {
|
|
8221
|
-
return JSON.parse(readFileSync8(p, "utf-8"));
|
|
8222
|
-
} catch {
|
|
8223
|
-
return null;
|
|
8224
|
-
}
|
|
8225
|
-
}
|
|
8226
|
-
function manifestHasDependency(root, name) {
|
|
8227
|
-
const manifest = readPackageManifest(root);
|
|
8228
|
-
if (!manifest) return false;
|
|
8229
|
-
for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
8230
|
-
const deps = manifest[key];
|
|
8231
|
-
if (deps && typeof deps === "object" && Object.prototype.hasOwnProperty.call(deps, name)) return true;
|
|
8232
|
-
}
|
|
8233
|
-
return false;
|
|
8234
|
-
}
|
|
8235
|
-
function lockfileMentions(root, name) {
|
|
8236
|
-
for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
|
|
8237
|
-
const p = join7(root, file);
|
|
8238
|
-
if (!existsSync10(p)) continue;
|
|
8239
|
-
try {
|
|
8240
|
-
if (readFileSync8(p, "utf-8").includes(name)) return true;
|
|
8241
|
-
} catch {
|
|
8242
|
-
}
|
|
8243
|
-
}
|
|
8244
|
-
return false;
|
|
8245
|
-
}
|
|
8246
|
-
function getInstallPackages(command) {
|
|
8247
|
-
const words = splitShellWords(command);
|
|
8248
|
-
const cmd = words[0]?.toLowerCase();
|
|
8249
|
-
const sub = words[1]?.toLowerCase();
|
|
8250
|
-
if (!cmd || !sub) return [];
|
|
8251
|
-
const isInstall = cmd === "npm" && (sub === "install" || sub === "i" || sub === "add") || (cmd === "pnpm" || cmd === "yarn") && (sub === "add" || sub === "install");
|
|
8252
|
-
if (!isInstall) return [];
|
|
8253
|
-
return words.slice(2).filter((w) => !w.startsWith("-")).filter((w) => w !== "." && w !== "./").map((w) => w.replace(/^@([^/]+)\/([^@]+)(?:@.+)?$/, "@$1/$2").replace(/^([^@]+)@.+$/, "$1"));
|
|
8254
|
-
}
|
|
8255
|
-
function isPlainGitPush(command) {
|
|
8256
|
-
if (!/^\s*git\s+push\b/i.test(command)) return false;
|
|
8257
|
-
if (/--force|-f\b|\+[^\s]+/i.test(command)) return false;
|
|
8258
|
-
return !/[;&|`$<>\n\r]/.test(command);
|
|
8259
|
-
}
|
|
8260
8137
|
function hasSecretExfiltrationShape(command) {
|
|
8261
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);
|
|
8262
8139
|
}
|
|
@@ -8278,7 +8155,7 @@ var RuleBasedActionClassifier = class {
|
|
|
8278
8155
|
}
|
|
8279
8156
|
return { decision: "confirm", reason: "destructive actions require confirmation", ruleId: "auto.destructive-confirm", confidence: "high" };
|
|
8280
8157
|
}
|
|
8281
|
-
classifyShell(call, dangerLevel,
|
|
8158
|
+
classifyShell(call, dangerLevel, _ctx) {
|
|
8282
8159
|
const command = String(call.arguments.command ?? "");
|
|
8283
8160
|
if (/\bcurl\b[^\n|;&]*\|\s*(?:sudo\s+)?(?:bash|sh|zsh)\b/i.test(command)) {
|
|
8284
8161
|
return { decision: "deny", reason: "curl pipe shell execution is blocked in auto mode", ruleId: "deny.curl-pipe-shell", confidence: "high" };
|
|
@@ -8304,18 +8181,7 @@ var RuleBasedActionClassifier = class {
|
|
|
8304
8181
|
if (/\b(npx|pnpm\s+dlx|yarn\s+dlx)\s+(claude|codex|aider|cursor-agent|gemini)\b/i.test(command)) {
|
|
8305
8182
|
return { decision: "confirm", reason: "third-party agent loop requires confirmation", ruleId: "confirm.third-party-agent", confidence: "high" };
|
|
8306
8183
|
}
|
|
8307
|
-
if (
|
|
8308
|
-
return { decision: "auto-approve", reason: "plain git push without force", ruleId: "auto.git-push", confidence: "high" };
|
|
8309
|
-
}
|
|
8310
|
-
const pkgs = getInstallPackages(command);
|
|
8311
|
-
if (pkgs.length > 0) {
|
|
8312
|
-
const allDeclared = pkgs.every((p) => manifestHasDependency(ctx.workspaceRoot, p) || lockfileMentions(ctx.workspaceRoot, p));
|
|
8313
|
-
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" };
|
|
8314
|
-
}
|
|
8315
|
-
if (/\b(curl|wget|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command) && !/[>|;&`$]/.test(command)) {
|
|
8316
|
-
return { decision: "auto-approve", reason: "read-only HTTP shell command", ruleId: "auto.readonly-http-shell", confidence: "medium" };
|
|
8317
|
-
}
|
|
8318
|
-
if (dangerLevel === "safe") {
|
|
8184
|
+
if (dangerLevel === "safe" && getDangerLevel(call.name, call.arguments) === "safe") {
|
|
8319
8185
|
return { decision: "auto-approve", reason: "safe shell action", ruleId: "auto.safe-shell", confidence: "medium" };
|
|
8320
8186
|
}
|
|
8321
8187
|
return { decision: "confirm", reason: "shell action is not proven low-risk", ruleId: "confirm.shell-default", confidence: "medium" };
|
|
@@ -8784,10 +8650,10 @@ var ToolExecutor = class {
|
|
|
8784
8650
|
if (call.name === "write_file") {
|
|
8785
8651
|
const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
|
|
8786
8652
|
if (!filePath) return;
|
|
8787
|
-
if (
|
|
8653
|
+
if (existsSync10(filePath)) {
|
|
8788
8654
|
let oldContent;
|
|
8789
8655
|
try {
|
|
8790
|
-
oldContent =
|
|
8656
|
+
oldContent = readFileSync7(filePath, "utf-8");
|
|
8791
8657
|
} catch {
|
|
8792
8658
|
return;
|
|
8793
8659
|
}
|
|
@@ -8811,7 +8677,7 @@ var ToolExecutor = class {
|
|
|
8811
8677
|
}
|
|
8812
8678
|
} else if (call.name === "edit_file") {
|
|
8813
8679
|
const filePath = String(call.arguments["path"] ?? "");
|
|
8814
|
-
if (!filePath || !
|
|
8680
|
+
if (!filePath || !existsSync10(filePath)) return;
|
|
8815
8681
|
const oldStr = call.arguments["old_str"];
|
|
8816
8682
|
const newStr = call.arguments["new_str"];
|
|
8817
8683
|
if (oldStr !== void 0) {
|
|
@@ -8838,7 +8704,7 @@ var ToolExecutor = class {
|
|
|
8838
8704
|
const to = Number(call.arguments["delete_to_line"] ?? from);
|
|
8839
8705
|
let fileContent;
|
|
8840
8706
|
try {
|
|
8841
|
-
fileContent =
|
|
8707
|
+
fileContent = readFileSync7(filePath, "utf-8");
|
|
8842
8708
|
} catch {
|
|
8843
8709
|
return;
|
|
8844
8710
|
}
|
|
@@ -8934,7 +8800,12 @@ import { resolve as resolve5, sep as sep2, basename as basename3 } from "path";
|
|
|
8934
8800
|
import { homedir as homedir4 } from "os";
|
|
8935
8801
|
var home = homedir4();
|
|
8936
8802
|
function norm(p) {
|
|
8937
|
-
|
|
8803
|
+
let abs;
|
|
8804
|
+
try {
|
|
8805
|
+
abs = canonicalPath(p);
|
|
8806
|
+
} catch {
|
|
8807
|
+
abs = resolve5(p);
|
|
8808
|
+
}
|
|
8938
8809
|
return process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
8939
8810
|
}
|
|
8940
8811
|
function homeRel(p) {
|
|
@@ -8948,6 +8819,7 @@ function homeRel(p) {
|
|
|
8948
8819
|
}
|
|
8949
8820
|
function classifyWritePath(path3) {
|
|
8950
8821
|
if (!path3) return { sensitive: false };
|
|
8822
|
+
if (isCredentialVault(path3)) return { sensitive: true, reason: "credential vault" };
|
|
8951
8823
|
const abs = norm(path3);
|
|
8952
8824
|
const base = basename3(abs);
|
|
8953
8825
|
const rel = homeRel(path3);
|
|
@@ -9030,7 +8902,7 @@ function countTextLines(text) {
|
|
|
9030
8902
|
|
|
9031
8903
|
// src/tools/builtin/write-file.ts
|
|
9032
8904
|
function countFileLines(filePath) {
|
|
9033
|
-
return countTextLines(
|
|
8905
|
+
return countTextLines(readFileSync8(filePath, "utf-8"));
|
|
9034
8906
|
}
|
|
9035
8907
|
var writeFileTool = {
|
|
9036
8908
|
definition: {
|
|
@@ -9094,7 +8966,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
9094
8966
|
const mode = appendMode ? "appended" : "written";
|
|
9095
8967
|
void (async () => {
|
|
9096
8968
|
try {
|
|
9097
|
-
const { updateFile } = await import("./indexer-
|
|
8969
|
+
const { updateFile } = await import("./indexer-VRXDZ74A.js");
|
|
9098
8970
|
await updateFile(process.cwd(), filePath);
|
|
9099
8971
|
} catch {
|
|
9100
8972
|
}
|
|
@@ -9113,7 +8985,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
9113
8985
|
};
|
|
9114
8986
|
|
|
9115
8987
|
// src/tools/builtin/edit-file.ts
|
|
9116
|
-
import { readFileSync as
|
|
8988
|
+
import { readFileSync as readFileSync9, existsSync as existsSync11 } from "fs";
|
|
9117
8989
|
|
|
9118
8990
|
// src/tools/builtin/patch-apply.ts
|
|
9119
8991
|
function parseUnifiedDiff(patch) {
|
|
@@ -9481,7 +9353,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
9481
9353
|
const filePath = String(args["path"] ?? args["file"] ?? "");
|
|
9482
9354
|
const encoding = args["encoding"] ?? "utf-8";
|
|
9483
9355
|
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
9484
|
-
if (!
|
|
9356
|
+
if (!existsSync11(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
9485
9357
|
const verdict = classifyWritePath(filePath);
|
|
9486
9358
|
if (verdict.sensitive && subAgentGuard.active) {
|
|
9487
9359
|
throw new ToolError(
|
|
@@ -9489,7 +9361,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
9489
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.`
|
|
9490
9362
|
);
|
|
9491
9363
|
}
|
|
9492
|
-
const original =
|
|
9364
|
+
const original = readFileSync9(filePath, encoding);
|
|
9493
9365
|
if (args["patch"] !== void 0) {
|
|
9494
9366
|
const patchText = String(args["patch"] ?? "");
|
|
9495
9367
|
const stopOnError = args["stop_on_error"] !== false;
|
|
@@ -9666,8 +9538,8 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
9666
9538
|
}
|
|
9667
9539
|
|
|
9668
9540
|
// src/tools/builtin/list-dir.ts
|
|
9669
|
-
import { readdirSync as readdirSync4, statSync as statSync5, existsSync as
|
|
9670
|
-
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";
|
|
9671
9543
|
var listDirTool = {
|
|
9672
9544
|
definition: {
|
|
9673
9545
|
name: "list_dir",
|
|
@@ -9689,7 +9561,7 @@ var listDirTool = {
|
|
|
9689
9561
|
async execute(args) {
|
|
9690
9562
|
const dirPath = String(args["path"] ?? process.cwd());
|
|
9691
9563
|
const recursive = Boolean(args["recursive"] ?? false);
|
|
9692
|
-
if (!
|
|
9564
|
+
if (!existsSync12(dirPath)) {
|
|
9693
9565
|
const targetName = basename4(dirPath).toLowerCase();
|
|
9694
9566
|
const cwd = process.cwd();
|
|
9695
9567
|
const suggestions = [];
|
|
@@ -9747,11 +9619,11 @@ function listRecursive(basePath, indent, recursive, lines) {
|
|
|
9747
9619
|
if (entry.isDirectory()) {
|
|
9748
9620
|
lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
|
|
9749
9621
|
if (recursive) {
|
|
9750
|
-
listRecursive(
|
|
9622
|
+
listRecursive(join7(basePath, entry.name), indent + " ", true, lines);
|
|
9751
9623
|
}
|
|
9752
9624
|
} else {
|
|
9753
9625
|
try {
|
|
9754
|
-
const stat = statSync5(
|
|
9626
|
+
const stat = statSync5(join7(basePath, entry.name));
|
|
9755
9627
|
const size = formatSize(stat.size);
|
|
9756
9628
|
lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
|
|
9757
9629
|
} catch {
|
|
@@ -9766,10 +9638,77 @@ function formatSize(bytes) {
|
|
|
9766
9638
|
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
9767
9639
|
}
|
|
9768
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
|
+
|
|
9769
9709
|
// src/tools/builtin/grep-files.ts
|
|
9770
|
-
import { readdirSync as readdirSync5,
|
|
9771
|
-
import {
|
|
9772
|
-
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";
|
|
9773
9712
|
var grepFilesTool = {
|
|
9774
9713
|
definition: {
|
|
9775
9714
|
name: "grep_files",
|
|
@@ -9817,43 +9756,24 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
9817
9756
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
9818
9757
|
const filePattern = args["file_pattern"] ? String(args["file_pattern"]) : void 0;
|
|
9819
9758
|
const ignoreCase = Boolean(args["ignore_case"] ?? false);
|
|
9820
|
-
const contextLines = Math.max(0, Number(args["context_lines"]
|
|
9821
|
-
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));
|
|
9822
9761
|
if (!pattern) throw new ToolError("grep_files", "pattern is required");
|
|
9823
|
-
if (!
|
|
9762
|
+
if (!existsSync13(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
|
|
9824
9763
|
const MAX_PATTERN_LENGTH = 1e3;
|
|
9825
|
-
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
9764
|
+
if (pattern.length > MAX_PATTERN_LENGTH || (filePattern?.length ?? 0) > MAX_PATTERN_LENGTH) {
|
|
9826
9765
|
throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
9827
9766
|
}
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
const results = [];
|
|
9836
|
-
const stat = statSync6(rootPath);
|
|
9837
|
-
if (stat.isFile()) {
|
|
9838
|
-
searchInFile(rootPath, rootPath, regex, contextLines, maxResults, results);
|
|
9839
|
-
} else {
|
|
9840
|
-
const filePaths = collectFilePaths(rootPath, filePattern, maxResults);
|
|
9841
|
-
const BATCH_SIZE = 16;
|
|
9842
|
-
for (let i = 0; i < filePaths.length && results.length < maxResults; i += BATCH_SIZE) {
|
|
9843
|
-
const batch = filePaths.slice(i, i + BATCH_SIZE);
|
|
9844
|
-
const batchResults = await Promise.all(
|
|
9845
|
-
batch.map(
|
|
9846
|
-
({ fullPath, relPath }) => searchInFileAsync(fullPath, relPath, regex, contextLines, maxResults - results.length)
|
|
9847
|
-
)
|
|
9848
|
-
);
|
|
9849
|
-
for (const br of batchResults) {
|
|
9850
|
-
for (const r of br) {
|
|
9851
|
-
if (results.length >= maxResults) break;
|
|
9852
|
-
results.push(r);
|
|
9853
|
-
}
|
|
9854
|
-
}
|
|
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;
|
|
9855
9774
|
}
|
|
9856
|
-
}
|
|
9775
|
+
});
|
|
9776
|
+
const results = await searchInWorker(files, pattern, ignoreCase, contextLines, maxResults);
|
|
9857
9777
|
if (results.length === 0) {
|
|
9858
9778
|
return `No matches found for pattern: ${pattern}
|
|
9859
9779
|
Searched in: ${rootPath}${filePattern ? `
|
|
@@ -9893,8 +9813,23 @@ File filter: ${filePattern}` : ""}`;
|
|
|
9893
9813
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", ".next", "__pycache__", ".cache", "coverage", ".nyc_output"]);
|
|
9894
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"]);
|
|
9895
9815
|
function matchesFilePattern(filename, pattern) {
|
|
9896
|
-
|
|
9897
|
-
|
|
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;
|
|
9898
9833
|
}
|
|
9899
9834
|
function isBinary(filename) {
|
|
9900
9835
|
const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
|
|
@@ -9902,8 +9837,9 @@ function isBinary(filename) {
|
|
|
9902
9837
|
}
|
|
9903
9838
|
function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
9904
9839
|
const paths = [];
|
|
9905
|
-
|
|
9906
|
-
|
|
9840
|
+
let visited = 0;
|
|
9841
|
+
function walk(dirPath, depth = 0) {
|
|
9842
|
+
if (paths.length >= maxFiles || depth > 20 || visited >= 2e4) return;
|
|
9907
9843
|
let entries;
|
|
9908
9844
|
try {
|
|
9909
9845
|
entries = readdirSync5(dirPath, { withFileTypes: true });
|
|
@@ -9911,14 +9847,14 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
9911
9847
|
return;
|
|
9912
9848
|
}
|
|
9913
9849
|
for (const entry of entries) {
|
|
9914
|
-
if (paths.length >= maxFiles) return;
|
|
9850
|
+
if (paths.length >= maxFiles || ++visited > 2e4) return;
|
|
9915
9851
|
if (entry.isDirectory()) {
|
|
9916
9852
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
9917
|
-
walk(
|
|
9853
|
+
walk(join8(dirPath, entry.name), depth + 1);
|
|
9918
9854
|
} else if (entry.isFile()) {
|
|
9919
9855
|
if (isBinary(entry.name)) continue;
|
|
9920
9856
|
if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
|
|
9921
|
-
const fullPath =
|
|
9857
|
+
const fullPath = join8(dirPath, entry.name);
|
|
9922
9858
|
try {
|
|
9923
9859
|
if (statSync6(fullPath).size > 1e6) continue;
|
|
9924
9860
|
} catch {
|
|
@@ -9931,81 +9867,10 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
9931
9867
|
walk(rootPath);
|
|
9932
9868
|
return paths;
|
|
9933
9869
|
}
|
|
9934
|
-
async function searchInFileAsync(fullPath, displayPath2, regex, contextLines, maxResults) {
|
|
9935
|
-
let content;
|
|
9936
|
-
try {
|
|
9937
|
-
content = await readFile(fullPath, "utf-8");
|
|
9938
|
-
} catch {
|
|
9939
|
-
return [];
|
|
9940
|
-
}
|
|
9941
|
-
const results = [];
|
|
9942
|
-
const lines = content.split("\n");
|
|
9943
|
-
regex.lastIndex = 0;
|
|
9944
|
-
for (let i = 0; i < lines.length && results.length < maxResults; i++) {
|
|
9945
|
-
regex.lastIndex = 0;
|
|
9946
|
-
if (regex.test(lines[i])) {
|
|
9947
|
-
const match = {
|
|
9948
|
-
file: displayPath2.replace(/\\/g, "/"),
|
|
9949
|
-
lineNumber: i + 1,
|
|
9950
|
-
lineText: lines[i]
|
|
9951
|
-
};
|
|
9952
|
-
if (contextLines > 0) {
|
|
9953
|
-
match.contextBefore = [];
|
|
9954
|
-
for (let j = Math.max(0, i - contextLines); j < i; j++) {
|
|
9955
|
-
match.contextBefore.push([j + 1, lines[j]]);
|
|
9956
|
-
}
|
|
9957
|
-
match.contextAfter = [];
|
|
9958
|
-
for (let j = i + 1; j <= Math.min(lines.length - 1, i + contextLines); j++) {
|
|
9959
|
-
match.contextAfter.push([j + 1, lines[j]]);
|
|
9960
|
-
}
|
|
9961
|
-
}
|
|
9962
|
-
results.push(match);
|
|
9963
|
-
}
|
|
9964
|
-
}
|
|
9965
|
-
return results;
|
|
9966
|
-
}
|
|
9967
|
-
function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, results) {
|
|
9968
|
-
try {
|
|
9969
|
-
const stat = statSync6(fullPath);
|
|
9970
|
-
if (stat.size > 1e6) return;
|
|
9971
|
-
} catch {
|
|
9972
|
-
return;
|
|
9973
|
-
}
|
|
9974
|
-
let content;
|
|
9975
|
-
try {
|
|
9976
|
-
content = readFileSync12(fullPath, "utf-8");
|
|
9977
|
-
} catch {
|
|
9978
|
-
return;
|
|
9979
|
-
}
|
|
9980
|
-
const lines = content.split("\n");
|
|
9981
|
-
regex.lastIndex = 0;
|
|
9982
|
-
for (let i = 0; i < lines.length; i++) {
|
|
9983
|
-
if (results.length >= maxResults) return;
|
|
9984
|
-
regex.lastIndex = 0;
|
|
9985
|
-
if (regex.test(lines[i])) {
|
|
9986
|
-
const result = {
|
|
9987
|
-
file: displayPath2,
|
|
9988
|
-
lineNumber: i + 1,
|
|
9989
|
-
lineText: lines[i].trimEnd()
|
|
9990
|
-
};
|
|
9991
|
-
if (contextLines > 0) {
|
|
9992
|
-
result.contextBefore = [];
|
|
9993
|
-
result.contextAfter = [];
|
|
9994
|
-
for (let c = Math.max(0, i - contextLines); c < i; c++) {
|
|
9995
|
-
result.contextBefore.push([c + 1, lines[c].trimEnd()]);
|
|
9996
|
-
}
|
|
9997
|
-
for (let c = i + 1; c <= Math.min(lines.length - 1, i + contextLines); c++) {
|
|
9998
|
-
result.contextAfter.push([c + 1, lines[c].trimEnd()]);
|
|
9999
|
-
}
|
|
10000
|
-
}
|
|
10001
|
-
results.push(result);
|
|
10002
|
-
}
|
|
10003
|
-
}
|
|
10004
|
-
}
|
|
10005
9870
|
|
|
10006
9871
|
// src/tools/builtin/glob-files.ts
|
|
10007
|
-
import { readdirSync as readdirSync6, statSync as statSync7, existsSync as
|
|
10008
|
-
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";
|
|
10009
9874
|
var globFilesTool = {
|
|
10010
9875
|
definition: {
|
|
10011
9876
|
name: "glob_files",
|
|
@@ -10038,7 +9903,7 @@ Results sorted by most recent modification time. Automatically skips node_module
|
|
|
10038
9903
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
10039
9904
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
|
|
10040
9905
|
if (!pattern) throw new ToolError("glob_files", "pattern is required");
|
|
10041
|
-
if (!
|
|
9906
|
+
if (!existsSync14(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
|
|
10042
9907
|
const regex = globToRegex(pattern);
|
|
10043
9908
|
const matches2 = [];
|
|
10044
9909
|
collectMatchingFiles(rootPath, rootPath, regex, matches2, maxResults);
|
|
@@ -10115,7 +9980,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
10115
9980
|
}
|
|
10116
9981
|
for (const entry of entries) {
|
|
10117
9982
|
if (results.length >= maxResults) break;
|
|
10118
|
-
const fullPath =
|
|
9983
|
+
const fullPath = join9(dirPath, entry.name);
|
|
10119
9984
|
if (entry.isDirectory()) {
|
|
10120
9985
|
if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
10121
9986
|
collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
|
|
@@ -10134,9 +9999,9 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
10134
9999
|
}
|
|
10135
10000
|
|
|
10136
10001
|
// src/tools/builtin/run-interactive.ts
|
|
10137
|
-
import { spawn as
|
|
10138
|
-
import { platform as
|
|
10139
|
-
var IS_WINDOWS4 =
|
|
10002
|
+
import { spawn as spawn3 } from "child_process";
|
|
10003
|
+
import { platform as platform3 } from "os";
|
|
10004
|
+
var IS_WINDOWS4 = platform3() === "win32";
|
|
10140
10005
|
var runInteractiveTool = {
|
|
10141
10006
|
definition: {
|
|
10142
10007
|
name: "run_interactive",
|
|
@@ -10198,7 +10063,7 @@ var runInteractiveTool = {
|
|
|
10198
10063
|
};
|
|
10199
10064
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
10200
10065
|
return new Promise((resolve13) => {
|
|
10201
|
-
const child =
|
|
10066
|
+
const child = spawn3(executable, cmdArgs.map(String), {
|
|
10202
10067
|
cwd: process.cwd(),
|
|
10203
10068
|
env,
|
|
10204
10069
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10468,8 +10333,8 @@ var webFetchTool = {
|
|
|
10468
10333
|
if (e.message.startsWith("Blocked:")) throw e;
|
|
10469
10334
|
throw new NetworkError(`Invalid URL: "${url}"`);
|
|
10470
10335
|
}
|
|
10471
|
-
const
|
|
10472
|
-
const timeoutId = setTimeout(() =>
|
|
10336
|
+
const controller = new AbortController();
|
|
10337
|
+
const timeoutId = setTimeout(() => controller.abort(), 2e4);
|
|
10473
10338
|
let rawHtml;
|
|
10474
10339
|
let finalUrl;
|
|
10475
10340
|
let contentType;
|
|
@@ -10490,7 +10355,7 @@ var webFetchTool = {
|
|
|
10490
10355
|
}
|
|
10491
10356
|
await resolveAndCheck(parsedHop.hostname);
|
|
10492
10357
|
const r = await fetch(currentUrl, {
|
|
10493
|
-
signal:
|
|
10358
|
+
signal: controller.signal,
|
|
10494
10359
|
headers: FETCH_HEADERS,
|
|
10495
10360
|
redirect: "manual",
|
|
10496
10361
|
// 手动控制重定向
|
|
@@ -10853,8 +10718,8 @@ function parseGoogleResults(html, maxResults) {
|
|
|
10853
10718
|
return results;
|
|
10854
10719
|
}
|
|
10855
10720
|
async function fetchHtml(url) {
|
|
10856
|
-
const
|
|
10857
|
-
const timer = setTimeout(() =>
|
|
10721
|
+
const controller = new AbortController();
|
|
10722
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
10858
10723
|
try {
|
|
10859
10724
|
const res = await fetch(url, {
|
|
10860
10725
|
method: "GET",
|
|
@@ -10863,7 +10728,7 @@ async function fetchHtml(url) {
|
|
|
10863
10728
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9",
|
|
10864
10729
|
"Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8"
|
|
10865
10730
|
},
|
|
10866
|
-
signal:
|
|
10731
|
+
signal: controller.signal
|
|
10867
10732
|
});
|
|
10868
10733
|
if (!res.ok) {
|
|
10869
10734
|
throw new NetworkError(`HTTP ${res.status} ${res.statusText}`, res.status);
|
|
@@ -10957,10 +10822,8 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
10957
10822
|
};
|
|
10958
10823
|
|
|
10959
10824
|
// src/tools/builtin/save-memory.ts
|
|
10960
|
-
import { join as join11 } from "path";
|
|
10961
|
-
import { homedir as homedir5 } from "os";
|
|
10962
10825
|
function getConfigDir() {
|
|
10963
|
-
return
|
|
10826
|
+
return getExecutionConfigDir();
|
|
10964
10827
|
}
|
|
10965
10828
|
function parseSensitivity(value) {
|
|
10966
10829
|
return value === "low" || value === "medium" || value === "high" ? value : void 0;
|
|
@@ -11224,15 +11087,15 @@ var googleSearchTool = {
|
|
|
11224
11087
|
url.searchParams.set("cx", cx);
|
|
11225
11088
|
url.searchParams.set("q", query);
|
|
11226
11089
|
url.searchParams.set("num", String(numResults));
|
|
11227
|
-
const
|
|
11228
|
-
const timeout = setTimeout(() =>
|
|
11090
|
+
const controller = new AbortController();
|
|
11091
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
|
|
11229
11092
|
try {
|
|
11230
11093
|
const response = await fetch(url.toString(), {
|
|
11231
11094
|
method: "GET",
|
|
11232
11095
|
headers: {
|
|
11233
11096
|
"Accept": "application/json"
|
|
11234
11097
|
},
|
|
11235
|
-
signal:
|
|
11098
|
+
signal: controller.signal
|
|
11236
11099
|
});
|
|
11237
11100
|
if (!response.ok) {
|
|
11238
11101
|
const errorBody = await response.text().catch(() => "");
|
|
@@ -11316,13 +11179,13 @@ function isToolCapableProvider(provider) {
|
|
|
11316
11179
|
}
|
|
11317
11180
|
|
|
11318
11181
|
// src/agents/agent-config.ts
|
|
11319
|
-
import { existsSync as
|
|
11320
|
-
import { join as
|
|
11321
|
-
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";
|
|
11322
11185
|
|
|
11323
11186
|
// src/plugins/plugin-manager.ts
|
|
11324
|
-
import { existsSync as
|
|
11325
|
-
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";
|
|
11326
11189
|
import { createHash as createHash2 } from "crypto";
|
|
11327
11190
|
import { z as z2 } from "zod";
|
|
11328
11191
|
var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
|
|
@@ -11382,19 +11245,19 @@ var PluginManifestSchema = z2.object({
|
|
|
11382
11245
|
permissionHints: z2.array(z2.string()).default([])
|
|
11383
11246
|
}).strict();
|
|
11384
11247
|
function pluginRoot(configDir) {
|
|
11385
|
-
return
|
|
11248
|
+
return join10(configDir, PLUGINS_DIR_NAME);
|
|
11386
11249
|
}
|
|
11387
11250
|
function pluginManifestPath(pluginDir) {
|
|
11388
|
-
return
|
|
11251
|
+
return join10(pluginDir, MANIFEST_RELATIVE);
|
|
11389
11252
|
}
|
|
11390
11253
|
function statePath(configDir) {
|
|
11391
|
-
return
|
|
11254
|
+
return join10(pluginRoot(configDir), STATE_FILE);
|
|
11392
11255
|
}
|
|
11393
11256
|
function loadState(configDir) {
|
|
11394
11257
|
const file = statePath(configDir);
|
|
11395
|
-
if (!
|
|
11258
|
+
if (!existsSync15(file)) return { version: 2, plugins: [] };
|
|
11396
11259
|
try {
|
|
11397
|
-
const parsed = JSON.parse(
|
|
11260
|
+
const parsed = JSON.parse(readFileSync10(file, "utf-8"));
|
|
11398
11261
|
const rawPlugins = Array.isArray(parsed.plugins) ? parsed.plugins : [];
|
|
11399
11262
|
return { version: 2, plugins: rawPlugins.map((p) => migrateEntry(p)) };
|
|
11400
11263
|
} catch {
|
|
@@ -11452,7 +11315,7 @@ function extractCommandPaths(command, pluginDir) {
|
|
|
11452
11315
|
try {
|
|
11453
11316
|
const abs = resolve6(resolvedDir, ref);
|
|
11454
11317
|
if (!abs.startsWith(resolvedDir + sep3)) continue;
|
|
11455
|
-
if (
|
|
11318
|
+
if (existsSync15(abs)) {
|
|
11456
11319
|
const real = realpathSync2(abs);
|
|
11457
11320
|
if (!real.startsWith(resolvedDir + sep3)) continue;
|
|
11458
11321
|
valid.push(relative3(resolvedDir, real));
|
|
@@ -11484,8 +11347,8 @@ function hashPluginContent(pluginDir, manifest) {
|
|
|
11484
11347
|
}
|
|
11485
11348
|
const sorted = [...assetRels].sort();
|
|
11486
11349
|
const hasher = createHash2("sha256");
|
|
11487
|
-
const manifestPath =
|
|
11488
|
-
hasher.update(normalizeForHash(
|
|
11350
|
+
const manifestPath = join10(resolvedDir, MANIFEST_RELATIVE);
|
|
11351
|
+
hasher.update(normalizeForHash(readFileSync10(manifestPath)));
|
|
11489
11352
|
for (const rel of sorted) {
|
|
11490
11353
|
const abs = resolve6(resolvedDir, rel);
|
|
11491
11354
|
if (!abs.startsWith(resolvedDir + sep3)) {
|
|
@@ -11505,7 +11368,7 @@ function hashPluginContent(pluginDir, manifest) {
|
|
|
11505
11368
|
if (!realPath.startsWith(resolvedDir + sep3)) {
|
|
11506
11369
|
throw new Error(`plugin asset symlink escapes plugin dir: ${rel} \u2192 ${realPath}`);
|
|
11507
11370
|
}
|
|
11508
|
-
hasher.update(normalizeForHash(
|
|
11371
|
+
hasher.update(normalizeForHash(readFileSync10(abs)));
|
|
11509
11372
|
}
|
|
11510
11373
|
return hasher.digest("hex");
|
|
11511
11374
|
}
|
|
@@ -11513,22 +11376,22 @@ function effectiveHash(entry) {
|
|
|
11513
11376
|
return entry.contentHash ?? entry.hash ?? "";
|
|
11514
11377
|
}
|
|
11515
11378
|
function readPluginManifest(manifestPath) {
|
|
11516
|
-
return PluginManifestSchema.parse(JSON.parse(
|
|
11379
|
+
return PluginManifestSchema.parse(JSON.parse(readFileSync10(manifestPath, "utf-8")));
|
|
11517
11380
|
}
|
|
11518
11381
|
function findPluginSourceDir(inputPath) {
|
|
11519
11382
|
const abs = resolve6(inputPath);
|
|
11520
11383
|
const direct = statSync8(abs).isDirectory() ? abs : dirname7(abs);
|
|
11521
|
-
if (
|
|
11522
|
-
const nested =
|
|
11523
|
-
if (
|
|
11384
|
+
if (existsSync15(pluginManifestPath(direct))) return direct;
|
|
11385
|
+
const nested = join10(direct, ".aicli-plugin");
|
|
11386
|
+
if (existsSync15(join10(nested, "plugin.json"))) return direct;
|
|
11524
11387
|
throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
|
|
11525
11388
|
}
|
|
11526
11389
|
function installPlugin(configDir, inputPath) {
|
|
11527
11390
|
const sourceDir = findPluginSourceDir(inputPath);
|
|
11528
11391
|
const manifest = readPluginManifest(pluginManifestPath(sourceDir));
|
|
11529
|
-
const targetDir =
|
|
11392
|
+
const targetDir = join10(pluginRoot(configDir), manifest.name);
|
|
11530
11393
|
mkdirSync8(pluginRoot(configDir), { recursive: true });
|
|
11531
|
-
if (
|
|
11394
|
+
if (existsSync15(targetDir)) rmSync(targetDir, { recursive: true, force: true });
|
|
11532
11395
|
cpSync(sourceDir, targetDir, { recursive: true });
|
|
11533
11396
|
const manifestPath = pluginManifestPath(targetDir);
|
|
11534
11397
|
const contentHash = hashPluginContent(targetDir, manifest);
|
|
@@ -11586,18 +11449,18 @@ function getInstalledPlugin(configDir, name) {
|
|
|
11586
11449
|
}
|
|
11587
11450
|
function listInstalledPlugins(configDir) {
|
|
11588
11451
|
const root = pluginRoot(configDir);
|
|
11589
|
-
if (!
|
|
11452
|
+
if (!existsSync15(root)) return [];
|
|
11590
11453
|
const state2 = loadState(configDir);
|
|
11591
11454
|
const out = [];
|
|
11592
11455
|
for (const entry of readdirSync7(root)) {
|
|
11593
|
-
const dir =
|
|
11456
|
+
const dir = join10(root, entry);
|
|
11594
11457
|
try {
|
|
11595
11458
|
if (!statSync8(dir).isDirectory()) continue;
|
|
11596
11459
|
} catch {
|
|
11597
11460
|
continue;
|
|
11598
11461
|
}
|
|
11599
11462
|
const manifestPath = pluginManifestPath(dir);
|
|
11600
|
-
if (!
|
|
11463
|
+
if (!existsSync15(manifestPath)) continue;
|
|
11601
11464
|
try {
|
|
11602
11465
|
const manifest = readPluginManifest(manifestPath);
|
|
11603
11466
|
const contentHash = hashPluginContent(dir, manifest);
|
|
@@ -11637,7 +11500,7 @@ function uniqueDirs(plugin, rels) {
|
|
|
11637
11500
|
for (const rel of rels) {
|
|
11638
11501
|
const abs = resolve6(plugin.dir, rel);
|
|
11639
11502
|
if (!abs.startsWith(resolve6(plugin.dir))) continue;
|
|
11640
|
-
if (
|
|
11503
|
+
if (existsSync15(abs)) dirs.add(statSync8(abs).isDirectory() ? abs : dirname7(abs));
|
|
11641
11504
|
}
|
|
11642
11505
|
return [...dirs];
|
|
11643
11506
|
}
|
|
@@ -11777,13 +11640,13 @@ function normalizeName(name) {
|
|
|
11777
11640
|
function isStringArray(value) {
|
|
11778
11641
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
11779
11642
|
}
|
|
11780
|
-
function parseAgentConfig(raw,
|
|
11643
|
+
function parseAgentConfig(raw, source2, path3) {
|
|
11781
11644
|
if (!raw || typeof raw !== "object") return null;
|
|
11782
11645
|
const obj = raw;
|
|
11783
11646
|
if (typeof obj.name !== "string" || obj.name.trim() === "") return null;
|
|
11784
11647
|
const cfg = {
|
|
11785
11648
|
name: obj.name.trim(),
|
|
11786
|
-
source,
|
|
11649
|
+
source: source2,
|
|
11787
11650
|
path: path3
|
|
11788
11651
|
};
|
|
11789
11652
|
if (typeof obj.description === "string") cfg.description = obj.description;
|
|
@@ -11799,15 +11662,15 @@ function parseAgentConfig(raw, source, path3) {
|
|
|
11799
11662
|
if (typeof obj.contextPolicy === "string") cfg.contextPolicy = obj.contextPolicy;
|
|
11800
11663
|
return cfg;
|
|
11801
11664
|
}
|
|
11802
|
-
function loadAgentDir(dir,
|
|
11803
|
-
if (!
|
|
11665
|
+
function loadAgentDir(dir, source2) {
|
|
11666
|
+
if (!existsSync16(dir)) return [];
|
|
11804
11667
|
const out = [];
|
|
11805
11668
|
for (const file of readdirSync8(dir)) {
|
|
11806
11669
|
if (!file.endsWith(".json")) continue;
|
|
11807
|
-
const path3 =
|
|
11670
|
+
const path3 = join11(dir, file);
|
|
11808
11671
|
try {
|
|
11809
|
-
const parsed = JSON.parse(
|
|
11810
|
-
const cfg = parseAgentConfig(parsed,
|
|
11672
|
+
const parsed = JSON.parse(readFileSync11(path3, "utf-8"));
|
|
11673
|
+
const cfg = parseAgentConfig(parsed, source2, path3);
|
|
11811
11674
|
if (cfg) out.push(cfg);
|
|
11812
11675
|
} catch {
|
|
11813
11676
|
}
|
|
@@ -11816,8 +11679,8 @@ function loadAgentDir(dir, source) {
|
|
|
11816
11679
|
}
|
|
11817
11680
|
function getAgentDirs(configDir, cwd = process.cwd()) {
|
|
11818
11681
|
return {
|
|
11819
|
-
user:
|
|
11820
|
-
project:
|
|
11682
|
+
user: join11(configDir ?? join11(homedir5(), CONFIG_DIR_NAME), "agents"),
|
|
11683
|
+
project: join11(cwd, CONFIG_DIR_NAME, "agents")
|
|
11821
11684
|
};
|
|
11822
11685
|
}
|
|
11823
11686
|
function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
@@ -12320,9 +12183,8 @@ var spawnAgentTool = {
|
|
|
12320
12183
|
};
|
|
12321
12184
|
|
|
12322
12185
|
// src/tools/builtin/task-manager.ts
|
|
12323
|
-
import { spawn as
|
|
12186
|
+
import { spawn as spawn4 } from "child_process";
|
|
12324
12187
|
import { randomUUID } from "crypto";
|
|
12325
|
-
import { platform as platform5 } from "os";
|
|
12326
12188
|
var MAX_OUTPUT_CHARS = 1e4;
|
|
12327
12189
|
var MAX_OUTPUT_BYTES = 64 * 1024;
|
|
12328
12190
|
var MAX_TASKS = 20;
|
|
@@ -12341,14 +12203,16 @@ function decodeField(chunks) {
|
|
|
12341
12203
|
}
|
|
12342
12204
|
function appendOutput(task, field, chunk) {
|
|
12343
12205
|
const chunks = field === "stdout" ? task._stdoutChunks : task._stderrChunks;
|
|
12344
|
-
chunks.push(chunk);
|
|
12206
|
+
chunks.push(chunk.length > MAX_OUTPUT_BYTES ? chunk.subarray(-MAX_OUTPUT_BYTES) : chunk);
|
|
12345
12207
|
capChunks(chunks);
|
|
12346
12208
|
task[field] = decodeField(chunks);
|
|
12347
12209
|
}
|
|
12348
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.");
|
|
12349
12213
|
if (tasks.size >= MAX_TASKS) {
|
|
12350
12214
|
for (const [id2, t] of tasks) {
|
|
12351
|
-
if (t.
|
|
12215
|
+
if (!t._active) {
|
|
12352
12216
|
tasks.delete(id2);
|
|
12353
12217
|
break;
|
|
12354
12218
|
}
|
|
@@ -12356,16 +12220,19 @@ function createTask(command, description) {
|
|
|
12356
12220
|
}
|
|
12357
12221
|
const id = randomUUID().slice(0, 8);
|
|
12358
12222
|
const { shell, args } = shellInvocation(command);
|
|
12359
|
-
const proc =
|
|
12223
|
+
const proc = spawn4(shell, args, {
|
|
12360
12224
|
stdio: ["ignore", "pipe", "pipe"],
|
|
12361
|
-
detached:
|
|
12225
|
+
detached: process.platform !== "win32",
|
|
12226
|
+
windowsHide: true,
|
|
12362
12227
|
env: { ...process.env, PYTHONUTF8: "1", PYTHONIOENCODING: "utf-8" }
|
|
12363
12228
|
});
|
|
12364
12229
|
const task = {
|
|
12365
12230
|
id,
|
|
12231
|
+
owner: getExecutionConfigDir(),
|
|
12366
12232
|
command,
|
|
12367
12233
|
description,
|
|
12368
12234
|
process: proc,
|
|
12235
|
+
_active: true,
|
|
12369
12236
|
startTime: Date.now(),
|
|
12370
12237
|
endTime: null,
|
|
12371
12238
|
status: "running",
|
|
@@ -12378,11 +12245,13 @@ function createTask(command, description) {
|
|
|
12378
12245
|
proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk));
|
|
12379
12246
|
proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk));
|
|
12380
12247
|
proc.on("close", (code) => {
|
|
12248
|
+
task._active = false;
|
|
12381
12249
|
task.endTime = Date.now();
|
|
12382
12250
|
task.exitCode = code;
|
|
12383
|
-
task.status = code === 0 ? "completed" : "failed";
|
|
12251
|
+
if (task.status !== "stopped") task.status = code === 0 ? "completed" : "failed";
|
|
12384
12252
|
});
|
|
12385
12253
|
proc.on("error", (err) => {
|
|
12254
|
+
task._active = false;
|
|
12386
12255
|
task.endTime = Date.now();
|
|
12387
12256
|
task.status = "failed";
|
|
12388
12257
|
task.stderr += `
|
|
@@ -12392,43 +12261,20 @@ Process error: ${err.message}`;
|
|
|
12392
12261
|
return task;
|
|
12393
12262
|
}
|
|
12394
12263
|
function listTasks() {
|
|
12395
|
-
return [...tasks.values()].map(
|
|
12396
|
-
({ 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
|
|
12397
12266
|
);
|
|
12398
12267
|
}
|
|
12399
12268
|
function getTaskOutput(id) {
|
|
12400
12269
|
const task = tasks.get(id);
|
|
12401
|
-
if (!task) return null;
|
|
12270
|
+
if (!task || task.owner !== getExecutionConfigDir()) return null;
|
|
12402
12271
|
return { stdout: task.stdout, stderr: task.stderr, status: task.status };
|
|
12403
12272
|
}
|
|
12404
12273
|
function stopTask(id) {
|
|
12405
12274
|
const task = tasks.get(id);
|
|
12406
|
-
if (!task || task.status !== "running") return false;
|
|
12275
|
+
if (!task || task.owner !== getExecutionConfigDir() || task.status !== "running") return false;
|
|
12407
12276
|
try {
|
|
12408
|
-
|
|
12409
|
-
if (platform5() === "win32" && proc.pid) {
|
|
12410
|
-
try {
|
|
12411
|
-
spawn5("taskkill", ["/PID", String(proc.pid), "/T", "/F"], {
|
|
12412
|
-
windowsHide: true,
|
|
12413
|
-
stdio: "ignore"
|
|
12414
|
-
});
|
|
12415
|
-
} catch {
|
|
12416
|
-
}
|
|
12417
|
-
try {
|
|
12418
|
-
proc.kill();
|
|
12419
|
-
} catch {
|
|
12420
|
-
}
|
|
12421
|
-
} else {
|
|
12422
|
-
proc.kill("SIGTERM");
|
|
12423
|
-
setTimeout(() => {
|
|
12424
|
-
if (proc.exitCode === null && proc.signalCode === null) {
|
|
12425
|
-
try {
|
|
12426
|
-
proc.kill("SIGKILL");
|
|
12427
|
-
} catch {
|
|
12428
|
-
}
|
|
12429
|
-
}
|
|
12430
|
-
}, 3e3).unref();
|
|
12431
|
-
}
|
|
12277
|
+
killProcessTree(task.process, true);
|
|
12432
12278
|
task.status = "stopped";
|
|
12433
12279
|
task.endTime = Date.now();
|
|
12434
12280
|
return true;
|
|
@@ -12536,14 +12382,14 @@ var taskStopTool = {
|
|
|
12536
12382
|
|
|
12537
12383
|
// src/tools/builtin/git-tools.ts
|
|
12538
12384
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
12539
|
-
import { existsSync as
|
|
12540
|
-
import { join as
|
|
12385
|
+
import { existsSync as existsSync17 } from "fs";
|
|
12386
|
+
import { join as join12 } from "path";
|
|
12541
12387
|
function assertGitRepo(cwd) {
|
|
12542
12388
|
let dir = cwd;
|
|
12543
12389
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
12544
12390
|
while (dir && dir !== root) {
|
|
12545
|
-
if (
|
|
12546
|
-
const parent =
|
|
12391
|
+
if (existsSync17(join12(dir, ".git"))) return;
|
|
12392
|
+
const parent = join12(dir, "..");
|
|
12547
12393
|
if (parent === dir) break;
|
|
12548
12394
|
dir = parent;
|
|
12549
12395
|
}
|
|
@@ -12808,7 +12654,7 @@ ${commitOutput.trim()}`;
|
|
|
12808
12654
|
};
|
|
12809
12655
|
|
|
12810
12656
|
// src/tools/builtin/notebook-edit.ts
|
|
12811
|
-
import { readFileSync as
|
|
12657
|
+
import { readFileSync as readFileSync12, existsSync as existsSync18 } from "fs";
|
|
12812
12658
|
import { writeFile } from "fs/promises";
|
|
12813
12659
|
import { resolve as resolve7, extname as extname2 } from "path";
|
|
12814
12660
|
var notebookEditTool = {
|
|
@@ -12863,10 +12709,10 @@ var notebookEditTool = {
|
|
|
12863
12709
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
12864
12710
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
12865
12711
|
}
|
|
12866
|
-
if (!
|
|
12712
|
+
if (!existsSync18(absPath)) {
|
|
12867
12713
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
12868
12714
|
}
|
|
12869
|
-
const raw =
|
|
12715
|
+
const raw = readFileSync12(absPath, "utf-8");
|
|
12870
12716
|
const nb = parseNotebook(raw);
|
|
12871
12717
|
const cellIdx0 = cellIndexRaw - 1;
|
|
12872
12718
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -13192,8 +13038,6 @@ ${lines.join("\n")}`;
|
|
|
13192
13038
|
};
|
|
13193
13039
|
|
|
13194
13040
|
// src/tools/builtin/recall-memory.ts
|
|
13195
|
-
import { join as join15 } from "path";
|
|
13196
|
-
import { homedir as homedir7 } from "os";
|
|
13197
13041
|
function formatHit(h, i) {
|
|
13198
13042
|
const ts = h.chunk.timestamp.slice(0, 16).replace("T", " ");
|
|
13199
13043
|
const title = h.chunk.sessionTitle ? ` \xB7 ${h.chunk.sessionTitle}` : "";
|
|
@@ -13243,7 +13087,7 @@ var recallMemoryTool = {
|
|
|
13243
13087
|
const excludeCurrent = Boolean(args["excludeCurrentSession"]);
|
|
13244
13088
|
const currentId = args["currentSessionId"] ? String(args["currentSessionId"]) : void 0;
|
|
13245
13089
|
const minScore = args["minScore"] !== void 0 ? Number(args["minScore"]) : 0.25;
|
|
13246
|
-
const configDir =
|
|
13090
|
+
const configDir = getExecutionConfigDir();
|
|
13247
13091
|
let persistentSection = "";
|
|
13248
13092
|
try {
|
|
13249
13093
|
const persistentHits = searchPersistentMemories(configDir, query, process.cwd(), 3);
|
|
@@ -13297,8 +13141,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
13297
13141
|
|
|
13298
13142
|
// src/tools/registry.ts
|
|
13299
13143
|
import { pathToFileURL } from "url";
|
|
13300
|
-
import { existsSync as
|
|
13301
|
-
import { join as
|
|
13144
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
|
|
13145
|
+
import { join as join13 } from "path";
|
|
13302
13146
|
var ToolRegistry = class {
|
|
13303
13147
|
tools = /* @__PURE__ */ new Map();
|
|
13304
13148
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -13459,7 +13303,7 @@ var ToolRegistry = class {
|
|
|
13459
13303
|
* Returns the number of successfully loaded plugins.
|
|
13460
13304
|
*/
|
|
13461
13305
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
13462
|
-
if (!
|
|
13306
|
+
if (!existsSync19(pluginsDir)) {
|
|
13463
13307
|
try {
|
|
13464
13308
|
mkdirSync9(pluginsDir, { recursive: true });
|
|
13465
13309
|
} catch {
|
|
@@ -13485,12 +13329,12 @@ var ToolRegistry = class {
|
|
|
13485
13329
|
process.stderr.write(
|
|
13486
13330
|
`
|
|
13487
13331
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
13488
|
-
` + files.map((f) => ` + ${
|
|
13332
|
+
` + files.map((f) => ` + ${join13(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
13489
13333
|
);
|
|
13490
13334
|
let loaded = 0;
|
|
13491
13335
|
for (const file of files) {
|
|
13492
13336
|
try {
|
|
13493
|
-
const fileUrl = pathToFileURL(
|
|
13337
|
+
const fileUrl = pathToFileURL(join13(pluginsDir, file)).href;
|
|
13494
13338
|
const mod = await import(fileUrl);
|
|
13495
13339
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
13496
13340
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -13518,7 +13362,7 @@ var ToolRegistry = class {
|
|
|
13518
13362
|
};
|
|
13519
13363
|
|
|
13520
13364
|
// src/mcp/client.ts
|
|
13521
|
-
import { spawn as
|
|
13365
|
+
import { spawn as spawn5 } from "child_process";
|
|
13522
13366
|
var McpClient = class {
|
|
13523
13367
|
serverId;
|
|
13524
13368
|
config;
|
|
@@ -13556,7 +13400,7 @@ var McpClient = class {
|
|
|
13556
13400
|
async connect() {
|
|
13557
13401
|
const timeout = this.config.timeout ?? MCP_CONNECT_TIMEOUT;
|
|
13558
13402
|
try {
|
|
13559
|
-
this.process =
|
|
13403
|
+
this.process = spawn5(this.config.command, this.config.args ?? [], {
|
|
13560
13404
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13561
13405
|
env: { ...process.env, ...this.config.env },
|
|
13562
13406
|
// Windows 上 npx 等是 .cmd 脚本,需要 shell 模式
|
|
@@ -14035,11 +13879,11 @@ var McpManager = class {
|
|
|
14035
13879
|
};
|
|
14036
13880
|
|
|
14037
13881
|
// src/skills/manager.ts
|
|
14038
|
-
import { existsSync as
|
|
14039
|
-
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";
|
|
14040
13884
|
|
|
14041
13885
|
// src/skills/types.ts
|
|
14042
|
-
import { readFileSync as
|
|
13886
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
14043
13887
|
import { basename as basename7 } from "path";
|
|
14044
13888
|
function parseSimpleYaml(yaml) {
|
|
14045
13889
|
const result = {};
|
|
@@ -14061,7 +13905,7 @@ function parseYamlArray(value) {
|
|
|
14061
13905
|
function parseSkillFile(filePath) {
|
|
14062
13906
|
let raw;
|
|
14063
13907
|
try {
|
|
14064
|
-
raw =
|
|
13908
|
+
raw = readFileSync13(filePath, "utf-8");
|
|
14065
13909
|
} catch {
|
|
14066
13910
|
return null;
|
|
14067
13911
|
}
|
|
@@ -14109,7 +13953,7 @@ var SkillManager = class {
|
|
|
14109
13953
|
return this.skills.size;
|
|
14110
13954
|
}
|
|
14111
13955
|
loadSkillDir(dir, createIfMissing) {
|
|
14112
|
-
if (!
|
|
13956
|
+
if (!existsSync20(dir)) {
|
|
14113
13957
|
if (createIfMissing) {
|
|
14114
13958
|
try {
|
|
14115
13959
|
mkdirSync10(dir, { recursive: true });
|
|
@@ -14126,14 +13970,14 @@ var SkillManager = class {
|
|
|
14126
13970
|
}
|
|
14127
13971
|
for (const entry of entries) {
|
|
14128
13972
|
let filePath;
|
|
14129
|
-
const fullPath =
|
|
13973
|
+
const fullPath = join14(dir, entry);
|
|
14130
13974
|
if (entry.endsWith(".md")) {
|
|
14131
13975
|
filePath = fullPath;
|
|
14132
13976
|
} else {
|
|
14133
13977
|
try {
|
|
14134
13978
|
if (statSync9(fullPath).isDirectory()) {
|
|
14135
|
-
const skillMd =
|
|
14136
|
-
if (
|
|
13979
|
+
const skillMd = join14(fullPath, "SKILL.md");
|
|
13980
|
+
if (existsSync20(skillMd)) filePath = skillMd;
|
|
14137
13981
|
else continue;
|
|
14138
13982
|
} else {
|
|
14139
13983
|
continue;
|
|
@@ -14246,7 +14090,7 @@ function sendSocketMessage(ws, message) {
|
|
|
14246
14090
|
}
|
|
14247
14091
|
|
|
14248
14092
|
// src/web/tool-executor-web.ts
|
|
14249
|
-
import { existsSync as
|
|
14093
|
+
import { existsSync as existsSync21, readFileSync as readFileSync14 } from "fs";
|
|
14250
14094
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
14251
14095
|
constructor(registry, ws) {
|
|
14252
14096
|
this.registry = registry;
|
|
@@ -14393,9 +14237,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
14393
14237
|
getDiffPreview(call) {
|
|
14394
14238
|
if (call.name === "write_file") {
|
|
14395
14239
|
const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
|
|
14396
|
-
if (filePath &&
|
|
14240
|
+
if (filePath && existsSync21(filePath)) {
|
|
14397
14241
|
try {
|
|
14398
|
-
const old =
|
|
14242
|
+
const old = readFileSync14(filePath, "utf-8");
|
|
14399
14243
|
return renderDiff(old, append ? old + chunk : chunk, { filePath });
|
|
14400
14244
|
} catch {
|
|
14401
14245
|
}
|
|
@@ -14631,16 +14475,16 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
14631
14475
|
};
|
|
14632
14476
|
|
|
14633
14477
|
// src/repl/dev-state.ts
|
|
14634
|
-
import { existsSync as
|
|
14635
|
-
import { join as
|
|
14636
|
-
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";
|
|
14637
14481
|
function getDevStatePath() {
|
|
14638
|
-
return
|
|
14482
|
+
return join15(homedir6(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
14639
14483
|
}
|
|
14640
14484
|
function loadDevState() {
|
|
14641
14485
|
const path3 = getDevStatePath();
|
|
14642
|
-
if (!
|
|
14643
|
-
const content =
|
|
14486
|
+
if (!existsSync22(path3)) return null;
|
|
14487
|
+
const content = readFileSync15(path3, "utf-8").trim();
|
|
14644
14488
|
return content || null;
|
|
14645
14489
|
}
|
|
14646
14490
|
|
|
@@ -15010,8 +14854,8 @@ var checkpointDeletedMessage = (name) => `\u2713 Deleted checkpoint "${name}"`;
|
|
|
15010
14854
|
var checkpointNotFoundMessage = (name) => `Checkpoint "${name}" not found.`;
|
|
15011
14855
|
|
|
15012
14856
|
// src/core/context-files.ts
|
|
15013
|
-
import { existsSync as
|
|
15014
|
-
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";
|
|
15015
14859
|
function uniqueNonEmpty(values) {
|
|
15016
14860
|
const seen = /* @__PURE__ */ new Set();
|
|
15017
14861
|
const out = [];
|
|
@@ -15041,7 +14885,7 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
15041
14885
|
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
15042
14886
|
const shown = displayPath(filePath, cwd);
|
|
15043
14887
|
try {
|
|
15044
|
-
const raw =
|
|
14888
|
+
const raw = readFileSync16(filePath);
|
|
15045
14889
|
const byteCount = raw.byteLength;
|
|
15046
14890
|
if (byteCount === 0) {
|
|
15047
14891
|
return {
|
|
@@ -15088,8 +14932,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
15088
14932
|
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
15089
14933
|
const skipped = [];
|
|
15090
14934
|
for (const candidate of candidates) {
|
|
15091
|
-
const filePath =
|
|
15092
|
-
if (!
|
|
14935
|
+
const filePath = join16(dir, candidate);
|
|
14936
|
+
if (!existsSync23(filePath)) continue;
|
|
15093
14937
|
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
15094
14938
|
if (result.skipped) skipped.push(result.skipped);
|
|
15095
14939
|
if (result.layer) return { layer: result.layer, skipped };
|
|
@@ -15116,7 +14960,7 @@ function loadContextFiles(options) {
|
|
|
15116
14960
|
});
|
|
15117
14961
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
15118
14962
|
}
|
|
15119
|
-
if (!
|
|
14963
|
+
if (!existsSync23(filePath)) {
|
|
15120
14964
|
skipped.push({
|
|
15121
14965
|
level: "single",
|
|
15122
14966
|
filePath,
|
|
@@ -15229,8 +15073,8 @@ import { mkdirSync as mkdirSync15, createWriteStream as createWriteStream2 } fro
|
|
|
15229
15073
|
import { dirname as dirname10 } from "path";
|
|
15230
15074
|
|
|
15231
15075
|
// src/repl/commands/project-init.ts
|
|
15232
|
-
import { existsSync as
|
|
15233
|
-
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";
|
|
15234
15078
|
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
15235
15079
|
"node_modules",
|
|
15236
15080
|
".git",
|
|
@@ -15274,11 +15118,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
15274
15118
|
const sorted = filtered.sort((a, b) => {
|
|
15275
15119
|
let aIsDir = false, bIsDir = false;
|
|
15276
15120
|
try {
|
|
15277
|
-
aIsDir = statSync11(
|
|
15121
|
+
aIsDir = statSync11(join17(d, a)).isDirectory();
|
|
15278
15122
|
} catch {
|
|
15279
15123
|
}
|
|
15280
15124
|
try {
|
|
15281
|
-
bIsDir = statSync11(
|
|
15125
|
+
bIsDir = statSync11(join17(d, b)).isDirectory();
|
|
15282
15126
|
} catch {
|
|
15283
15127
|
}
|
|
15284
15128
|
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
@@ -15286,7 +15130,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
15286
15130
|
});
|
|
15287
15131
|
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
15288
15132
|
const name = sorted[i];
|
|
15289
|
-
const fullPath =
|
|
15133
|
+
const fullPath = join17(d, name);
|
|
15290
15134
|
const isLast = i === sorted.length - 1;
|
|
15291
15135
|
const connector = isLast ? "+-- " : "|-- ";
|
|
15292
15136
|
let isDir;
|
|
@@ -15313,7 +15157,7 @@ function scanProject(cwd) {
|
|
|
15313
15157
|
configFiles: [],
|
|
15314
15158
|
directoryStructure: ""
|
|
15315
15159
|
};
|
|
15316
|
-
const check = (file) =>
|
|
15160
|
+
const check = (file) => existsSync24(join17(cwd, file));
|
|
15317
15161
|
const configCandidates = [
|
|
15318
15162
|
"package.json",
|
|
15319
15163
|
"tsconfig.json",
|
|
@@ -15340,7 +15184,7 @@ function scanProject(cwd) {
|
|
|
15340
15184
|
info.type = "node";
|
|
15341
15185
|
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
15342
15186
|
try {
|
|
15343
|
-
const pkg = JSON.parse(
|
|
15187
|
+
const pkg = JSON.parse(readFileSync17(join17(cwd, "package.json"), "utf-8"));
|
|
15344
15188
|
const scripts = pkg.scripts ?? {};
|
|
15345
15189
|
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
15346
15190
|
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
@@ -16108,7 +15952,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
|
|
|
16108
15952
|
}
|
|
16109
15953
|
|
|
16110
15954
|
// src/hub/persist.ts
|
|
16111
|
-
import { join as
|
|
15955
|
+
import { join as join18 } from "path";
|
|
16112
15956
|
function discussionToMessages(state2) {
|
|
16113
15957
|
const out = [];
|
|
16114
15958
|
const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -16146,7 +15990,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
|
|
|
16146
15990
|
session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
|
|
16147
15991
|
session.titleAiGenerated = true;
|
|
16148
15992
|
await sm.save();
|
|
16149
|
-
return { id: session.id, path:
|
|
15993
|
+
return { id: session.id, path: join18(config.getHistoryDir(), `${session.id}.json`) };
|
|
16150
15994
|
}
|
|
16151
15995
|
|
|
16152
15996
|
// src/core/think-command.ts
|
|
@@ -16267,10 +16111,10 @@ var PLAN_MODE_TOOL_HINT = [
|
|
|
16267
16111
|
"read_file \xB7 list_dir \xB7 grep_files \xB7 glob_files",
|
|
16268
16112
|
"web_fetch \xB7 google_search \xB7 ask_user \xB7 write_todos"
|
|
16269
16113
|
];
|
|
16270
|
-
function applyPlanCommand(sub,
|
|
16271
|
-
const key = (sub ?? "enter").toLowerCase() === "toggle" ?
|
|
16114
|
+
function applyPlanCommand(sub, active2) {
|
|
16115
|
+
const key = (sub ?? "enter").toLowerCase() === "toggle" ? active2 ? "exit" : "enter" : (sub ?? "enter").toLowerCase();
|
|
16272
16116
|
if (key === "enter") {
|
|
16273
|
-
if (
|
|
16117
|
+
if (active2) {
|
|
16274
16118
|
return {
|
|
16275
16119
|
planMode: true,
|
|
16276
16120
|
changed: false,
|
|
@@ -16293,7 +16137,7 @@ function applyPlanCommand(sub, active) {
|
|
|
16293
16137
|
};
|
|
16294
16138
|
}
|
|
16295
16139
|
if (key === "execute") {
|
|
16296
|
-
if (!
|
|
16140
|
+
if (!active2) {
|
|
16297
16141
|
return { planMode: false, changed: false, status: "not-active", summary: "Not in Plan Mode. Enter first with /plan", details: [] };
|
|
16298
16142
|
}
|
|
16299
16143
|
return {
|
|
@@ -16305,22 +16149,22 @@ function applyPlanCommand(sub, active) {
|
|
|
16305
16149
|
};
|
|
16306
16150
|
}
|
|
16307
16151
|
if (key === "exit" || key === "cancel") {
|
|
16308
|
-
if (!
|
|
16152
|
+
if (!active2) {
|
|
16309
16153
|
return { planMode: false, changed: false, status: "not-active", summary: "Not in Plan Mode.", details: [] };
|
|
16310
16154
|
}
|
|
16311
16155
|
return { planMode: false, changed: true, status: "cancelled", summary: "Plan Mode cancelled. Returning to normal mode.", details: [] };
|
|
16312
16156
|
}
|
|
16313
16157
|
if (key === "status" || key === "show") {
|
|
16314
16158
|
return {
|
|
16315
|
-
planMode:
|
|
16159
|
+
planMode: active2,
|
|
16316
16160
|
changed: false,
|
|
16317
16161
|
status: "status",
|
|
16318
|
-
summary: `Plan Mode: ${
|
|
16319
|
-
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."] : []
|
|
16320
16164
|
};
|
|
16321
16165
|
}
|
|
16322
16166
|
return {
|
|
16323
|
-
planMode:
|
|
16167
|
+
planMode: active2,
|
|
16324
16168
|
changed: false,
|
|
16325
16169
|
status: "unknown",
|
|
16326
16170
|
summary: `Unknown /plan option "${sub}" \u2014 expected execute | exit | status`,
|
|
@@ -16592,8 +16436,8 @@ function buildSessionCostReport(input) {
|
|
|
16592
16436
|
const cacheHitPct = usage.cacheReadTokens > 0 ? Math.round(usage.cacheReadTokens / (usage.cacheReadTokens + usage.inputTokens) * 100) : null;
|
|
16593
16437
|
return { hasUsage: true, totalTokens, cacheHitPct, cost, costSource, savings };
|
|
16594
16438
|
}
|
|
16595
|
-
function costSourceNote(
|
|
16596
|
-
switch (
|
|
16439
|
+
function costSourceNote(source2) {
|
|
16440
|
+
switch (source2) {
|
|
16597
16441
|
case "accrued-partial":
|
|
16598
16442
|
return "(partial \u2014 some turns had unknown pricing)";
|
|
16599
16443
|
case "estimated":
|
|
@@ -16604,8 +16448,8 @@ function costSourceNote(source) {
|
|
|
16604
16448
|
}
|
|
16605
16449
|
|
|
16606
16450
|
// src/web/commands/_shared.ts
|
|
16607
|
-
import { existsSync as
|
|
16608
|
-
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";
|
|
16609
16453
|
|
|
16610
16454
|
// src/cli/review-prompts.ts
|
|
16611
16455
|
function buildReviewPrompt(diff, gitContextStr, detailed) {
|
|
@@ -16677,34 +16521,34 @@ function readGitDiff(options = {}) {
|
|
|
16677
16521
|
}
|
|
16678
16522
|
|
|
16679
16523
|
// src/diagnostics/doctor-report.ts
|
|
16680
|
-
import { existsSync as
|
|
16681
|
-
import { join as
|
|
16682
|
-
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";
|
|
16683
16527
|
|
|
16684
16528
|
// src/diagnostics/crash-log.ts
|
|
16685
16529
|
import {
|
|
16686
|
-
existsSync as
|
|
16530
|
+
existsSync as existsSync25,
|
|
16687
16531
|
mkdirSync as mkdirSync12,
|
|
16688
16532
|
readdirSync as readdirSync12,
|
|
16689
|
-
readFileSync as
|
|
16533
|
+
readFileSync as readFileSync18,
|
|
16690
16534
|
statSync as statSync12,
|
|
16691
16535
|
unlinkSync as unlinkSync6,
|
|
16692
16536
|
writeFileSync as writeFileSync2
|
|
16693
16537
|
} from "fs";
|
|
16694
|
-
import { join as
|
|
16695
|
-
import { homedir as
|
|
16538
|
+
import { join as join19 } from "path";
|
|
16539
|
+
import { homedir as homedir7, platform as platform4, release, arch } from "os";
|
|
16696
16540
|
var LOGS_DIR_NAME = "logs";
|
|
16697
16541
|
var CRASH_LOG_PREFIX = "crash-";
|
|
16698
16542
|
function getLogsDir(configDir) {
|
|
16699
|
-
const base = configDir ??
|
|
16700
|
-
return
|
|
16543
|
+
const base = configDir ?? join19(homedir7(), CONFIG_DIR_NAME);
|
|
16544
|
+
return join19(base, LOGS_DIR_NAME);
|
|
16701
16545
|
}
|
|
16702
16546
|
function truncate(s, n) {
|
|
16703
16547
|
return s.length <= n ? s : s.slice(0, n) + "\u2026";
|
|
16704
16548
|
}
|
|
16705
16549
|
function listRecentCrashes(limit = 10, configDir) {
|
|
16706
16550
|
const dir = getLogsDir(configDir);
|
|
16707
|
-
if (!
|
|
16551
|
+
if (!existsSync25(dir)) return [];
|
|
16708
16552
|
const files = [];
|
|
16709
16553
|
let names;
|
|
16710
16554
|
try {
|
|
@@ -16714,10 +16558,10 @@ function listRecentCrashes(limit = 10, configDir) {
|
|
|
16714
16558
|
}
|
|
16715
16559
|
for (const name of names) {
|
|
16716
16560
|
if (!name.startsWith(CRASH_LOG_PREFIX) || !name.endsWith(".log")) continue;
|
|
16717
|
-
const full =
|
|
16561
|
+
const full = join19(dir, name);
|
|
16718
16562
|
try {
|
|
16719
16563
|
const st = statSync12(full);
|
|
16720
|
-
const head =
|
|
16564
|
+
const head = readFileSync18(full, "utf-8").split("\n").slice(0, 20);
|
|
16721
16565
|
const tsLine = head.find((l) => l.startsWith("timestamp:"));
|
|
16722
16566
|
const kindLine = head.find((l) => l.startsWith("kind:"));
|
|
16723
16567
|
const msgIdx = head.findIndex((l) => l.trim() === "## message");
|
|
@@ -16736,8 +16580,8 @@ function listRecentCrashes(limit = 10, configDir) {
|
|
|
16736
16580
|
return files.slice(0, limit);
|
|
16737
16581
|
}
|
|
16738
16582
|
function getConfigDirUsage(configDir) {
|
|
16739
|
-
const base = configDir ??
|
|
16740
|
-
if (!
|
|
16583
|
+
const base = configDir ?? join19(homedir7(), CONFIG_DIR_NAME);
|
|
16584
|
+
if (!existsSync25(base)) return { totalBytes: 0, entries: [] };
|
|
16741
16585
|
const entries = [];
|
|
16742
16586
|
let total = 0;
|
|
16743
16587
|
let names;
|
|
@@ -16747,7 +16591,7 @@ function getConfigDirUsage(configDir) {
|
|
|
16747
16591
|
return { totalBytes: 0, entries: [] };
|
|
16748
16592
|
}
|
|
16749
16593
|
for (const name of names) {
|
|
16750
|
-
const full =
|
|
16594
|
+
const full = join19(base, name);
|
|
16751
16595
|
const bytes = dirSize(full);
|
|
16752
16596
|
entries.push({ name, bytes });
|
|
16753
16597
|
total += bytes;
|
|
@@ -16762,7 +16606,7 @@ function dirSize(path3) {
|
|
|
16762
16606
|
if (!st.isDirectory()) return 0;
|
|
16763
16607
|
let sum = 0;
|
|
16764
16608
|
for (const name of readdirSync12(path3)) {
|
|
16765
|
-
sum += dirSize(
|
|
16609
|
+
sum += dirSize(join19(path3, name));
|
|
16766
16610
|
}
|
|
16767
16611
|
return sum;
|
|
16768
16612
|
} catch {
|
|
@@ -16772,7 +16616,7 @@ function dirSize(path3) {
|
|
|
16772
16616
|
|
|
16773
16617
|
// src/diagnostics/doctor-report.ts
|
|
16774
16618
|
function checkFile(label, path3) {
|
|
16775
|
-
const exists =
|
|
16619
|
+
const exists = existsSync26(path3);
|
|
16776
16620
|
let sizeBytes = null;
|
|
16777
16621
|
if (exists) {
|
|
16778
16622
|
try {
|
|
@@ -16809,18 +16653,18 @@ function buildDoctorReport(options) {
|
|
|
16809
16653
|
version: VERSION,
|
|
16810
16654
|
npmCheck: options.npmCheck,
|
|
16811
16655
|
node: process.version,
|
|
16812
|
-
platform: `${
|
|
16656
|
+
platform: `${platform5()} ${release2()} (${arch2()})`,
|
|
16813
16657
|
cwd,
|
|
16814
16658
|
projectRoot,
|
|
16815
16659
|
configDir,
|
|
16816
16660
|
providers: options.providers.listAll().map((p) => ({ id: p.id, displayName: p.displayName, configured: p.configured })),
|
|
16817
16661
|
files: [
|
|
16818
|
-
checkFile("config.json",
|
|
16819
|
-
checkFile("memory.md",
|
|
16820
|
-
checkFile("memory.jsonl",
|
|
16821
|
-
checkFile("dev-state.md",
|
|
16822
|
-
checkFile("hooks-trust.json",
|
|
16823
|
-
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"))
|
|
16824
16668
|
],
|
|
16825
16669
|
context: {
|
|
16826
16670
|
enabled: contextSetting !== false,
|
|
@@ -16855,7 +16699,7 @@ function buildDoctorReport(options) {
|
|
|
16855
16699
|
disabled: hooks.filter((h) => h.disabled).length
|
|
16856
16700
|
},
|
|
16857
16701
|
plugins: {
|
|
16858
|
-
root:
|
|
16702
|
+
root: join20(configDir, "plugins"),
|
|
16859
16703
|
installed: plugins.length,
|
|
16860
16704
|
active: activeAssets.plugins.length,
|
|
16861
16705
|
invalid: plugins.filter((p) => !p.valid).length,
|
|
@@ -16870,7 +16714,7 @@ function buildDoctorReport(options) {
|
|
|
16870
16714
|
mcp: {
|
|
16871
16715
|
enabled: config.get("mcpEnabled"),
|
|
16872
16716
|
configured: Object.keys(mcpServers).length,
|
|
16873
|
-
projectConfigExists:
|
|
16717
|
+
projectConfigExists: existsSync26(join20(projectRoot, MCP_PROJECT_CONFIG_NAME)),
|
|
16874
16718
|
statuses: options.mcpStatuses ?? []
|
|
16875
16719
|
},
|
|
16876
16720
|
recentCrashes: listRecentCrashes(5, configDir),
|
|
@@ -17392,7 +17236,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
|
|
|
17392
17236
|
Cost: ${formatCost(cost)}` : "";
|
|
17393
17237
|
let memoryLine = "";
|
|
17394
17238
|
try {
|
|
17395
|
-
const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-
|
|
17239
|
+
const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-UNLCXUWH.js");
|
|
17396
17240
|
const pending = countPendingMemories2(ctx.config.getConfigDir());
|
|
17397
17241
|
if (pending > 0) memoryLine = `
|
|
17398
17242
|
Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
|
|
@@ -18015,7 +17859,7 @@ async function handleRewind(args, ctx) {
|
|
|
18015
17859
|
ctx.send({ type: "error", message: intent.message });
|
|
18016
17860
|
return;
|
|
18017
17861
|
}
|
|
18018
|
-
const { fileCheckpoints: fileCheckpoints2 } = await import("./file-checkpoint-
|
|
17862
|
+
const { fileCheckpoints: fileCheckpoints2 } = await import("./file-checkpoint-64A2AXHE.js");
|
|
18019
17863
|
if (intent.kind === "list") {
|
|
18020
17864
|
const rows = buildRewindList(session.messages, fileCheckpoints2.getMessageIndices());
|
|
18021
17865
|
ctx.send({
|
|
@@ -18072,9 +17916,9 @@ function parseAgentCommand(args) {
|
|
|
18072
17916
|
function formatAgentList(agents, preferred) {
|
|
18073
17917
|
const lines = [`Agents (${agents.length})`, ""];
|
|
18074
17918
|
for (const agent of agents) {
|
|
18075
|
-
const
|
|
17919
|
+
const active2 = agent.name === preferred ? " *" : " ";
|
|
18076
17920
|
const model = [agent.provider, agent.model].filter(Boolean).join("/") || "inherit";
|
|
18077
|
-
lines.push(`${
|
|
17921
|
+
lines.push(`${active2} ${agent.name} [${agent.source ?? "builtin"}] \u2014 ${agent.description ?? ""}`);
|
|
18078
17922
|
lines.push(
|
|
18079
17923
|
` model: ${model} \xB7 permission: ${agent.permissionProfile ?? "workspace-write"} \xB7 maxRounds: ${agent.maxToolRounds ?? "inherit"}`
|
|
18080
17924
|
);
|
|
@@ -18133,7 +17977,7 @@ async function handlePlugin(args, ctx) {
|
|
|
18133
17977
|
lines.push("");
|
|
18134
17978
|
}
|
|
18135
17979
|
if (pluginTools.length > 0) {
|
|
18136
|
-
const legacyDir =
|
|
17980
|
+
const legacyDir = join21(configDir, PLUGINS_DIR_NAME);
|
|
18137
17981
|
lines.push(`**Legacy JS tools** (${pluginTools.length}):`);
|
|
18138
17982
|
lines.push(`Dir: ${legacyDir}`);
|
|
18139
17983
|
for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
|
|
@@ -18215,14 +18059,14 @@ async function handleSkill(args, ctx) {
|
|
|
18215
18059
|
return;
|
|
18216
18060
|
}
|
|
18217
18061
|
const intent = parseSkillCommand(args);
|
|
18218
|
-
const
|
|
18062
|
+
const active2 = ctx.skillManager.getActive();
|
|
18219
18063
|
if (intent.kind === "list") {
|
|
18220
18064
|
const skills = ctx.skillManager.listSkills();
|
|
18221
18065
|
if (skills.length === 0) {
|
|
18222
18066
|
ctx.send({ type: "info", message: "No skills available. Add .md files to ~/.aicli/skills/" });
|
|
18223
18067
|
} else {
|
|
18224
18068
|
const lines = skills.map((s) => {
|
|
18225
|
-
const isActive =
|
|
18069
|
+
const isActive = active2?.meta.name === s.meta.name;
|
|
18226
18070
|
const marker = isActive ? " \u2705 (active)" : "";
|
|
18227
18071
|
return ` ${s.meta.name}${marker} \u2014 ${s.meta.description || "(no description)"}`;
|
|
18228
18072
|
});
|
|
@@ -18238,12 +18082,12 @@ ${lines.join("\n")}` });
|
|
|
18238
18082
|
return;
|
|
18239
18083
|
}
|
|
18240
18084
|
const note = skillHistoryNote(
|
|
18241
|
-
|
|
18085
|
+
active2?.meta.name ?? "(none)",
|
|
18242
18086
|
intent.kind === "deactivate" ? "(none)" : intent.name,
|
|
18243
18087
|
ctx.sessions.current?.messages.length ?? 0
|
|
18244
18088
|
);
|
|
18245
18089
|
if (intent.kind === "deactivate") {
|
|
18246
|
-
if (!
|
|
18090
|
+
if (!active2) {
|
|
18247
18091
|
ctx.send({ type: "info", message: "No skill is currently active." });
|
|
18248
18092
|
return;
|
|
18249
18093
|
}
|
|
@@ -18254,7 +18098,7 @@ ${note}` : "\u{1F50C} Skill deactivated." });
|
|
|
18254
18098
|
ctx.sendStatus();
|
|
18255
18099
|
return;
|
|
18256
18100
|
}
|
|
18257
|
-
if (
|
|
18101
|
+
if (active2?.meta.name === intent.name) {
|
|
18258
18102
|
ctx.send({ type: "info", message: `Skill '${intent.name}' is already active.` });
|
|
18259
18103
|
return;
|
|
18260
18104
|
}
|
|
@@ -18273,8 +18117,8 @@ ${note}` : head });
|
|
|
18273
18117
|
}
|
|
18274
18118
|
async function handleCommands(_args, ctx) {
|
|
18275
18119
|
const configDir = ctx.config.getConfigDir();
|
|
18276
|
-
const commandsDir =
|
|
18277
|
-
if (!
|
|
18120
|
+
const commandsDir = join21(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
18121
|
+
if (!existsSync27(commandsDir)) {
|
|
18278
18122
|
ctx.send({ type: "info", message: `No custom commands directory.
|
|
18279
18123
|
Create: ${commandsDir}/ with .md files.` });
|
|
18280
18124
|
return;
|
|
@@ -18380,7 +18224,7 @@ async function handleTest(args, ctx) {
|
|
|
18380
18224
|
const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
|
|
18381
18225
|
testArgs = isCommand ? { command: argStr } : { filter: argStr };
|
|
18382
18226
|
}
|
|
18383
|
-
const runTests = ctx.runTests ?? (await import("./run-tests-
|
|
18227
|
+
const runTests = ctx.runTests ?? (await import("./run-tests-KRNMFXAN.js")).executeTests;
|
|
18384
18228
|
const report = await runTests(testArgs);
|
|
18385
18229
|
ctx.send({ type: "info", message: report });
|
|
18386
18230
|
} catch (err) {
|
|
@@ -18404,9 +18248,9 @@ async function handleDiff(args, ctx) {
|
|
|
18404
18248
|
let modifiedFiles = 0;
|
|
18405
18249
|
const diffLines2 = [];
|
|
18406
18250
|
for (const [filePath, { earliest }] of fileMap) {
|
|
18407
|
-
const currentContent =
|
|
18251
|
+
const currentContent = existsSync27(filePath) ? (() => {
|
|
18408
18252
|
try {
|
|
18409
|
-
return
|
|
18253
|
+
return readFileSync19(filePath, "utf-8");
|
|
18410
18254
|
} catch {
|
|
18411
18255
|
return null;
|
|
18412
18256
|
}
|
|
@@ -18441,9 +18285,9 @@ async function handleDiff(args, ctx) {
|
|
|
18441
18285
|
}
|
|
18442
18286
|
async function handleInit(args, ctx) {
|
|
18443
18287
|
const cwd = process.cwd();
|
|
18444
|
-
const targetPath =
|
|
18288
|
+
const targetPath = join21(cwd, "AICLI.md");
|
|
18445
18289
|
const force = args.includes("--force");
|
|
18446
|
-
if (
|
|
18290
|
+
if (existsSync27(targetPath) && !force) {
|
|
18447
18291
|
ctx.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
18448
18292
|
Use /init --force to overwrite.` });
|
|
18449
18293
|
return;
|
|
@@ -18672,9 +18516,9 @@ async function handleContext(_args, ctx) {
|
|
|
18672
18516
|
async function handleIndex(args, ctx) {
|
|
18673
18517
|
const sub = (args[0] ?? "status").toLowerCase();
|
|
18674
18518
|
const root = process.cwd();
|
|
18675
|
-
const { loadIndex: loadIndex2, clearIndex } = await import("./store-
|
|
18676
|
-
const { indexProject: indexProject2 } = await import("./indexer-
|
|
18677
|
-
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");
|
|
18678
18522
|
if (sub === "status") {
|
|
18679
18523
|
const idx = loadIndex2(root);
|
|
18680
18524
|
const vec = loadVectorStore(root);
|
|
@@ -18724,7 +18568,7 @@ async function handleIndex(args, ctx) {
|
|
|
18724
18568
|
message: `Building semantic index for ${idx.symbolCount} symbols\u2026 (first run downloads ~117 MB model)`
|
|
18725
18569
|
});
|
|
18726
18570
|
try {
|
|
18727
|
-
const { rebuildSemanticIndex } = await import("./semantic-
|
|
18571
|
+
const { rebuildSemanticIndex } = await import("./semantic-TRQAZSRY.js");
|
|
18728
18572
|
const stats = await rebuildSemanticIndex(root);
|
|
18729
18573
|
const first = stats.modelFirstLoadMs ? ` (model load+first batch ${stats.modelFirstLoadMs}ms)` : "";
|
|
18730
18574
|
ctx.send({
|
|
@@ -18755,7 +18599,7 @@ async function handleMemory(args, ctx) {
|
|
|
18755
18599
|
if (intent.kind === "rebuild") {
|
|
18756
18600
|
ctx.send({ type: "info", message: rebuildStartMessage(intent.full) });
|
|
18757
18601
|
try {
|
|
18758
|
-
const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-
|
|
18602
|
+
const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
|
|
18759
18603
|
const stats = await buildChatIndex2({ full: intent.full });
|
|
18760
18604
|
ctx.send({ type: "info", message: rebuildDoneMessage(stats) });
|
|
18761
18605
|
} catch (err) {
|
|
@@ -18764,14 +18608,14 @@ async function handleMemory(args, ctx) {
|
|
|
18764
18608
|
return;
|
|
18765
18609
|
}
|
|
18766
18610
|
if (intent.kind === "index-clear") {
|
|
18767
|
-
const { clearChatIndex: clearChatIndex2 } = await import("./chat-index-
|
|
18611
|
+
const { clearChatIndex: clearChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
|
|
18768
18612
|
clearChatIndex2();
|
|
18769
18613
|
ctx.send({ type: "info", message: "Chat memory index cleared (persistent memory untouched)." });
|
|
18770
18614
|
return;
|
|
18771
18615
|
}
|
|
18772
18616
|
if (intent.kind === "status") {
|
|
18773
|
-
const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-
|
|
18774
|
-
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");
|
|
18775
18619
|
const memories = listMemoryEntries2(configDir, { includeExpired: true, includeRejected: true });
|
|
18776
18620
|
const injected = formatMemoryForPrompt2(configDir, process.cwd(), ctx.config.get("memory")?.maxChars);
|
|
18777
18621
|
const view = buildMemoryStatusView({
|
|
@@ -18794,8 +18638,8 @@ async function handleMemory(args, ctx) {
|
|
|
18794
18638
|
return;
|
|
18795
18639
|
}
|
|
18796
18640
|
if (intent.kind === "recall") {
|
|
18797
|
-
const { searchPersistentMemories: searchPersistentMemories2, touchMemoryReferences: touchMemoryReferences2 } = await import("./persistent-memory-
|
|
18798
|
-
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");
|
|
18799
18643
|
const persistentHits = searchPersistentMemories2(configDir, intent.query, process.cwd(), 3);
|
|
18800
18644
|
if (persistentHits.length > 0) {
|
|
18801
18645
|
touchMemoryReferences2(configDir, persistentHits.map((h) => h.entry.id));
|
|
@@ -18915,7 +18759,7 @@ async function handleMemory(args, ctx) {
|
|
|
18915
18759
|
ctx.handleMemoryManage(intent.action, args[1], intent.action === "expire" ? args[2] : void 0);
|
|
18916
18760
|
} else if (intent.kind === "export") {
|
|
18917
18761
|
if (args[1] === "md") {
|
|
18918
|
-
const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-
|
|
18762
|
+
const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-UNLCXUWH.js");
|
|
18919
18763
|
ctx.send({
|
|
18920
18764
|
type: "export_data",
|
|
18921
18765
|
format: "md",
|
|
@@ -18953,7 +18797,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
18953
18797
|
return;
|
|
18954
18798
|
}
|
|
18955
18799
|
const dirPath = resolve9(sub);
|
|
18956
|
-
if (!
|
|
18800
|
+
if (!existsSync27(dirPath)) {
|
|
18957
18801
|
ctx.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
18958
18802
|
return;
|
|
18959
18803
|
}
|
|
@@ -19317,7 +19161,7 @@ async function handleAgent(args, ctx) {
|
|
|
19317
19161
|
|
|
19318
19162
|
// src/repl/commands/utils.ts
|
|
19319
19163
|
import { execSync as execSync2 } from "child_process";
|
|
19320
|
-
import { platform as
|
|
19164
|
+
import { platform as platform6 } from "os";
|
|
19321
19165
|
|
|
19322
19166
|
// src/repl/commands/constants.ts
|
|
19323
19167
|
var SESSION_PAGE_SIZE = 20;
|
|
@@ -19330,7 +19174,7 @@ function fmtCtx(tokens) {
|
|
|
19330
19174
|
return `${tokens}`;
|
|
19331
19175
|
}
|
|
19332
19176
|
function copyToClipboard(text) {
|
|
19333
|
-
const plat =
|
|
19177
|
+
const plat = platform6();
|
|
19334
19178
|
if (plat === "win32") {
|
|
19335
19179
|
execSync2("clip", { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
19336
19180
|
} else if (plat === "darwin") {
|
|
@@ -19917,16 +19761,16 @@ No tools match "${filter}".
|
|
|
19917
19761
|
usage: "/mcp [reconnect [serverId] | trust-project]",
|
|
19918
19762
|
async execute(args, ctx) {
|
|
19919
19763
|
if (args[0] === "trust-project") {
|
|
19920
|
-
const { join:
|
|
19921
|
-
const { existsSync:
|
|
19764
|
+
const { join: join26 } = await import("path");
|
|
19765
|
+
const { existsSync: existsSync32 } = await import("fs");
|
|
19922
19766
|
const { getGitRoot: getGitRoot2 } = await import("./git-context-VNCA2GU7.js");
|
|
19923
|
-
const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-
|
|
19924
|
-
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");
|
|
19925
19769
|
const cwd = process.cwd();
|
|
19926
19770
|
const projectRoot = getGitRoot2(cwd) ?? cwd;
|
|
19927
|
-
const mcpPath =
|
|
19771
|
+
const mcpPath = join26(projectRoot, MCP_PROJECT_CONFIG_NAME2);
|
|
19928
19772
|
console.log();
|
|
19929
|
-
if (!
|
|
19773
|
+
if (!existsSync32(mcpPath)) {
|
|
19930
19774
|
console.log(theme.dim(` No .mcp.json in ${projectRoot}.`));
|
|
19931
19775
|
console.log();
|
|
19932
19776
|
return;
|
|
@@ -19999,8 +19843,8 @@ No tools match "${filter}".
|
|
|
19999
19843
|
const errorStr = s.error ? theme.error(`
|
|
20000
19844
|
Error: ${s.error}`) : "";
|
|
20001
19845
|
const serverLabel = s.serverName !== s.serverId ? ` (${s.serverName})` : "";
|
|
20002
|
-
const
|
|
20003
|
-
const sourceTag =
|
|
19846
|
+
const source2 = ctx.getMcpServerSource(s.serverId);
|
|
19847
|
+
const sourceTag = source2 === "project" ? theme.accent(" [project]") : theme.dim(" [global]");
|
|
20004
19848
|
console.log(` ${theme.accent(s.serverId)}${theme.dim(serverLabel)}${sourceTag} [${statusBadge}]${toolCountStr}${errorStr}`);
|
|
20005
19849
|
if (s.connected) {
|
|
20006
19850
|
const mcpTools = ctx.tools.listMcpTools().filter(
|
|
@@ -20028,7 +19872,7 @@ No tools match "${filter}".
|
|
|
20028
19872
|
}
|
|
20029
19873
|
if (!sub || sub === "list") {
|
|
20030
19874
|
const skills = manager.listSkills();
|
|
20031
|
-
const
|
|
19875
|
+
const active2 = manager.getActive();
|
|
20032
19876
|
console.log();
|
|
20033
19877
|
console.log(theme.heading(" \u{1F3AF} Agent Skills"));
|
|
20034
19878
|
console.log(theme.dim(" " + "\u2500".repeat(50)));
|
|
@@ -20038,7 +19882,7 @@ No tools match "${filter}".
|
|
|
20038
19882
|
console.log(theme.dim(" Format: YAML frontmatter (name/description/tools) + prompt body"));
|
|
20039
19883
|
} else {
|
|
20040
19884
|
for (const s of skills) {
|
|
20041
|
-
const isActive =
|
|
19885
|
+
const isActive = active2?.meta.name === s.meta.name;
|
|
20042
19886
|
const badge = isActive ? theme.success(" \u2713 active") : "";
|
|
20043
19887
|
const toolInfo = s.meta.tools ? theme.dim(` [${s.meta.tools.join(", ")}]`) : "";
|
|
20044
19888
|
console.log(
|
|
@@ -20126,7 +19970,7 @@ No tools match "${filter}".
|
|
|
20126
19970
|
}
|
|
20127
19971
|
|
|
20128
19972
|
// src/repl/commands/security-commands.ts
|
|
20129
|
-
import { platform as
|
|
19973
|
+
import { platform as platform7 } from "os";
|
|
20130
19974
|
import chalk6 from "chalk";
|
|
20131
19975
|
function createSecurityCommands() {
|
|
20132
19976
|
return [
|
|
@@ -20207,7 +20051,7 @@ ${outcome.summary}
|
|
|
20207
20051
|
usage: "/bug [--copy]",
|
|
20208
20052
|
execute(args, ctx) {
|
|
20209
20053
|
const doCopy = args.includes("--copy");
|
|
20210
|
-
const os =
|
|
20054
|
+
const os = platform7();
|
|
20211
20055
|
const nodeVer = process.version;
|
|
20212
20056
|
const provider = ctx.getCurrentProvider();
|
|
20213
20057
|
const model = ctx.getCurrentModel();
|
|
@@ -20361,12 +20205,12 @@ ${outcome.summary}
|
|
|
20361
20205
|
if (scanAll) {
|
|
20362
20206
|
const metas = ctx.sessions.listSessions();
|
|
20363
20207
|
console.log(theme.info(` Scanning ${metas.length} session(s)\u2026`));
|
|
20364
|
-
const { readFileSync:
|
|
20365
|
-
const { join:
|
|
20208
|
+
const { readFileSync: readFileSync23 } = await import("fs");
|
|
20209
|
+
const { join: join26 } = await import("path");
|
|
20366
20210
|
const historyDir = ctx.config.getHistoryDir();
|
|
20367
20211
|
for (const m of metas) {
|
|
20368
20212
|
try {
|
|
20369
|
-
const content =
|
|
20213
|
+
const content = readFileSync23(join26(historyDir, `${m.id}.json`), "utf-8");
|
|
20370
20214
|
const hits2 = scanString(content, opts);
|
|
20371
20215
|
if (hits2.length) {
|
|
20372
20216
|
filesWithHits++;
|
|
@@ -20413,8 +20257,8 @@ ${outcome.summary}
|
|
|
20413
20257
|
}
|
|
20414
20258
|
|
|
20415
20259
|
// src/repl/commands/review-commands.ts
|
|
20416
|
-
import { existsSync as
|
|
20417
|
-
import { join as
|
|
20260
|
+
import { existsSync as existsSync28, readFileSync as readFileSync20, writeFileSync as writeFileSync4 } from "fs";
|
|
20261
|
+
import { join as join22 } from "path";
|
|
20418
20262
|
function createReviewCommands() {
|
|
20419
20263
|
return [
|
|
20420
20264
|
{
|
|
@@ -20588,11 +20432,11 @@ function createReviewCommands() {
|
|
|
20588
20432
|
Branches (${branches.length}):
|
|
20589
20433
|
`));
|
|
20590
20434
|
for (const b of branches) {
|
|
20591
|
-
const
|
|
20435
|
+
const active2 = b.id === session.activeBranchId ? theme.success("\u25CF ") : " ";
|
|
20592
20436
|
const parent = b.parentBranchId ? theme.dim(` \u2190 ${b.parentBranchId}@${b.parentMessageIndex}`) : "";
|
|
20593
20437
|
const count = b.id === session.activeBranchId ? session.messages.length : session.getBranchMessages(b.id)?.length ?? 0;
|
|
20594
20438
|
console.log(
|
|
20595
|
-
` ${
|
|
20439
|
+
` ${active2}${theme.accent(b.id.padEnd(10))} ${b.title.padEnd(20)} ` + theme.dim(`(${count} msgs)`) + parent
|
|
20596
20440
|
);
|
|
20597
20441
|
}
|
|
20598
20442
|
console.log();
|
|
@@ -20705,9 +20549,9 @@ function createReviewCommands() {
|
|
|
20705
20549
|
ctx.renderer.renderError(`Branch "${id}" not found.`);
|
|
20706
20550
|
return;
|
|
20707
20551
|
}
|
|
20708
|
-
const
|
|
20552
|
+
const active2 = session.activeBranchId;
|
|
20709
20553
|
console.log(`
|
|
20710
|
-
${theme.info("diff")} ${id} (source) \u2194 ${
|
|
20554
|
+
${theme.info("diff")} ${id} (source) \u2194 ${active2} (active)`);
|
|
20711
20555
|
console.log(theme.dim(` Common prefix: ${d.commonPrefix} message(s)`));
|
|
20712
20556
|
if (d.sourceOnly.length === 0 && d.targetOnly.length === 0) {
|
|
20713
20557
|
console.log(theme.success(" Branches are identical.\n"));
|
|
@@ -20726,7 +20570,7 @@ function createReviewCommands() {
|
|
|
20726
20570
|
}
|
|
20727
20571
|
if (d.targetOnly.length > 0) {
|
|
20728
20572
|
console.log(theme.dim(`
|
|
20729
|
-
Only in ${
|
|
20573
|
+
Only in ${active2} (active) (${d.targetOnly.length}):`));
|
|
20730
20574
|
d.targetOnly.forEach((m, i) => {
|
|
20731
20575
|
console.log(` ${theme.dim(`[${d.commonPrefix + i}]`)} ${m.role}: ${preview(m)}`);
|
|
20732
20576
|
});
|
|
@@ -20773,9 +20617,9 @@ function createReviewCommands() {
|
|
|
20773
20617
|
usage: "/init [--force]",
|
|
20774
20618
|
async execute(args, ctx) {
|
|
20775
20619
|
const cwd = process.cwd();
|
|
20776
|
-
const targetPath =
|
|
20620
|
+
const targetPath = join22(cwd, "AICLI.md");
|
|
20777
20621
|
const force = args.includes("--force");
|
|
20778
|
-
if (
|
|
20622
|
+
if (existsSync28(targetPath) && !force) {
|
|
20779
20623
|
ctx.renderer.printInfo(`AICLI.md already exists at ${targetPath}`);
|
|
20780
20624
|
ctx.renderer.printInfo("Use /init --force to overwrite, or edit it manually.");
|
|
20781
20625
|
return;
|
|
@@ -20839,7 +20683,7 @@ function createReviewCommands() {
|
|
|
20839
20683
|
usage: "/test [command|filter]",
|
|
20840
20684
|
async execute(args, ctx) {
|
|
20841
20685
|
try {
|
|
20842
|
-
const { executeTests } = await import("./run-tests-
|
|
20686
|
+
const { executeTests } = await import("./run-tests-KRNMFXAN.js");
|
|
20843
20687
|
const argStr = args.join(" ").trim();
|
|
20844
20688
|
let testArgs = {};
|
|
20845
20689
|
if (argStr) {
|
|
@@ -20874,9 +20718,9 @@ function createReviewCommands() {
|
|
|
20874
20718
|
let newFiles = 0;
|
|
20875
20719
|
let modifiedFiles = 0;
|
|
20876
20720
|
for (const [filePath, { earliest }] of fileMap) {
|
|
20877
|
-
const currentContent =
|
|
20721
|
+
const currentContent = existsSync28(filePath) ? (() => {
|
|
20878
20722
|
try {
|
|
20879
|
-
return
|
|
20723
|
+
return readFileSync20(filePath, "utf-8");
|
|
20880
20724
|
} catch {
|
|
20881
20725
|
return null;
|
|
20882
20726
|
}
|
|
@@ -21090,9 +20934,9 @@ function createMemoryCommands() {
|
|
|
21090
20934
|
async execute(args, ctx) {
|
|
21091
20935
|
const sub = (args[0] ?? "status").toLowerCase();
|
|
21092
20936
|
const root = process.cwd();
|
|
21093
|
-
const { loadIndex: loadIndex2, clearIndex } = await import("./store-
|
|
21094
|
-
const { indexProject: indexProject2 } = await import("./indexer-
|
|
21095
|
-
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");
|
|
21096
20940
|
if (sub === "status") {
|
|
21097
20941
|
const idx = loadIndex2(root);
|
|
21098
20942
|
const vec = loadVectorStore(root);
|
|
@@ -21152,7 +20996,7 @@ function createMemoryCommands() {
|
|
|
21152
20996
|
}
|
|
21153
20997
|
console.log(theme.dim(` Building semantic index for ${idx.symbolCount} symbols\u2026`));
|
|
21154
20998
|
console.log(theme.dim(" (First run downloads ~117 MB embedding model to ~/.aicli/models/)"));
|
|
21155
|
-
const { rebuildSemanticIndex } = await import("./semantic-
|
|
20999
|
+
const { rebuildSemanticIndex } = await import("./semantic-TRQAZSRY.js");
|
|
21156
21000
|
try {
|
|
21157
21001
|
const stats = await rebuildSemanticIndex(root, {
|
|
21158
21002
|
onProgress: (done, total) => {
|
|
@@ -21351,14 +21195,14 @@ ${view.chatHeading}
|
|
|
21351
21195
|
console.log(theme.heading("\n\u{1F4DD} Governed Persistent Memory:") + filterNote + "\n" + theme.dim("\u2500".repeat(72)));
|
|
21352
21196
|
for (const entry of entries) {
|
|
21353
21197
|
const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
|
|
21354
|
-
const
|
|
21198
|
+
const source2 = [
|
|
21355
21199
|
entry.source ?? "unknown",
|
|
21356
21200
|
entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : "",
|
|
21357
21201
|
entry.sourceProject ? `project ${entry.sourceProject}` : ""
|
|
21358
21202
|
].filter(Boolean).join(" \xB7 ");
|
|
21359
21203
|
console.log(theme.accent(` ${entry.id.slice(0, 8)} ${status} scope:${entry.scope} sensitivity:${entry.sensitivity}`));
|
|
21360
21204
|
const lastRef = entry.lastReferencedAt ? ` \xB7 last-referenced: ${entry.lastReferencedAt.slice(0, 10)}` : "";
|
|
21361
|
-
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}` : ""}`));
|
|
21362
21206
|
console.log(" " + entry.content.replace(/\n/g, "\n "));
|
|
21363
21207
|
console.log();
|
|
21364
21208
|
}
|
|
@@ -22039,16 +21883,16 @@ function createSessionCommands() {
|
|
|
22039
21883
|
|
|
22040
21884
|
// src/repl/commands/misc-commands.ts
|
|
22041
21885
|
import { basename as basename9 } from "path";
|
|
22042
|
-
import { platform as
|
|
21886
|
+
import { platform as platform8 } from "os";
|
|
22043
21887
|
|
|
22044
21888
|
// src/repl/clipboard.ts
|
|
22045
21889
|
import { execSync as execSync3 } from "child_process";
|
|
22046
|
-
import { existsSync as
|
|
21890
|
+
import { existsSync as existsSync29, statSync as statSync15, unlinkSync as unlinkSync7 } from "fs";
|
|
22047
21891
|
import { tmpdir as tmpdir3 } from "os";
|
|
22048
|
-
import { join as
|
|
21892
|
+
import { join as join23 } from "path";
|
|
22049
21893
|
var CLIPBOARD_TIMEOUT = 5e3;
|
|
22050
21894
|
function readClipboardImage() {
|
|
22051
|
-
const outPath =
|
|
21895
|
+
const outPath = join23(tmpdir3(), `aicli-paste-${Date.now()}.png`);
|
|
22052
21896
|
try {
|
|
22053
21897
|
if (process.platform === "win32") {
|
|
22054
21898
|
_readWin32(outPath);
|
|
@@ -22061,7 +21905,7 @@ function readClipboardImage() {
|
|
|
22061
21905
|
_cleanup(outPath);
|
|
22062
21906
|
return null;
|
|
22063
21907
|
}
|
|
22064
|
-
if (!
|
|
21908
|
+
if (!existsSync29(outPath) || statSync15(outPath).size === 0) {
|
|
22065
21909
|
_cleanup(outPath);
|
|
22066
21910
|
return null;
|
|
22067
21911
|
}
|
|
@@ -22114,7 +21958,7 @@ function _readLinux(outPath) {
|
|
|
22114
21958
|
}
|
|
22115
21959
|
function _cleanup(path3) {
|
|
22116
21960
|
try {
|
|
22117
|
-
if (
|
|
21961
|
+
if (existsSync29(path3)) unlinkSync7(path3);
|
|
22118
21962
|
} catch {
|
|
22119
21963
|
}
|
|
22120
21964
|
}
|
|
@@ -22361,7 +22205,7 @@ No commands match "${filter}".
|
|
|
22361
22205
|
ctx.renderer.printSuccess(`Copied to clipboard (${lines} lines, ${chars} chars)`);
|
|
22362
22206
|
} catch (err) {
|
|
22363
22207
|
const msg = err instanceof Error ? err.message : String(err);
|
|
22364
|
-
if (
|
|
22208
|
+
if (platform8() === "linux" && msg.includes("not found")) {
|
|
22365
22209
|
ctx.renderer.renderError("Clipboard tool not found. Install xclip or xsel: sudo apt install xclip");
|
|
22366
22210
|
} else {
|
|
22367
22211
|
ctx.renderer.renderError(`Failed to copy: ${msg}`);
|
|
@@ -22956,7 +22800,18 @@ var SessionHandler = class _SessionHandler {
|
|
|
22956
22800
|
}))
|
|
22957
22801
|
});
|
|
22958
22802
|
}
|
|
22803
|
+
executionState = /* @__PURE__ */ new Map();
|
|
22804
|
+
connectionAbort = new AbortController();
|
|
22959
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) {
|
|
22960
22815
|
if (this.disconnected) return;
|
|
22961
22816
|
let msg;
|
|
22962
22817
|
try {
|
|
@@ -22967,6 +22822,7 @@ var SessionHandler = class _SessionHandler {
|
|
|
22967
22822
|
}
|
|
22968
22823
|
switch (msg.type) {
|
|
22969
22824
|
case "chat": {
|
|
22825
|
+
if (this.connectionAbort.signal.aborted) this.connectionAbort = new AbortController();
|
|
22970
22826
|
if (msg.messageId) {
|
|
22971
22827
|
if (typeof msg.messageId !== "string" || msg.messageId.length > 200) throw new Error("Invalid message ID");
|
|
22972
22828
|
const scope = this.config.getConfigDir();
|
|
@@ -23027,6 +22883,7 @@ var SessionHandler = class _SessionHandler {
|
|
|
23027
22883
|
return;
|
|
23028
22884
|
}
|
|
23029
22885
|
case "abort":
|
|
22886
|
+
this.connectionAbort.abort();
|
|
23030
22887
|
if (this.abortController) {
|
|
23031
22888
|
this.abortController.abort();
|
|
23032
22889
|
}
|
|
@@ -23050,8 +22907,16 @@ var SessionHandler = class _SessionHandler {
|
|
|
23050
22907
|
}
|
|
23051
22908
|
}
|
|
23052
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) {
|
|
23053
22917
|
if (this.disconnected) return;
|
|
23054
22918
|
this.disconnected = true;
|
|
22919
|
+
this.connectionAbort.abort();
|
|
23055
22920
|
if (closeSocket && this.ws.readyState === this.ws.OPEN) this.ws.close(1001, "Connection closed; reconnect to restore session");
|
|
23056
22921
|
runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
|
|
23057
22922
|
this.toolExecutor.cancelAll();
|
|
@@ -24030,20 +23895,20 @@ ${summaryContent}`,
|
|
|
24030
23895
|
this.send({ type: "error", message: "Usage: /memory show --stale <days> (positive number)" });
|
|
24031
23896
|
return;
|
|
24032
23897
|
}
|
|
24033
|
-
const
|
|
24034
|
-
const { entries, applied } = filterMemoryEntries(
|
|
23898
|
+
const source2 = filters.orphaned ? listOrphanedProjectMemories(configDir) : listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
|
|
23899
|
+
const { entries, applied } = filterMemoryEntries(source2, filters);
|
|
24035
23900
|
const filterNote = applied.length > 0 ? ` (filtered: ${applied.join(", ")})` : "";
|
|
24036
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}
|
|
24037
23902
|
|
|
24038
23903
|
` + entries.map((entry) => {
|
|
24039
23904
|
const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
|
|
24040
|
-
const
|
|
23905
|
+
const source3 = [
|
|
24041
23906
|
entry.source ?? "unknown",
|
|
24042
23907
|
entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : "",
|
|
24043
23908
|
entry.sourceProject ? `project ${entry.sourceProject}` : ""
|
|
24044
23909
|
].filter(Boolean).join(" \xB7 ");
|
|
24045
23910
|
return `## ${entry.id.slice(0, 8)} \xB7 ${status} \xB7 scope:${entry.scope} \xB7 sensitivity:${entry.sensitivity}
|
|
24046
|
-
Source: ${
|
|
23911
|
+
Source: ${source3}
|
|
24047
23912
|
${entry.content}`;
|
|
24048
23913
|
}).join("\n\n");
|
|
24049
23914
|
this.send({
|
|
@@ -24153,7 +24018,7 @@ Backup saved: ${result.backupPath}` : "";
|
|
|
24153
24018
|
return;
|
|
24154
24019
|
}
|
|
24155
24020
|
try {
|
|
24156
|
-
const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-
|
|
24021
|
+
const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
|
|
24157
24022
|
const loaded = loadChatIndex2();
|
|
24158
24023
|
if (!loaded || loaded.idx.chunks.length === 0) {
|
|
24159
24024
|
this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
|
|
@@ -24189,7 +24054,7 @@ Backup saved: ${result.backupPath}` : "";
|
|
|
24189
24054
|
}
|
|
24190
24055
|
async handleMemoryStatus() {
|
|
24191
24056
|
try {
|
|
24192
|
-
const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-
|
|
24057
|
+
const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-RLVLQRRC.js");
|
|
24193
24058
|
const s = getChatIndexStatus2();
|
|
24194
24059
|
this.send({
|
|
24195
24060
|
type: "memory_status",
|
|
@@ -24214,7 +24079,7 @@ Backup saved: ${result.backupPath}` : "";
|
|
|
24214
24079
|
type: "info",
|
|
24215
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"
|
|
24216
24081
|
});
|
|
24217
|
-
const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-
|
|
24082
|
+
const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
|
|
24218
24083
|
const stats = await buildChatIndex2({
|
|
24219
24084
|
full,
|
|
24220
24085
|
onProgress: (p) => {
|
|
@@ -24372,9 +24237,9 @@ async function setupProxy(configProxy) {
|
|
|
24372
24237
|
}
|
|
24373
24238
|
|
|
24374
24239
|
// src/web/auth.ts
|
|
24375
|
-
import { existsSync as
|
|
24376
|
-
import { isAbsolute as isAbsolute3, join as
|
|
24377
|
-
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";
|
|
24378
24243
|
var USERS_FILE = "users.json";
|
|
24379
24244
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
24380
24245
|
var TOKEN_EXPIRY_MS = TOKEN_EXPIRY_HOURS * 3600 * 1e3;
|
|
@@ -24386,14 +24251,16 @@ var AuthManager = class {
|
|
|
24386
24251
|
usersFile;
|
|
24387
24252
|
baseDir;
|
|
24388
24253
|
db;
|
|
24254
|
+
loadedRaw;
|
|
24389
24255
|
constructor(baseDir) {
|
|
24390
24256
|
this.baseDir = baseDir;
|
|
24391
|
-
this.usersFile =
|
|
24257
|
+
this.usersFile = join24(baseDir, USERS_FILE);
|
|
24392
24258
|
this.db = this.loadOrCreate();
|
|
24393
24259
|
}
|
|
24394
24260
|
// ── Public API ─────────────────────────────────────────────────
|
|
24395
24261
|
/** Check if any users exist (first-run detection) */
|
|
24396
24262
|
hasUsers() {
|
|
24263
|
+
this.refresh();
|
|
24397
24264
|
return this.db.users.length > 0;
|
|
24398
24265
|
}
|
|
24399
24266
|
/** Check if auth is enabled (has users.json with at least 1 user) */
|
|
@@ -24402,6 +24269,18 @@ var AuthManager = class {
|
|
|
24402
24269
|
}
|
|
24403
24270
|
/** Register a new user. Returns error string or null on success. */
|
|
24404
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";
|
|
24405
24284
|
username = username.trim().toLowerCase();
|
|
24406
24285
|
if (!username || username.length < 2 || username.length > 32) {
|
|
24407
24286
|
return "Username must be 2-32 characters";
|
|
@@ -24415,11 +24294,12 @@ var AuthManager = class {
|
|
|
24415
24294
|
if (this.db.users.find((u) => u.username === username)) {
|
|
24416
24295
|
return "Username already exists";
|
|
24417
24296
|
}
|
|
24418
|
-
const salt =
|
|
24419
|
-
const passwordHash = this.hashPassword(password, salt);
|
|
24420
|
-
const dataDir =
|
|
24421
|
-
const fullDataDir =
|
|
24422
|
-
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 });
|
|
24423
24303
|
const user = {
|
|
24424
24304
|
username,
|
|
24425
24305
|
passwordHash,
|
|
@@ -24442,6 +24322,8 @@ var AuthManager = class {
|
|
|
24442
24322
|
* resets the counter.
|
|
24443
24323
|
*/
|
|
24444
24324
|
login(username, password) {
|
|
24325
|
+
this.refresh();
|
|
24326
|
+
if (!validCredentials(username, password)) return null;
|
|
24445
24327
|
username = username.trim().toLowerCase();
|
|
24446
24328
|
const lockState = this.getLockState(username);
|
|
24447
24329
|
if (lockState.locked) return null;
|
|
@@ -24454,30 +24336,62 @@ var AuthManager = class {
|
|
|
24454
24336
|
const hash = isLegacy ? this.hashPasswordLegacy(password, user.salt) : this.hashPassword(password, user.salt);
|
|
24455
24337
|
const a = Buffer.from(hash, "utf-8");
|
|
24456
24338
|
const b = Buffer.from(user.passwordHash, "utf-8");
|
|
24457
|
-
if (a.length !== b.length || !
|
|
24339
|
+
if (a.length !== b.length || !timingSafeEqual2(a, b)) {
|
|
24458
24340
|
this.recordFailedLogin(username);
|
|
24459
24341
|
return null;
|
|
24460
24342
|
}
|
|
24461
24343
|
if (isLegacy) {
|
|
24462
|
-
const newSalt =
|
|
24344
|
+
const newSalt = randomBytes2(16).toString("hex");
|
|
24463
24345
|
user.passwordHash = this.hashPassword(password, newSalt);
|
|
24464
24346
|
user.salt = newSalt;
|
|
24465
24347
|
user.hashVersion = 2;
|
|
24466
24348
|
this.save();
|
|
24467
24349
|
}
|
|
24468
|
-
loginAttempts.delete(username);
|
|
24350
|
+
loginAttempts.delete(this.attemptKey(username));
|
|
24469
24351
|
return this.createToken(username);
|
|
24470
24352
|
}
|
|
24471
24353
|
/**
|
|
24472
24354
|
* Returns current lockout state for a username and lazily expires it.
|
|
24473
24355
|
* Exposed (read-only) for tests and the `aicli user` CLI status output.
|
|
24474
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
|
+
}
|
|
24475
24389
|
getLockState(username) {
|
|
24476
24390
|
username = username.trim().toLowerCase();
|
|
24477
|
-
const state2 = loginAttempts.get(username);
|
|
24391
|
+
const state2 = loginAttempts.get(this.attemptKey(username));
|
|
24478
24392
|
if (!state2) return { locked: false, remainingMs: 0, fails: 0 };
|
|
24479
|
-
if (
|
|
24480
|
-
loginAttempts.delete(username);
|
|
24393
|
+
if (Date.now() >= state2.expiresAt) {
|
|
24394
|
+
loginAttempts.delete(this.attemptKey(username));
|
|
24481
24395
|
return { locked: false, remainingMs: 0, fails: 0 };
|
|
24482
24396
|
}
|
|
24483
24397
|
if (state2.lockedUntil > 0) {
|
|
@@ -24485,8 +24399,15 @@ var AuthManager = class {
|
|
|
24485
24399
|
}
|
|
24486
24400
|
return { locked: false, remainingMs: 0, fails: state2.fails };
|
|
24487
24401
|
}
|
|
24402
|
+
attemptKey(username) {
|
|
24403
|
+
return this.usersFile + "\0" + username;
|
|
24404
|
+
}
|
|
24488
24405
|
recordFailedLogin(username) {
|
|
24489
|
-
|
|
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 };
|
|
24490
24411
|
state2.fails += 1;
|
|
24491
24412
|
if (state2.fails >= LOGIN_MAX_FAILS) {
|
|
24492
24413
|
state2.lockedUntil = Date.now() + LOGIN_LOCKOUT_MS;
|
|
@@ -24496,17 +24417,19 @@ var AuthManager = class {
|
|
|
24496
24417
|
/** Verify a token. Returns username or null. */
|
|
24497
24418
|
verifyToken(token) {
|
|
24498
24419
|
try {
|
|
24420
|
+
this.refresh();
|
|
24421
|
+
if (typeof token !== "string" || token.length > 4096) return null;
|
|
24499
24422
|
const parts = token.split(".");
|
|
24500
24423
|
if (parts.length !== 2) return null;
|
|
24501
24424
|
const [payloadB64, signature] = parts;
|
|
24502
24425
|
const expectedSig = this.sign(payloadB64);
|
|
24503
24426
|
const sigBuf = Buffer.from(signature ?? "", "utf-8");
|
|
24504
24427
|
const expBuf = Buffer.from(expectedSig, "utf-8");
|
|
24505
|
-
if (sigBuf.length !== expBuf.length || !
|
|
24428
|
+
if (sigBuf.length !== expBuf.length || !timingSafeEqual2(sigBuf, expBuf)) return null;
|
|
24506
24429
|
const payload = JSON.parse(
|
|
24507
24430
|
Buffer.from(payloadB64, "base64url").toString("utf-8")
|
|
24508
24431
|
);
|
|
24509
|
-
if (Date.now()
|
|
24432
|
+
if (!Number.isFinite(payload.exp) || Date.now() >= payload.exp) return null;
|
|
24510
24433
|
const user = this.db.users.find((u) => u.username === payload.username);
|
|
24511
24434
|
if (!user) return null;
|
|
24512
24435
|
if (user.tokensRevokedBefore && (!payload.iat || payload.iat < user.tokensRevokedBefore)) {
|
|
@@ -24523,6 +24446,7 @@ var AuthManager = class {
|
|
|
24523
24446
|
* v0.4.114). Returns true if the user existed.
|
|
24524
24447
|
*/
|
|
24525
24448
|
logoutAll(username) {
|
|
24449
|
+
this.refresh();
|
|
24526
24450
|
username = username.trim().toLowerCase();
|
|
24527
24451
|
const user = this.db.users.find((u) => u.username === username);
|
|
24528
24452
|
if (!user) return false;
|
|
@@ -24532,16 +24456,19 @@ var AuthManager = class {
|
|
|
24532
24456
|
}
|
|
24533
24457
|
/** Get user's data directory (absolute path) */
|
|
24534
24458
|
getUserDataDir(username) {
|
|
24459
|
+
this.refresh();
|
|
24535
24460
|
const user = this.db.users.find((u) => u.username === username);
|
|
24536
24461
|
if (!user) throw new Error(`User not found: ${username}`);
|
|
24537
|
-
return
|
|
24462
|
+
return join24(this.baseDir, user.dataDir);
|
|
24538
24463
|
}
|
|
24539
24464
|
/** List all usernames */
|
|
24540
24465
|
listUsers() {
|
|
24466
|
+
this.refresh();
|
|
24541
24467
|
return this.db.users.map((u) => u.username);
|
|
24542
24468
|
}
|
|
24543
24469
|
/** Delete a user. Returns error string or null on success. */
|
|
24544
24470
|
deleteUser(username) {
|
|
24471
|
+
this.refresh();
|
|
24545
24472
|
username = username.trim().toLowerCase();
|
|
24546
24473
|
const idx = this.db.users.findIndex((u) => u.username === username);
|
|
24547
24474
|
if (idx === -1) return `User '${username}' not found`;
|
|
@@ -24551,13 +24478,14 @@ var AuthManager = class {
|
|
|
24551
24478
|
}
|
|
24552
24479
|
/** Reset a user's password. Returns error string or null on success. */
|
|
24553
24480
|
resetPassword(username, newPassword) {
|
|
24481
|
+
this.refresh();
|
|
24554
24482
|
username = username.trim().toLowerCase();
|
|
24555
24483
|
const user = this.db.users.find((u) => u.username === username);
|
|
24556
24484
|
if (!user) return `User '${username}' not found`;
|
|
24557
|
-
if (
|
|
24485
|
+
if (typeof newPassword !== "string" || newPassword.length > 1024 || newPassword.length < 4) {
|
|
24558
24486
|
return "Password must be at least 4 characters";
|
|
24559
24487
|
}
|
|
24560
|
-
const salt =
|
|
24488
|
+
const salt = randomBytes2(16).toString("hex");
|
|
24561
24489
|
user.passwordHash = this.hashPassword(newPassword, salt);
|
|
24562
24490
|
user.salt = salt;
|
|
24563
24491
|
user.hashVersion = 2;
|
|
@@ -24570,30 +24498,30 @@ var AuthManager = class {
|
|
|
24570
24498
|
const err = this.register(username, password);
|
|
24571
24499
|
if (err) return err;
|
|
24572
24500
|
const userDir = this.getUserDataDir(username);
|
|
24573
|
-
const globalConfig =
|
|
24574
|
-
if (
|
|
24501
|
+
const globalConfig = join24(this.baseDir, "config.json");
|
|
24502
|
+
if (existsSync30(globalConfig)) {
|
|
24575
24503
|
try {
|
|
24576
|
-
const content =
|
|
24577
|
-
|
|
24504
|
+
const content = readFileSync21(globalConfig, "utf-8");
|
|
24505
|
+
atomicWriteFileSync(join24(userDir, "config.json"), content, { mode: 384 });
|
|
24578
24506
|
} catch {
|
|
24579
24507
|
}
|
|
24580
24508
|
}
|
|
24581
|
-
const globalMemory =
|
|
24582
|
-
if (
|
|
24509
|
+
const globalMemory = join24(this.baseDir, "memory.md");
|
|
24510
|
+
if (existsSync30(globalMemory)) {
|
|
24583
24511
|
try {
|
|
24584
|
-
const content =
|
|
24585
|
-
|
|
24512
|
+
const content = readFileSync21(globalMemory, "utf-8");
|
|
24513
|
+
atomicWriteFileSync(join24(userDir, "memory.md"), content, { mode: 384 });
|
|
24586
24514
|
} catch {
|
|
24587
24515
|
}
|
|
24588
24516
|
}
|
|
24589
|
-
const globalHistory =
|
|
24590
|
-
if (
|
|
24517
|
+
const globalHistory = join24(this.baseDir, "history");
|
|
24518
|
+
if (existsSync30(globalHistory)) {
|
|
24591
24519
|
try {
|
|
24592
24520
|
const files = readdirSync14(globalHistory).filter((f) => f.endsWith(".json"));
|
|
24593
|
-
const userHistory =
|
|
24521
|
+
const userHistory = join24(userDir, "history");
|
|
24594
24522
|
for (const f of files) {
|
|
24595
24523
|
try {
|
|
24596
|
-
|
|
24524
|
+
atomicWriteFileSync(join24(userHistory, f), readFileSync21(join24(globalHistory, f), "utf8"), { mode: 384 });
|
|
24597
24525
|
} catch {
|
|
24598
24526
|
}
|
|
24599
24527
|
}
|
|
@@ -24603,13 +24531,22 @@ var AuthManager = class {
|
|
|
24603
24531
|
return null;
|
|
24604
24532
|
}
|
|
24605
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
|
+
}
|
|
24606
24541
|
loadOrCreate() {
|
|
24607
|
-
if (
|
|
24542
|
+
if (existsSync30(this.usersFile)) {
|
|
24608
24543
|
try {
|
|
24609
|
-
const
|
|
24544
|
+
const raw = readFileSync21(this.usersFile, "utf-8");
|
|
24545
|
+
const parsed = JSON.parse(raw);
|
|
24610
24546
|
if (!this.isValidUsersDB(parsed)) {
|
|
24611
24547
|
throw new Error("database structure is invalid");
|
|
24612
24548
|
}
|
|
24549
|
+
this.loadedRaw = raw;
|
|
24613
24550
|
return parsed;
|
|
24614
24551
|
} catch (err) {
|
|
24615
24552
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -24621,7 +24558,7 @@ var AuthManager = class {
|
|
|
24621
24558
|
}
|
|
24622
24559
|
const db = {
|
|
24623
24560
|
version: 1,
|
|
24624
|
-
secret:
|
|
24561
|
+
secret: randomBytes2(32).toString("hex"),
|
|
24625
24562
|
users: []
|
|
24626
24563
|
};
|
|
24627
24564
|
this.saveDB(db);
|
|
@@ -24640,7 +24577,7 @@ var AuthManager = class {
|
|
|
24640
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)) {
|
|
24641
24578
|
return false;
|
|
24642
24579
|
}
|
|
24643
|
-
const dataDirFromBase = relative5(this.baseDir,
|
|
24580
|
+
const dataDirFromBase = relative5(this.baseDir, join24(this.baseDir, record.dataDir));
|
|
24644
24581
|
if (dataDirFromBase.startsWith("..") || isAbsolute3(dataDirFromBase)) return false;
|
|
24645
24582
|
if (record.hashVersion !== void 0 && record.hashVersion !== 1 && record.hashVersion !== 2) return false;
|
|
24646
24583
|
if (record.tokensRevokedBefore !== void 0 && !Number.isFinite(record.tokensRevokedBefore)) return false;
|
|
@@ -24652,8 +24589,19 @@ var AuthManager = class {
|
|
|
24652
24589
|
this.saveDB(this.db);
|
|
24653
24590
|
}
|
|
24654
24591
|
saveDB(db) {
|
|
24655
|
-
mkdirSync16(this.baseDir, { recursive: true });
|
|
24656
|
-
|
|
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
|
+
}
|
|
24657
24605
|
}
|
|
24658
24606
|
/** Legacy hash — kept only for migrating old users (v0.2.x) */
|
|
24659
24607
|
hashPasswordLegacy(password, salt) {
|
|
@@ -24677,6 +24625,19 @@ var AuthManager = class {
|
|
|
24677
24625
|
return createHmac("sha256", this.db.secret).update(data).digest("base64url");
|
|
24678
24626
|
}
|
|
24679
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
|
+
}
|
|
24680
24641
|
|
|
24681
24642
|
// src/web/server.ts
|
|
24682
24643
|
function getModuleDir() {
|
|
@@ -24696,6 +24657,10 @@ async function startWebServer(options = {}) {
|
|
|
24696
24657
|
console.log(`\u{1F916} ai-cli Web UI v${VERSION}`);
|
|
24697
24658
|
console.log(` Initializing...`);
|
|
24698
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
|
+
}
|
|
24699
24664
|
await setupProxy(config.get("proxy"));
|
|
24700
24665
|
const providers = new ProviderRegistry();
|
|
24701
24666
|
const sessions = new SessionManager(config);
|
|
@@ -24729,7 +24694,7 @@ async function startWebServer(options = {}) {
|
|
|
24729
24694
|
const projectMcpResolved = mcpEnabled ? resolveProjectMcpPath() : null;
|
|
24730
24695
|
let projectMcpServers = {};
|
|
24731
24696
|
if (projectMcpResolved) {
|
|
24732
|
-
const { checkTrust } = await import("./project-trust-
|
|
24697
|
+
const { checkTrust } = await import("./project-trust-6CJB5ZMX.js");
|
|
24733
24698
|
const verdict = checkTrust(config.getConfigDir(), projectMcpResolved);
|
|
24734
24699
|
if (verdict.trusted) {
|
|
24735
24700
|
projectMcpServers = loadProjectMcpConfig() ?? {};
|
|
@@ -24756,8 +24721,8 @@ async function startWebServer(options = {}) {
|
|
|
24756
24721
|
}
|
|
24757
24722
|
}
|
|
24758
24723
|
let skillManager = null;
|
|
24759
|
-
const skillsDir =
|
|
24760
|
-
if (
|
|
24724
|
+
const skillsDir = join25(config.getConfigDir(), SKILLS_DIR_NAME);
|
|
24725
|
+
if (existsSync31(skillsDir)) {
|
|
24761
24726
|
skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
|
|
24762
24727
|
skillManager.loadSkills(pluginAssets.skillDirs);
|
|
24763
24728
|
const count = skillManager.listSkills().length;
|
|
@@ -24776,6 +24741,16 @@ async function startWebServer(options = {}) {
|
|
|
24776
24741
|
skillManager
|
|
24777
24742
|
};
|
|
24778
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
|
+
});
|
|
24779
24754
|
app.use((_req, res, next) => {
|
|
24780
24755
|
res.setHeader(
|
|
24781
24756
|
"Content-Security-Policy",
|
|
@@ -24804,19 +24779,23 @@ async function startWebServer(options = {}) {
|
|
|
24804
24779
|
const WS_MSG_RATE_PER_SEC = 30;
|
|
24805
24780
|
const WS_MSG_BURST = 60;
|
|
24806
24781
|
const WS_PREAUTH_TIMEOUT_MS = 3e4;
|
|
24807
|
-
const wss = new WebSocketServer({ server, maxPayload: WS_MAX_PAYLOAD })
|
|
24808
|
-
|
|
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
|
+
} });
|
|
24809
24788
|
if (authManager.isEnabled()) {
|
|
24810
24789
|
console.log(` Auth: ${authManager.listUsers().length} user(s) registered`);
|
|
24811
24790
|
} else {
|
|
24812
|
-
console.log(
|
|
24813
|
-
if (host === "0.0.0.0" || host === "::") {
|
|
24814
|
-
console.log(" WARNING: Web UI is reachable from the network without authentication.");
|
|
24815
|
-
console.log(" Create a user first for LAN/team use: aicli user create <name>");
|
|
24816
|
-
}
|
|
24791
|
+
console.log(" Auth: local launch token (no users registered)");
|
|
24817
24792
|
}
|
|
24818
24793
|
const requireAuth = (req, res, next) => {
|
|
24819
|
-
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
|
+
}
|
|
24820
24799
|
const authHeader = req.headers.authorization;
|
|
24821
24800
|
const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
24822
24801
|
const cookieToken = parseCookie(req.headers.cookie ?? "")["aicli_token"];
|
|
@@ -24834,19 +24813,20 @@ async function startWebServer(options = {}) {
|
|
|
24834
24813
|
next();
|
|
24835
24814
|
};
|
|
24836
24815
|
const moduleDir = getModuleDir();
|
|
24837
|
-
let clientDir =
|
|
24838
|
-
if (!
|
|
24839
|
-
clientDir =
|
|
24816
|
+
let clientDir = join25(moduleDir, "web", "client");
|
|
24817
|
+
if (!existsSync31(clientDir)) {
|
|
24818
|
+
clientDir = join25(moduleDir, "client");
|
|
24840
24819
|
}
|
|
24841
|
-
if (!
|
|
24842
|
-
clientDir =
|
|
24820
|
+
if (!existsSync31(clientDir)) {
|
|
24821
|
+
clientDir = join25(moduleDir, "..", "..", "src", "web", "client");
|
|
24843
24822
|
}
|
|
24844
|
-
if (!
|
|
24845
|
-
clientDir =
|
|
24823
|
+
if (!existsSync31(clientDir)) {
|
|
24824
|
+
clientDir = join25(process.cwd(), "src", "web", "client");
|
|
24846
24825
|
}
|
|
24847
24826
|
console.log(` Static files: ${clientDir}`);
|
|
24848
24827
|
const PRECOMPRESSED_TYPES = {
|
|
24849
24828
|
".js": "text/javascript; charset=utf-8",
|
|
24829
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
24850
24830
|
".css": "text/css; charset=utf-8",
|
|
24851
24831
|
".html": "text/html; charset=utf-8",
|
|
24852
24832
|
".json": "application/json; charset=utf-8",
|
|
@@ -24861,14 +24841,14 @@ async function startWebServer(options = {}) {
|
|
|
24861
24841
|
const encoding = /\bbr\b/.test(accept) ? "br" : /\bgzip\b/.test(accept) ? "gzip" : null;
|
|
24862
24842
|
if (!encoding) return next();
|
|
24863
24843
|
const target = resolvePath(clientDir, `.${req.path}`) + (encoding === "br" ? ".br" : ".gz");
|
|
24864
|
-
if (!target.startsWith(clientDir) || !
|
|
24844
|
+
if (!target.startsWith(clientDir) || !existsSync31(target)) return next();
|
|
24865
24845
|
res.setHeader("Content-Type", contentType);
|
|
24866
24846
|
res.setHeader("Content-Encoding", encoding);
|
|
24867
24847
|
res.setHeader("Vary", "Accept-Encoding");
|
|
24868
24848
|
if (req.path.startsWith("/vendor/")) res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
24869
24849
|
createReadStream(target).pipe(res);
|
|
24870
24850
|
});
|
|
24871
|
-
app.use("/vendor", express.static(
|
|
24851
|
+
app.use("/vendor", express.static(join25(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
|
|
24872
24852
|
app.use(express.static(clientDir));
|
|
24873
24853
|
app.get("/api/status", (_req, res) => {
|
|
24874
24854
|
res.json({
|
|
@@ -24887,9 +24867,28 @@ async function startWebServer(options = {}) {
|
|
|
24887
24867
|
}
|
|
24888
24868
|
});
|
|
24889
24869
|
});
|
|
24890
|
-
app.use(express.json());
|
|
24891
|
-
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) => {
|
|
24892
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
|
+
}
|
|
24893
24892
|
if (!firstRun) {
|
|
24894
24893
|
const authHeader = req.headers.authorization;
|
|
24895
24894
|
const token2 = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
@@ -24905,23 +24904,24 @@ async function startWebServer(options = {}) {
|
|
|
24905
24904
|
res.status(400).json({ error: "Username and password required" });
|
|
24906
24905
|
return;
|
|
24907
24906
|
}
|
|
24908
|
-
const err = authManager.
|
|
24907
|
+
const err = await authManager.registerAsync(username, password);
|
|
24909
24908
|
if (err) {
|
|
24910
24909
|
res.status(400).json({ error: err });
|
|
24911
24910
|
return;
|
|
24912
24911
|
}
|
|
24913
|
-
const token = authManager.
|
|
24912
|
+
const token = await authManager.loginAsync(username, password);
|
|
24913
|
+
invalidateConnections();
|
|
24914
24914
|
console.log(` \u2713 User registered via API: ${username}${firstRun ? " (first-run)" : ""}`);
|
|
24915
24915
|
res.cookie("aicli_token", token, authCookieOptions(req));
|
|
24916
24916
|
res.json({ success: true, username });
|
|
24917
24917
|
});
|
|
24918
|
-
app.post("/api/auth/login", (req, res) => {
|
|
24918
|
+
app.post("/api/auth/login", async (req, res) => {
|
|
24919
24919
|
const { username, password } = req.body ?? {};
|
|
24920
24920
|
if (!username || !password) {
|
|
24921
24921
|
res.status(400).json({ error: "Username and password required" });
|
|
24922
24922
|
return;
|
|
24923
24923
|
}
|
|
24924
|
-
const token = authManager.
|
|
24924
|
+
const token = await authManager.loginAsync(username, password);
|
|
24925
24925
|
if (!token) {
|
|
24926
24926
|
res.status(401).json({ error: "Invalid username or password" });
|
|
24927
24927
|
return;
|
|
@@ -24930,13 +24930,16 @@ async function startWebServer(options = {}) {
|
|
|
24930
24930
|
res.json({ success: true, username });
|
|
24931
24931
|
});
|
|
24932
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();
|
|
24933
24936
|
res.clearCookie("aicli_token", clearAuthCookieOptions(req));
|
|
24934
24937
|
res.json({ success: true });
|
|
24935
24938
|
});
|
|
24936
24939
|
app.get("/api/files", requireAuth, (req, res) => {
|
|
24937
24940
|
const cwd = process.cwd();
|
|
24938
24941
|
const prefix = req.query.prefix || "";
|
|
24939
|
-
const targetDir =
|
|
24942
|
+
const targetDir = join25(cwd, prefix);
|
|
24940
24943
|
try {
|
|
24941
24944
|
const canonicalTarget = realpathSync3(resolve12(targetDir));
|
|
24942
24945
|
const canonicalCwd = realpathSync3(resolve12(cwd));
|
|
@@ -24953,7 +24956,7 @@ async function startWebServer(options = {}) {
|
|
|
24953
24956
|
const entries = readdirSync15(targetDir, { withFileTypes: true });
|
|
24954
24957
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
24955
24958
|
name: e.name,
|
|
24956
|
-
path: relative6(cwd,
|
|
24959
|
+
path: relative6(cwd, join25(targetDir, e.name)).replace(/\\/g, "/"),
|
|
24957
24960
|
isDir: e.isDirectory()
|
|
24958
24961
|
}));
|
|
24959
24962
|
res.json({ files });
|
|
@@ -24993,8 +24996,8 @@ async function startWebServer(options = {}) {
|
|
|
24993
24996
|
try {
|
|
24994
24997
|
const authUser = req._authUser;
|
|
24995
24998
|
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
24996
|
-
const filePath =
|
|
24997
|
-
if (!
|
|
24999
|
+
const filePath = join25(histDir, `${id}.json`);
|
|
25000
|
+
if (!existsSync31(filePath)) {
|
|
24998
25001
|
res.status(404).json({ error: "Session not found" });
|
|
24999
25002
|
return;
|
|
25000
25003
|
}
|
|
@@ -25009,7 +25012,7 @@ async function startWebServer(options = {}) {
|
|
|
25009
25012
|
res.status(404).json({ error: "Session not found" });
|
|
25010
25013
|
return;
|
|
25011
25014
|
}
|
|
25012
|
-
const data = JSON.parse(
|
|
25015
|
+
const data = JSON.parse(readFileSync22(filePath, "utf-8"));
|
|
25013
25016
|
res.json({ session: data });
|
|
25014
25017
|
} catch (err) {
|
|
25015
25018
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -25022,9 +25025,10 @@ async function startWebServer(options = {}) {
|
|
|
25022
25025
|
return;
|
|
25023
25026
|
}
|
|
25024
25027
|
const cwd = process.cwd();
|
|
25025
|
-
const fullPath = resolve12(
|
|
25028
|
+
const fullPath = resolve12(join25(cwd, filePath));
|
|
25026
25029
|
try {
|
|
25027
25030
|
const canonicalFull = realpathSync3(fullPath);
|
|
25031
|
+
assertReadablePath(canonicalFull);
|
|
25028
25032
|
const canonicalCwd = realpathSync3(resolve12(cwd));
|
|
25029
25033
|
if (!canonicalFull.startsWith(canonicalCwd + sep5) && canonicalFull !== canonicalCwd) {
|
|
25030
25034
|
res.json({ error: "Access denied" });
|
|
@@ -25040,7 +25044,7 @@ async function startWebServer(options = {}) {
|
|
|
25040
25044
|
res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
|
|
25041
25045
|
return;
|
|
25042
25046
|
}
|
|
25043
|
-
const content =
|
|
25047
|
+
const content = readFileSync22(fullPath, "utf-8");
|
|
25044
25048
|
res.json({ content, size: stat.size });
|
|
25045
25049
|
} catch {
|
|
25046
25050
|
res.json({ error: "Cannot read file" });
|
|
@@ -25052,6 +25056,9 @@ async function startWebServer(options = {}) {
|
|
|
25052
25056
|
if (cached) return cached;
|
|
25053
25057
|
const userDataDir = authManager.getUserDataDir(username);
|
|
25054
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
|
+
}
|
|
25055
25062
|
if (userConfig.isFirstRun()) {
|
|
25056
25063
|
userConfig.setTransient("defaultProvider", config.get("defaultProvider"));
|
|
25057
25064
|
userConfig.setTransient("defaultModels", config.get("defaultModels"));
|
|
@@ -25062,7 +25069,7 @@ async function startWebServer(options = {}) {
|
|
|
25062
25069
|
// 每用户各自的 config 目录 → 各自的 cost-history.json
|
|
25063
25070
|
costTracker: new CostTracker(userConfig.getConfigDir()),
|
|
25064
25071
|
providers,
|
|
25065
|
-
//
|
|
25072
|
+
// Host-owned service credentials; shared explicitly, never per-user keys.
|
|
25066
25073
|
sessions: userSessions,
|
|
25067
25074
|
toolRegistry,
|
|
25068
25075
|
// Shared
|
|
@@ -25075,6 +25082,21 @@ async function startWebServer(options = {}) {
|
|
|
25075
25082
|
return userShared;
|
|
25076
25083
|
}
|
|
25077
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
|
+
});
|
|
25078
25100
|
wss.on("connection", (ws, req) => {
|
|
25079
25101
|
const urlStr = req.url ?? "";
|
|
25080
25102
|
const qMark = urlStr.indexOf("?");
|
|
@@ -25082,6 +25104,8 @@ async function startWebServer(options = {}) {
|
|
|
25082
25104
|
const tabId = params.get("tabId") || `tab-${Date.now()}`;
|
|
25083
25105
|
const cookies = parseCookie(req.headers.cookie ?? "");
|
|
25084
25106
|
const token = cookies["aicli_token"] || "";
|
|
25107
|
+
let activeToken = token;
|
|
25108
|
+
const localAuthorized = equalToken(cookies.aicli_local, access.localToken);
|
|
25085
25109
|
let tokens = WS_MSG_BURST;
|
|
25086
25110
|
let lastRefill = Date.now();
|
|
25087
25111
|
const takeToken = () => {
|
|
@@ -25141,7 +25165,7 @@ async function startWebServer(options = {}) {
|
|
|
25141
25165
|
authenticatedUser = authManager.verifyToken(token);
|
|
25142
25166
|
}
|
|
25143
25167
|
const authRequired = authManager.isEnabled();
|
|
25144
|
-
if (!authRequired) {
|
|
25168
|
+
if (!authRequired && localAuthorized) {
|
|
25145
25169
|
console.log(` \u2713 Tab connected: ${tabId.slice(0, 12)} (no auth) (total: ${handlers.size + 1})`);
|
|
25146
25170
|
handler = attachHandler(shared);
|
|
25147
25171
|
clearPreAuthTimer();
|
|
@@ -25159,7 +25183,26 @@ async function startWebServer(options = {}) {
|
|
|
25159
25183
|
}));
|
|
25160
25184
|
}
|
|
25161
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;
|
|
25162
25203
|
ws.on("message", async (data) => {
|
|
25204
|
+
validate();
|
|
25205
|
+
if (ws.readyState !== ws.OPEN) return;
|
|
25163
25206
|
if (!takeToken()) {
|
|
25164
25207
|
if (ws.readyState === ws.OPEN) {
|
|
25165
25208
|
try {
|
|
@@ -25179,57 +25222,73 @@ async function startWebServer(options = {}) {
|
|
|
25179
25222
|
return;
|
|
25180
25223
|
}
|
|
25181
25224
|
if (parsed.type === "auth") {
|
|
25182
|
-
|
|
25183
|
-
|
|
25184
|
-
|
|
25185
|
-
|
|
25225
|
+
if (authBusy) return;
|
|
25226
|
+
authBusy = true;
|
|
25227
|
+
try {
|
|
25228
|
+
if (!authLimits.take(req.socket.remoteAddress ?? "unknown")) {
|
|
25229
|
+
ws.close(1008, "Authentication rate limit");
|
|
25186
25230
|
return;
|
|
25187
25231
|
}
|
|
25188
|
-
const
|
|
25189
|
-
|
|
25190
|
-
|
|
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 }));
|
|
25191
25253
|
return;
|
|
25192
25254
|
}
|
|
25193
|
-
|
|
25194
|
-
|
|
25195
|
-
|
|
25196
|
-
|
|
25197
|
-
|
|
25198
|
-
|
|
25199
|
-
|
|
25200
|
-
|
|
25201
|
-
|
|
25202
|
-
|
|
25203
|
-
|
|
25204
|
-
|
|
25205
|
-
|
|
25206
|
-
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 }));
|
|
25207
25269
|
return;
|
|
25208
25270
|
}
|
|
25209
|
-
|
|
25210
|
-
|
|
25211
|
-
|
|
25212
|
-
|
|
25213
|
-
|
|
25214
|
-
|
|
25215
|
-
|
|
25216
|
-
|
|
25217
|
-
|
|
25218
|
-
|
|
25219
|
-
|
|
25220
|
-
|
|
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 }));
|
|
25221
25285
|
return;
|
|
25222
25286
|
}
|
|
25223
|
-
|
|
25224
|
-
const userShared = getUserShared(username);
|
|
25225
|
-
handler = attachHandler(userShared);
|
|
25226
|
-
clearPreAuthTimer();
|
|
25227
|
-
console.log(` \u2713 User logged in: ${username} (tab: ${tabId.slice(0, 12)})`);
|
|
25228
|
-
ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
|
|
25287
|
+
ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
|
|
25229
25288
|
return;
|
|
25289
|
+
} finally {
|
|
25290
|
+
authBusy = false;
|
|
25230
25291
|
}
|
|
25231
|
-
ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
|
|
25232
|
-
return;
|
|
25233
25292
|
}
|
|
25234
25293
|
if (!handler) {
|
|
25235
25294
|
if (ws.readyState === ws.OPEN) {
|
|
@@ -25275,13 +25334,15 @@ async function startWebServer(options = {}) {
|
|
|
25275
25334
|
}
|
|
25276
25335
|
});
|
|
25277
25336
|
server.listen(actualPort, host, () => {
|
|
25278
|
-
|
|
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}` : "");
|
|
25279
25340
|
console.log(`
|
|
25280
25341
|
\u{1F310} Web UI ready: ${url}`);
|
|
25281
25342
|
if (actualPort !== port) console.log(` (port ${port} was in use, using ${actualPort})`);
|
|
25282
25343
|
if (host === "0.0.0.0" || host === "::") {
|
|
25283
25344
|
try {
|
|
25284
|
-
const nets =
|
|
25345
|
+
const nets = networkInterfaces2();
|
|
25285
25346
|
for (const name of Object.keys(nets)) {
|
|
25286
25347
|
for (const net2 of nets[name] ?? []) {
|
|
25287
25348
|
if (net2.family === "IPv4" && !net2.internal) {
|
|
@@ -25314,27 +25375,24 @@ function resolveProjectMcpPath() {
|
|
|
25314
25375
|
const cwd = process.cwd();
|
|
25315
25376
|
const gitRoot = getGitRoot(cwd);
|
|
25316
25377
|
const projectRoot = gitRoot ?? cwd;
|
|
25317
|
-
const configPath =
|
|
25318
|
-
return
|
|
25378
|
+
const configPath = join25(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
25379
|
+
return existsSync31(configPath) ? configPath : null;
|
|
25319
25380
|
}
|
|
25320
25381
|
function loadProjectMcpConfig() {
|
|
25321
25382
|
const cwd = process.cwd();
|
|
25322
25383
|
const gitRoot = getGitRoot(cwd);
|
|
25323
25384
|
const projectRoot = gitRoot ?? cwd;
|
|
25324
|
-
const configPath =
|
|
25325
|
-
if (!
|
|
25385
|
+
const configPath = join25(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
25386
|
+
if (!existsSync31(configPath)) return null;
|
|
25326
25387
|
try {
|
|
25327
|
-
const raw = JSON.parse(
|
|
25388
|
+
const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
|
|
25328
25389
|
return raw.mcpServers ?? raw;
|
|
25329
25390
|
} catch {
|
|
25330
25391
|
return null;
|
|
25331
25392
|
}
|
|
25332
25393
|
}
|
|
25333
25394
|
function isHttpsRequest(req) {
|
|
25334
|
-
|
|
25335
|
-
const forwardedProto = req.headers["x-forwarded-proto"];
|
|
25336
|
-
const proto = Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto;
|
|
25337
|
-
return proto?.split(",")[0]?.trim().toLowerCase() === "https";
|
|
25395
|
+
return req.secure || req.res?.locals.externalSecure === true;
|
|
25338
25396
|
}
|
|
25339
25397
|
function authCookieOptions(req) {
|
|
25340
25398
|
return {
|
|
@@ -25361,13 +25419,13 @@ function parseCookie(cookie) {
|
|
|
25361
25419
|
}
|
|
25362
25420
|
async function openBrowser(url) {
|
|
25363
25421
|
try {
|
|
25364
|
-
const { spawn:
|
|
25422
|
+
const { spawn: spawn6 } = await import("child_process");
|
|
25365
25423
|
if (process.platform === "win32") {
|
|
25366
|
-
|
|
25424
|
+
spawn6("cmd", ["/c", "start", '""', url], { detached: true, stdio: "ignore" }).unref();
|
|
25367
25425
|
} else if (process.platform === "darwin") {
|
|
25368
|
-
|
|
25426
|
+
spawn6("open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
25369
25427
|
} else {
|
|
25370
|
-
|
|
25428
|
+
spawn6("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
25371
25429
|
}
|
|
25372
25430
|
} catch {
|
|
25373
25431
|
}
|