jinzd-ai-cli 0.4.307 → 0.4.309
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 +2 -1
- package/README.zh-CN.md +1 -1
- 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-H4WWSZQ3.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-CIM2PV2F.js → chunk-2E5W2E64.js} +2 -2
- package/dist/chunk-2XMUCC42.js +67 -0
- package/dist/{chunk-VAV2IJ3R.js → chunk-3CNPJLVI.js} +1 -1
- package/dist/{chunk-KRCYR46A.js → chunk-3GEO2LPO.js} +264 -55
- package/dist/{chunk-OAXLMF3U.js → chunk-3YVNDYOY.js} +1 -1
- package/dist/{chunk-5GQL4TSZ.js → chunk-4BXTGXZ6.js} +2 -2
- package/dist/{chunk-RYQBBEMM.js → chunk-4PR5AVJ5.js} +115 -19
- package/dist/{chunk-GHWZVPUV.js → chunk-6JHACXB3.js} +7 -5
- package/dist/{chunk-IUVDZVZ4.js → chunk-AXTLYOA7.js} +24 -17
- package/dist/{chunk-XJOK5353.js → chunk-B3MPYI4E.js} +2 -2
- package/dist/chunk-BMLQPSHP.js +69 -0
- package/dist/{chunk-NRSYBKWA.js → chunk-C7CKDFJS.js} +1 -1
- package/dist/{chunk-SJFZPS65.js → chunk-EAVX3RLD.js} +1 -1
- package/dist/{chunk-VXEF2SWG.js → chunk-EHMMBIZT.js} +16 -12
- package/dist/chunk-FVXNEZEW.js +161 -0
- package/dist/{chunk-AHL6KOGM.js → chunk-HXINWG37.js} +2 -2
- package/dist/{chunk-3CKJOTVM.js → chunk-IW6Q66WS.js} +4 -4
- package/dist/chunk-JJFJWD35.js +162 -0
- package/dist/{chunk-NYNSYLFY.js → chunk-JL44HGZC.js} +1 -1
- package/dist/{chunk-3TBXHLSC.js → chunk-K4F7QEGW.js} +1 -1
- package/dist/{chunk-NUJTNH76.js → chunk-LBWKMTT3.js} +1 -1
- package/dist/{chunk-YKJO3SI4.js → chunk-LONB36SP.js} +16 -3
- package/dist/{chunk-WQ3OMEC4.js → chunk-M6HLYG33.js} +1 -1
- package/dist/{chunk-I4GQRMFV.js → chunk-M6YV5KSS.js} +6 -4
- package/dist/{chunk-KBDKKHXB.js → chunk-O6CGOVD4.js} +2 -2
- package/dist/{chunk-L2RJPVHV.js → chunk-O7MMYJAD.js} +266 -512
- package/dist/chunk-QMFMST2T.js +39 -0
- package/dist/{chunk-AQF6IXSR.js → chunk-RBCKX2FZ.js} +11 -6
- package/dist/{chunk-4W6VDVQU.js → chunk-RLERORUI.js} +246 -56
- 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-NQJSPMY2.js → chunk-SCG2VBPB.js} +4 -4
- package/dist/chunk-SXIJLSVF.js +41 -0
- package/dist/{chunk-YFDQIUQ3.js → chunk-T42GXE7Y.js} +18 -18
- package/dist/chunk-TDQW2YHK.js +65 -0
- package/dist/{chunk-45UVJXFQ.js → chunk-W2UOVGRC.js} +1 -1
- package/dist/{ci-QD2Q7NWY.js → ci-H6GPHG4T.js} +7 -7
- package/dist/{ci-format-42XIZO5P.js → ci-format-U47GSGYU.js} +2 -2
- package/dist/{constants-5CMEFZQB.js → constants-GYJTB6KI.js} +1 -1
- package/dist/{constants-NMASHFDL.js → constants-SNJR6546.js} +1 -1
- package/dist/doctor-cli-J6LAQ5LU.js +18 -0
- package/dist/electron-server.js +922 -842
- 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-G5FMMTVZ.js} +3 -3
- package/dist/{hub-server-CBKCP5VD.js → hub-server-JLC7CNP7.js} +55 -17
- package/dist/index.js +48 -42
- 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-EXJBXL33.js} +3 -3
- package/dist/{persistent-memory-MBSBTBGY.js → persistent-memory-LEE454DW.js} +3 -3
- package/dist/{pr-SXRW24JG.js → pr-6PUHVJ6K.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-2C7MWZBN.js +12 -0
- package/dist/run-tests-33ZVQE3Q.js +13 -0
- package/dist/{semantic-7GM5M4MU.js → semantic-N227CLSC.js} +4 -4
- package/dist/{semantic-QOBAXEQ4.js → semantic-TRQAZSRY.js} +4 -4
- package/dist/{server-GGEO7SA2.js → server-AJH5TBVO.js} +231 -103
- package/dist/{server-MO3EU2XD.js → server-CYAB56O3.js} +16 -14
- 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-HNRV3YUD.js} +15 -14
- package/dist/{usage-B7RIAPPT.js → usage-ZN5DU6Z7.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-2E5W2E64.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-RLERORUI.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-EAVX3RLD.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";
|
|
@@ -604,8 +683,13 @@ ${err}`
|
|
|
604
683
|
* @returns true if config was modified and should be persisted.
|
|
605
684
|
*/
|
|
606
685
|
migrateLegacyModels() {
|
|
607
|
-
const LEGACY_DEEPSEEK = /* @__PURE__ */ new Set([
|
|
608
|
-
|
|
686
|
+
const LEGACY_DEEPSEEK = /* @__PURE__ */ new Set([
|
|
687
|
+
"deepseek-chat",
|
|
688
|
+
"deepseek-reasoner",
|
|
689
|
+
"deepseek-v4-flash",
|
|
690
|
+
"deepseek-v4-flash-vision-exp"
|
|
691
|
+
]);
|
|
692
|
+
const DEEPSEEK_TARGET = "deepseek-flash";
|
|
609
693
|
const LEGACY_KIMI = /* @__PURE__ */ new Set(["kimi-k2-0711-preview", "kimi-k2-turbo-preview"]);
|
|
610
694
|
const KIMI_TARGET = "kimi-k2.6";
|
|
611
695
|
let changed = false;
|
|
@@ -650,8 +734,8 @@ ${err}`
|
|
|
650
734
|
return changed;
|
|
651
735
|
}
|
|
652
736
|
save() {
|
|
653
|
-
mkdirSync(this.configDir, { recursive: true });
|
|
654
|
-
atomicWriteFileSync(this.configPath, JSON.stringify(this.config, null, 2));
|
|
737
|
+
mkdirSync(this.configDir, { recursive: true, mode: 448 });
|
|
738
|
+
atomicWriteFileSync(this.configPath, JSON.stringify(this.config, null, 2), { mode: 384 });
|
|
655
739
|
}
|
|
656
740
|
getApiKey(providerId) {
|
|
657
741
|
const envKey = EnvLoader.getApiKey(providerId);
|
|
@@ -804,15 +888,28 @@ function deepSeekCnyPricing(cacheRead, input, output) {
|
|
|
804
888
|
output: output / CNY_PER_USD
|
|
805
889
|
};
|
|
806
890
|
}
|
|
891
|
+
var DEEPSEEK_V4_PRO_ROUTED_TO_FLASH_AT = /* @__PURE__ */ new Date("2026-09-14T04:00:00.000Z");
|
|
892
|
+
var DEEPSEEK_V41_FLASH_ROUTING_CONFIRMED_AT = /* @__PURE__ */ new Date("2026-09-11T00:00:00.000Z");
|
|
893
|
+
function isDeepSeekPeakHour(at) {
|
|
894
|
+
const weekday = at.getUTCDay();
|
|
895
|
+
if (weekday === 0 || weekday === 6) return false;
|
|
896
|
+
const hour = at.getUTCHours();
|
|
897
|
+
return hour >= 1 && hour < 4 || hour >= 6 && hour < 10;
|
|
898
|
+
}
|
|
899
|
+
function deepSeekFlash41Pricing(at) {
|
|
900
|
+
return isDeepSeekPeakHour(at) ? { cacheRead: 6e-3, input: 0.3, output: 1.2 } : { cacheRead: 3e-3, input: 0.15, output: 0.6 };
|
|
901
|
+
}
|
|
807
902
|
function getDeepSeekV4Pricing(model, at) {
|
|
903
|
+
if (model.startsWith("deepseek-flash")) return deepSeekFlash41Pricing(at);
|
|
808
904
|
const isPro = model.startsWith("deepseek-v4-pro");
|
|
809
905
|
const isFlash = model.startsWith("deepseek-v4-flash");
|
|
810
906
|
if (!isPro && !isFlash) return null;
|
|
907
|
+
if (isPro && at >= DEEPSEEK_V4_PRO_ROUTED_TO_FLASH_AT) return deepSeekFlash41Pricing(at);
|
|
908
|
+
if (isFlash && at >= DEEPSEEK_V41_FLASH_ROUTING_CONFIRMED_AT) return deepSeekFlash41Pricing(at);
|
|
811
909
|
if (at < DEEPSEEK_TIME_PRICING_EFFECTIVE_AT) {
|
|
812
910
|
return isPro ? deepSeekCnyPricing(0.025, 3, 6) : deepSeekCnyPricing(0.02, 1, 2);
|
|
813
911
|
}
|
|
814
|
-
const
|
|
815
|
-
const isPeak = beijingHour >= 9 && beijingHour < 12 || beijingHour >= 14 && beijingHour < 18;
|
|
912
|
+
const isPeak = isDeepSeekPeakHour(at);
|
|
816
913
|
if (isPro) {
|
|
817
914
|
return isPeak ? deepSeekCnyPricing(0.3, 9, 27) : deepSeekCnyPricing(0.15, 4.5, 13.5);
|
|
818
915
|
}
|
|
@@ -1293,6 +1390,7 @@ function getDangerLevel(toolName, args) {
|
|
|
1293
1390
|
}
|
|
1294
1391
|
if (toolName === "bash") {
|
|
1295
1392
|
const cmd = String(args["command"] ?? "");
|
|
1393
|
+
if (/\bgit\s+(?:clean\b|reset\s+--hard\b|push\b[^\n]*(?:--force\b|\s-f\b))/i.test(cmd)) return "destructive";
|
|
1296
1394
|
if (/\brm\s+[^\n]*(?:-\w*[rRfF]\w*|--recursive|--force)\b/.test(cmd)) return "destructive";
|
|
1297
1395
|
if (/\brm\s+\S/.test(cmd)) return "destructive";
|
|
1298
1396
|
if (/\brmdir\b|\bformat\b|\bmkfs\b|\bdd\s+if=|\bshred\b|\bfdisk\b|\bparted\b/.test(cmd)) return "destructive";
|
|
@@ -1308,8 +1406,10 @@ function getDangerLevel(toolName, args) {
|
|
|
1308
1406
|
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
1407
|
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
1408
|
if (/\bcmd(\.exe)?\s+\/[ck]\b/i.test(cmd)) return "write";
|
|
1311
|
-
return "safe";
|
|
1409
|
+
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";
|
|
1410
|
+
return "write";
|
|
1312
1411
|
}
|
|
1412
|
+
if (toolName === "run_tests") return "write";
|
|
1313
1413
|
if (toolName === "write_file") return "write";
|
|
1314
1414
|
if (toolName === "edit_file") return "write";
|
|
1315
1415
|
if (toolName === "save_last_response") return "write";
|
|
@@ -1324,7 +1424,7 @@ function getDangerLevel(toolName, args) {
|
|
|
1324
1424
|
if (toolName === "git_commit") return "write";
|
|
1325
1425
|
if (toolName === "git_status" || toolName === "git_diff" || toolName === "git_log") return "safe";
|
|
1326
1426
|
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"
|
|
1427
|
+
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
1428
|
return "write";
|
|
1329
1429
|
}
|
|
1330
1430
|
function schemaToJsonSchema(schema) {
|
|
@@ -4084,30 +4184,34 @@ var DeepSeekProvider = class extends OpenAICompatibleProvider {
|
|
|
4084
4184
|
info = {
|
|
4085
4185
|
id: "deepseek",
|
|
4086
4186
|
displayName: "DeepSeek",
|
|
4087
|
-
defaultModel: "deepseek-
|
|
4187
|
+
defaultModel: "deepseek-flash",
|
|
4088
4188
|
apiKeyEnvVar: "AICLI_API_KEY_DEEPSEEK",
|
|
4089
4189
|
requiresApiKey: true,
|
|
4090
4190
|
baseUrl: this.defaultBaseUrl,
|
|
4091
4191
|
models: [
|
|
4092
|
-
// ── V4
|
|
4192
|
+
// ── V4.1 Flash (2026-09):1M context,原生多模态,Thinking / Non-Thinking 双模式 ──
|
|
4193
|
+
// model code 实测取自端点本身:`/v1/models` 只列 `deepseek-flash` 与 `deepseek-v4-pro`,
|
|
4194
|
+
// 传 `deepseek-v4.1-flash` 报 400 并把合法名字写在错误消息里。**不要**按发布公告里的
|
|
4195
|
+
// 版本号拼 code(v0.4.198 的 `glm-5.2[1m]` 就是这么栽的)。
|
|
4093
4196
|
{
|
|
4094
|
-
id: "deepseek-
|
|
4095
|
-
displayName: "DeepSeek V4
|
|
4197
|
+
id: "deepseek-flash",
|
|
4198
|
+
displayName: "DeepSeek V4.1 Flash (multimodal, 1M)",
|
|
4096
4199
|
contextWindow: 1048576,
|
|
4097
4200
|
supportsStreaming: true,
|
|
4098
4201
|
supportsThinking: true
|
|
4099
4202
|
},
|
|
4100
4203
|
{
|
|
4101
|
-
id: "deepseek-v4-
|
|
4102
|
-
displayName: "DeepSeek V4
|
|
4204
|
+
id: "deepseek-v4-pro",
|
|
4205
|
+
displayName: "DeepSeek V4 Pro (text-only; routes to V4.1 Flash from 2026-09-14)",
|
|
4103
4206
|
contextWindow: 1048576,
|
|
4104
4207
|
supportsStreaming: true,
|
|
4105
4208
|
supportsThinking: true
|
|
4106
4209
|
}
|
|
4107
|
-
// Legacy aliases
|
|
4108
|
-
//
|
|
4109
|
-
//
|
|
4110
|
-
//
|
|
4210
|
+
// Legacy aliases:`deepseek-v4-flash` / `deepseek-v4-flash-vision-exp` 的底层模型已退役,
|
|
4211
|
+
// 端点仍接受这两个名字并路由到 V4.1 Flash(实测传 `deepseek-v4-flash` 返回 200 且响应
|
|
4212
|
+
// `model` 字段回显 `deepseek-flash`)。它们不再列进推荐清单,config 里 pin 过的会被
|
|
4213
|
+
// `migrateLegacyModels()` 自愈到 `deepseek-flash`;pricing.ts 保留旧 id 供历史成本回算。
|
|
4214
|
+
// `deepseek-chat` / `deepseek-reasoner` 早在 v0.4.140 移除(2026-07-24 停用)。
|
|
4111
4215
|
]
|
|
4112
4216
|
};
|
|
4113
4217
|
/**
|
|
@@ -4678,7 +4782,7 @@ function clearModelCache(configDir, providerId) {
|
|
|
4678
4782
|
delete cache.providers[providerId];
|
|
4679
4783
|
saveModelCache(configDir, cache);
|
|
4680
4784
|
}
|
|
4681
|
-
function normalizeLiveModels(models, fetchedAt,
|
|
4785
|
+
function normalizeLiveModels(models, fetchedAt, source2 = "live") {
|
|
4682
4786
|
const seen = /* @__PURE__ */ new Set();
|
|
4683
4787
|
const normalized = [];
|
|
4684
4788
|
for (const model of models) {
|
|
@@ -4690,7 +4794,7 @@ function normalizeLiveModels(models, fetchedAt, source = "live") {
|
|
|
4690
4794
|
contextWindow: model.contextWindow || LIVE_DEFAULT_CONTEXT,
|
|
4691
4795
|
supportsStreaming: model.supportsStreaming ?? true,
|
|
4692
4796
|
...model.supportsThinking !== void 0 ? { supportsThinking: model.supportsThinking } : {},
|
|
4693
|
-
source,
|
|
4797
|
+
source: source2,
|
|
4694
4798
|
available: true,
|
|
4695
4799
|
metadataUnknown: model.metadataUnknown ?? !model.contextWindow,
|
|
4696
4800
|
...fetchedAt ? { fetchedAt } : {}
|
|
@@ -5463,26 +5567,33 @@ var SessionManager = class {
|
|
|
5463
5567
|
}
|
|
5464
5568
|
/** P1-PERF-03: `force` 跳过脏检查(用于进程退出、fork 等必须落盘场景) */
|
|
5465
5569
|
async save(force = false) {
|
|
5466
|
-
|
|
5467
|
-
if (
|
|
5468
|
-
if (
|
|
5570
|
+
const session = this._current;
|
|
5571
|
+
if (!session) return;
|
|
5572
|
+
if (session.messages.length === 0) return;
|
|
5573
|
+
if (!force && !session.dirty) return;
|
|
5469
5574
|
mkdirSync3(this.historyDir, { recursive: true });
|
|
5470
|
-
const filePath = join4(this.historyDir, `${
|
|
5471
|
-
const raw =
|
|
5575
|
+
const filePath = join4(this.historyDir, `${session.id}.json`);
|
|
5576
|
+
const raw = session.toJSON();
|
|
5472
5577
|
const opts = this.redactOptionsForSave();
|
|
5473
5578
|
const { value: payload, hits } = redactJson(raw, opts);
|
|
5474
5579
|
this.lastRedactionHits = hits.length;
|
|
5475
|
-
|
|
5476
|
-
|
|
5580
|
+
const serialized = JSON.stringify(payload, null, 2);
|
|
5581
|
+
session.dirty = false;
|
|
5582
|
+
try {
|
|
5583
|
+
await atomicWriteFile(filePath, serialized, { mode: 384 });
|
|
5584
|
+
} catch (error) {
|
|
5585
|
+
session.dirty = true;
|
|
5586
|
+
throw error;
|
|
5587
|
+
}
|
|
5477
5588
|
try {
|
|
5478
5589
|
updateSessionInIndex(this.historyDir, {
|
|
5479
|
-
id:
|
|
5480
|
-
provider:
|
|
5481
|
-
model:
|
|
5482
|
-
title:
|
|
5483
|
-
messageCount:
|
|
5484
|
-
created:
|
|
5485
|
-
updated:
|
|
5590
|
+
id: session.id,
|
|
5591
|
+
provider: session.provider,
|
|
5592
|
+
model: session.model,
|
|
5593
|
+
title: session.title,
|
|
5594
|
+
messageCount: session.messages.length,
|
|
5595
|
+
created: session.created,
|
|
5596
|
+
updated: session.updated
|
|
5486
5597
|
}, filePath);
|
|
5487
5598
|
} catch {
|
|
5488
5599
|
}
|
|
@@ -5633,16 +5744,22 @@ var SessionManager = class {
|
|
|
5633
5744
|
};
|
|
5634
5745
|
|
|
5635
5746
|
// src/tools/builtin/bash.ts
|
|
5636
|
-
import { spawn as
|
|
5747
|
+
import { spawn as spawn2 } from "child_process";
|
|
5637
5748
|
import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
5638
|
-
import { platform as
|
|
5749
|
+
import { platform as platform2 } from "os";
|
|
5639
5750
|
import { resolve } from "path";
|
|
5640
5751
|
|
|
5641
5752
|
// src/tools/undo-stack.ts
|
|
5642
|
-
import {
|
|
5753
|
+
import { writeFileSync, unlinkSync as unlinkSync3, rmdirSync, existsSync as existsSync5 } from "fs";
|
|
5643
5754
|
var MAX_UNDO_DEPTH = 20;
|
|
5644
5755
|
var UndoStack = class {
|
|
5645
|
-
|
|
5756
|
+
stateKey = /* @__PURE__ */ Symbol("stack");
|
|
5757
|
+
get stack() {
|
|
5758
|
+
return scopedState(this.stateKey, () => ({ items: [] })).items;
|
|
5759
|
+
}
|
|
5760
|
+
set stack(items) {
|
|
5761
|
+
scopedState(this.stateKey, () => ({ items: [] })).items = items;
|
|
5762
|
+
}
|
|
5646
5763
|
/**
|
|
5647
5764
|
* 在执行文件写入操作之前调用,保存当前状态。
|
|
5648
5765
|
* @param filePath 将要被写入的文件路径
|
|
@@ -5651,11 +5768,7 @@ var UndoStack = class {
|
|
|
5651
5768
|
push(filePath, description) {
|
|
5652
5769
|
let previousContent = null;
|
|
5653
5770
|
if (existsSync5(filePath)) {
|
|
5654
|
-
|
|
5655
|
-
previousContent = readFileSync5(filePath, "utf-8");
|
|
5656
|
-
} catch {
|
|
5657
|
-
return;
|
|
5658
|
-
}
|
|
5771
|
+
previousContent = readSnapshot(filePath);
|
|
5659
5772
|
}
|
|
5660
5773
|
this.pushEntry({
|
|
5661
5774
|
filePath,
|
|
@@ -5692,7 +5805,7 @@ var UndoStack = class {
|
|
|
5692
5805
|
/** 内部统一入栈方法,含溢出裁剪 */
|
|
5693
5806
|
pushEntry(entry) {
|
|
5694
5807
|
this.stack.push(entry);
|
|
5695
|
-
|
|
5808
|
+
while (this.stack.length > MAX_UNDO_DEPTH || this.stack.reduce((n, e) => n + Buffer.byteLength(e.previousContent ?? ""), 0) > MAX_SNAPSHOT_TOTAL_BYTES) {
|
|
5696
5809
|
this.stack.shift();
|
|
5697
5810
|
}
|
|
5698
5811
|
}
|
|
@@ -5753,186 +5866,17 @@ var UndoStack = class {
|
|
|
5753
5866
|
};
|
|
5754
5867
|
var undoStack = new UndoStack();
|
|
5755
5868
|
|
|
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
5869
|
// src/tools/shell-pool.ts
|
|
5926
|
-
import { spawn
|
|
5927
|
-
import { platform
|
|
5928
|
-
var IS_WINDOWS2 =
|
|
5870
|
+
import { spawn } from "child_process";
|
|
5871
|
+
import { platform } from "os";
|
|
5872
|
+
var IS_WINDOWS2 = platform() === "win32";
|
|
5929
5873
|
var SHELL = IS_WINDOWS2 ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
|
|
5930
5874
|
var pool = /* @__PURE__ */ new Map();
|
|
5931
5875
|
var IDLE_EVICT_MS = 5 * 6e4;
|
|
5932
5876
|
var evictionTimer = null;
|
|
5933
5877
|
function spawnShell(cwd) {
|
|
5934
5878
|
const shellArgs = IS_WINDOWS2 ? ["-NoProfile", "-NonInteractive", "-Command", "-"] : ["-s"];
|
|
5935
|
-
const proc =
|
|
5879
|
+
const proc = spawn(SHELL, shellArgs, {
|
|
5936
5880
|
cwd,
|
|
5937
5881
|
windowsHide: true,
|
|
5938
5882
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -6123,7 +6067,7 @@ function cleanupPoolSession(sessionKey) {
|
|
|
6123
6067
|
}
|
|
6124
6068
|
|
|
6125
6069
|
// src/tools/builtin/bash.ts
|
|
6126
|
-
var IS_WINDOWS3 =
|
|
6070
|
+
var IS_WINDOWS3 = platform2() === "win32";
|
|
6127
6071
|
var SHELL2 = IS_WINDOWS3 ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
|
|
6128
6072
|
var cwdBySession = /* @__PURE__ */ new Map();
|
|
6129
6073
|
function getCwd() {
|
|
@@ -6706,7 +6650,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
|
|
|
6706
6650
|
function runShell(command, opts) {
|
|
6707
6651
|
return new Promise((resolvePromise) => {
|
|
6708
6652
|
const shellArgs = IS_WINDOWS3 ? ["-NoProfile", "-NonInteractive", "-Command", WIN_UTF8_PREAMBLE + command] : ["-c", command];
|
|
6709
|
-
const child =
|
|
6653
|
+
const child = spawn2(SHELL2, shellArgs, {
|
|
6710
6654
|
cwd: opts.cwd,
|
|
6711
6655
|
env: opts.env,
|
|
6712
6656
|
windowsHide: true,
|
|
@@ -6785,16 +6729,16 @@ function updateCwdFromCommand(command, baseCwd) {
|
|
|
6785
6729
|
}
|
|
6786
6730
|
|
|
6787
6731
|
// src/tools/builtin/read-file.ts
|
|
6788
|
-
import { readFileSync as
|
|
6732
|
+
import { readFileSync as readFileSync5, existsSync as existsSync7, statSync as statSync3, readdirSync as readdirSync3 } from "fs";
|
|
6789
6733
|
import { execFileSync } from "child_process";
|
|
6790
6734
|
import { extname, resolve as resolve2, basename, sep, dirname as dirname2 } from "path";
|
|
6791
6735
|
import { homedir as homedir2 } from "os";
|
|
6792
6736
|
|
|
6793
6737
|
// src/tools/builtin/notebook-utils.ts
|
|
6794
|
-
function cellSourceToString(
|
|
6795
|
-
if (!
|
|
6796
|
-
if (Array.isArray(
|
|
6797
|
-
return
|
|
6738
|
+
function cellSourceToString(source2) {
|
|
6739
|
+
if (!source2) return "";
|
|
6740
|
+
if (Array.isArray(source2)) return source2.join("");
|
|
6741
|
+
return source2;
|
|
6798
6742
|
}
|
|
6799
6743
|
function stringToCellSource(content) {
|
|
6800
6744
|
if (content.length === 0) return [];
|
|
@@ -6849,16 +6793,16 @@ function renderNotebookAsText(nb, filePath) {
|
|
|
6849
6793
|
nb.cells.forEach((cell, idx) => {
|
|
6850
6794
|
const cellNum = idx + 1;
|
|
6851
6795
|
const type = cell.cell_type;
|
|
6852
|
-
const
|
|
6796
|
+
const source2 = cellSourceToString(cell.source);
|
|
6853
6797
|
if (type === "markdown") {
|
|
6854
6798
|
lines.push(`## Cell ${cellNum} [markdown]`);
|
|
6855
|
-
lines.push(
|
|
6799
|
+
lines.push(source2);
|
|
6856
6800
|
lines.push("");
|
|
6857
6801
|
} else if (type === "code") {
|
|
6858
6802
|
const execLabel = cell.execution_count != null ? ` (execution #${cell.execution_count})` : "";
|
|
6859
6803
|
lines.push(`## Cell ${cellNum} [code${execLabel}]`);
|
|
6860
6804
|
lines.push("```" + language);
|
|
6861
|
-
lines.push(
|
|
6805
|
+
lines.push(source2);
|
|
6862
6806
|
lines.push("```");
|
|
6863
6807
|
const outputText = extractOutputText(cell.outputs);
|
|
6864
6808
|
if (outputText) {
|
|
@@ -6870,7 +6814,7 @@ function renderNotebookAsText(nb, filePath) {
|
|
|
6870
6814
|
lines.push("");
|
|
6871
6815
|
} else if (type === "raw") {
|
|
6872
6816
|
lines.push(`## Cell ${cellNum} [raw]`);
|
|
6873
|
-
lines.push(
|
|
6817
|
+
lines.push(source2);
|
|
6874
6818
|
lines.push("");
|
|
6875
6819
|
}
|
|
6876
6820
|
});
|
|
@@ -6895,6 +6839,7 @@ function createCell(cellType, content) {
|
|
|
6895
6839
|
// src/tools/builtin/read-file.ts
|
|
6896
6840
|
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
6897
6841
|
function getHardRefusal(normalizedPath) {
|
|
6842
|
+
if (isCredentialVault(normalizedPath)) return "[Refused] Credential vault files cannot be read by tools.";
|
|
6898
6843
|
const home2 = homedir2();
|
|
6899
6844
|
const p = normalizedPath.toLowerCase();
|
|
6900
6845
|
const base = basename(normalizedPath).toLowerCase();
|
|
@@ -7069,7 +7014,7 @@ var readFileTool = {
|
|
|
7069
7014
|
const offsetLine = typeof rawOffset === "number" && Number.isFinite(rawOffset) && rawOffset > 0 ? Math.floor(rawOffset) : void 0;
|
|
7070
7015
|
const limitLines = typeof rawLimit === "number" && Number.isFinite(rawLimit) && rawLimit > 0 ? Math.floor(rawLimit) : void 0;
|
|
7071
7016
|
if (!filePath) throw new ToolError("read_file", "path is required");
|
|
7072
|
-
const normalizedPath =
|
|
7017
|
+
const normalizedPath = canonicalPath(filePath);
|
|
7073
7018
|
const refusal = getHardRefusal(normalizedPath);
|
|
7074
7019
|
if (refusal) return refusal;
|
|
7075
7020
|
if (!existsSync7(normalizedPath)) {
|
|
@@ -7136,7 +7081,7 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
|
|
|
7136
7081
|
}
|
|
7137
7082
|
if (ext === ".ipynb") {
|
|
7138
7083
|
try {
|
|
7139
|
-
const raw =
|
|
7084
|
+
const raw = readFileSync5(normalizedPath, "utf-8");
|
|
7140
7085
|
const nb = parseNotebook(raw);
|
|
7141
7086
|
return renderNotebookAsText(nb, filePath);
|
|
7142
7087
|
} catch (err) {
|
|
@@ -7154,8 +7099,8 @@ Do NOT claim you inspected this image \u2014 you have not seen it.`;
|
|
|
7154
7099
|
return `[Binary file: ${filePath} (${ext})]
|
|
7155
7100
|
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
7101
|
}
|
|
7157
|
-
const { readFile
|
|
7158
|
-
const buf = size > 1048576 ? await
|
|
7102
|
+
const { readFile } = await import("fs/promises");
|
|
7103
|
+
const buf = size > 1048576 ? await readFile(normalizedPath) : readFileSync5(normalizedPath);
|
|
7159
7104
|
if (encoding === "base64") {
|
|
7160
7105
|
return `[File: ${filePath} | base64]
|
|
7161
7106
|
|
|
@@ -7193,12 +7138,12 @@ ${content}`;
|
|
|
7193
7138
|
};
|
|
7194
7139
|
|
|
7195
7140
|
// src/tools/builtin/write-file.ts
|
|
7196
|
-
import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as
|
|
7141
|
+
import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
|
|
7197
7142
|
import { dirname as dirname5 } from "path";
|
|
7198
7143
|
|
|
7199
7144
|
// src/tools/executor.ts
|
|
7200
7145
|
import chalk3 from "chalk";
|
|
7201
|
-
import { existsSync as
|
|
7146
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
|
|
7202
7147
|
import { tmpdir } from "os";
|
|
7203
7148
|
|
|
7204
7149
|
// src/core/readline-internal.ts
|
|
@@ -7438,7 +7383,7 @@ function simpleDiff(oldLines, newLines) {
|
|
|
7438
7383
|
// src/tools/hooks.ts
|
|
7439
7384
|
import { execSync } from "child_process";
|
|
7440
7385
|
import { createHash } from "crypto";
|
|
7441
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as
|
|
7386
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6 } from "fs";
|
|
7442
7387
|
import { join as join5 } from "path";
|
|
7443
7388
|
var TRUST_FILE = "hooks-trust.json";
|
|
7444
7389
|
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
@@ -7454,7 +7399,7 @@ function loadHookTrustStore(configDir) {
|
|
|
7454
7399
|
const file = hookTrustPath(configDir);
|
|
7455
7400
|
if (!existsSync8(file)) return { records: [] };
|
|
7456
7401
|
try {
|
|
7457
|
-
const parsed = JSON.parse(
|
|
7402
|
+
const parsed = JSON.parse(readFileSync6(file, "utf-8"));
|
|
7458
7403
|
return { records: Array.isArray(parsed.records) ? parsed.records : [] };
|
|
7459
7404
|
} catch {
|
|
7460
7405
|
return { records: [] };
|
|
@@ -7468,9 +7413,9 @@ function hashHook(event, command) {
|
|
|
7468
7413
|
return createHash("sha256").update(`${event}
|
|
7469
7414
|
${command}`, "utf-8").digest("hex");
|
|
7470
7415
|
}
|
|
7471
|
-
function isTrusted(
|
|
7472
|
-
if (
|
|
7473
|
-
return store.records.some((r) => r.source ===
|
|
7416
|
+
function isTrusted(source2, event, hash, store) {
|
|
7417
|
+
if (source2 === "user" || source2 === "managed") return true;
|
|
7418
|
+
return store.records.some((r) => r.source === source2 && r.event === event && r.hash === hash);
|
|
7474
7419
|
}
|
|
7475
7420
|
function listHooks(config, configDir) {
|
|
7476
7421
|
if (!config || config.enabled === false) return [];
|
|
@@ -7478,15 +7423,15 @@ function listHooks(config, configDir) {
|
|
|
7478
7423
|
const out = [];
|
|
7479
7424
|
const push = (event, hook, index) => {
|
|
7480
7425
|
if (!hook.command) return;
|
|
7481
|
-
const
|
|
7426
|
+
const source2 = hook.source ?? "user";
|
|
7482
7427
|
const hash = hashHook(event, hook.command);
|
|
7483
7428
|
out.push({
|
|
7484
7429
|
id: `${event}:${index}`,
|
|
7485
7430
|
event,
|
|
7486
7431
|
command: hook.command,
|
|
7487
|
-
source,
|
|
7432
|
+
source: source2,
|
|
7488
7433
|
hash,
|
|
7489
|
-
trusted: isTrusted(
|
|
7434
|
+
trusted: isTrusted(source2, event, hash, store),
|
|
7490
7435
|
disabled: hook.disabled === true,
|
|
7491
7436
|
description: hook.description
|
|
7492
7437
|
});
|
|
@@ -7562,15 +7507,15 @@ function runLifecycleHooks(config, event, payload, opts) {
|
|
|
7562
7507
|
for (let i = 0; i < hooks.length; i++) {
|
|
7563
7508
|
const hook = hooks[i];
|
|
7564
7509
|
if (!hook.command || hook.disabled) continue;
|
|
7565
|
-
const
|
|
7510
|
+
const source2 = hook.source ?? "user";
|
|
7566
7511
|
const hash = hashHook(event, hook.command);
|
|
7567
7512
|
const descriptor = {
|
|
7568
7513
|
id: `${event}:${i}`,
|
|
7569
7514
|
event,
|
|
7570
7515
|
command: hook.command,
|
|
7571
|
-
source,
|
|
7516
|
+
source: source2,
|
|
7572
7517
|
hash,
|
|
7573
|
-
trusted: isTrusted(
|
|
7518
|
+
trusted: isTrusted(source2, event, hash, store),
|
|
7574
7519
|
disabled: false,
|
|
7575
7520
|
description: hook.description
|
|
7576
7521
|
};
|
|
@@ -7719,7 +7664,8 @@ var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
|
|
|
7719
7664
|
"recall_memory"
|
|
7720
7665
|
]);
|
|
7721
7666
|
function normalizePathForPermission(value) {
|
|
7722
|
-
|
|
7667
|
+
const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
7668
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
7723
7669
|
}
|
|
7724
7670
|
function canonicalizeWithNearestParent(value) {
|
|
7725
7671
|
const missing = [];
|
|
@@ -7807,7 +7753,7 @@ function profileHardDecision(profileName, toolName, args, dangerLevel, ctx) {
|
|
|
7807
7753
|
return { action: "deny", reason: "file write target is outside workspace/temp roots", profileName, hardBoundary: true };
|
|
7808
7754
|
}
|
|
7809
7755
|
}
|
|
7810
|
-
if (toolName === "bash" || toolName === "run_interactive" || toolName === "task_create") {
|
|
7756
|
+
if (toolName === "bash" || toolName === "run_interactive" || toolName === "task_create" || toolName === "run_tests") {
|
|
7811
7757
|
return {
|
|
7812
7758
|
action: "confirm",
|
|
7813
7759
|
reason: "shell/process writes cannot be proven to stay inside workspace/temp roots",
|
|
@@ -7909,9 +7855,9 @@ function checkNetworkPolicy(toolName, args, policy) {
|
|
|
7909
7855
|
if (!policy?.enabled) return null;
|
|
7910
7856
|
let action = networkToolAction(toolName, policy);
|
|
7911
7857
|
let reason = "network access requires confirmation";
|
|
7912
|
-
if (
|
|
7858
|
+
if (["bash", "run_tests", "run_interactive", "task_create"].includes(toolName)) {
|
|
7913
7859
|
const command = String(args["command"] ?? "");
|
|
7914
|
-
if (!isShellNetworkCommand(command)) return null;
|
|
7860
|
+
if (toolName === "bash" && !isShellNetworkCommand(command) && dangerIsReadOnly(command)) return null;
|
|
7915
7861
|
action = policy.tools?.shell ?? policy.defaultAction ?? "confirm";
|
|
7916
7862
|
reason = "shell command may access the network";
|
|
7917
7863
|
}
|
|
@@ -7947,6 +7893,9 @@ function checkNetworkPolicy(toolName, args, policy) {
|
|
|
7947
7893
|
if (!permissionAction) return null;
|
|
7948
7894
|
return { action: permissionAction, reason, profileName: "networkPolicy" };
|
|
7949
7895
|
}
|
|
7896
|
+
function dangerIsReadOnly(command) {
|
|
7897
|
+
return /^(?:pwd|echo [\w .-]+|git status(?: --short)?)$/.test(command.trim());
|
|
7898
|
+
}
|
|
7950
7899
|
|
|
7951
7900
|
// src/tools/truncate.ts
|
|
7952
7901
|
var DEFAULT_MAX_TOOL_OUTPUT_CHARS = 12e3;
|
|
@@ -8173,8 +8122,7 @@ function getTopUsedTools(limit = 5) {
|
|
|
8173
8122
|
}
|
|
8174
8123
|
|
|
8175
8124
|
// src/tools/action-classifier.ts
|
|
8176
|
-
import {
|
|
8177
|
-
import { isAbsolute as isAbsolute2, join as join7, resolve as resolve4 } from "path";
|
|
8125
|
+
import { isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
|
|
8178
8126
|
var RECENT_DENIED_LIMIT = 50;
|
|
8179
8127
|
var recentlyDenied = [];
|
|
8180
8128
|
function recordRecentlyDeniedAutoAction(call, classification) {
|
|
@@ -8194,7 +8142,8 @@ function clearRecentlyDeniedAutoActions() {
|
|
|
8194
8142
|
recentlyDenied.splice(0, recentlyDenied.length);
|
|
8195
8143
|
}
|
|
8196
8144
|
function normalizePath(value) {
|
|
8197
|
-
|
|
8145
|
+
const normalized = canonicalPath(value).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
8146
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
8198
8147
|
}
|
|
8199
8148
|
function isInside(root, target) {
|
|
8200
8149
|
const absoluteRoot = resolve4(root);
|
|
@@ -8207,56 +8156,6 @@ function getPathArg(call) {
|
|
|
8207
8156
|
const raw = call.arguments.path ?? call.arguments.filePath ?? call.arguments.outputPath ?? call.arguments.file;
|
|
8208
8157
|
return typeof raw === "string" && raw.trim() ? raw.trim() : null;
|
|
8209
8158
|
}
|
|
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
8159
|
function hasSecretExfiltrationShape(command) {
|
|
8261
8160
|
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
8161
|
}
|
|
@@ -8278,7 +8177,7 @@ var RuleBasedActionClassifier = class {
|
|
|
8278
8177
|
}
|
|
8279
8178
|
return { decision: "confirm", reason: "destructive actions require confirmation", ruleId: "auto.destructive-confirm", confidence: "high" };
|
|
8280
8179
|
}
|
|
8281
|
-
classifyShell(call, dangerLevel,
|
|
8180
|
+
classifyShell(call, dangerLevel, _ctx) {
|
|
8282
8181
|
const command = String(call.arguments.command ?? "");
|
|
8283
8182
|
if (/\bcurl\b[^\n|;&]*\|\s*(?:sudo\s+)?(?:bash|sh|zsh)\b/i.test(command)) {
|
|
8284
8183
|
return { decision: "deny", reason: "curl pipe shell execution is blocked in auto mode", ruleId: "deny.curl-pipe-shell", confidence: "high" };
|
|
@@ -8304,18 +8203,7 @@ var RuleBasedActionClassifier = class {
|
|
|
8304
8203
|
if (/\b(npx|pnpm\s+dlx|yarn\s+dlx)\s+(claude|codex|aider|cursor-agent|gemini)\b/i.test(command)) {
|
|
8305
8204
|
return { decision: "confirm", reason: "third-party agent loop requires confirmation", ruleId: "confirm.third-party-agent", confidence: "high" };
|
|
8306
8205
|
}
|
|
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") {
|
|
8206
|
+
if (dangerLevel === "safe" && getDangerLevel(call.name, call.arguments) === "safe") {
|
|
8319
8207
|
return { decision: "auto-approve", reason: "safe shell action", ruleId: "auto.safe-shell", confidence: "medium" };
|
|
8320
8208
|
}
|
|
8321
8209
|
return { decision: "confirm", reason: "shell action is not proven low-risk", ruleId: "confirm.shell-default", confidence: "medium" };
|
|
@@ -8784,10 +8672,10 @@ var ToolExecutor = class {
|
|
|
8784
8672
|
if (call.name === "write_file") {
|
|
8785
8673
|
const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
|
|
8786
8674
|
if (!filePath) return;
|
|
8787
|
-
if (
|
|
8675
|
+
if (existsSync10(filePath)) {
|
|
8788
8676
|
let oldContent;
|
|
8789
8677
|
try {
|
|
8790
|
-
oldContent =
|
|
8678
|
+
oldContent = readFileSync7(filePath, "utf-8");
|
|
8791
8679
|
} catch {
|
|
8792
8680
|
return;
|
|
8793
8681
|
}
|
|
@@ -8811,7 +8699,7 @@ var ToolExecutor = class {
|
|
|
8811
8699
|
}
|
|
8812
8700
|
} else if (call.name === "edit_file") {
|
|
8813
8701
|
const filePath = String(call.arguments["path"] ?? "");
|
|
8814
|
-
if (!filePath || !
|
|
8702
|
+
if (!filePath || !existsSync10(filePath)) return;
|
|
8815
8703
|
const oldStr = call.arguments["old_str"];
|
|
8816
8704
|
const newStr = call.arguments["new_str"];
|
|
8817
8705
|
if (oldStr !== void 0) {
|
|
@@ -8838,7 +8726,7 @@ var ToolExecutor = class {
|
|
|
8838
8726
|
const to = Number(call.arguments["delete_to_line"] ?? from);
|
|
8839
8727
|
let fileContent;
|
|
8840
8728
|
try {
|
|
8841
|
-
fileContent =
|
|
8729
|
+
fileContent = readFileSync7(filePath, "utf-8");
|
|
8842
8730
|
} catch {
|
|
8843
8731
|
return;
|
|
8844
8732
|
}
|
|
@@ -8934,7 +8822,12 @@ import { resolve as resolve5, sep as sep2, basename as basename3 } from "path";
|
|
|
8934
8822
|
import { homedir as homedir4 } from "os";
|
|
8935
8823
|
var home = homedir4();
|
|
8936
8824
|
function norm(p) {
|
|
8937
|
-
|
|
8825
|
+
let abs;
|
|
8826
|
+
try {
|
|
8827
|
+
abs = canonicalPath(p);
|
|
8828
|
+
} catch {
|
|
8829
|
+
abs = resolve5(p);
|
|
8830
|
+
}
|
|
8938
8831
|
return process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
8939
8832
|
}
|
|
8940
8833
|
function homeRel(p) {
|
|
@@ -8948,6 +8841,7 @@ function homeRel(p) {
|
|
|
8948
8841
|
}
|
|
8949
8842
|
function classifyWritePath(path3) {
|
|
8950
8843
|
if (!path3) return { sensitive: false };
|
|
8844
|
+
if (isCredentialVault(path3)) return { sensitive: true, reason: "credential vault" };
|
|
8951
8845
|
const abs = norm(path3);
|
|
8952
8846
|
const base = basename3(abs);
|
|
8953
8847
|
const rel = homeRel(path3);
|
|
@@ -9030,7 +8924,7 @@ function countTextLines(text) {
|
|
|
9030
8924
|
|
|
9031
8925
|
// src/tools/builtin/write-file.ts
|
|
9032
8926
|
function countFileLines(filePath) {
|
|
9033
|
-
return countTextLines(
|
|
8927
|
+
return countTextLines(readFileSync8(filePath, "utf-8"));
|
|
9034
8928
|
}
|
|
9035
8929
|
var writeFileTool = {
|
|
9036
8930
|
definition: {
|
|
@@ -9094,7 +8988,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
9094
8988
|
const mode = appendMode ? "appended" : "written";
|
|
9095
8989
|
void (async () => {
|
|
9096
8990
|
try {
|
|
9097
|
-
const { updateFile } = await import("./indexer-
|
|
8991
|
+
const { updateFile } = await import("./indexer-VRXDZ74A.js");
|
|
9098
8992
|
await updateFile(process.cwd(), filePath);
|
|
9099
8993
|
} catch {
|
|
9100
8994
|
}
|
|
@@ -9113,7 +9007,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
9113
9007
|
};
|
|
9114
9008
|
|
|
9115
9009
|
// src/tools/builtin/edit-file.ts
|
|
9116
|
-
import { readFileSync as
|
|
9010
|
+
import { readFileSync as readFileSync9, existsSync as existsSync11 } from "fs";
|
|
9117
9011
|
|
|
9118
9012
|
// src/tools/builtin/patch-apply.ts
|
|
9119
9013
|
function parseUnifiedDiff(patch) {
|
|
@@ -9481,7 +9375,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
9481
9375
|
const filePath = String(args["path"] ?? args["file"] ?? "");
|
|
9482
9376
|
const encoding = args["encoding"] ?? "utf-8";
|
|
9483
9377
|
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
9484
|
-
if (!
|
|
9378
|
+
if (!existsSync11(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
9485
9379
|
const verdict = classifyWritePath(filePath);
|
|
9486
9380
|
if (verdict.sensitive && subAgentGuard.active) {
|
|
9487
9381
|
throw new ToolError(
|
|
@@ -9489,7 +9383,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
9489
9383
|
`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
9384
|
);
|
|
9491
9385
|
}
|
|
9492
|
-
const original =
|
|
9386
|
+
const original = readFileSync9(filePath, encoding);
|
|
9493
9387
|
if (args["patch"] !== void 0) {
|
|
9494
9388
|
const patchText = String(args["patch"] ?? "");
|
|
9495
9389
|
const stopOnError = args["stop_on_error"] !== false;
|
|
@@ -9666,8 +9560,8 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
9666
9560
|
}
|
|
9667
9561
|
|
|
9668
9562
|
// src/tools/builtin/list-dir.ts
|
|
9669
|
-
import { readdirSync as readdirSync4, statSync as statSync5, existsSync as
|
|
9670
|
-
import { join as
|
|
9563
|
+
import { readdirSync as readdirSync4, statSync as statSync5, existsSync as existsSync12 } from "fs";
|
|
9564
|
+
import { join as join7, basename as basename4 } from "path";
|
|
9671
9565
|
var listDirTool = {
|
|
9672
9566
|
definition: {
|
|
9673
9567
|
name: "list_dir",
|
|
@@ -9689,7 +9583,7 @@ var listDirTool = {
|
|
|
9689
9583
|
async execute(args) {
|
|
9690
9584
|
const dirPath = String(args["path"] ?? process.cwd());
|
|
9691
9585
|
const recursive = Boolean(args["recursive"] ?? false);
|
|
9692
|
-
if (!
|
|
9586
|
+
if (!existsSync12(dirPath)) {
|
|
9693
9587
|
const targetName = basename4(dirPath).toLowerCase();
|
|
9694
9588
|
const cwd = process.cwd();
|
|
9695
9589
|
const suggestions = [];
|
|
@@ -9747,11 +9641,11 @@ function listRecursive(basePath, indent, recursive, lines) {
|
|
|
9747
9641
|
if (entry.isDirectory()) {
|
|
9748
9642
|
lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
|
|
9749
9643
|
if (recursive) {
|
|
9750
|
-
listRecursive(
|
|
9644
|
+
listRecursive(join7(basePath, entry.name), indent + " ", true, lines);
|
|
9751
9645
|
}
|
|
9752
9646
|
} else {
|
|
9753
9647
|
try {
|
|
9754
|
-
const stat = statSync5(
|
|
9648
|
+
const stat = statSync5(join7(basePath, entry.name));
|
|
9755
9649
|
const size = formatSize(stat.size);
|
|
9756
9650
|
lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
|
|
9757
9651
|
} catch {
|
|
@@ -9766,10 +9660,77 @@ function formatSize(bytes) {
|
|
|
9766
9660
|
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
9767
9661
|
}
|
|
9768
9662
|
|
|
9663
|
+
// src/tools/regex-search.ts
|
|
9664
|
+
import { Worker } from "worker_threads";
|
|
9665
|
+
var source = String.raw`
|
|
9666
|
+
const { parentPort, workerData: d } = require('node:worker_threads');
|
|
9667
|
+
const fs = require('node:fs');
|
|
9668
|
+
let re;
|
|
9669
|
+
try { re = new RegExp(d.pattern, d.ignoreCase ? 'i' : ''); }
|
|
9670
|
+
catch { re = new RegExp(d.pattern.replace(/[.*+?^$()|[\]\\]/g, '\\$&'), d.ignoreCase ? 'i' : ''); }
|
|
9671
|
+
const results = [];
|
|
9672
|
+
for (const f of d.files) {
|
|
9673
|
+
if (results.length >= d.maxResults) break;
|
|
9674
|
+
let fd;
|
|
9675
|
+
try {
|
|
9676
|
+
fd = fs.openSync(f.fullPath, 'r');
|
|
9677
|
+
const buf = Buffer.alloc(1000001);
|
|
9678
|
+
let size = 0;
|
|
9679
|
+
while (size < buf.length) { const n = fs.readSync(fd, buf, size, buf.length - size, null); if (!n) break; size += n; }
|
|
9680
|
+
if (size > 1000000 || buf.subarray(0, Math.min(size,512)).includes(0)) continue;
|
|
9681
|
+
const lines = buf.subarray(0,size).toString('utf8').split('\n');
|
|
9682
|
+
for (let i=0; i<lines.length && results.length<d.maxResults; i++) {
|
|
9683
|
+
if (!re.test(lines[i])) continue;
|
|
9684
|
+
const match = { file:f.relPath.replace(/\\/g,'/'), lineNumber:i+1, lineText:lines[i].slice(0,10000) };
|
|
9685
|
+
if (d.context) {
|
|
9686
|
+
match.contextBefore=[]; match.contextAfter=[];
|
|
9687
|
+
for(let j=Math.max(0,i-d.context);j<i;j++) match.contextBefore.push([j+1,lines[j].slice(0,10000)]);
|
|
9688
|
+
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)]);
|
|
9689
|
+
}
|
|
9690
|
+
results.push(match);
|
|
9691
|
+
}
|
|
9692
|
+
} catch {} finally { if (fd !== undefined) fs.closeSync(fd); }
|
|
9693
|
+
}
|
|
9694
|
+
parentPort.postMessage(results);
|
|
9695
|
+
`;
|
|
9696
|
+
var active = 0;
|
|
9697
|
+
async function searchInWorker(files, pattern, ignoreCase, context, maxResults) {
|
|
9698
|
+
const signal = currentAbortSignal();
|
|
9699
|
+
if (signal.aborted) throw new Error("Search cancelled");
|
|
9700
|
+
if (active >= 4) throw new Error("Search capacity reached; wait for another search to finish");
|
|
9701
|
+
active++;
|
|
9702
|
+
let worker;
|
|
9703
|
+
try {
|
|
9704
|
+
return await new Promise((resolve13, reject) => {
|
|
9705
|
+
worker = new Worker(source, { eval: true, workerData: { files, pattern, ignoreCase, context, maxResults }, resourceLimits: { maxOldGenerationSizeMb: 64 } });
|
|
9706
|
+
let settled = false;
|
|
9707
|
+
const finish = (error, results) => {
|
|
9708
|
+
if (settled) return;
|
|
9709
|
+
settled = true;
|
|
9710
|
+
clearTimeout(timer);
|
|
9711
|
+
signal.removeEventListener("abort", abort);
|
|
9712
|
+
if (error) reject(error);
|
|
9713
|
+
else resolve13(results ?? []);
|
|
9714
|
+
};
|
|
9715
|
+
const timer = setTimeout(() => finish(new Error("Search exceeded 2 second CPU/time budget; simplify the pattern or narrow the path")), 2e3);
|
|
9716
|
+
const abort = () => finish(new Error("Search cancelled"));
|
|
9717
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
9718
|
+
if (signal.aborted) abort();
|
|
9719
|
+
worker.once("message", (results) => finish(void 0, results));
|
|
9720
|
+
worker.once("error", (error) => finish(error));
|
|
9721
|
+
worker.once("exit", (code) => {
|
|
9722
|
+
if (!settled) finish(new Error(`Search worker exited (${code})`));
|
|
9723
|
+
});
|
|
9724
|
+
});
|
|
9725
|
+
} finally {
|
|
9726
|
+
if (worker) await worker.terminate();
|
|
9727
|
+
active--;
|
|
9728
|
+
}
|
|
9729
|
+
}
|
|
9730
|
+
|
|
9769
9731
|
// src/tools/builtin/grep-files.ts
|
|
9770
|
-
import { readdirSync as readdirSync5,
|
|
9771
|
-
import {
|
|
9772
|
-
import { join as join9, relative } from "path";
|
|
9732
|
+
import { readdirSync as readdirSync5, statSync as statSync6, existsSync as existsSync13 } from "fs";
|
|
9733
|
+
import { join as join8, relative } from "path";
|
|
9773
9734
|
var grepFilesTool = {
|
|
9774
9735
|
definition: {
|
|
9775
9736
|
name: "grep_files",
|
|
@@ -9817,43 +9778,24 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
9817
9778
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
9818
9779
|
const filePattern = args["file_pattern"] ? String(args["file_pattern"]) : void 0;
|
|
9819
9780
|
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"]
|
|
9781
|
+
const contextLines = Math.min(10, Math.max(0, Number(args["context_lines"]) || 0));
|
|
9782
|
+
const maxResults = Math.min(200, Math.max(1, Number(args["max_results"]) || 50));
|
|
9822
9783
|
if (!pattern) throw new ToolError("grep_files", "pattern is required");
|
|
9823
|
-
if (!
|
|
9784
|
+
if (!existsSync13(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
|
|
9824
9785
|
const MAX_PATTERN_LENGTH = 1e3;
|
|
9825
|
-
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
9786
|
+
if (pattern.length > MAX_PATTERN_LENGTH || (filePattern?.length ?? 0) > MAX_PATTERN_LENGTH) {
|
|
9826
9787
|
throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
9827
9788
|
}
|
|
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
|
-
}
|
|
9789
|
+
assertReadablePath(rootPath);
|
|
9790
|
+
const paths = statSync6(rootPath).isFile() ? [{ fullPath: rootPath, relPath: rootPath }] : collectFilePaths(rootPath, filePattern, maxResults);
|
|
9791
|
+
const files = paths.filter((f) => {
|
|
9792
|
+
try {
|
|
9793
|
+
return !isCredentialVault(f.fullPath);
|
|
9794
|
+
} catch {
|
|
9795
|
+
return false;
|
|
9855
9796
|
}
|
|
9856
|
-
}
|
|
9797
|
+
});
|
|
9798
|
+
const results = await searchInWorker(files, pattern, ignoreCase, contextLines, maxResults);
|
|
9857
9799
|
if (results.length === 0) {
|
|
9858
9800
|
return `No matches found for pattern: ${pattern}
|
|
9859
9801
|
Searched in: ${rootPath}${filePattern ? `
|
|
@@ -9893,8 +9835,23 @@ File filter: ${filePattern}` : ""}`;
|
|
|
9893
9835
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", ".next", "__pycache__", ".cache", "coverage", ".nyc_output"]);
|
|
9894
9836
|
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
9837
|
function matchesFilePattern(filename, pattern) {
|
|
9896
|
-
|
|
9897
|
-
|
|
9838
|
+
filename = filename.toLowerCase();
|
|
9839
|
+
pattern = pattern.toLowerCase();
|
|
9840
|
+
let i = 0, j = 0, star = -1, restart = 0;
|
|
9841
|
+
while (i < filename.length) {
|
|
9842
|
+
if (pattern[j] === filename[i]) {
|
|
9843
|
+
i++;
|
|
9844
|
+
j++;
|
|
9845
|
+
} else if (pattern[j] === "*") {
|
|
9846
|
+
star = j++;
|
|
9847
|
+
restart = i;
|
|
9848
|
+
} else if (star >= 0) {
|
|
9849
|
+
j = star + 1;
|
|
9850
|
+
i = ++restart;
|
|
9851
|
+
} else return false;
|
|
9852
|
+
}
|
|
9853
|
+
while (pattern[j] === "*") j++;
|
|
9854
|
+
return j === pattern.length;
|
|
9898
9855
|
}
|
|
9899
9856
|
function isBinary(filename) {
|
|
9900
9857
|
const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
|
|
@@ -9902,8 +9859,9 @@ function isBinary(filename) {
|
|
|
9902
9859
|
}
|
|
9903
9860
|
function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
9904
9861
|
const paths = [];
|
|
9905
|
-
|
|
9906
|
-
|
|
9862
|
+
let visited = 0;
|
|
9863
|
+
function walk(dirPath, depth = 0) {
|
|
9864
|
+
if (paths.length >= maxFiles || depth > 20 || visited >= 2e4) return;
|
|
9907
9865
|
let entries;
|
|
9908
9866
|
try {
|
|
9909
9867
|
entries = readdirSync5(dirPath, { withFileTypes: true });
|
|
@@ -9911,14 +9869,14 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
9911
9869
|
return;
|
|
9912
9870
|
}
|
|
9913
9871
|
for (const entry of entries) {
|
|
9914
|
-
if (paths.length >= maxFiles) return;
|
|
9872
|
+
if (paths.length >= maxFiles || ++visited > 2e4) return;
|
|
9915
9873
|
if (entry.isDirectory()) {
|
|
9916
9874
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
9917
|
-
walk(
|
|
9875
|
+
walk(join8(dirPath, entry.name), depth + 1);
|
|
9918
9876
|
} else if (entry.isFile()) {
|
|
9919
9877
|
if (isBinary(entry.name)) continue;
|
|
9920
9878
|
if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
|
|
9921
|
-
const fullPath =
|
|
9879
|
+
const fullPath = join8(dirPath, entry.name);
|
|
9922
9880
|
try {
|
|
9923
9881
|
if (statSync6(fullPath).size > 1e6) continue;
|
|
9924
9882
|
} catch {
|
|
@@ -9931,81 +9889,10 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
9931
9889
|
walk(rootPath);
|
|
9932
9890
|
return paths;
|
|
9933
9891
|
}
|
|
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
9892
|
|
|
10006
9893
|
// src/tools/builtin/glob-files.ts
|
|
10007
|
-
import { readdirSync as readdirSync6, statSync as statSync7, existsSync as
|
|
10008
|
-
import { join as
|
|
9894
|
+
import { readdirSync as readdirSync6, statSync as statSync7, existsSync as existsSync14 } from "fs";
|
|
9895
|
+
import { join as join9, relative as relative2, basename as basename5 } from "path";
|
|
10009
9896
|
var globFilesTool = {
|
|
10010
9897
|
definition: {
|
|
10011
9898
|
name: "glob_files",
|
|
@@ -10038,7 +9925,7 @@ Results sorted by most recent modification time. Automatically skips node_module
|
|
|
10038
9925
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
10039
9926
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
|
|
10040
9927
|
if (!pattern) throw new ToolError("glob_files", "pattern is required");
|
|
10041
|
-
if (!
|
|
9928
|
+
if (!existsSync14(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
|
|
10042
9929
|
const regex = globToRegex(pattern);
|
|
10043
9930
|
const matches2 = [];
|
|
10044
9931
|
collectMatchingFiles(rootPath, rootPath, regex, matches2, maxResults);
|
|
@@ -10115,7 +10002,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
10115
10002
|
}
|
|
10116
10003
|
for (const entry of entries) {
|
|
10117
10004
|
if (results.length >= maxResults) break;
|
|
10118
|
-
const fullPath =
|
|
10005
|
+
const fullPath = join9(dirPath, entry.name);
|
|
10119
10006
|
if (entry.isDirectory()) {
|
|
10120
10007
|
if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
10121
10008
|
collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
|
|
@@ -10134,9 +10021,9 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
10134
10021
|
}
|
|
10135
10022
|
|
|
10136
10023
|
// src/tools/builtin/run-interactive.ts
|
|
10137
|
-
import { spawn as
|
|
10138
|
-
import { platform as
|
|
10139
|
-
var IS_WINDOWS4 =
|
|
10024
|
+
import { spawn as spawn3 } from "child_process";
|
|
10025
|
+
import { platform as platform3 } from "os";
|
|
10026
|
+
var IS_WINDOWS4 = platform3() === "win32";
|
|
10140
10027
|
var runInteractiveTool = {
|
|
10141
10028
|
definition: {
|
|
10142
10029
|
name: "run_interactive",
|
|
@@ -10198,7 +10085,7 @@ var runInteractiveTool = {
|
|
|
10198
10085
|
};
|
|
10199
10086
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
10200
10087
|
return new Promise((resolve13) => {
|
|
10201
|
-
const child =
|
|
10088
|
+
const child = spawn3(executable, cmdArgs.map(String), {
|
|
10202
10089
|
cwd: process.cwd(),
|
|
10203
10090
|
env,
|
|
10204
10091
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10468,8 +10355,8 @@ var webFetchTool = {
|
|
|
10468
10355
|
if (e.message.startsWith("Blocked:")) throw e;
|
|
10469
10356
|
throw new NetworkError(`Invalid URL: "${url}"`);
|
|
10470
10357
|
}
|
|
10471
|
-
const
|
|
10472
|
-
const timeoutId = setTimeout(() =>
|
|
10358
|
+
const controller = new AbortController();
|
|
10359
|
+
const timeoutId = setTimeout(() => controller.abort(), 2e4);
|
|
10473
10360
|
let rawHtml;
|
|
10474
10361
|
let finalUrl;
|
|
10475
10362
|
let contentType;
|
|
@@ -10490,7 +10377,7 @@ var webFetchTool = {
|
|
|
10490
10377
|
}
|
|
10491
10378
|
await resolveAndCheck(parsedHop.hostname);
|
|
10492
10379
|
const r = await fetch(currentUrl, {
|
|
10493
|
-
signal:
|
|
10380
|
+
signal: controller.signal,
|
|
10494
10381
|
headers: FETCH_HEADERS,
|
|
10495
10382
|
redirect: "manual",
|
|
10496
10383
|
// 手动控制重定向
|
|
@@ -10853,8 +10740,8 @@ function parseGoogleResults(html, maxResults) {
|
|
|
10853
10740
|
return results;
|
|
10854
10741
|
}
|
|
10855
10742
|
async function fetchHtml(url) {
|
|
10856
|
-
const
|
|
10857
|
-
const timer = setTimeout(() =>
|
|
10743
|
+
const controller = new AbortController();
|
|
10744
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
10858
10745
|
try {
|
|
10859
10746
|
const res = await fetch(url, {
|
|
10860
10747
|
method: "GET",
|
|
@@ -10863,7 +10750,7 @@ async function fetchHtml(url) {
|
|
|
10863
10750
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9",
|
|
10864
10751
|
"Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8"
|
|
10865
10752
|
},
|
|
10866
|
-
signal:
|
|
10753
|
+
signal: controller.signal
|
|
10867
10754
|
});
|
|
10868
10755
|
if (!res.ok) {
|
|
10869
10756
|
throw new NetworkError(`HTTP ${res.status} ${res.statusText}`, res.status);
|
|
@@ -10957,10 +10844,8 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
10957
10844
|
};
|
|
10958
10845
|
|
|
10959
10846
|
// src/tools/builtin/save-memory.ts
|
|
10960
|
-
import { join as join11 } from "path";
|
|
10961
|
-
import { homedir as homedir5 } from "os";
|
|
10962
10847
|
function getConfigDir() {
|
|
10963
|
-
return
|
|
10848
|
+
return getExecutionConfigDir();
|
|
10964
10849
|
}
|
|
10965
10850
|
function parseSensitivity(value) {
|
|
10966
10851
|
return value === "low" || value === "medium" || value === "high" ? value : void 0;
|
|
@@ -11224,15 +11109,15 @@ var googleSearchTool = {
|
|
|
11224
11109
|
url.searchParams.set("cx", cx);
|
|
11225
11110
|
url.searchParams.set("q", query);
|
|
11226
11111
|
url.searchParams.set("num", String(numResults));
|
|
11227
|
-
const
|
|
11228
|
-
const timeout = setTimeout(() =>
|
|
11112
|
+
const controller = new AbortController();
|
|
11113
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
|
|
11229
11114
|
try {
|
|
11230
11115
|
const response = await fetch(url.toString(), {
|
|
11231
11116
|
method: "GET",
|
|
11232
11117
|
headers: {
|
|
11233
11118
|
"Accept": "application/json"
|
|
11234
11119
|
},
|
|
11235
|
-
signal:
|
|
11120
|
+
signal: controller.signal
|
|
11236
11121
|
});
|
|
11237
11122
|
if (!response.ok) {
|
|
11238
11123
|
const errorBody = await response.text().catch(() => "");
|
|
@@ -11316,13 +11201,13 @@ function isToolCapableProvider(provider) {
|
|
|
11316
11201
|
}
|
|
11317
11202
|
|
|
11318
11203
|
// src/agents/agent-config.ts
|
|
11319
|
-
import { existsSync as
|
|
11320
|
-
import { join as
|
|
11321
|
-
import { homedir as
|
|
11204
|
+
import { existsSync as existsSync16, readdirSync as readdirSync8, readFileSync as readFileSync11 } from "fs";
|
|
11205
|
+
import { join as join11 } from "path";
|
|
11206
|
+
import { homedir as homedir5 } from "os";
|
|
11322
11207
|
|
|
11323
11208
|
// src/plugins/plugin-manager.ts
|
|
11324
|
-
import { existsSync as
|
|
11325
|
-
import { basename as basename6, dirname as dirname7, join as
|
|
11209
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync10, realpathSync as realpathSync2, cpSync, rmSync, statSync as statSync8 } from "fs";
|
|
11210
|
+
import { basename as basename6, dirname as dirname7, join as join10, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
|
|
11326
11211
|
import { createHash as createHash2 } from "crypto";
|
|
11327
11212
|
import { z as z2 } from "zod";
|
|
11328
11213
|
var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
|
|
@@ -11382,19 +11267,19 @@ var PluginManifestSchema = z2.object({
|
|
|
11382
11267
|
permissionHints: z2.array(z2.string()).default([])
|
|
11383
11268
|
}).strict();
|
|
11384
11269
|
function pluginRoot(configDir) {
|
|
11385
|
-
return
|
|
11270
|
+
return join10(configDir, PLUGINS_DIR_NAME);
|
|
11386
11271
|
}
|
|
11387
11272
|
function pluginManifestPath(pluginDir) {
|
|
11388
|
-
return
|
|
11273
|
+
return join10(pluginDir, MANIFEST_RELATIVE);
|
|
11389
11274
|
}
|
|
11390
11275
|
function statePath(configDir) {
|
|
11391
|
-
return
|
|
11276
|
+
return join10(pluginRoot(configDir), STATE_FILE);
|
|
11392
11277
|
}
|
|
11393
11278
|
function loadState(configDir) {
|
|
11394
11279
|
const file = statePath(configDir);
|
|
11395
|
-
if (!
|
|
11280
|
+
if (!existsSync15(file)) return { version: 2, plugins: [] };
|
|
11396
11281
|
try {
|
|
11397
|
-
const parsed = JSON.parse(
|
|
11282
|
+
const parsed = JSON.parse(readFileSync10(file, "utf-8"));
|
|
11398
11283
|
const rawPlugins = Array.isArray(parsed.plugins) ? parsed.plugins : [];
|
|
11399
11284
|
return { version: 2, plugins: rawPlugins.map((p) => migrateEntry(p)) };
|
|
11400
11285
|
} catch {
|
|
@@ -11452,7 +11337,7 @@ function extractCommandPaths(command, pluginDir) {
|
|
|
11452
11337
|
try {
|
|
11453
11338
|
const abs = resolve6(resolvedDir, ref);
|
|
11454
11339
|
if (!abs.startsWith(resolvedDir + sep3)) continue;
|
|
11455
|
-
if (
|
|
11340
|
+
if (existsSync15(abs)) {
|
|
11456
11341
|
const real = realpathSync2(abs);
|
|
11457
11342
|
if (!real.startsWith(resolvedDir + sep3)) continue;
|
|
11458
11343
|
valid.push(relative3(resolvedDir, real));
|
|
@@ -11484,8 +11369,8 @@ function hashPluginContent(pluginDir, manifest) {
|
|
|
11484
11369
|
}
|
|
11485
11370
|
const sorted = [...assetRels].sort();
|
|
11486
11371
|
const hasher = createHash2("sha256");
|
|
11487
|
-
const manifestPath =
|
|
11488
|
-
hasher.update(normalizeForHash(
|
|
11372
|
+
const manifestPath = join10(resolvedDir, MANIFEST_RELATIVE);
|
|
11373
|
+
hasher.update(normalizeForHash(readFileSync10(manifestPath)));
|
|
11489
11374
|
for (const rel of sorted) {
|
|
11490
11375
|
const abs = resolve6(resolvedDir, rel);
|
|
11491
11376
|
if (!abs.startsWith(resolvedDir + sep3)) {
|
|
@@ -11505,7 +11390,7 @@ function hashPluginContent(pluginDir, manifest) {
|
|
|
11505
11390
|
if (!realPath.startsWith(resolvedDir + sep3)) {
|
|
11506
11391
|
throw new Error(`plugin asset symlink escapes plugin dir: ${rel} \u2192 ${realPath}`);
|
|
11507
11392
|
}
|
|
11508
|
-
hasher.update(normalizeForHash(
|
|
11393
|
+
hasher.update(normalizeForHash(readFileSync10(abs)));
|
|
11509
11394
|
}
|
|
11510
11395
|
return hasher.digest("hex");
|
|
11511
11396
|
}
|
|
@@ -11513,22 +11398,22 @@ function effectiveHash(entry) {
|
|
|
11513
11398
|
return entry.contentHash ?? entry.hash ?? "";
|
|
11514
11399
|
}
|
|
11515
11400
|
function readPluginManifest(manifestPath) {
|
|
11516
|
-
return PluginManifestSchema.parse(JSON.parse(
|
|
11401
|
+
return PluginManifestSchema.parse(JSON.parse(readFileSync10(manifestPath, "utf-8")));
|
|
11517
11402
|
}
|
|
11518
11403
|
function findPluginSourceDir(inputPath) {
|
|
11519
11404
|
const abs = resolve6(inputPath);
|
|
11520
11405
|
const direct = statSync8(abs).isDirectory() ? abs : dirname7(abs);
|
|
11521
|
-
if (
|
|
11522
|
-
const nested =
|
|
11523
|
-
if (
|
|
11406
|
+
if (existsSync15(pluginManifestPath(direct))) return direct;
|
|
11407
|
+
const nested = join10(direct, ".aicli-plugin");
|
|
11408
|
+
if (existsSync15(join10(nested, "plugin.json"))) return direct;
|
|
11524
11409
|
throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
|
|
11525
11410
|
}
|
|
11526
11411
|
function installPlugin(configDir, inputPath) {
|
|
11527
11412
|
const sourceDir = findPluginSourceDir(inputPath);
|
|
11528
11413
|
const manifest = readPluginManifest(pluginManifestPath(sourceDir));
|
|
11529
|
-
const targetDir =
|
|
11414
|
+
const targetDir = join10(pluginRoot(configDir), manifest.name);
|
|
11530
11415
|
mkdirSync8(pluginRoot(configDir), { recursive: true });
|
|
11531
|
-
if (
|
|
11416
|
+
if (existsSync15(targetDir)) rmSync(targetDir, { recursive: true, force: true });
|
|
11532
11417
|
cpSync(sourceDir, targetDir, { recursive: true });
|
|
11533
11418
|
const manifestPath = pluginManifestPath(targetDir);
|
|
11534
11419
|
const contentHash = hashPluginContent(targetDir, manifest);
|
|
@@ -11586,18 +11471,18 @@ function getInstalledPlugin(configDir, name) {
|
|
|
11586
11471
|
}
|
|
11587
11472
|
function listInstalledPlugins(configDir) {
|
|
11588
11473
|
const root = pluginRoot(configDir);
|
|
11589
|
-
if (!
|
|
11474
|
+
if (!existsSync15(root)) return [];
|
|
11590
11475
|
const state2 = loadState(configDir);
|
|
11591
11476
|
const out = [];
|
|
11592
11477
|
for (const entry of readdirSync7(root)) {
|
|
11593
|
-
const dir =
|
|
11478
|
+
const dir = join10(root, entry);
|
|
11594
11479
|
try {
|
|
11595
11480
|
if (!statSync8(dir).isDirectory()) continue;
|
|
11596
11481
|
} catch {
|
|
11597
11482
|
continue;
|
|
11598
11483
|
}
|
|
11599
11484
|
const manifestPath = pluginManifestPath(dir);
|
|
11600
|
-
if (!
|
|
11485
|
+
if (!existsSync15(manifestPath)) continue;
|
|
11601
11486
|
try {
|
|
11602
11487
|
const manifest = readPluginManifest(manifestPath);
|
|
11603
11488
|
const contentHash = hashPluginContent(dir, manifest);
|
|
@@ -11637,7 +11522,7 @@ function uniqueDirs(plugin, rels) {
|
|
|
11637
11522
|
for (const rel of rels) {
|
|
11638
11523
|
const abs = resolve6(plugin.dir, rel);
|
|
11639
11524
|
if (!abs.startsWith(resolve6(plugin.dir))) continue;
|
|
11640
|
-
if (
|
|
11525
|
+
if (existsSync15(abs)) dirs.add(statSync8(abs).isDirectory() ? abs : dirname7(abs));
|
|
11641
11526
|
}
|
|
11642
11527
|
return [...dirs];
|
|
11643
11528
|
}
|
|
@@ -11777,13 +11662,13 @@ function normalizeName(name) {
|
|
|
11777
11662
|
function isStringArray(value) {
|
|
11778
11663
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
11779
11664
|
}
|
|
11780
|
-
function parseAgentConfig(raw,
|
|
11665
|
+
function parseAgentConfig(raw, source2, path3) {
|
|
11781
11666
|
if (!raw || typeof raw !== "object") return null;
|
|
11782
11667
|
const obj = raw;
|
|
11783
11668
|
if (typeof obj.name !== "string" || obj.name.trim() === "") return null;
|
|
11784
11669
|
const cfg = {
|
|
11785
11670
|
name: obj.name.trim(),
|
|
11786
|
-
source,
|
|
11671
|
+
source: source2,
|
|
11787
11672
|
path: path3
|
|
11788
11673
|
};
|
|
11789
11674
|
if (typeof obj.description === "string") cfg.description = obj.description;
|
|
@@ -11799,15 +11684,15 @@ function parseAgentConfig(raw, source, path3) {
|
|
|
11799
11684
|
if (typeof obj.contextPolicy === "string") cfg.contextPolicy = obj.contextPolicy;
|
|
11800
11685
|
return cfg;
|
|
11801
11686
|
}
|
|
11802
|
-
function loadAgentDir(dir,
|
|
11803
|
-
if (!
|
|
11687
|
+
function loadAgentDir(dir, source2) {
|
|
11688
|
+
if (!existsSync16(dir)) return [];
|
|
11804
11689
|
const out = [];
|
|
11805
11690
|
for (const file of readdirSync8(dir)) {
|
|
11806
11691
|
if (!file.endsWith(".json")) continue;
|
|
11807
|
-
const path3 =
|
|
11692
|
+
const path3 = join11(dir, file);
|
|
11808
11693
|
try {
|
|
11809
|
-
const parsed = JSON.parse(
|
|
11810
|
-
const cfg = parseAgentConfig(parsed,
|
|
11694
|
+
const parsed = JSON.parse(readFileSync11(path3, "utf-8"));
|
|
11695
|
+
const cfg = parseAgentConfig(parsed, source2, path3);
|
|
11811
11696
|
if (cfg) out.push(cfg);
|
|
11812
11697
|
} catch {
|
|
11813
11698
|
}
|
|
@@ -11816,8 +11701,8 @@ function loadAgentDir(dir, source) {
|
|
|
11816
11701
|
}
|
|
11817
11702
|
function getAgentDirs(configDir, cwd = process.cwd()) {
|
|
11818
11703
|
return {
|
|
11819
|
-
user:
|
|
11820
|
-
project:
|
|
11704
|
+
user: join11(configDir ?? join11(homedir5(), CONFIG_DIR_NAME), "agents"),
|
|
11705
|
+
project: join11(cwd, CONFIG_DIR_NAME, "agents")
|
|
11821
11706
|
};
|
|
11822
11707
|
}
|
|
11823
11708
|
function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
@@ -12320,9 +12205,8 @@ var spawnAgentTool = {
|
|
|
12320
12205
|
};
|
|
12321
12206
|
|
|
12322
12207
|
// src/tools/builtin/task-manager.ts
|
|
12323
|
-
import { spawn as
|
|
12208
|
+
import { spawn as spawn4 } from "child_process";
|
|
12324
12209
|
import { randomUUID } from "crypto";
|
|
12325
|
-
import { platform as platform5 } from "os";
|
|
12326
12210
|
var MAX_OUTPUT_CHARS = 1e4;
|
|
12327
12211
|
var MAX_OUTPUT_BYTES = 64 * 1024;
|
|
12328
12212
|
var MAX_TASKS = 20;
|
|
@@ -12341,14 +12225,16 @@ function decodeField(chunks) {
|
|
|
12341
12225
|
}
|
|
12342
12226
|
function appendOutput(task, field, chunk) {
|
|
12343
12227
|
const chunks = field === "stdout" ? task._stdoutChunks : task._stderrChunks;
|
|
12344
|
-
chunks.push(chunk);
|
|
12228
|
+
chunks.push(chunk.length > MAX_OUTPUT_BYTES ? chunk.subarray(-MAX_OUTPUT_BYTES) : chunk);
|
|
12345
12229
|
capChunks(chunks);
|
|
12346
12230
|
task[field] = decodeField(chunks);
|
|
12347
12231
|
}
|
|
12348
12232
|
function createTask(command, description) {
|
|
12233
|
+
if ([...tasks.values()].filter((t) => t._active && t.owner === getExecutionConfigDir()).length >= 10) throw new Error("Background task limit reached (10 running per user).");
|
|
12234
|
+
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
12235
|
if (tasks.size >= MAX_TASKS) {
|
|
12350
12236
|
for (const [id2, t] of tasks) {
|
|
12351
|
-
if (t.
|
|
12237
|
+
if (!t._active) {
|
|
12352
12238
|
tasks.delete(id2);
|
|
12353
12239
|
break;
|
|
12354
12240
|
}
|
|
@@ -12356,16 +12242,19 @@ function createTask(command, description) {
|
|
|
12356
12242
|
}
|
|
12357
12243
|
const id = randomUUID().slice(0, 8);
|
|
12358
12244
|
const { shell, args } = shellInvocation(command);
|
|
12359
|
-
const proc =
|
|
12245
|
+
const proc = spawn4(shell, args, {
|
|
12360
12246
|
stdio: ["ignore", "pipe", "pipe"],
|
|
12361
|
-
detached:
|
|
12247
|
+
detached: process.platform !== "win32",
|
|
12248
|
+
windowsHide: true,
|
|
12362
12249
|
env: { ...process.env, PYTHONUTF8: "1", PYTHONIOENCODING: "utf-8" }
|
|
12363
12250
|
});
|
|
12364
12251
|
const task = {
|
|
12365
12252
|
id,
|
|
12253
|
+
owner: getExecutionConfigDir(),
|
|
12366
12254
|
command,
|
|
12367
12255
|
description,
|
|
12368
12256
|
process: proc,
|
|
12257
|
+
_active: true,
|
|
12369
12258
|
startTime: Date.now(),
|
|
12370
12259
|
endTime: null,
|
|
12371
12260
|
status: "running",
|
|
@@ -12378,11 +12267,13 @@ function createTask(command, description) {
|
|
|
12378
12267
|
proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk));
|
|
12379
12268
|
proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk));
|
|
12380
12269
|
proc.on("close", (code) => {
|
|
12270
|
+
task._active = false;
|
|
12381
12271
|
task.endTime = Date.now();
|
|
12382
12272
|
task.exitCode = code;
|
|
12383
|
-
task.status = code === 0 ? "completed" : "failed";
|
|
12273
|
+
if (task.status !== "stopped") task.status = code === 0 ? "completed" : "failed";
|
|
12384
12274
|
});
|
|
12385
12275
|
proc.on("error", (err) => {
|
|
12276
|
+
task._active = false;
|
|
12386
12277
|
task.endTime = Date.now();
|
|
12387
12278
|
task.status = "failed";
|
|
12388
12279
|
task.stderr += `
|
|
@@ -12392,43 +12283,20 @@ Process error: ${err.message}`;
|
|
|
12392
12283
|
return task;
|
|
12393
12284
|
}
|
|
12394
12285
|
function listTasks() {
|
|
12395
|
-
return [...tasks.values()].map(
|
|
12396
|
-
({ process: _p, _stdoutChunks: _o, _stderrChunks: _e, ...rest }) => rest
|
|
12286
|
+
return [...tasks.values()].filter((t) => t.owner === getExecutionConfigDir()).map(
|
|
12287
|
+
({ process: _p, _stdoutChunks: _o, _stderrChunks: _e, _active: _a, ...rest }) => rest
|
|
12397
12288
|
);
|
|
12398
12289
|
}
|
|
12399
12290
|
function getTaskOutput(id) {
|
|
12400
12291
|
const task = tasks.get(id);
|
|
12401
|
-
if (!task) return null;
|
|
12292
|
+
if (!task || task.owner !== getExecutionConfigDir()) return null;
|
|
12402
12293
|
return { stdout: task.stdout, stderr: task.stderr, status: task.status };
|
|
12403
12294
|
}
|
|
12404
12295
|
function stopTask(id) {
|
|
12405
12296
|
const task = tasks.get(id);
|
|
12406
|
-
if (!task || task.status !== "running") return false;
|
|
12297
|
+
if (!task || task.owner !== getExecutionConfigDir() || task.status !== "running") return false;
|
|
12407
12298
|
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
|
-
}
|
|
12299
|
+
killProcessTree(task.process, true);
|
|
12432
12300
|
task.status = "stopped";
|
|
12433
12301
|
task.endTime = Date.now();
|
|
12434
12302
|
return true;
|
|
@@ -12536,14 +12404,14 @@ var taskStopTool = {
|
|
|
12536
12404
|
|
|
12537
12405
|
// src/tools/builtin/git-tools.ts
|
|
12538
12406
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
12539
|
-
import { existsSync as
|
|
12540
|
-
import { join as
|
|
12407
|
+
import { existsSync as existsSync17 } from "fs";
|
|
12408
|
+
import { join as join12 } from "path";
|
|
12541
12409
|
function assertGitRepo(cwd) {
|
|
12542
12410
|
let dir = cwd;
|
|
12543
12411
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
12544
12412
|
while (dir && dir !== root) {
|
|
12545
|
-
if (
|
|
12546
|
-
const parent =
|
|
12413
|
+
if (existsSync17(join12(dir, ".git"))) return;
|
|
12414
|
+
const parent = join12(dir, "..");
|
|
12547
12415
|
if (parent === dir) break;
|
|
12548
12416
|
dir = parent;
|
|
12549
12417
|
}
|
|
@@ -12808,7 +12676,7 @@ ${commitOutput.trim()}`;
|
|
|
12808
12676
|
};
|
|
12809
12677
|
|
|
12810
12678
|
// src/tools/builtin/notebook-edit.ts
|
|
12811
|
-
import { readFileSync as
|
|
12679
|
+
import { readFileSync as readFileSync12, existsSync as existsSync18 } from "fs";
|
|
12812
12680
|
import { writeFile } from "fs/promises";
|
|
12813
12681
|
import { resolve as resolve7, extname as extname2 } from "path";
|
|
12814
12682
|
var notebookEditTool = {
|
|
@@ -12863,10 +12731,10 @@ var notebookEditTool = {
|
|
|
12863
12731
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
12864
12732
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
12865
12733
|
}
|
|
12866
|
-
if (!
|
|
12734
|
+
if (!existsSync18(absPath)) {
|
|
12867
12735
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
12868
12736
|
}
|
|
12869
|
-
const raw =
|
|
12737
|
+
const raw = readFileSync12(absPath, "utf-8");
|
|
12870
12738
|
const nb = parseNotebook(raw);
|
|
12871
12739
|
const cellIdx0 = cellIndexRaw - 1;
|
|
12872
12740
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -13192,8 +13060,6 @@ ${lines.join("\n")}`;
|
|
|
13192
13060
|
};
|
|
13193
13061
|
|
|
13194
13062
|
// src/tools/builtin/recall-memory.ts
|
|
13195
|
-
import { join as join15 } from "path";
|
|
13196
|
-
import { homedir as homedir7 } from "os";
|
|
13197
13063
|
function formatHit(h, i) {
|
|
13198
13064
|
const ts = h.chunk.timestamp.slice(0, 16).replace("T", " ");
|
|
13199
13065
|
const title = h.chunk.sessionTitle ? ` \xB7 ${h.chunk.sessionTitle}` : "";
|
|
@@ -13243,7 +13109,7 @@ var recallMemoryTool = {
|
|
|
13243
13109
|
const excludeCurrent = Boolean(args["excludeCurrentSession"]);
|
|
13244
13110
|
const currentId = args["currentSessionId"] ? String(args["currentSessionId"]) : void 0;
|
|
13245
13111
|
const minScore = args["minScore"] !== void 0 ? Number(args["minScore"]) : 0.25;
|
|
13246
|
-
const configDir =
|
|
13112
|
+
const configDir = getExecutionConfigDir();
|
|
13247
13113
|
let persistentSection = "";
|
|
13248
13114
|
try {
|
|
13249
13115
|
const persistentHits = searchPersistentMemories(configDir, query, process.cwd(), 3);
|
|
@@ -13297,8 +13163,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
13297
13163
|
|
|
13298
13164
|
// src/tools/registry.ts
|
|
13299
13165
|
import { pathToFileURL } from "url";
|
|
13300
|
-
import { existsSync as
|
|
13301
|
-
import { join as
|
|
13166
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
|
|
13167
|
+
import { join as join13 } from "path";
|
|
13302
13168
|
var ToolRegistry = class {
|
|
13303
13169
|
tools = /* @__PURE__ */ new Map();
|
|
13304
13170
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -13459,7 +13325,7 @@ var ToolRegistry = class {
|
|
|
13459
13325
|
* Returns the number of successfully loaded plugins.
|
|
13460
13326
|
*/
|
|
13461
13327
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
13462
|
-
if (!
|
|
13328
|
+
if (!existsSync19(pluginsDir)) {
|
|
13463
13329
|
try {
|
|
13464
13330
|
mkdirSync9(pluginsDir, { recursive: true });
|
|
13465
13331
|
} catch {
|
|
@@ -13485,12 +13351,12 @@ var ToolRegistry = class {
|
|
|
13485
13351
|
process.stderr.write(
|
|
13486
13352
|
`
|
|
13487
13353
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
13488
|
-
` + files.map((f) => ` + ${
|
|
13354
|
+
` + files.map((f) => ` + ${join13(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
13489
13355
|
);
|
|
13490
13356
|
let loaded = 0;
|
|
13491
13357
|
for (const file of files) {
|
|
13492
13358
|
try {
|
|
13493
|
-
const fileUrl = pathToFileURL(
|
|
13359
|
+
const fileUrl = pathToFileURL(join13(pluginsDir, file)).href;
|
|
13494
13360
|
const mod = await import(fileUrl);
|
|
13495
13361
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
13496
13362
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -13518,7 +13384,7 @@ var ToolRegistry = class {
|
|
|
13518
13384
|
};
|
|
13519
13385
|
|
|
13520
13386
|
// src/mcp/client.ts
|
|
13521
|
-
import { spawn as
|
|
13387
|
+
import { spawn as spawn5 } from "child_process";
|
|
13522
13388
|
var McpClient = class {
|
|
13523
13389
|
serverId;
|
|
13524
13390
|
config;
|
|
@@ -13556,7 +13422,7 @@ var McpClient = class {
|
|
|
13556
13422
|
async connect() {
|
|
13557
13423
|
const timeout = this.config.timeout ?? MCP_CONNECT_TIMEOUT;
|
|
13558
13424
|
try {
|
|
13559
|
-
this.process =
|
|
13425
|
+
this.process = spawn5(this.config.command, this.config.args ?? [], {
|
|
13560
13426
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13561
13427
|
env: { ...process.env, ...this.config.env },
|
|
13562
13428
|
// Windows 上 npx 等是 .cmd 脚本,需要 shell 模式
|
|
@@ -14035,11 +13901,11 @@ var McpManager = class {
|
|
|
14035
13901
|
};
|
|
14036
13902
|
|
|
14037
13903
|
// src/skills/manager.ts
|
|
14038
|
-
import { existsSync as
|
|
14039
|
-
import { join as
|
|
13904
|
+
import { existsSync as existsSync20, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync9 } from "fs";
|
|
13905
|
+
import { join as join14 } from "path";
|
|
14040
13906
|
|
|
14041
13907
|
// src/skills/types.ts
|
|
14042
|
-
import { readFileSync as
|
|
13908
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
14043
13909
|
import { basename as basename7 } from "path";
|
|
14044
13910
|
function parseSimpleYaml(yaml) {
|
|
14045
13911
|
const result = {};
|
|
@@ -14061,7 +13927,7 @@ function parseYamlArray(value) {
|
|
|
14061
13927
|
function parseSkillFile(filePath) {
|
|
14062
13928
|
let raw;
|
|
14063
13929
|
try {
|
|
14064
|
-
raw =
|
|
13930
|
+
raw = readFileSync13(filePath, "utf-8");
|
|
14065
13931
|
} catch {
|
|
14066
13932
|
return null;
|
|
14067
13933
|
}
|
|
@@ -14109,7 +13975,7 @@ var SkillManager = class {
|
|
|
14109
13975
|
return this.skills.size;
|
|
14110
13976
|
}
|
|
14111
13977
|
loadSkillDir(dir, createIfMissing) {
|
|
14112
|
-
if (!
|
|
13978
|
+
if (!existsSync20(dir)) {
|
|
14113
13979
|
if (createIfMissing) {
|
|
14114
13980
|
try {
|
|
14115
13981
|
mkdirSync10(dir, { recursive: true });
|
|
@@ -14126,14 +13992,14 @@ var SkillManager = class {
|
|
|
14126
13992
|
}
|
|
14127
13993
|
for (const entry of entries) {
|
|
14128
13994
|
let filePath;
|
|
14129
|
-
const fullPath =
|
|
13995
|
+
const fullPath = join14(dir, entry);
|
|
14130
13996
|
if (entry.endsWith(".md")) {
|
|
14131
13997
|
filePath = fullPath;
|
|
14132
13998
|
} else {
|
|
14133
13999
|
try {
|
|
14134
14000
|
if (statSync9(fullPath).isDirectory()) {
|
|
14135
|
-
const skillMd =
|
|
14136
|
-
if (
|
|
14001
|
+
const skillMd = join14(fullPath, "SKILL.md");
|
|
14002
|
+
if (existsSync20(skillMd)) filePath = skillMd;
|
|
14137
14003
|
else continue;
|
|
14138
14004
|
} else {
|
|
14139
14005
|
continue;
|
|
@@ -14246,7 +14112,7 @@ function sendSocketMessage(ws, message) {
|
|
|
14246
14112
|
}
|
|
14247
14113
|
|
|
14248
14114
|
// src/web/tool-executor-web.ts
|
|
14249
|
-
import { existsSync as
|
|
14115
|
+
import { existsSync as existsSync21, readFileSync as readFileSync14 } from "fs";
|
|
14250
14116
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
14251
14117
|
constructor(registry, ws) {
|
|
14252
14118
|
this.registry = registry;
|
|
@@ -14393,9 +14259,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
14393
14259
|
getDiffPreview(call) {
|
|
14394
14260
|
if (call.name === "write_file") {
|
|
14395
14261
|
const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
|
|
14396
|
-
if (filePath &&
|
|
14262
|
+
if (filePath && existsSync21(filePath)) {
|
|
14397
14263
|
try {
|
|
14398
|
-
const old =
|
|
14264
|
+
const old = readFileSync14(filePath, "utf-8");
|
|
14399
14265
|
return renderDiff(old, append ? old + chunk : chunk, { filePath });
|
|
14400
14266
|
} catch {
|
|
14401
14267
|
}
|
|
@@ -14631,16 +14497,16 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
14631
14497
|
};
|
|
14632
14498
|
|
|
14633
14499
|
// src/repl/dev-state.ts
|
|
14634
|
-
import { existsSync as
|
|
14635
|
-
import { join as
|
|
14636
|
-
import { homedir as
|
|
14500
|
+
import { existsSync as existsSync22, readFileSync as readFileSync15, unlinkSync as unlinkSync5, mkdirSync as mkdirSync11 } from "fs";
|
|
14501
|
+
import { join as join15 } from "path";
|
|
14502
|
+
import { homedir as homedir6 } from "os";
|
|
14637
14503
|
function getDevStatePath() {
|
|
14638
|
-
return
|
|
14504
|
+
return join15(homedir6(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
14639
14505
|
}
|
|
14640
14506
|
function loadDevState() {
|
|
14641
14507
|
const path3 = getDevStatePath();
|
|
14642
|
-
if (!
|
|
14643
|
-
const content =
|
|
14508
|
+
if (!existsSync22(path3)) return null;
|
|
14509
|
+
const content = readFileSync15(path3, "utf-8").trim();
|
|
14644
14510
|
return content || null;
|
|
14645
14511
|
}
|
|
14646
14512
|
|
|
@@ -15010,8 +14876,8 @@ var checkpointDeletedMessage = (name) => `\u2713 Deleted checkpoint "${name}"`;
|
|
|
15010
14876
|
var checkpointNotFoundMessage = (name) => `Checkpoint "${name}" not found.`;
|
|
15011
14877
|
|
|
15012
14878
|
// src/core/context-files.ts
|
|
15013
|
-
import { existsSync as
|
|
15014
|
-
import { join as
|
|
14879
|
+
import { existsSync as existsSync23, readFileSync as readFileSync16 } from "fs";
|
|
14880
|
+
import { join as join16, relative as relative4, resolve as resolve8 } from "path";
|
|
15015
14881
|
function uniqueNonEmpty(values) {
|
|
15016
14882
|
const seen = /* @__PURE__ */ new Set();
|
|
15017
14883
|
const out = [];
|
|
@@ -15041,7 +14907,7 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
15041
14907
|
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
15042
14908
|
const shown = displayPath(filePath, cwd);
|
|
15043
14909
|
try {
|
|
15044
|
-
const raw =
|
|
14910
|
+
const raw = readFileSync16(filePath);
|
|
15045
14911
|
const byteCount = raw.byteLength;
|
|
15046
14912
|
if (byteCount === 0) {
|
|
15047
14913
|
return {
|
|
@@ -15088,8 +14954,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
15088
14954
|
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
15089
14955
|
const skipped = [];
|
|
15090
14956
|
for (const candidate of candidates) {
|
|
15091
|
-
const filePath =
|
|
15092
|
-
if (!
|
|
14957
|
+
const filePath = join16(dir, candidate);
|
|
14958
|
+
if (!existsSync23(filePath)) continue;
|
|
15093
14959
|
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
15094
14960
|
if (result.skipped) skipped.push(result.skipped);
|
|
15095
14961
|
if (result.layer) return { layer: result.layer, skipped };
|
|
@@ -15116,7 +14982,7 @@ function loadContextFiles(options) {
|
|
|
15116
14982
|
});
|
|
15117
14983
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
15118
14984
|
}
|
|
15119
|
-
if (!
|
|
14985
|
+
if (!existsSync23(filePath)) {
|
|
15120
14986
|
skipped.push({
|
|
15121
14987
|
level: "single",
|
|
15122
14988
|
filePath,
|
|
@@ -15229,8 +15095,8 @@ import { mkdirSync as mkdirSync15, createWriteStream as createWriteStream2 } fro
|
|
|
15229
15095
|
import { dirname as dirname10 } from "path";
|
|
15230
15096
|
|
|
15231
15097
|
// src/repl/commands/project-init.ts
|
|
15232
|
-
import { existsSync as
|
|
15233
|
-
import { join as
|
|
15098
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17, readdirSync as readdirSync11, statSync as statSync11 } from "fs";
|
|
15099
|
+
import { join as join17 } from "path";
|
|
15234
15100
|
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
15235
15101
|
"node_modules",
|
|
15236
15102
|
".git",
|
|
@@ -15274,11 +15140,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
15274
15140
|
const sorted = filtered.sort((a, b) => {
|
|
15275
15141
|
let aIsDir = false, bIsDir = false;
|
|
15276
15142
|
try {
|
|
15277
|
-
aIsDir = statSync11(
|
|
15143
|
+
aIsDir = statSync11(join17(d, a)).isDirectory();
|
|
15278
15144
|
} catch {
|
|
15279
15145
|
}
|
|
15280
15146
|
try {
|
|
15281
|
-
bIsDir = statSync11(
|
|
15147
|
+
bIsDir = statSync11(join17(d, b)).isDirectory();
|
|
15282
15148
|
} catch {
|
|
15283
15149
|
}
|
|
15284
15150
|
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
@@ -15286,7 +15152,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
15286
15152
|
});
|
|
15287
15153
|
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
15288
15154
|
const name = sorted[i];
|
|
15289
|
-
const fullPath =
|
|
15155
|
+
const fullPath = join17(d, name);
|
|
15290
15156
|
const isLast = i === sorted.length - 1;
|
|
15291
15157
|
const connector = isLast ? "+-- " : "|-- ";
|
|
15292
15158
|
let isDir;
|
|
@@ -15313,7 +15179,7 @@ function scanProject(cwd) {
|
|
|
15313
15179
|
configFiles: [],
|
|
15314
15180
|
directoryStructure: ""
|
|
15315
15181
|
};
|
|
15316
|
-
const check = (file) =>
|
|
15182
|
+
const check = (file) => existsSync24(join17(cwd, file));
|
|
15317
15183
|
const configCandidates = [
|
|
15318
15184
|
"package.json",
|
|
15319
15185
|
"tsconfig.json",
|
|
@@ -15340,7 +15206,7 @@ function scanProject(cwd) {
|
|
|
15340
15206
|
info.type = "node";
|
|
15341
15207
|
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
15342
15208
|
try {
|
|
15343
|
-
const pkg = JSON.parse(
|
|
15209
|
+
const pkg = JSON.parse(readFileSync17(join17(cwd, "package.json"), "utf-8"));
|
|
15344
15210
|
const scripts = pkg.scripts ?? {};
|
|
15345
15211
|
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
15346
15212
|
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
@@ -16108,7 +15974,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
|
|
|
16108
15974
|
}
|
|
16109
15975
|
|
|
16110
15976
|
// src/hub/persist.ts
|
|
16111
|
-
import { join as
|
|
15977
|
+
import { join as join18 } from "path";
|
|
16112
15978
|
function discussionToMessages(state2) {
|
|
16113
15979
|
const out = [];
|
|
16114
15980
|
const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -16146,7 +16012,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
|
|
|
16146
16012
|
session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
|
|
16147
16013
|
session.titleAiGenerated = true;
|
|
16148
16014
|
await sm.save();
|
|
16149
|
-
return { id: session.id, path:
|
|
16015
|
+
return { id: session.id, path: join18(config.getHistoryDir(), `${session.id}.json`) };
|
|
16150
16016
|
}
|
|
16151
16017
|
|
|
16152
16018
|
// src/core/think-command.ts
|
|
@@ -16267,10 +16133,10 @@ var PLAN_MODE_TOOL_HINT = [
|
|
|
16267
16133
|
"read_file \xB7 list_dir \xB7 grep_files \xB7 glob_files",
|
|
16268
16134
|
"web_fetch \xB7 google_search \xB7 ask_user \xB7 write_todos"
|
|
16269
16135
|
];
|
|
16270
|
-
function applyPlanCommand(sub,
|
|
16271
|
-
const key = (sub ?? "enter").toLowerCase() === "toggle" ?
|
|
16136
|
+
function applyPlanCommand(sub, active2) {
|
|
16137
|
+
const key = (sub ?? "enter").toLowerCase() === "toggle" ? active2 ? "exit" : "enter" : (sub ?? "enter").toLowerCase();
|
|
16272
16138
|
if (key === "enter") {
|
|
16273
|
-
if (
|
|
16139
|
+
if (active2) {
|
|
16274
16140
|
return {
|
|
16275
16141
|
planMode: true,
|
|
16276
16142
|
changed: false,
|
|
@@ -16293,7 +16159,7 @@ function applyPlanCommand(sub, active) {
|
|
|
16293
16159
|
};
|
|
16294
16160
|
}
|
|
16295
16161
|
if (key === "execute") {
|
|
16296
|
-
if (!
|
|
16162
|
+
if (!active2) {
|
|
16297
16163
|
return { planMode: false, changed: false, status: "not-active", summary: "Not in Plan Mode. Enter first with /plan", details: [] };
|
|
16298
16164
|
}
|
|
16299
16165
|
return {
|
|
@@ -16305,22 +16171,22 @@ function applyPlanCommand(sub, active) {
|
|
|
16305
16171
|
};
|
|
16306
16172
|
}
|
|
16307
16173
|
if (key === "exit" || key === "cancel") {
|
|
16308
|
-
if (!
|
|
16174
|
+
if (!active2) {
|
|
16309
16175
|
return { planMode: false, changed: false, status: "not-active", summary: "Not in Plan Mode.", details: [] };
|
|
16310
16176
|
}
|
|
16311
16177
|
return { planMode: false, changed: true, status: "cancelled", summary: "Plan Mode cancelled. Returning to normal mode.", details: [] };
|
|
16312
16178
|
}
|
|
16313
16179
|
if (key === "status" || key === "show") {
|
|
16314
16180
|
return {
|
|
16315
|
-
planMode:
|
|
16181
|
+
planMode: active2,
|
|
16316
16182
|
changed: false,
|
|
16317
16183
|
status: "status",
|
|
16318
|
-
summary: `Plan Mode: ${
|
|
16319
|
-
details:
|
|
16184
|
+
summary: `Plan Mode: ${active2 ? "ACTIVE \u{1F4CB}" : "inactive"}`,
|
|
16185
|
+
details: active2 ? ["Tools restricted to read-only set.", "Use /plan execute to start executing, or /plan exit to cancel."] : []
|
|
16320
16186
|
};
|
|
16321
16187
|
}
|
|
16322
16188
|
return {
|
|
16323
|
-
planMode:
|
|
16189
|
+
planMode: active2,
|
|
16324
16190
|
changed: false,
|
|
16325
16191
|
status: "unknown",
|
|
16326
16192
|
summary: `Unknown /plan option "${sub}" \u2014 expected execute | exit | status`,
|
|
@@ -16592,8 +16458,8 @@ function buildSessionCostReport(input) {
|
|
|
16592
16458
|
const cacheHitPct = usage.cacheReadTokens > 0 ? Math.round(usage.cacheReadTokens / (usage.cacheReadTokens + usage.inputTokens) * 100) : null;
|
|
16593
16459
|
return { hasUsage: true, totalTokens, cacheHitPct, cost, costSource, savings };
|
|
16594
16460
|
}
|
|
16595
|
-
function costSourceNote(
|
|
16596
|
-
switch (
|
|
16461
|
+
function costSourceNote(source2) {
|
|
16462
|
+
switch (source2) {
|
|
16597
16463
|
case "accrued-partial":
|
|
16598
16464
|
return "(partial \u2014 some turns had unknown pricing)";
|
|
16599
16465
|
case "estimated":
|
|
@@ -16604,8 +16470,8 @@ function costSourceNote(source) {
|
|
|
16604
16470
|
}
|
|
16605
16471
|
|
|
16606
16472
|
// src/web/commands/_shared.ts
|
|
16607
|
-
import { existsSync as
|
|
16608
|
-
import { join as
|
|
16473
|
+
import { existsSync as existsSync27, readFileSync as readFileSync19, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, statSync as statSync14, createWriteStream } from "fs";
|
|
16474
|
+
import { join as join21, resolve as resolve9, dirname as dirname8 } from "path";
|
|
16609
16475
|
|
|
16610
16476
|
// src/cli/review-prompts.ts
|
|
16611
16477
|
function buildReviewPrompt(diff, gitContextStr, detailed) {
|
|
@@ -16677,34 +16543,34 @@ function readGitDiff(options = {}) {
|
|
|
16677
16543
|
}
|
|
16678
16544
|
|
|
16679
16545
|
// src/diagnostics/doctor-report.ts
|
|
16680
|
-
import { existsSync as
|
|
16681
|
-
import { join as
|
|
16682
|
-
import { arch as arch2, platform as
|
|
16546
|
+
import { existsSync as existsSync26, statSync as statSync13 } from "fs";
|
|
16547
|
+
import { join as join20 } from "path";
|
|
16548
|
+
import { arch as arch2, platform as platform5, release as release2 } from "os";
|
|
16683
16549
|
|
|
16684
16550
|
// src/diagnostics/crash-log.ts
|
|
16685
16551
|
import {
|
|
16686
|
-
existsSync as
|
|
16552
|
+
existsSync as existsSync25,
|
|
16687
16553
|
mkdirSync as mkdirSync12,
|
|
16688
16554
|
readdirSync as readdirSync12,
|
|
16689
|
-
readFileSync as
|
|
16555
|
+
readFileSync as readFileSync18,
|
|
16690
16556
|
statSync as statSync12,
|
|
16691
16557
|
unlinkSync as unlinkSync6,
|
|
16692
16558
|
writeFileSync as writeFileSync2
|
|
16693
16559
|
} from "fs";
|
|
16694
|
-
import { join as
|
|
16695
|
-
import { homedir as
|
|
16560
|
+
import { join as join19 } from "path";
|
|
16561
|
+
import { homedir as homedir7, platform as platform4, release, arch } from "os";
|
|
16696
16562
|
var LOGS_DIR_NAME = "logs";
|
|
16697
16563
|
var CRASH_LOG_PREFIX = "crash-";
|
|
16698
16564
|
function getLogsDir(configDir) {
|
|
16699
|
-
const base = configDir ??
|
|
16700
|
-
return
|
|
16565
|
+
const base = configDir ?? join19(homedir7(), CONFIG_DIR_NAME);
|
|
16566
|
+
return join19(base, LOGS_DIR_NAME);
|
|
16701
16567
|
}
|
|
16702
16568
|
function truncate(s, n) {
|
|
16703
16569
|
return s.length <= n ? s : s.slice(0, n) + "\u2026";
|
|
16704
16570
|
}
|
|
16705
16571
|
function listRecentCrashes(limit = 10, configDir) {
|
|
16706
16572
|
const dir = getLogsDir(configDir);
|
|
16707
|
-
if (!
|
|
16573
|
+
if (!existsSync25(dir)) return [];
|
|
16708
16574
|
const files = [];
|
|
16709
16575
|
let names;
|
|
16710
16576
|
try {
|
|
@@ -16714,10 +16580,10 @@ function listRecentCrashes(limit = 10, configDir) {
|
|
|
16714
16580
|
}
|
|
16715
16581
|
for (const name of names) {
|
|
16716
16582
|
if (!name.startsWith(CRASH_LOG_PREFIX) || !name.endsWith(".log")) continue;
|
|
16717
|
-
const full =
|
|
16583
|
+
const full = join19(dir, name);
|
|
16718
16584
|
try {
|
|
16719
16585
|
const st = statSync12(full);
|
|
16720
|
-
const head =
|
|
16586
|
+
const head = readFileSync18(full, "utf-8").split("\n").slice(0, 20);
|
|
16721
16587
|
const tsLine = head.find((l) => l.startsWith("timestamp:"));
|
|
16722
16588
|
const kindLine = head.find((l) => l.startsWith("kind:"));
|
|
16723
16589
|
const msgIdx = head.findIndex((l) => l.trim() === "## message");
|
|
@@ -16736,8 +16602,8 @@ function listRecentCrashes(limit = 10, configDir) {
|
|
|
16736
16602
|
return files.slice(0, limit);
|
|
16737
16603
|
}
|
|
16738
16604
|
function getConfigDirUsage(configDir) {
|
|
16739
|
-
const base = configDir ??
|
|
16740
|
-
if (!
|
|
16605
|
+
const base = configDir ?? join19(homedir7(), CONFIG_DIR_NAME);
|
|
16606
|
+
if (!existsSync25(base)) return { totalBytes: 0, entries: [] };
|
|
16741
16607
|
const entries = [];
|
|
16742
16608
|
let total = 0;
|
|
16743
16609
|
let names;
|
|
@@ -16747,7 +16613,7 @@ function getConfigDirUsage(configDir) {
|
|
|
16747
16613
|
return { totalBytes: 0, entries: [] };
|
|
16748
16614
|
}
|
|
16749
16615
|
for (const name of names) {
|
|
16750
|
-
const full =
|
|
16616
|
+
const full = join19(base, name);
|
|
16751
16617
|
const bytes = dirSize(full);
|
|
16752
16618
|
entries.push({ name, bytes });
|
|
16753
16619
|
total += bytes;
|
|
@@ -16762,7 +16628,7 @@ function dirSize(path3) {
|
|
|
16762
16628
|
if (!st.isDirectory()) return 0;
|
|
16763
16629
|
let sum = 0;
|
|
16764
16630
|
for (const name of readdirSync12(path3)) {
|
|
16765
|
-
sum += dirSize(
|
|
16631
|
+
sum += dirSize(join19(path3, name));
|
|
16766
16632
|
}
|
|
16767
16633
|
return sum;
|
|
16768
16634
|
} catch {
|
|
@@ -16772,7 +16638,7 @@ function dirSize(path3) {
|
|
|
16772
16638
|
|
|
16773
16639
|
// src/diagnostics/doctor-report.ts
|
|
16774
16640
|
function checkFile(label, path3) {
|
|
16775
|
-
const exists =
|
|
16641
|
+
const exists = existsSync26(path3);
|
|
16776
16642
|
let sizeBytes = null;
|
|
16777
16643
|
if (exists) {
|
|
16778
16644
|
try {
|
|
@@ -16809,18 +16675,18 @@ function buildDoctorReport(options) {
|
|
|
16809
16675
|
version: VERSION,
|
|
16810
16676
|
npmCheck: options.npmCheck,
|
|
16811
16677
|
node: process.version,
|
|
16812
|
-
platform: `${
|
|
16678
|
+
platform: `${platform5()} ${release2()} (${arch2()})`,
|
|
16813
16679
|
cwd,
|
|
16814
16680
|
projectRoot,
|
|
16815
16681
|
configDir,
|
|
16816
16682
|
providers: options.providers.listAll().map((p) => ({ id: p.id, displayName: p.displayName, configured: p.configured })),
|
|
16817
16683
|
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",
|
|
16684
|
+
checkFile("config.json", join20(configDir, "config.json")),
|
|
16685
|
+
checkFile("memory.md", join20(configDir, MEMORY_FILE_NAME)),
|
|
16686
|
+
checkFile("memory.jsonl", join20(configDir, "memory.jsonl")),
|
|
16687
|
+
checkFile("dev-state.md", join20(configDir, DEV_STATE_FILE_NAME)),
|
|
16688
|
+
checkFile("hooks-trust.json", join20(configDir, "hooks-trust.json")),
|
|
16689
|
+
checkFile("plugin-state.json", join20(configDir, "plugins", "plugin-state.json"))
|
|
16824
16690
|
],
|
|
16825
16691
|
context: {
|
|
16826
16692
|
enabled: contextSetting !== false,
|
|
@@ -16855,7 +16721,7 @@ function buildDoctorReport(options) {
|
|
|
16855
16721
|
disabled: hooks.filter((h) => h.disabled).length
|
|
16856
16722
|
},
|
|
16857
16723
|
plugins: {
|
|
16858
|
-
root:
|
|
16724
|
+
root: join20(configDir, "plugins"),
|
|
16859
16725
|
installed: plugins.length,
|
|
16860
16726
|
active: activeAssets.plugins.length,
|
|
16861
16727
|
invalid: plugins.filter((p) => !p.valid).length,
|
|
@@ -16870,7 +16736,7 @@ function buildDoctorReport(options) {
|
|
|
16870
16736
|
mcp: {
|
|
16871
16737
|
enabled: config.get("mcpEnabled"),
|
|
16872
16738
|
configured: Object.keys(mcpServers).length,
|
|
16873
|
-
projectConfigExists:
|
|
16739
|
+
projectConfigExists: existsSync26(join20(projectRoot, MCP_PROJECT_CONFIG_NAME)),
|
|
16874
16740
|
statuses: options.mcpStatuses ?? []
|
|
16875
16741
|
},
|
|
16876
16742
|
recentCrashes: listRecentCrashes(5, configDir),
|
|
@@ -17392,7 +17258,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
|
|
|
17392
17258
|
Cost: ${formatCost(cost)}` : "";
|
|
17393
17259
|
let memoryLine = "";
|
|
17394
17260
|
try {
|
|
17395
|
-
const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-
|
|
17261
|
+
const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-EXJBXL33.js");
|
|
17396
17262
|
const pending = countPendingMemories2(ctx.config.getConfigDir());
|
|
17397
17263
|
if (pending > 0) memoryLine = `
|
|
17398
17264
|
Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
|
|
@@ -18015,7 +17881,7 @@ async function handleRewind(args, ctx) {
|
|
|
18015
17881
|
ctx.send({ type: "error", message: intent.message });
|
|
18016
17882
|
return;
|
|
18017
17883
|
}
|
|
18018
|
-
const { fileCheckpoints: fileCheckpoints2 } = await import("./file-checkpoint-
|
|
17884
|
+
const { fileCheckpoints: fileCheckpoints2 } = await import("./file-checkpoint-64A2AXHE.js");
|
|
18019
17885
|
if (intent.kind === "list") {
|
|
18020
17886
|
const rows = buildRewindList(session.messages, fileCheckpoints2.getMessageIndices());
|
|
18021
17887
|
ctx.send({
|
|
@@ -18072,9 +17938,9 @@ function parseAgentCommand(args) {
|
|
|
18072
17938
|
function formatAgentList(agents, preferred) {
|
|
18073
17939
|
const lines = [`Agents (${agents.length})`, ""];
|
|
18074
17940
|
for (const agent of agents) {
|
|
18075
|
-
const
|
|
17941
|
+
const active2 = agent.name === preferred ? " *" : " ";
|
|
18076
17942
|
const model = [agent.provider, agent.model].filter(Boolean).join("/") || "inherit";
|
|
18077
|
-
lines.push(`${
|
|
17943
|
+
lines.push(`${active2} ${agent.name} [${agent.source ?? "builtin"}] \u2014 ${agent.description ?? ""}`);
|
|
18078
17944
|
lines.push(
|
|
18079
17945
|
` model: ${model} \xB7 permission: ${agent.permissionProfile ?? "workspace-write"} \xB7 maxRounds: ${agent.maxToolRounds ?? "inherit"}`
|
|
18080
17946
|
);
|
|
@@ -18133,7 +17999,7 @@ async function handlePlugin(args, ctx) {
|
|
|
18133
17999
|
lines.push("");
|
|
18134
18000
|
}
|
|
18135
18001
|
if (pluginTools.length > 0) {
|
|
18136
|
-
const legacyDir =
|
|
18002
|
+
const legacyDir = join21(configDir, PLUGINS_DIR_NAME);
|
|
18137
18003
|
lines.push(`**Legacy JS tools** (${pluginTools.length}):`);
|
|
18138
18004
|
lines.push(`Dir: ${legacyDir}`);
|
|
18139
18005
|
for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
|
|
@@ -18215,14 +18081,14 @@ async function handleSkill(args, ctx) {
|
|
|
18215
18081
|
return;
|
|
18216
18082
|
}
|
|
18217
18083
|
const intent = parseSkillCommand(args);
|
|
18218
|
-
const
|
|
18084
|
+
const active2 = ctx.skillManager.getActive();
|
|
18219
18085
|
if (intent.kind === "list") {
|
|
18220
18086
|
const skills = ctx.skillManager.listSkills();
|
|
18221
18087
|
if (skills.length === 0) {
|
|
18222
18088
|
ctx.send({ type: "info", message: "No skills available. Add .md files to ~/.aicli/skills/" });
|
|
18223
18089
|
} else {
|
|
18224
18090
|
const lines = skills.map((s) => {
|
|
18225
|
-
const isActive =
|
|
18091
|
+
const isActive = active2?.meta.name === s.meta.name;
|
|
18226
18092
|
const marker = isActive ? " \u2705 (active)" : "";
|
|
18227
18093
|
return ` ${s.meta.name}${marker} \u2014 ${s.meta.description || "(no description)"}`;
|
|
18228
18094
|
});
|
|
@@ -18238,12 +18104,12 @@ ${lines.join("\n")}` });
|
|
|
18238
18104
|
return;
|
|
18239
18105
|
}
|
|
18240
18106
|
const note = skillHistoryNote(
|
|
18241
|
-
|
|
18107
|
+
active2?.meta.name ?? "(none)",
|
|
18242
18108
|
intent.kind === "deactivate" ? "(none)" : intent.name,
|
|
18243
18109
|
ctx.sessions.current?.messages.length ?? 0
|
|
18244
18110
|
);
|
|
18245
18111
|
if (intent.kind === "deactivate") {
|
|
18246
|
-
if (!
|
|
18112
|
+
if (!active2) {
|
|
18247
18113
|
ctx.send({ type: "info", message: "No skill is currently active." });
|
|
18248
18114
|
return;
|
|
18249
18115
|
}
|
|
@@ -18254,7 +18120,7 @@ ${note}` : "\u{1F50C} Skill deactivated." });
|
|
|
18254
18120
|
ctx.sendStatus();
|
|
18255
18121
|
return;
|
|
18256
18122
|
}
|
|
18257
|
-
if (
|
|
18123
|
+
if (active2?.meta.name === intent.name) {
|
|
18258
18124
|
ctx.send({ type: "info", message: `Skill '${intent.name}' is already active.` });
|
|
18259
18125
|
return;
|
|
18260
18126
|
}
|
|
@@ -18273,8 +18139,8 @@ ${note}` : head });
|
|
|
18273
18139
|
}
|
|
18274
18140
|
async function handleCommands(_args, ctx) {
|
|
18275
18141
|
const configDir = ctx.config.getConfigDir();
|
|
18276
|
-
const commandsDir =
|
|
18277
|
-
if (!
|
|
18142
|
+
const commandsDir = join21(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
18143
|
+
if (!existsSync27(commandsDir)) {
|
|
18278
18144
|
ctx.send({ type: "info", message: `No custom commands directory.
|
|
18279
18145
|
Create: ${commandsDir}/ with .md files.` });
|
|
18280
18146
|
return;
|
|
@@ -18380,7 +18246,7 @@ async function handleTest(args, ctx) {
|
|
|
18380
18246
|
const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
|
|
18381
18247
|
testArgs = isCommand ? { command: argStr } : { filter: argStr };
|
|
18382
18248
|
}
|
|
18383
|
-
const runTests = ctx.runTests ?? (await import("./run-tests-
|
|
18249
|
+
const runTests = ctx.runTests ?? (await import("./run-tests-2C7MWZBN.js")).executeTests;
|
|
18384
18250
|
const report = await runTests(testArgs);
|
|
18385
18251
|
ctx.send({ type: "info", message: report });
|
|
18386
18252
|
} catch (err) {
|
|
@@ -18404,9 +18270,9 @@ async function handleDiff(args, ctx) {
|
|
|
18404
18270
|
let modifiedFiles = 0;
|
|
18405
18271
|
const diffLines2 = [];
|
|
18406
18272
|
for (const [filePath, { earliest }] of fileMap) {
|
|
18407
|
-
const currentContent =
|
|
18273
|
+
const currentContent = existsSync27(filePath) ? (() => {
|
|
18408
18274
|
try {
|
|
18409
|
-
return
|
|
18275
|
+
return readFileSync19(filePath, "utf-8");
|
|
18410
18276
|
} catch {
|
|
18411
18277
|
return null;
|
|
18412
18278
|
}
|
|
@@ -18441,9 +18307,9 @@ async function handleDiff(args, ctx) {
|
|
|
18441
18307
|
}
|
|
18442
18308
|
async function handleInit(args, ctx) {
|
|
18443
18309
|
const cwd = process.cwd();
|
|
18444
|
-
const targetPath =
|
|
18310
|
+
const targetPath = join21(cwd, "AICLI.md");
|
|
18445
18311
|
const force = args.includes("--force");
|
|
18446
|
-
if (
|
|
18312
|
+
if (existsSync27(targetPath) && !force) {
|
|
18447
18313
|
ctx.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
18448
18314
|
Use /init --force to overwrite.` });
|
|
18449
18315
|
return;
|
|
@@ -18672,9 +18538,9 @@ async function handleContext(_args, ctx) {
|
|
|
18672
18538
|
async function handleIndex(args, ctx) {
|
|
18673
18539
|
const sub = (args[0] ?? "status").toLowerCase();
|
|
18674
18540
|
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-
|
|
18541
|
+
const { loadIndex: loadIndex2, clearIndex } = await import("./store-2N6PPPBP.js");
|
|
18542
|
+
const { indexProject: indexProject2 } = await import("./indexer-VRXDZ74A.js");
|
|
18543
|
+
const { loadVectorStore, clearVectorStore } = await import("./vector-store-T2DVUMY4.js");
|
|
18678
18544
|
if (sub === "status") {
|
|
18679
18545
|
const idx = loadIndex2(root);
|
|
18680
18546
|
const vec = loadVectorStore(root);
|
|
@@ -18724,7 +18590,7 @@ async function handleIndex(args, ctx) {
|
|
|
18724
18590
|
message: `Building semantic index for ${idx.symbolCount} symbols\u2026 (first run downloads ~117 MB model)`
|
|
18725
18591
|
});
|
|
18726
18592
|
try {
|
|
18727
|
-
const { rebuildSemanticIndex } = await import("./semantic-
|
|
18593
|
+
const { rebuildSemanticIndex } = await import("./semantic-TRQAZSRY.js");
|
|
18728
18594
|
const stats = await rebuildSemanticIndex(root);
|
|
18729
18595
|
const first = stats.modelFirstLoadMs ? ` (model load+first batch ${stats.modelFirstLoadMs}ms)` : "";
|
|
18730
18596
|
ctx.send({
|
|
@@ -18755,7 +18621,7 @@ async function handleMemory(args, ctx) {
|
|
|
18755
18621
|
if (intent.kind === "rebuild") {
|
|
18756
18622
|
ctx.send({ type: "info", message: rebuildStartMessage(intent.full) });
|
|
18757
18623
|
try {
|
|
18758
|
-
const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-
|
|
18624
|
+
const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
|
|
18759
18625
|
const stats = await buildChatIndex2({ full: intent.full });
|
|
18760
18626
|
ctx.send({ type: "info", message: rebuildDoneMessage(stats) });
|
|
18761
18627
|
} catch (err) {
|
|
@@ -18764,14 +18630,14 @@ async function handleMemory(args, ctx) {
|
|
|
18764
18630
|
return;
|
|
18765
18631
|
}
|
|
18766
18632
|
if (intent.kind === "index-clear") {
|
|
18767
|
-
const { clearChatIndex: clearChatIndex2 } = await import("./chat-index-
|
|
18633
|
+
const { clearChatIndex: clearChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
|
|
18768
18634
|
clearChatIndex2();
|
|
18769
18635
|
ctx.send({ type: "info", message: "Chat memory index cleared (persistent memory untouched)." });
|
|
18770
18636
|
return;
|
|
18771
18637
|
}
|
|
18772
18638
|
if (intent.kind === "status") {
|
|
18773
|
-
const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-
|
|
18774
|
-
const { listMemoryEntries: listMemoryEntries2, isMemoryExpired: isMemoryExpired2, formatMemoryForPrompt: formatMemoryForPrompt2 } = await import("./persistent-memory-
|
|
18639
|
+
const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-RLVLQRRC.js");
|
|
18640
|
+
const { listMemoryEntries: listMemoryEntries2, isMemoryExpired: isMemoryExpired2, formatMemoryForPrompt: formatMemoryForPrompt2 } = await import("./persistent-memory-EXJBXL33.js");
|
|
18775
18641
|
const memories = listMemoryEntries2(configDir, { includeExpired: true, includeRejected: true });
|
|
18776
18642
|
const injected = formatMemoryForPrompt2(configDir, process.cwd(), ctx.config.get("memory")?.maxChars);
|
|
18777
18643
|
const view = buildMemoryStatusView({
|
|
@@ -18794,8 +18660,8 @@ async function handleMemory(args, ctx) {
|
|
|
18794
18660
|
return;
|
|
18795
18661
|
}
|
|
18796
18662
|
if (intent.kind === "recall") {
|
|
18797
|
-
const { searchPersistentMemories: searchPersistentMemories2, touchMemoryReferences: touchMemoryReferences2 } = await import("./persistent-memory-
|
|
18798
|
-
const { searchChatMemory: searchChatMemory2 } = await import("./chat-index-
|
|
18663
|
+
const { searchPersistentMemories: searchPersistentMemories2, touchMemoryReferences: touchMemoryReferences2 } = await import("./persistent-memory-EXJBXL33.js");
|
|
18664
|
+
const { searchChatMemory: searchChatMemory2 } = await import("./chat-index-RLVLQRRC.js");
|
|
18799
18665
|
const persistentHits = searchPersistentMemories2(configDir, intent.query, process.cwd(), 3);
|
|
18800
18666
|
if (persistentHits.length > 0) {
|
|
18801
18667
|
touchMemoryReferences2(configDir, persistentHits.map((h) => h.entry.id));
|
|
@@ -18915,7 +18781,7 @@ async function handleMemory(args, ctx) {
|
|
|
18915
18781
|
ctx.handleMemoryManage(intent.action, args[1], intent.action === "expire" ? args[2] : void 0);
|
|
18916
18782
|
} else if (intent.kind === "export") {
|
|
18917
18783
|
if (args[1] === "md") {
|
|
18918
|
-
const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-
|
|
18784
|
+
const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-EXJBXL33.js");
|
|
18919
18785
|
ctx.send({
|
|
18920
18786
|
type: "export_data",
|
|
18921
18787
|
format: "md",
|
|
@@ -18953,7 +18819,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
18953
18819
|
return;
|
|
18954
18820
|
}
|
|
18955
18821
|
const dirPath = resolve9(sub);
|
|
18956
|
-
if (!
|
|
18822
|
+
if (!existsSync27(dirPath)) {
|
|
18957
18823
|
ctx.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
18958
18824
|
return;
|
|
18959
18825
|
}
|
|
@@ -19317,7 +19183,7 @@ async function handleAgent(args, ctx) {
|
|
|
19317
19183
|
|
|
19318
19184
|
// src/repl/commands/utils.ts
|
|
19319
19185
|
import { execSync as execSync2 } from "child_process";
|
|
19320
|
-
import { platform as
|
|
19186
|
+
import { platform as platform6 } from "os";
|
|
19321
19187
|
|
|
19322
19188
|
// src/repl/commands/constants.ts
|
|
19323
19189
|
var SESSION_PAGE_SIZE = 20;
|
|
@@ -19330,7 +19196,7 @@ function fmtCtx(tokens) {
|
|
|
19330
19196
|
return `${tokens}`;
|
|
19331
19197
|
}
|
|
19332
19198
|
function copyToClipboard(text) {
|
|
19333
|
-
const plat =
|
|
19199
|
+
const plat = platform6();
|
|
19334
19200
|
if (plat === "win32") {
|
|
19335
19201
|
execSync2("clip", { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
19336
19202
|
} else if (plat === "darwin") {
|
|
@@ -19917,16 +19783,16 @@ No tools match "${filter}".
|
|
|
19917
19783
|
usage: "/mcp [reconnect [serverId] | trust-project]",
|
|
19918
19784
|
async execute(args, ctx) {
|
|
19919
19785
|
if (args[0] === "trust-project") {
|
|
19920
|
-
const { join:
|
|
19921
|
-
const { existsSync:
|
|
19786
|
+
const { join: join26 } = await import("path");
|
|
19787
|
+
const { existsSync: existsSync32 } = await import("fs");
|
|
19922
19788
|
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-
|
|
19789
|
+
const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-GYJTB6KI.js");
|
|
19790
|
+
const { approveProject, hashMcpFile } = await import("./project-trust-6CJB5ZMX.js");
|
|
19925
19791
|
const cwd = process.cwd();
|
|
19926
19792
|
const projectRoot = getGitRoot2(cwd) ?? cwd;
|
|
19927
|
-
const mcpPath =
|
|
19793
|
+
const mcpPath = join26(projectRoot, MCP_PROJECT_CONFIG_NAME2);
|
|
19928
19794
|
console.log();
|
|
19929
|
-
if (!
|
|
19795
|
+
if (!existsSync32(mcpPath)) {
|
|
19930
19796
|
console.log(theme.dim(` No .mcp.json in ${projectRoot}.`));
|
|
19931
19797
|
console.log();
|
|
19932
19798
|
return;
|
|
@@ -19999,8 +19865,8 @@ No tools match "${filter}".
|
|
|
19999
19865
|
const errorStr = s.error ? theme.error(`
|
|
20000
19866
|
Error: ${s.error}`) : "";
|
|
20001
19867
|
const serverLabel = s.serverName !== s.serverId ? ` (${s.serverName})` : "";
|
|
20002
|
-
const
|
|
20003
|
-
const sourceTag =
|
|
19868
|
+
const source2 = ctx.getMcpServerSource(s.serverId);
|
|
19869
|
+
const sourceTag = source2 === "project" ? theme.accent(" [project]") : theme.dim(" [global]");
|
|
20004
19870
|
console.log(` ${theme.accent(s.serverId)}${theme.dim(serverLabel)}${sourceTag} [${statusBadge}]${toolCountStr}${errorStr}`);
|
|
20005
19871
|
if (s.connected) {
|
|
20006
19872
|
const mcpTools = ctx.tools.listMcpTools().filter(
|
|
@@ -20028,7 +19894,7 @@ No tools match "${filter}".
|
|
|
20028
19894
|
}
|
|
20029
19895
|
if (!sub || sub === "list") {
|
|
20030
19896
|
const skills = manager.listSkills();
|
|
20031
|
-
const
|
|
19897
|
+
const active2 = manager.getActive();
|
|
20032
19898
|
console.log();
|
|
20033
19899
|
console.log(theme.heading(" \u{1F3AF} Agent Skills"));
|
|
20034
19900
|
console.log(theme.dim(" " + "\u2500".repeat(50)));
|
|
@@ -20038,7 +19904,7 @@ No tools match "${filter}".
|
|
|
20038
19904
|
console.log(theme.dim(" Format: YAML frontmatter (name/description/tools) + prompt body"));
|
|
20039
19905
|
} else {
|
|
20040
19906
|
for (const s of skills) {
|
|
20041
|
-
const isActive =
|
|
19907
|
+
const isActive = active2?.meta.name === s.meta.name;
|
|
20042
19908
|
const badge = isActive ? theme.success(" \u2713 active") : "";
|
|
20043
19909
|
const toolInfo = s.meta.tools ? theme.dim(` [${s.meta.tools.join(", ")}]`) : "";
|
|
20044
19910
|
console.log(
|
|
@@ -20126,7 +19992,7 @@ No tools match "${filter}".
|
|
|
20126
19992
|
}
|
|
20127
19993
|
|
|
20128
19994
|
// src/repl/commands/security-commands.ts
|
|
20129
|
-
import { platform as
|
|
19995
|
+
import { platform as platform7 } from "os";
|
|
20130
19996
|
import chalk6 from "chalk";
|
|
20131
19997
|
function createSecurityCommands() {
|
|
20132
19998
|
return [
|
|
@@ -20207,7 +20073,7 @@ ${outcome.summary}
|
|
|
20207
20073
|
usage: "/bug [--copy]",
|
|
20208
20074
|
execute(args, ctx) {
|
|
20209
20075
|
const doCopy = args.includes("--copy");
|
|
20210
|
-
const os =
|
|
20076
|
+
const os = platform7();
|
|
20211
20077
|
const nodeVer = process.version;
|
|
20212
20078
|
const provider = ctx.getCurrentProvider();
|
|
20213
20079
|
const model = ctx.getCurrentModel();
|
|
@@ -20361,12 +20227,12 @@ ${outcome.summary}
|
|
|
20361
20227
|
if (scanAll) {
|
|
20362
20228
|
const metas = ctx.sessions.listSessions();
|
|
20363
20229
|
console.log(theme.info(` Scanning ${metas.length} session(s)\u2026`));
|
|
20364
|
-
const { readFileSync:
|
|
20365
|
-
const { join:
|
|
20230
|
+
const { readFileSync: readFileSync23 } = await import("fs");
|
|
20231
|
+
const { join: join26 } = await import("path");
|
|
20366
20232
|
const historyDir = ctx.config.getHistoryDir();
|
|
20367
20233
|
for (const m of metas) {
|
|
20368
20234
|
try {
|
|
20369
|
-
const content =
|
|
20235
|
+
const content = readFileSync23(join26(historyDir, `${m.id}.json`), "utf-8");
|
|
20370
20236
|
const hits2 = scanString(content, opts);
|
|
20371
20237
|
if (hits2.length) {
|
|
20372
20238
|
filesWithHits++;
|
|
@@ -20413,8 +20279,8 @@ ${outcome.summary}
|
|
|
20413
20279
|
}
|
|
20414
20280
|
|
|
20415
20281
|
// src/repl/commands/review-commands.ts
|
|
20416
|
-
import { existsSync as
|
|
20417
|
-
import { join as
|
|
20282
|
+
import { existsSync as existsSync28, readFileSync as readFileSync20, writeFileSync as writeFileSync4 } from "fs";
|
|
20283
|
+
import { join as join22 } from "path";
|
|
20418
20284
|
function createReviewCommands() {
|
|
20419
20285
|
return [
|
|
20420
20286
|
{
|
|
@@ -20588,11 +20454,11 @@ function createReviewCommands() {
|
|
|
20588
20454
|
Branches (${branches.length}):
|
|
20589
20455
|
`));
|
|
20590
20456
|
for (const b of branches) {
|
|
20591
|
-
const
|
|
20457
|
+
const active2 = b.id === session.activeBranchId ? theme.success("\u25CF ") : " ";
|
|
20592
20458
|
const parent = b.parentBranchId ? theme.dim(` \u2190 ${b.parentBranchId}@${b.parentMessageIndex}`) : "";
|
|
20593
20459
|
const count = b.id === session.activeBranchId ? session.messages.length : session.getBranchMessages(b.id)?.length ?? 0;
|
|
20594
20460
|
console.log(
|
|
20595
|
-
` ${
|
|
20461
|
+
` ${active2}${theme.accent(b.id.padEnd(10))} ${b.title.padEnd(20)} ` + theme.dim(`(${count} msgs)`) + parent
|
|
20596
20462
|
);
|
|
20597
20463
|
}
|
|
20598
20464
|
console.log();
|
|
@@ -20705,9 +20571,9 @@ function createReviewCommands() {
|
|
|
20705
20571
|
ctx.renderer.renderError(`Branch "${id}" not found.`);
|
|
20706
20572
|
return;
|
|
20707
20573
|
}
|
|
20708
|
-
const
|
|
20574
|
+
const active2 = session.activeBranchId;
|
|
20709
20575
|
console.log(`
|
|
20710
|
-
${theme.info("diff")} ${id} (source) \u2194 ${
|
|
20576
|
+
${theme.info("diff")} ${id} (source) \u2194 ${active2} (active)`);
|
|
20711
20577
|
console.log(theme.dim(` Common prefix: ${d.commonPrefix} message(s)`));
|
|
20712
20578
|
if (d.sourceOnly.length === 0 && d.targetOnly.length === 0) {
|
|
20713
20579
|
console.log(theme.success(" Branches are identical.\n"));
|
|
@@ -20726,7 +20592,7 @@ function createReviewCommands() {
|
|
|
20726
20592
|
}
|
|
20727
20593
|
if (d.targetOnly.length > 0) {
|
|
20728
20594
|
console.log(theme.dim(`
|
|
20729
|
-
Only in ${
|
|
20595
|
+
Only in ${active2} (active) (${d.targetOnly.length}):`));
|
|
20730
20596
|
d.targetOnly.forEach((m, i) => {
|
|
20731
20597
|
console.log(` ${theme.dim(`[${d.commonPrefix + i}]`)} ${m.role}: ${preview(m)}`);
|
|
20732
20598
|
});
|
|
@@ -20773,9 +20639,9 @@ function createReviewCommands() {
|
|
|
20773
20639
|
usage: "/init [--force]",
|
|
20774
20640
|
async execute(args, ctx) {
|
|
20775
20641
|
const cwd = process.cwd();
|
|
20776
|
-
const targetPath =
|
|
20642
|
+
const targetPath = join22(cwd, "AICLI.md");
|
|
20777
20643
|
const force = args.includes("--force");
|
|
20778
|
-
if (
|
|
20644
|
+
if (existsSync28(targetPath) && !force) {
|
|
20779
20645
|
ctx.renderer.printInfo(`AICLI.md already exists at ${targetPath}`);
|
|
20780
20646
|
ctx.renderer.printInfo("Use /init --force to overwrite, or edit it manually.");
|
|
20781
20647
|
return;
|
|
@@ -20839,7 +20705,7 @@ function createReviewCommands() {
|
|
|
20839
20705
|
usage: "/test [command|filter]",
|
|
20840
20706
|
async execute(args, ctx) {
|
|
20841
20707
|
try {
|
|
20842
|
-
const { executeTests } = await import("./run-tests-
|
|
20708
|
+
const { executeTests } = await import("./run-tests-2C7MWZBN.js");
|
|
20843
20709
|
const argStr = args.join(" ").trim();
|
|
20844
20710
|
let testArgs = {};
|
|
20845
20711
|
if (argStr) {
|
|
@@ -20874,9 +20740,9 @@ function createReviewCommands() {
|
|
|
20874
20740
|
let newFiles = 0;
|
|
20875
20741
|
let modifiedFiles = 0;
|
|
20876
20742
|
for (const [filePath, { earliest }] of fileMap) {
|
|
20877
|
-
const currentContent =
|
|
20743
|
+
const currentContent = existsSync28(filePath) ? (() => {
|
|
20878
20744
|
try {
|
|
20879
|
-
return
|
|
20745
|
+
return readFileSync20(filePath, "utf-8");
|
|
20880
20746
|
} catch {
|
|
20881
20747
|
return null;
|
|
20882
20748
|
}
|
|
@@ -21090,9 +20956,9 @@ function createMemoryCommands() {
|
|
|
21090
20956
|
async execute(args, ctx) {
|
|
21091
20957
|
const sub = (args[0] ?? "status").toLowerCase();
|
|
21092
20958
|
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-
|
|
20959
|
+
const { loadIndex: loadIndex2, clearIndex } = await import("./store-2N6PPPBP.js");
|
|
20960
|
+
const { indexProject: indexProject2 } = await import("./indexer-VRXDZ74A.js");
|
|
20961
|
+
const { loadVectorStore, clearVectorStore } = await import("./vector-store-T2DVUMY4.js");
|
|
21096
20962
|
if (sub === "status") {
|
|
21097
20963
|
const idx = loadIndex2(root);
|
|
21098
20964
|
const vec = loadVectorStore(root);
|
|
@@ -21152,7 +21018,7 @@ function createMemoryCommands() {
|
|
|
21152
21018
|
}
|
|
21153
21019
|
console.log(theme.dim(` Building semantic index for ${idx.symbolCount} symbols\u2026`));
|
|
21154
21020
|
console.log(theme.dim(" (First run downloads ~117 MB embedding model to ~/.aicli/models/)"));
|
|
21155
|
-
const { rebuildSemanticIndex } = await import("./semantic-
|
|
21021
|
+
const { rebuildSemanticIndex } = await import("./semantic-TRQAZSRY.js");
|
|
21156
21022
|
try {
|
|
21157
21023
|
const stats = await rebuildSemanticIndex(root, {
|
|
21158
21024
|
onProgress: (done, total) => {
|
|
@@ -21351,14 +21217,14 @@ ${view.chatHeading}
|
|
|
21351
21217
|
console.log(theme.heading("\n\u{1F4DD} Governed Persistent Memory:") + filterNote + "\n" + theme.dim("\u2500".repeat(72)));
|
|
21352
21218
|
for (const entry of entries) {
|
|
21353
21219
|
const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
|
|
21354
|
-
const
|
|
21220
|
+
const source2 = [
|
|
21355
21221
|
entry.source ?? "unknown",
|
|
21356
21222
|
entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : "",
|
|
21357
21223
|
entry.sourceProject ? `project ${entry.sourceProject}` : ""
|
|
21358
21224
|
].filter(Boolean).join(" \xB7 ");
|
|
21359
21225
|
console.log(theme.accent(` ${entry.id.slice(0, 8)} ${status} scope:${entry.scope} sensitivity:${entry.sensitivity}`));
|
|
21360
21226
|
const lastRef = entry.lastReferencedAt ? ` \xB7 last-referenced: ${entry.lastReferencedAt.slice(0, 10)}` : "";
|
|
21361
|
-
console.log(theme.dim(` source: ${
|
|
21227
|
+
console.log(theme.dim(` source: ${source2} \xB7 created: ${entry.createdAt.replace("T", " ").slice(0, 19)}${lastRef}${entry.expiresAt ? ` \xB7 expires: ${entry.expiresAt}` : ""}`));
|
|
21362
21228
|
console.log(" " + entry.content.replace(/\n/g, "\n "));
|
|
21363
21229
|
console.log();
|
|
21364
21230
|
}
|
|
@@ -22039,16 +21905,16 @@ function createSessionCommands() {
|
|
|
22039
21905
|
|
|
22040
21906
|
// src/repl/commands/misc-commands.ts
|
|
22041
21907
|
import { basename as basename9 } from "path";
|
|
22042
|
-
import { platform as
|
|
21908
|
+
import { platform as platform8 } from "os";
|
|
22043
21909
|
|
|
22044
21910
|
// src/repl/clipboard.ts
|
|
22045
21911
|
import { execSync as execSync3 } from "child_process";
|
|
22046
|
-
import { existsSync as
|
|
21912
|
+
import { existsSync as existsSync29, statSync as statSync15, unlinkSync as unlinkSync7 } from "fs";
|
|
22047
21913
|
import { tmpdir as tmpdir3 } from "os";
|
|
22048
|
-
import { join as
|
|
21914
|
+
import { join as join23 } from "path";
|
|
22049
21915
|
var CLIPBOARD_TIMEOUT = 5e3;
|
|
22050
21916
|
function readClipboardImage() {
|
|
22051
|
-
const outPath =
|
|
21917
|
+
const outPath = join23(tmpdir3(), `aicli-paste-${Date.now()}.png`);
|
|
22052
21918
|
try {
|
|
22053
21919
|
if (process.platform === "win32") {
|
|
22054
21920
|
_readWin32(outPath);
|
|
@@ -22061,7 +21927,7 @@ function readClipboardImage() {
|
|
|
22061
21927
|
_cleanup(outPath);
|
|
22062
21928
|
return null;
|
|
22063
21929
|
}
|
|
22064
|
-
if (!
|
|
21930
|
+
if (!existsSync29(outPath) || statSync15(outPath).size === 0) {
|
|
22065
21931
|
_cleanup(outPath);
|
|
22066
21932
|
return null;
|
|
22067
21933
|
}
|
|
@@ -22114,7 +21980,7 @@ function _readLinux(outPath) {
|
|
|
22114
21980
|
}
|
|
22115
21981
|
function _cleanup(path3) {
|
|
22116
21982
|
try {
|
|
22117
|
-
if (
|
|
21983
|
+
if (existsSync29(path3)) unlinkSync7(path3);
|
|
22118
21984
|
} catch {
|
|
22119
21985
|
}
|
|
22120
21986
|
}
|
|
@@ -22361,7 +22227,7 @@ No commands match "${filter}".
|
|
|
22361
22227
|
ctx.renderer.printSuccess(`Copied to clipboard (${lines} lines, ${chars} chars)`);
|
|
22362
22228
|
} catch (err) {
|
|
22363
22229
|
const msg = err instanceof Error ? err.message : String(err);
|
|
22364
|
-
if (
|
|
22230
|
+
if (platform8() === "linux" && msg.includes("not found")) {
|
|
22365
22231
|
ctx.renderer.renderError("Clipboard tool not found. Install xclip or xsel: sudo apt install xclip");
|
|
22366
22232
|
} else {
|
|
22367
22233
|
ctx.renderer.renderError(`Failed to copy: ${msg}`);
|
|
@@ -22956,7 +22822,18 @@ var SessionHandler = class _SessionHandler {
|
|
|
22956
22822
|
}))
|
|
22957
22823
|
});
|
|
22958
22824
|
}
|
|
22825
|
+
executionState = /* @__PURE__ */ new Map();
|
|
22826
|
+
connectionAbort = new AbortController();
|
|
22959
22827
|
async handleMessage(raw) {
|
|
22828
|
+
if (this.connectionAbort.signal.aborted && /"type"\s*:\s*"(?:chat|command|hub_start)"/.test(raw)) this.connectionAbort = new AbortController();
|
|
22829
|
+
return runWithExecutionContext({
|
|
22830
|
+
configDir: this.config.getConfigDir(),
|
|
22831
|
+
sessionKey: this._bashSessionKey,
|
|
22832
|
+
state: this.executionState,
|
|
22833
|
+
signal: this.connectionAbort.signal
|
|
22834
|
+
}, () => this.handleMessageScoped(raw));
|
|
22835
|
+
}
|
|
22836
|
+
async handleMessageScoped(raw) {
|
|
22960
22837
|
if (this.disconnected) return;
|
|
22961
22838
|
let msg;
|
|
22962
22839
|
try {
|
|
@@ -22967,6 +22844,7 @@ var SessionHandler = class _SessionHandler {
|
|
|
22967
22844
|
}
|
|
22968
22845
|
switch (msg.type) {
|
|
22969
22846
|
case "chat": {
|
|
22847
|
+
if (this.connectionAbort.signal.aborted) this.connectionAbort = new AbortController();
|
|
22970
22848
|
if (msg.messageId) {
|
|
22971
22849
|
if (typeof msg.messageId !== "string" || msg.messageId.length > 200) throw new Error("Invalid message ID");
|
|
22972
22850
|
const scope = this.config.getConfigDir();
|
|
@@ -23027,6 +22905,7 @@ var SessionHandler = class _SessionHandler {
|
|
|
23027
22905
|
return;
|
|
23028
22906
|
}
|
|
23029
22907
|
case "abort":
|
|
22908
|
+
this.connectionAbort.abort();
|
|
23030
22909
|
if (this.abortController) {
|
|
23031
22910
|
this.abortController.abort();
|
|
23032
22911
|
}
|
|
@@ -23050,8 +22929,16 @@ var SessionHandler = class _SessionHandler {
|
|
|
23050
22929
|
}
|
|
23051
22930
|
}
|
|
23052
22931
|
onDisconnect(closeSocket = true) {
|
|
22932
|
+
runWithExecutionContext({
|
|
22933
|
+
configDir: this.config.getConfigDir(),
|
|
22934
|
+
sessionKey: this._bashSessionKey,
|
|
22935
|
+
state: this.executionState
|
|
22936
|
+
}, () => this.disconnectScoped(closeSocket));
|
|
22937
|
+
}
|
|
22938
|
+
disconnectScoped(closeSocket) {
|
|
23053
22939
|
if (this.disconnected) return;
|
|
23054
22940
|
this.disconnected = true;
|
|
22941
|
+
this.connectionAbort.abort();
|
|
23055
22942
|
if (closeSocket && this.ws.readyState === this.ws.OPEN) this.ws.close(1001, "Connection closed; reconnect to restore session");
|
|
23056
22943
|
runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
|
|
23057
22944
|
this.toolExecutor.cancelAll();
|
|
@@ -24030,20 +23917,20 @@ ${summaryContent}`,
|
|
|
24030
23917
|
this.send({ type: "error", message: "Usage: /memory show --stale <days> (positive number)" });
|
|
24031
23918
|
return;
|
|
24032
23919
|
}
|
|
24033
|
-
const
|
|
24034
|
-
const { entries, applied } = filterMemoryEntries(
|
|
23920
|
+
const source2 = filters.orphaned ? listOrphanedProjectMemories(configDir) : listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
|
|
23921
|
+
const { entries, applied } = filterMemoryEntries(source2, filters);
|
|
24035
23922
|
const filterNote = applied.length > 0 ? ` (filtered: ${applied.join(", ")})` : "";
|
|
24036
23923
|
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
23924
|
|
|
24038
23925
|
` + entries.map((entry) => {
|
|
24039
23926
|
const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
|
|
24040
|
-
const
|
|
23927
|
+
const source3 = [
|
|
24041
23928
|
entry.source ?? "unknown",
|
|
24042
23929
|
entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : "",
|
|
24043
23930
|
entry.sourceProject ? `project ${entry.sourceProject}` : ""
|
|
24044
23931
|
].filter(Boolean).join(" \xB7 ");
|
|
24045
23932
|
return `## ${entry.id.slice(0, 8)} \xB7 ${status} \xB7 scope:${entry.scope} \xB7 sensitivity:${entry.sensitivity}
|
|
24046
|
-
Source: ${
|
|
23933
|
+
Source: ${source3}
|
|
24047
23934
|
${entry.content}`;
|
|
24048
23935
|
}).join("\n\n");
|
|
24049
23936
|
this.send({
|
|
@@ -24153,7 +24040,7 @@ Backup saved: ${result.backupPath}` : "";
|
|
|
24153
24040
|
return;
|
|
24154
24041
|
}
|
|
24155
24042
|
try {
|
|
24156
|
-
const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-
|
|
24043
|
+
const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
|
|
24157
24044
|
const loaded = loadChatIndex2();
|
|
24158
24045
|
if (!loaded || loaded.idx.chunks.length === 0) {
|
|
24159
24046
|
this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
|
|
@@ -24189,7 +24076,7 @@ Backup saved: ${result.backupPath}` : "";
|
|
|
24189
24076
|
}
|
|
24190
24077
|
async handleMemoryStatus() {
|
|
24191
24078
|
try {
|
|
24192
|
-
const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-
|
|
24079
|
+
const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-RLVLQRRC.js");
|
|
24193
24080
|
const s = getChatIndexStatus2();
|
|
24194
24081
|
this.send({
|
|
24195
24082
|
type: "memory_status",
|
|
@@ -24214,7 +24101,7 @@ Backup saved: ${result.backupPath}` : "";
|
|
|
24214
24101
|
type: "info",
|
|
24215
24102
|
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
24103
|
});
|
|
24217
|
-
const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-
|
|
24104
|
+
const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
|
|
24218
24105
|
const stats = await buildChatIndex2({
|
|
24219
24106
|
full,
|
|
24220
24107
|
onProgress: (p) => {
|
|
@@ -24372,9 +24259,9 @@ async function setupProxy(configProxy) {
|
|
|
24372
24259
|
}
|
|
24373
24260
|
|
|
24374
24261
|
// src/web/auth.ts
|
|
24375
|
-
import { existsSync as
|
|
24376
|
-
import { isAbsolute as isAbsolute3, join as
|
|
24377
|
-
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
24262
|
+
import { existsSync as existsSync30, readFileSync as readFileSync21, mkdirSync as mkdirSync16, readdirSync as readdirSync14, openSync, closeSync, unlinkSync as unlinkSync8 } from "fs";
|
|
24263
|
+
import { isAbsolute as isAbsolute3, join as join24, relative as relative5 } from "path";
|
|
24264
|
+
import { createHmac, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2, pbkdf2Sync, pbkdf2 } from "crypto";
|
|
24378
24265
|
var USERS_FILE = "users.json";
|
|
24379
24266
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
24380
24267
|
var TOKEN_EXPIRY_MS = TOKEN_EXPIRY_HOURS * 3600 * 1e3;
|
|
@@ -24386,14 +24273,16 @@ var AuthManager = class {
|
|
|
24386
24273
|
usersFile;
|
|
24387
24274
|
baseDir;
|
|
24388
24275
|
db;
|
|
24276
|
+
loadedRaw;
|
|
24389
24277
|
constructor(baseDir) {
|
|
24390
24278
|
this.baseDir = baseDir;
|
|
24391
|
-
this.usersFile =
|
|
24279
|
+
this.usersFile = join24(baseDir, USERS_FILE);
|
|
24392
24280
|
this.db = this.loadOrCreate();
|
|
24393
24281
|
}
|
|
24394
24282
|
// ── Public API ─────────────────────────────────────────────────
|
|
24395
24283
|
/** Check if any users exist (first-run detection) */
|
|
24396
24284
|
hasUsers() {
|
|
24285
|
+
this.refresh();
|
|
24397
24286
|
return this.db.users.length > 0;
|
|
24398
24287
|
}
|
|
24399
24288
|
/** Check if auth is enabled (has users.json with at least 1 user) */
|
|
@@ -24402,6 +24291,18 @@ var AuthManager = class {
|
|
|
24402
24291
|
}
|
|
24403
24292
|
/** Register a new user. Returns error string or null on success. */
|
|
24404
24293
|
register(username, password) {
|
|
24294
|
+
return this.registerPrepared(username, password);
|
|
24295
|
+
}
|
|
24296
|
+
async registerAsync(username, password) {
|
|
24297
|
+
if (typeof username !== "string" || typeof password !== "string" || password.length > 1024) return "Invalid username or password";
|
|
24298
|
+
if (!/^[a-z0-9_-]{2,32}$/i.test(username.trim()) || password.length < 4) return "Invalid username or password";
|
|
24299
|
+
const salt = randomBytes2(16).toString("hex");
|
|
24300
|
+
const hash = await hashPasswordAsync(password, salt);
|
|
24301
|
+
return this.registerPrepared(username, password, { salt, hash });
|
|
24302
|
+
}
|
|
24303
|
+
registerPrepared(username, password, prepared) {
|
|
24304
|
+
this.refresh();
|
|
24305
|
+
if (typeof username !== "string" || typeof password !== "string" || password.length > 1024) return "Invalid username or password";
|
|
24405
24306
|
username = username.trim().toLowerCase();
|
|
24406
24307
|
if (!username || username.length < 2 || username.length > 32) {
|
|
24407
24308
|
return "Username must be 2-32 characters";
|
|
@@ -24415,11 +24316,12 @@ var AuthManager = class {
|
|
|
24415
24316
|
if (this.db.users.find((u) => u.username === username)) {
|
|
24416
24317
|
return "Username already exists";
|
|
24417
24318
|
}
|
|
24418
|
-
const salt =
|
|
24419
|
-
const passwordHash = this.hashPassword(password, salt);
|
|
24420
|
-
const dataDir =
|
|
24421
|
-
const fullDataDir =
|
|
24422
|
-
mkdirSync16(
|
|
24319
|
+
const salt = prepared?.salt ?? randomBytes2(16).toString("hex");
|
|
24320
|
+
const passwordHash = prepared?.hash ?? this.hashPassword(password, salt);
|
|
24321
|
+
const dataDir = join24(USERS_DIR, username);
|
|
24322
|
+
const fullDataDir = join24(this.baseDir, dataDir);
|
|
24323
|
+
mkdirSync16(fullDataDir, { recursive: true, mode: 448 });
|
|
24324
|
+
mkdirSync16(join24(fullDataDir, "history"), { recursive: true, mode: 448 });
|
|
24423
24325
|
const user = {
|
|
24424
24326
|
username,
|
|
24425
24327
|
passwordHash,
|
|
@@ -24442,6 +24344,8 @@ var AuthManager = class {
|
|
|
24442
24344
|
* resets the counter.
|
|
24443
24345
|
*/
|
|
24444
24346
|
login(username, password) {
|
|
24347
|
+
this.refresh();
|
|
24348
|
+
if (!validCredentials(username, password)) return null;
|
|
24445
24349
|
username = username.trim().toLowerCase();
|
|
24446
24350
|
const lockState = this.getLockState(username);
|
|
24447
24351
|
if (lockState.locked) return null;
|
|
@@ -24454,30 +24358,62 @@ var AuthManager = class {
|
|
|
24454
24358
|
const hash = isLegacy ? this.hashPasswordLegacy(password, user.salt) : this.hashPassword(password, user.salt);
|
|
24455
24359
|
const a = Buffer.from(hash, "utf-8");
|
|
24456
24360
|
const b = Buffer.from(user.passwordHash, "utf-8");
|
|
24457
|
-
if (a.length !== b.length || !
|
|
24361
|
+
if (a.length !== b.length || !timingSafeEqual2(a, b)) {
|
|
24458
24362
|
this.recordFailedLogin(username);
|
|
24459
24363
|
return null;
|
|
24460
24364
|
}
|
|
24461
24365
|
if (isLegacy) {
|
|
24462
|
-
const newSalt =
|
|
24366
|
+
const newSalt = randomBytes2(16).toString("hex");
|
|
24463
24367
|
user.passwordHash = this.hashPassword(password, newSalt);
|
|
24464
24368
|
user.salt = newSalt;
|
|
24465
24369
|
user.hashVersion = 2;
|
|
24466
24370
|
this.save();
|
|
24467
24371
|
}
|
|
24468
|
-
loginAttempts.delete(username);
|
|
24372
|
+
loginAttempts.delete(this.attemptKey(username));
|
|
24469
24373
|
return this.createToken(username);
|
|
24470
24374
|
}
|
|
24471
24375
|
/**
|
|
24472
24376
|
* Returns current lockout state for a username and lazily expires it.
|
|
24473
24377
|
* Exposed (read-only) for tests and the `aicli user` CLI status output.
|
|
24474
24378
|
*/
|
|
24379
|
+
async loginAsync(username, password) {
|
|
24380
|
+
this.refresh();
|
|
24381
|
+
if (!validCredentials(username, password)) return null;
|
|
24382
|
+
username = username.trim().toLowerCase();
|
|
24383
|
+
if (this.getLockState(username).locked) return null;
|
|
24384
|
+
const user = this.db.users.find((u) => u.username === username);
|
|
24385
|
+
if (!user) {
|
|
24386
|
+
this.recordFailedLogin(username);
|
|
24387
|
+
return null;
|
|
24388
|
+
}
|
|
24389
|
+
const snapshot = JSON.stringify(user);
|
|
24390
|
+
const legacy = !user.hashVersion || user.hashVersion < 2;
|
|
24391
|
+
const hash = legacy ? this.hashPasswordLegacy(password, user.salt) : await hashPasswordAsync(password, user.salt);
|
|
24392
|
+
const salt = legacy ? randomBytes2(16).toString("hex") : user.salt;
|
|
24393
|
+
const upgraded = legacy ? await hashPasswordAsync(password, salt) : hash;
|
|
24394
|
+
this.refresh();
|
|
24395
|
+
const current = this.db.users.find((u) => u.username === username);
|
|
24396
|
+
if (!current || JSON.stringify(current) !== snapshot || this.getLockState(username).locked) return null;
|
|
24397
|
+
const a = Buffer.from(hash), b = Buffer.from(current.passwordHash);
|
|
24398
|
+
if (a.length !== b.length || !timingSafeEqual2(a, b)) {
|
|
24399
|
+
this.recordFailedLogin(username);
|
|
24400
|
+
return null;
|
|
24401
|
+
}
|
|
24402
|
+
if (legacy) {
|
|
24403
|
+
current.salt = salt;
|
|
24404
|
+
current.passwordHash = upgraded;
|
|
24405
|
+
current.hashVersion = 2;
|
|
24406
|
+
this.save();
|
|
24407
|
+
}
|
|
24408
|
+
loginAttempts.delete(this.attemptKey(username));
|
|
24409
|
+
return this.createToken(username);
|
|
24410
|
+
}
|
|
24475
24411
|
getLockState(username) {
|
|
24476
24412
|
username = username.trim().toLowerCase();
|
|
24477
|
-
const state2 = loginAttempts.get(username);
|
|
24413
|
+
const state2 = loginAttempts.get(this.attemptKey(username));
|
|
24478
24414
|
if (!state2) return { locked: false, remainingMs: 0, fails: 0 };
|
|
24479
|
-
if (
|
|
24480
|
-
loginAttempts.delete(username);
|
|
24415
|
+
if (Date.now() >= state2.expiresAt) {
|
|
24416
|
+
loginAttempts.delete(this.attemptKey(username));
|
|
24481
24417
|
return { locked: false, remainingMs: 0, fails: 0 };
|
|
24482
24418
|
}
|
|
24483
24419
|
if (state2.lockedUntil > 0) {
|
|
@@ -24485,8 +24421,15 @@ var AuthManager = class {
|
|
|
24485
24421
|
}
|
|
24486
24422
|
return { locked: false, remainingMs: 0, fails: state2.fails };
|
|
24487
24423
|
}
|
|
24424
|
+
attemptKey(username) {
|
|
24425
|
+
return this.usersFile + "\0" + username;
|
|
24426
|
+
}
|
|
24488
24427
|
recordFailedLogin(username) {
|
|
24489
|
-
|
|
24428
|
+
username = this.attemptKey(username);
|
|
24429
|
+
const now = Date.now();
|
|
24430
|
+
for (const [key, value] of loginAttempts) if (value.expiresAt <= now) loginAttempts.delete(key);
|
|
24431
|
+
if (!loginAttempts.has(username) && loginAttempts.size >= 1e4) loginAttempts.delete(loginAttempts.keys().next().value);
|
|
24432
|
+
const state2 = loginAttempts.get(username) ?? { fails: 0, lockedUntil: 0, expiresAt: now + LOGIN_LOCKOUT_MS };
|
|
24490
24433
|
state2.fails += 1;
|
|
24491
24434
|
if (state2.fails >= LOGIN_MAX_FAILS) {
|
|
24492
24435
|
state2.lockedUntil = Date.now() + LOGIN_LOCKOUT_MS;
|
|
@@ -24496,17 +24439,19 @@ var AuthManager = class {
|
|
|
24496
24439
|
/** Verify a token. Returns username or null. */
|
|
24497
24440
|
verifyToken(token) {
|
|
24498
24441
|
try {
|
|
24442
|
+
this.refresh();
|
|
24443
|
+
if (typeof token !== "string" || token.length > 4096) return null;
|
|
24499
24444
|
const parts = token.split(".");
|
|
24500
24445
|
if (parts.length !== 2) return null;
|
|
24501
24446
|
const [payloadB64, signature] = parts;
|
|
24502
24447
|
const expectedSig = this.sign(payloadB64);
|
|
24503
24448
|
const sigBuf = Buffer.from(signature ?? "", "utf-8");
|
|
24504
24449
|
const expBuf = Buffer.from(expectedSig, "utf-8");
|
|
24505
|
-
if (sigBuf.length !== expBuf.length || !
|
|
24450
|
+
if (sigBuf.length !== expBuf.length || !timingSafeEqual2(sigBuf, expBuf)) return null;
|
|
24506
24451
|
const payload = JSON.parse(
|
|
24507
24452
|
Buffer.from(payloadB64, "base64url").toString("utf-8")
|
|
24508
24453
|
);
|
|
24509
|
-
if (Date.now()
|
|
24454
|
+
if (!Number.isFinite(payload.exp) || Date.now() >= payload.exp) return null;
|
|
24510
24455
|
const user = this.db.users.find((u) => u.username === payload.username);
|
|
24511
24456
|
if (!user) return null;
|
|
24512
24457
|
if (user.tokensRevokedBefore && (!payload.iat || payload.iat < user.tokensRevokedBefore)) {
|
|
@@ -24523,6 +24468,7 @@ var AuthManager = class {
|
|
|
24523
24468
|
* v0.4.114). Returns true if the user existed.
|
|
24524
24469
|
*/
|
|
24525
24470
|
logoutAll(username) {
|
|
24471
|
+
this.refresh();
|
|
24526
24472
|
username = username.trim().toLowerCase();
|
|
24527
24473
|
const user = this.db.users.find((u) => u.username === username);
|
|
24528
24474
|
if (!user) return false;
|
|
@@ -24532,16 +24478,19 @@ var AuthManager = class {
|
|
|
24532
24478
|
}
|
|
24533
24479
|
/** Get user's data directory (absolute path) */
|
|
24534
24480
|
getUserDataDir(username) {
|
|
24481
|
+
this.refresh();
|
|
24535
24482
|
const user = this.db.users.find((u) => u.username === username);
|
|
24536
24483
|
if (!user) throw new Error(`User not found: ${username}`);
|
|
24537
|
-
return
|
|
24484
|
+
return join24(this.baseDir, user.dataDir);
|
|
24538
24485
|
}
|
|
24539
24486
|
/** List all usernames */
|
|
24540
24487
|
listUsers() {
|
|
24488
|
+
this.refresh();
|
|
24541
24489
|
return this.db.users.map((u) => u.username);
|
|
24542
24490
|
}
|
|
24543
24491
|
/** Delete a user. Returns error string or null on success. */
|
|
24544
24492
|
deleteUser(username) {
|
|
24493
|
+
this.refresh();
|
|
24545
24494
|
username = username.trim().toLowerCase();
|
|
24546
24495
|
const idx = this.db.users.findIndex((u) => u.username === username);
|
|
24547
24496
|
if (idx === -1) return `User '${username}' not found`;
|
|
@@ -24551,13 +24500,14 @@ var AuthManager = class {
|
|
|
24551
24500
|
}
|
|
24552
24501
|
/** Reset a user's password. Returns error string or null on success. */
|
|
24553
24502
|
resetPassword(username, newPassword) {
|
|
24503
|
+
this.refresh();
|
|
24554
24504
|
username = username.trim().toLowerCase();
|
|
24555
24505
|
const user = this.db.users.find((u) => u.username === username);
|
|
24556
24506
|
if (!user) return `User '${username}' not found`;
|
|
24557
|
-
if (
|
|
24507
|
+
if (typeof newPassword !== "string" || newPassword.length > 1024 || newPassword.length < 4) {
|
|
24558
24508
|
return "Password must be at least 4 characters";
|
|
24559
24509
|
}
|
|
24560
|
-
const salt =
|
|
24510
|
+
const salt = randomBytes2(16).toString("hex");
|
|
24561
24511
|
user.passwordHash = this.hashPassword(newPassword, salt);
|
|
24562
24512
|
user.salt = salt;
|
|
24563
24513
|
user.hashVersion = 2;
|
|
@@ -24570,30 +24520,30 @@ var AuthManager = class {
|
|
|
24570
24520
|
const err = this.register(username, password);
|
|
24571
24521
|
if (err) return err;
|
|
24572
24522
|
const userDir = this.getUserDataDir(username);
|
|
24573
|
-
const globalConfig =
|
|
24574
|
-
if (
|
|
24523
|
+
const globalConfig = join24(this.baseDir, "config.json");
|
|
24524
|
+
if (existsSync30(globalConfig)) {
|
|
24575
24525
|
try {
|
|
24576
|
-
const content =
|
|
24577
|
-
|
|
24526
|
+
const content = readFileSync21(globalConfig, "utf-8");
|
|
24527
|
+
atomicWriteFileSync(join24(userDir, "config.json"), content, { mode: 384 });
|
|
24578
24528
|
} catch {
|
|
24579
24529
|
}
|
|
24580
24530
|
}
|
|
24581
|
-
const globalMemory =
|
|
24582
|
-
if (
|
|
24531
|
+
const globalMemory = join24(this.baseDir, "memory.md");
|
|
24532
|
+
if (existsSync30(globalMemory)) {
|
|
24583
24533
|
try {
|
|
24584
|
-
const content =
|
|
24585
|
-
|
|
24534
|
+
const content = readFileSync21(globalMemory, "utf-8");
|
|
24535
|
+
atomicWriteFileSync(join24(userDir, "memory.md"), content, { mode: 384 });
|
|
24586
24536
|
} catch {
|
|
24587
24537
|
}
|
|
24588
24538
|
}
|
|
24589
|
-
const globalHistory =
|
|
24590
|
-
if (
|
|
24539
|
+
const globalHistory = join24(this.baseDir, "history");
|
|
24540
|
+
if (existsSync30(globalHistory)) {
|
|
24591
24541
|
try {
|
|
24592
24542
|
const files = readdirSync14(globalHistory).filter((f) => f.endsWith(".json"));
|
|
24593
|
-
const userHistory =
|
|
24543
|
+
const userHistory = join24(userDir, "history");
|
|
24594
24544
|
for (const f of files) {
|
|
24595
24545
|
try {
|
|
24596
|
-
|
|
24546
|
+
atomicWriteFileSync(join24(userHistory, f), readFileSync21(join24(globalHistory, f), "utf8"), { mode: 384 });
|
|
24597
24547
|
} catch {
|
|
24598
24548
|
}
|
|
24599
24549
|
}
|
|
@@ -24603,13 +24553,22 @@ var AuthManager = class {
|
|
|
24603
24553
|
return null;
|
|
24604
24554
|
}
|
|
24605
24555
|
// ── Private methods ────────────────────────────────────────────
|
|
24556
|
+
refresh() {
|
|
24557
|
+
const raw = readFileSync21(this.usersFile, "utf8");
|
|
24558
|
+
const parsed = JSON.parse(raw);
|
|
24559
|
+
if (!this.isValidUsersDB(parsed)) throw new Error("Authentication database is invalid; access refused");
|
|
24560
|
+
this.db = parsed;
|
|
24561
|
+
this.loadedRaw = raw;
|
|
24562
|
+
}
|
|
24606
24563
|
loadOrCreate() {
|
|
24607
|
-
if (
|
|
24564
|
+
if (existsSync30(this.usersFile)) {
|
|
24608
24565
|
try {
|
|
24609
|
-
const
|
|
24566
|
+
const raw = readFileSync21(this.usersFile, "utf-8");
|
|
24567
|
+
const parsed = JSON.parse(raw);
|
|
24610
24568
|
if (!this.isValidUsersDB(parsed)) {
|
|
24611
24569
|
throw new Error("database structure is invalid");
|
|
24612
24570
|
}
|
|
24571
|
+
this.loadedRaw = raw;
|
|
24613
24572
|
return parsed;
|
|
24614
24573
|
} catch (err) {
|
|
24615
24574
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -24621,7 +24580,7 @@ var AuthManager = class {
|
|
|
24621
24580
|
}
|
|
24622
24581
|
const db = {
|
|
24623
24582
|
version: 1,
|
|
24624
|
-
secret:
|
|
24583
|
+
secret: randomBytes2(32).toString("hex"),
|
|
24625
24584
|
users: []
|
|
24626
24585
|
};
|
|
24627
24586
|
this.saveDB(db);
|
|
@@ -24640,7 +24599,7 @@ var AuthManager = class {
|
|
|
24640
24599
|
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
24600
|
return false;
|
|
24642
24601
|
}
|
|
24643
|
-
const dataDirFromBase = relative5(this.baseDir,
|
|
24602
|
+
const dataDirFromBase = relative5(this.baseDir, join24(this.baseDir, record.dataDir));
|
|
24644
24603
|
if (dataDirFromBase.startsWith("..") || isAbsolute3(dataDirFromBase)) return false;
|
|
24645
24604
|
if (record.hashVersion !== void 0 && record.hashVersion !== 1 && record.hashVersion !== 2) return false;
|
|
24646
24605
|
if (record.tokensRevokedBefore !== void 0 && !Number.isFinite(record.tokensRevokedBefore)) return false;
|
|
@@ -24652,8 +24611,19 @@ var AuthManager = class {
|
|
|
24652
24611
|
this.saveDB(this.db);
|
|
24653
24612
|
}
|
|
24654
24613
|
saveDB(db) {
|
|
24655
|
-
mkdirSync16(this.baseDir, { recursive: true });
|
|
24656
|
-
|
|
24614
|
+
mkdirSync16(this.baseDir, { recursive: true, mode: 448 });
|
|
24615
|
+
const lock = this.usersFile + ".lock";
|
|
24616
|
+
const fd = openSync(lock, "wx", 384);
|
|
24617
|
+
try {
|
|
24618
|
+
const current = existsSync30(this.usersFile) ? readFileSync21(this.usersFile, "utf8") : void 0;
|
|
24619
|
+
if (current !== this.loadedRaw) throw new Error("Authentication database changed concurrently; retry the operation");
|
|
24620
|
+
const raw = JSON.stringify(db, null, 2);
|
|
24621
|
+
atomicWriteFileSync(this.usersFile, raw, { mode: 384 });
|
|
24622
|
+
this.loadedRaw = raw;
|
|
24623
|
+
} finally {
|
|
24624
|
+
closeSync(fd);
|
|
24625
|
+
unlinkSync8(lock);
|
|
24626
|
+
}
|
|
24657
24627
|
}
|
|
24658
24628
|
/** Legacy hash — kept only for migrating old users (v0.2.x) */
|
|
24659
24629
|
hashPasswordLegacy(password, salt) {
|
|
@@ -24677,6 +24647,19 @@ var AuthManager = class {
|
|
|
24677
24647
|
return createHmac("sha256", this.db.secret).update(data).digest("base64url");
|
|
24678
24648
|
}
|
|
24679
24649
|
};
|
|
24650
|
+
function validCredentials(username, password) {
|
|
24651
|
+
return typeof username === "string" && /^[a-z0-9_-]{2,32}$/i.test(username.trim()) && typeof password === "string" && password.length > 0 && password.length <= 1024;
|
|
24652
|
+
}
|
|
24653
|
+
var activeHashes = 0;
|
|
24654
|
+
async function hashPasswordAsync(password, salt) {
|
|
24655
|
+
if (activeHashes >= 4) throw new Error("Authentication capacity reached; try again later");
|
|
24656
|
+
activeHashes++;
|
|
24657
|
+
try {
|
|
24658
|
+
return await new Promise((resolve13, reject) => pbkdf2(password, salt, 1e5, 64, "sha512", (error, hash) => error ? reject(error) : resolve13(hash.toString("hex"))));
|
|
24659
|
+
} finally {
|
|
24660
|
+
activeHashes--;
|
|
24661
|
+
}
|
|
24662
|
+
}
|
|
24680
24663
|
|
|
24681
24664
|
// src/web/server.ts
|
|
24682
24665
|
function getModuleDir() {
|
|
@@ -24696,6 +24679,10 @@ async function startWebServer(options = {}) {
|
|
|
24696
24679
|
console.log(`\u{1F916} ai-cli Web UI v${VERSION}`);
|
|
24697
24680
|
console.log(` Initializing...`);
|
|
24698
24681
|
const config = new ConfigManager();
|
|
24682
|
+
const authManager = new AuthManager(config.getConfigDir());
|
|
24683
|
+
if (!authManager.isEnabled() && !["localhost", "127.0.0.1", "::1"].includes(host)) {
|
|
24684
|
+
throw new Error("Network listening requires authentication. Create a user with aicli user create first.");
|
|
24685
|
+
}
|
|
24699
24686
|
await setupProxy(config.get("proxy"));
|
|
24700
24687
|
const providers = new ProviderRegistry();
|
|
24701
24688
|
const sessions = new SessionManager(config);
|
|
@@ -24729,7 +24716,7 @@ async function startWebServer(options = {}) {
|
|
|
24729
24716
|
const projectMcpResolved = mcpEnabled ? resolveProjectMcpPath() : null;
|
|
24730
24717
|
let projectMcpServers = {};
|
|
24731
24718
|
if (projectMcpResolved) {
|
|
24732
|
-
const { checkTrust } = await import("./project-trust-
|
|
24719
|
+
const { checkTrust } = await import("./project-trust-6CJB5ZMX.js");
|
|
24733
24720
|
const verdict = checkTrust(config.getConfigDir(), projectMcpResolved);
|
|
24734
24721
|
if (verdict.trusted) {
|
|
24735
24722
|
projectMcpServers = loadProjectMcpConfig() ?? {};
|
|
@@ -24756,8 +24743,8 @@ async function startWebServer(options = {}) {
|
|
|
24756
24743
|
}
|
|
24757
24744
|
}
|
|
24758
24745
|
let skillManager = null;
|
|
24759
|
-
const skillsDir =
|
|
24760
|
-
if (
|
|
24746
|
+
const skillsDir = join25(config.getConfigDir(), SKILLS_DIR_NAME);
|
|
24747
|
+
if (existsSync31(skillsDir)) {
|
|
24761
24748
|
skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
|
|
24762
24749
|
skillManager.loadSkills(pluginAssets.skillDirs);
|
|
24763
24750
|
const count = skillManager.listSkills().length;
|
|
@@ -24776,6 +24763,16 @@ async function startWebServer(options = {}) {
|
|
|
24776
24763
|
skillManager
|
|
24777
24764
|
};
|
|
24778
24765
|
const app = express();
|
|
24766
|
+
const access = new WebAccess(host, options.externalOrigin);
|
|
24767
|
+
const authLimits = new AuthRateLimiter();
|
|
24768
|
+
app.use((req, res, next) => {
|
|
24769
|
+
if (!access.accepts(req)) {
|
|
24770
|
+
res.status(403).json({ error: "Untrusted request origin or host" });
|
|
24771
|
+
return;
|
|
24772
|
+
}
|
|
24773
|
+
res.locals.externalSecure = !!options.externalOrigin?.startsWith("https://") && req.headers.host === new URL(options.externalOrigin).host;
|
|
24774
|
+
next();
|
|
24775
|
+
});
|
|
24779
24776
|
app.use((_req, res, next) => {
|
|
24780
24777
|
res.setHeader(
|
|
24781
24778
|
"Content-Security-Policy",
|
|
@@ -24804,19 +24801,23 @@ async function startWebServer(options = {}) {
|
|
|
24804
24801
|
const WS_MSG_RATE_PER_SEC = 30;
|
|
24805
24802
|
const WS_MSG_BURST = 60;
|
|
24806
24803
|
const WS_PREAUTH_TIMEOUT_MS = 3e4;
|
|
24807
|
-
const wss = new WebSocketServer({ server, maxPayload: WS_MAX_PAYLOAD })
|
|
24808
|
-
|
|
24804
|
+
const wss = new WebSocketServer({ server, maxPayload: WS_MAX_PAYLOAD, verifyClient: ({ req }) => {
|
|
24805
|
+
if (!access.accepts(req) || wss.clients.size >= 128) return false;
|
|
24806
|
+
if (req.headers.origin) return true;
|
|
24807
|
+
const cookie = parseCookie(req.headers.cookie ?? "");
|
|
24808
|
+
return equalToken(cookie.aicli_local, access.localToken) || !!authManager.verifyToken(cookie.aicli_token ?? "");
|
|
24809
|
+
} });
|
|
24809
24810
|
if (authManager.isEnabled()) {
|
|
24810
24811
|
console.log(` Auth: ${authManager.listUsers().length} user(s) registered`);
|
|
24811
24812
|
} 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
|
-
}
|
|
24813
|
+
console.log(" Auth: local launch token (no users registered)");
|
|
24817
24814
|
}
|
|
24818
24815
|
const requireAuth = (req, res, next) => {
|
|
24819
|
-
if (!authManager.isEnabled())
|
|
24816
|
+
if (!authManager.isEnabled()) {
|
|
24817
|
+
if (equalToken(parseCookie(req.headers.cookie ?? "").aicli_local, access.localToken)) return next();
|
|
24818
|
+
res.status(401).json({ error: "Open the launch URL printed by aicli to authorize this browser" });
|
|
24819
|
+
return;
|
|
24820
|
+
}
|
|
24820
24821
|
const authHeader = req.headers.authorization;
|
|
24821
24822
|
const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
24822
24823
|
const cookieToken = parseCookie(req.headers.cookie ?? "")["aicli_token"];
|
|
@@ -24834,19 +24835,20 @@ async function startWebServer(options = {}) {
|
|
|
24834
24835
|
next();
|
|
24835
24836
|
};
|
|
24836
24837
|
const moduleDir = getModuleDir();
|
|
24837
|
-
let clientDir =
|
|
24838
|
-
if (!
|
|
24839
|
-
clientDir =
|
|
24838
|
+
let clientDir = join25(moduleDir, "web", "client");
|
|
24839
|
+
if (!existsSync31(clientDir)) {
|
|
24840
|
+
clientDir = join25(moduleDir, "client");
|
|
24840
24841
|
}
|
|
24841
|
-
if (!
|
|
24842
|
-
clientDir =
|
|
24842
|
+
if (!existsSync31(clientDir)) {
|
|
24843
|
+
clientDir = join25(moduleDir, "..", "..", "src", "web", "client");
|
|
24843
24844
|
}
|
|
24844
|
-
if (!
|
|
24845
|
-
clientDir =
|
|
24845
|
+
if (!existsSync31(clientDir)) {
|
|
24846
|
+
clientDir = join25(process.cwd(), "src", "web", "client");
|
|
24846
24847
|
}
|
|
24847
24848
|
console.log(` Static files: ${clientDir}`);
|
|
24848
24849
|
const PRECOMPRESSED_TYPES = {
|
|
24849
24850
|
".js": "text/javascript; charset=utf-8",
|
|
24851
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
24850
24852
|
".css": "text/css; charset=utf-8",
|
|
24851
24853
|
".html": "text/html; charset=utf-8",
|
|
24852
24854
|
".json": "application/json; charset=utf-8",
|
|
@@ -24861,14 +24863,14 @@ async function startWebServer(options = {}) {
|
|
|
24861
24863
|
const encoding = /\bbr\b/.test(accept) ? "br" : /\bgzip\b/.test(accept) ? "gzip" : null;
|
|
24862
24864
|
if (!encoding) return next();
|
|
24863
24865
|
const target = resolvePath(clientDir, `.${req.path}`) + (encoding === "br" ? ".br" : ".gz");
|
|
24864
|
-
if (!target.startsWith(clientDir) || !
|
|
24866
|
+
if (!target.startsWith(clientDir) || !existsSync31(target)) return next();
|
|
24865
24867
|
res.setHeader("Content-Type", contentType);
|
|
24866
24868
|
res.setHeader("Content-Encoding", encoding);
|
|
24867
24869
|
res.setHeader("Vary", "Accept-Encoding");
|
|
24868
24870
|
if (req.path.startsWith("/vendor/")) res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
24869
24871
|
createReadStream(target).pipe(res);
|
|
24870
24872
|
});
|
|
24871
|
-
app.use("/vendor", express.static(
|
|
24873
|
+
app.use("/vendor", express.static(join25(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
|
|
24872
24874
|
app.use(express.static(clientDir));
|
|
24873
24875
|
app.get("/api/status", (_req, res) => {
|
|
24874
24876
|
res.json({
|
|
@@ -24887,9 +24889,28 @@ async function startWebServer(options = {}) {
|
|
|
24887
24889
|
}
|
|
24888
24890
|
});
|
|
24889
24891
|
});
|
|
24890
|
-
app.use(express.json());
|
|
24891
|
-
app.post("/api/auth/
|
|
24892
|
+
app.use(express.json({ limit: "16kb" }));
|
|
24893
|
+
app.post("/api/auth/bootstrap", (req, res) => {
|
|
24894
|
+
if (!equalToken(req.body?.token, access.localToken)) {
|
|
24895
|
+
res.status(403).json({ error: "Invalid launch token" });
|
|
24896
|
+
return;
|
|
24897
|
+
}
|
|
24898
|
+
res.cookie("aicli_local", access.localToken, { httpOnly: true, sameSite: "strict", secure: isHttpsRequest(req) });
|
|
24899
|
+
res.json({ success: true });
|
|
24900
|
+
});
|
|
24901
|
+
app.use("/api/auth", (req, res, next) => {
|
|
24902
|
+
if (!authLimits.take(req.socket.remoteAddress ?? "unknown")) {
|
|
24903
|
+
res.status(429).json({ error: "Too many authentication attempts; try again later" });
|
|
24904
|
+
return;
|
|
24905
|
+
}
|
|
24906
|
+
next();
|
|
24907
|
+
});
|
|
24908
|
+
app.post("/api/auth/register", async (req, res) => {
|
|
24892
24909
|
const firstRun = !authManager.hasUsers();
|
|
24910
|
+
if (firstRun && !equalToken(parseCookie(req.headers.cookie ?? "").aicli_local, access.localToken)) {
|
|
24911
|
+
res.status(403).json({ error: "Open the launch URL before enabling authentication" });
|
|
24912
|
+
return;
|
|
24913
|
+
}
|
|
24893
24914
|
if (!firstRun) {
|
|
24894
24915
|
const authHeader = req.headers.authorization;
|
|
24895
24916
|
const token2 = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
@@ -24905,23 +24926,24 @@ async function startWebServer(options = {}) {
|
|
|
24905
24926
|
res.status(400).json({ error: "Username and password required" });
|
|
24906
24927
|
return;
|
|
24907
24928
|
}
|
|
24908
|
-
const err = authManager.
|
|
24929
|
+
const err = await authManager.registerAsync(username, password);
|
|
24909
24930
|
if (err) {
|
|
24910
24931
|
res.status(400).json({ error: err });
|
|
24911
24932
|
return;
|
|
24912
24933
|
}
|
|
24913
|
-
const token = authManager.
|
|
24934
|
+
const token = await authManager.loginAsync(username, password);
|
|
24935
|
+
invalidateConnections();
|
|
24914
24936
|
console.log(` \u2713 User registered via API: ${username}${firstRun ? " (first-run)" : ""}`);
|
|
24915
24937
|
res.cookie("aicli_token", token, authCookieOptions(req));
|
|
24916
24938
|
res.json({ success: true, username });
|
|
24917
24939
|
});
|
|
24918
|
-
app.post("/api/auth/login", (req, res) => {
|
|
24940
|
+
app.post("/api/auth/login", async (req, res) => {
|
|
24919
24941
|
const { username, password } = req.body ?? {};
|
|
24920
24942
|
if (!username || !password) {
|
|
24921
24943
|
res.status(400).json({ error: "Username and password required" });
|
|
24922
24944
|
return;
|
|
24923
24945
|
}
|
|
24924
|
-
const token = authManager.
|
|
24946
|
+
const token = await authManager.loginAsync(username, password);
|
|
24925
24947
|
if (!token) {
|
|
24926
24948
|
res.status(401).json({ error: "Invalid username or password" });
|
|
24927
24949
|
return;
|
|
@@ -24930,13 +24952,16 @@ async function startWebServer(options = {}) {
|
|
|
24930
24952
|
res.json({ success: true, username });
|
|
24931
24953
|
});
|
|
24932
24954
|
app.post("/api/auth/logout", (req, res) => {
|
|
24955
|
+
const user = authManager.verifyToken(parseCookie(req.headers.cookie ?? "").aicli_token ?? "");
|
|
24956
|
+
if (user) authManager.logoutAll(user);
|
|
24957
|
+
invalidateConnections();
|
|
24933
24958
|
res.clearCookie("aicli_token", clearAuthCookieOptions(req));
|
|
24934
24959
|
res.json({ success: true });
|
|
24935
24960
|
});
|
|
24936
24961
|
app.get("/api/files", requireAuth, (req, res) => {
|
|
24937
24962
|
const cwd = process.cwd();
|
|
24938
24963
|
const prefix = req.query.prefix || "";
|
|
24939
|
-
const targetDir =
|
|
24964
|
+
const targetDir = join25(cwd, prefix);
|
|
24940
24965
|
try {
|
|
24941
24966
|
const canonicalTarget = realpathSync3(resolve12(targetDir));
|
|
24942
24967
|
const canonicalCwd = realpathSync3(resolve12(cwd));
|
|
@@ -24953,7 +24978,7 @@ async function startWebServer(options = {}) {
|
|
|
24953
24978
|
const entries = readdirSync15(targetDir, { withFileTypes: true });
|
|
24954
24979
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
24955
24980
|
name: e.name,
|
|
24956
|
-
path: relative6(cwd,
|
|
24981
|
+
path: relative6(cwd, join25(targetDir, e.name)).replace(/\\/g, "/"),
|
|
24957
24982
|
isDir: e.isDirectory()
|
|
24958
24983
|
}));
|
|
24959
24984
|
res.json({ files });
|
|
@@ -24993,8 +25018,8 @@ async function startWebServer(options = {}) {
|
|
|
24993
25018
|
try {
|
|
24994
25019
|
const authUser = req._authUser;
|
|
24995
25020
|
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
24996
|
-
const filePath =
|
|
24997
|
-
if (!
|
|
25021
|
+
const filePath = join25(histDir, `${id}.json`);
|
|
25022
|
+
if (!existsSync31(filePath)) {
|
|
24998
25023
|
res.status(404).json({ error: "Session not found" });
|
|
24999
25024
|
return;
|
|
25000
25025
|
}
|
|
@@ -25009,7 +25034,7 @@ async function startWebServer(options = {}) {
|
|
|
25009
25034
|
res.status(404).json({ error: "Session not found" });
|
|
25010
25035
|
return;
|
|
25011
25036
|
}
|
|
25012
|
-
const data = JSON.parse(
|
|
25037
|
+
const data = JSON.parse(readFileSync22(filePath, "utf-8"));
|
|
25013
25038
|
res.json({ session: data });
|
|
25014
25039
|
} catch (err) {
|
|
25015
25040
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -25022,9 +25047,10 @@ async function startWebServer(options = {}) {
|
|
|
25022
25047
|
return;
|
|
25023
25048
|
}
|
|
25024
25049
|
const cwd = process.cwd();
|
|
25025
|
-
const fullPath = resolve12(
|
|
25050
|
+
const fullPath = resolve12(join25(cwd, filePath));
|
|
25026
25051
|
try {
|
|
25027
25052
|
const canonicalFull = realpathSync3(fullPath);
|
|
25053
|
+
assertReadablePath(canonicalFull);
|
|
25028
25054
|
const canonicalCwd = realpathSync3(resolve12(cwd));
|
|
25029
25055
|
if (!canonicalFull.startsWith(canonicalCwd + sep5) && canonicalFull !== canonicalCwd) {
|
|
25030
25056
|
res.json({ error: "Access denied" });
|
|
@@ -25040,7 +25066,7 @@ async function startWebServer(options = {}) {
|
|
|
25040
25066
|
res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
|
|
25041
25067
|
return;
|
|
25042
25068
|
}
|
|
25043
|
-
const content =
|
|
25069
|
+
const content = readFileSync22(fullPath, "utf-8");
|
|
25044
25070
|
res.json({ content, size: stat.size });
|
|
25045
25071
|
} catch {
|
|
25046
25072
|
res.json({ error: "Cannot read file" });
|
|
@@ -25052,6 +25078,9 @@ async function startWebServer(options = {}) {
|
|
|
25052
25078
|
if (cached) return cached;
|
|
25053
25079
|
const userDataDir = authManager.getUserDataDir(username);
|
|
25054
25080
|
const userConfig = new ConfigManager(userDataDir);
|
|
25081
|
+
for (const key of ["defaultPermissionProfile", "allowedPermissionProfiles", "permissionProfiles", "permissionRules", "defaultPermission", "networkPolicy"]) {
|
|
25082
|
+
userConfig.setTransient(key, structuredClone(config.get(key)));
|
|
25083
|
+
}
|
|
25055
25084
|
if (userConfig.isFirstRun()) {
|
|
25056
25085
|
userConfig.setTransient("defaultProvider", config.get("defaultProvider"));
|
|
25057
25086
|
userConfig.setTransient("defaultModels", config.get("defaultModels"));
|
|
@@ -25062,7 +25091,7 @@ async function startWebServer(options = {}) {
|
|
|
25062
25091
|
// 每用户各自的 config 目录 → 各自的 cost-history.json
|
|
25063
25092
|
costTracker: new CostTracker(userConfig.getConfigDir()),
|
|
25064
25093
|
providers,
|
|
25065
|
-
//
|
|
25094
|
+
// Host-owned service credentials; shared explicitly, never per-user keys.
|
|
25066
25095
|
sessions: userSessions,
|
|
25067
25096
|
toolRegistry,
|
|
25068
25097
|
// Shared
|
|
@@ -25075,6 +25104,21 @@ async function startWebServer(options = {}) {
|
|
|
25075
25104
|
return userShared;
|
|
25076
25105
|
}
|
|
25077
25106
|
const handlers = /* @__PURE__ */ new Map();
|
|
25107
|
+
const validators = /* @__PURE__ */ new Set();
|
|
25108
|
+
function invalidateConnections() {
|
|
25109
|
+
for (const validate of validators) validate();
|
|
25110
|
+
}
|
|
25111
|
+
let authRefreshTimer;
|
|
25112
|
+
server.on("listening", () => {
|
|
25113
|
+
if (!authRefreshTimer) {
|
|
25114
|
+
authRefreshTimer = setInterval(invalidateConnections, 1e3);
|
|
25115
|
+
authRefreshTimer.unref();
|
|
25116
|
+
}
|
|
25117
|
+
});
|
|
25118
|
+
server.on("close", () => {
|
|
25119
|
+
clearInterval(authRefreshTimer);
|
|
25120
|
+
authRefreshTimer = void 0;
|
|
25121
|
+
});
|
|
25078
25122
|
wss.on("connection", (ws, req) => {
|
|
25079
25123
|
const urlStr = req.url ?? "";
|
|
25080
25124
|
const qMark = urlStr.indexOf("?");
|
|
@@ -25082,6 +25126,8 @@ async function startWebServer(options = {}) {
|
|
|
25082
25126
|
const tabId = params.get("tabId") || `tab-${Date.now()}`;
|
|
25083
25127
|
const cookies = parseCookie(req.headers.cookie ?? "");
|
|
25084
25128
|
const token = cookies["aicli_token"] || "";
|
|
25129
|
+
let activeToken = token;
|
|
25130
|
+
const localAuthorized = equalToken(cookies.aicli_local, access.localToken);
|
|
25085
25131
|
let tokens = WS_MSG_BURST;
|
|
25086
25132
|
let lastRefill = Date.now();
|
|
25087
25133
|
const takeToken = () => {
|
|
@@ -25141,7 +25187,7 @@ async function startWebServer(options = {}) {
|
|
|
25141
25187
|
authenticatedUser = authManager.verifyToken(token);
|
|
25142
25188
|
}
|
|
25143
25189
|
const authRequired = authManager.isEnabled();
|
|
25144
|
-
if (!authRequired) {
|
|
25190
|
+
if (!authRequired && localAuthorized) {
|
|
25145
25191
|
console.log(` \u2713 Tab connected: ${tabId.slice(0, 12)} (no auth) (total: ${handlers.size + 1})`);
|
|
25146
25192
|
handler = attachHandler(shared);
|
|
25147
25193
|
clearPreAuthTimer();
|
|
@@ -25159,7 +25205,26 @@ async function startWebServer(options = {}) {
|
|
|
25159
25205
|
}));
|
|
25160
25206
|
}
|
|
25161
25207
|
}
|
|
25208
|
+
const validate = () => {
|
|
25209
|
+
if (!handler) return;
|
|
25210
|
+
let valid = false;
|
|
25211
|
+
try {
|
|
25212
|
+
valid = authManager.isEnabled() ? !!activeToken && authManager.verifyToken(activeToken) === authenticatedUser : localAuthorized && !authenticatedUser;
|
|
25213
|
+
} catch {
|
|
25214
|
+
}
|
|
25215
|
+
if (!valid) {
|
|
25216
|
+
handler.onDisconnect(false);
|
|
25217
|
+
if (handlers.get(handlerKey) === handler) handlers.delete(handlerKey);
|
|
25218
|
+
handler = null;
|
|
25219
|
+
ws.close(1008, "Authentication changed; reconnect");
|
|
25220
|
+
}
|
|
25221
|
+
};
|
|
25222
|
+
validators.add(validate);
|
|
25223
|
+
ws.on("close", () => validators.delete(validate));
|
|
25224
|
+
let authBusy = false;
|
|
25162
25225
|
ws.on("message", async (data) => {
|
|
25226
|
+
validate();
|
|
25227
|
+
if (ws.readyState !== ws.OPEN) return;
|
|
25163
25228
|
if (!takeToken()) {
|
|
25164
25229
|
if (ws.readyState === ws.OPEN) {
|
|
25165
25230
|
try {
|
|
@@ -25179,57 +25244,73 @@ async function startWebServer(options = {}) {
|
|
|
25179
25244
|
return;
|
|
25180
25245
|
}
|
|
25181
25246
|
if (parsed.type === "auth") {
|
|
25182
|
-
|
|
25183
|
-
|
|
25184
|
-
|
|
25185
|
-
|
|
25247
|
+
if (authBusy) return;
|
|
25248
|
+
authBusy = true;
|
|
25249
|
+
try {
|
|
25250
|
+
if (!authLimits.take(req.socket.remoteAddress ?? "unknown")) {
|
|
25251
|
+
ws.close(1008, "Authentication rate limit");
|
|
25186
25252
|
return;
|
|
25187
25253
|
}
|
|
25188
|
-
const
|
|
25189
|
-
|
|
25190
|
-
|
|
25254
|
+
const { action, password } = parsed;
|
|
25255
|
+
const username = typeof parsed.username === "string" ? parsed.username.trim().toLowerCase() : "";
|
|
25256
|
+
if (action === "register") {
|
|
25257
|
+
if (!authenticatedUser && (authManager.hasUsers() || !localAuthorized)) {
|
|
25258
|
+
ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Registration is closed. Use `aicli user create` on the host to add users." }));
|
|
25259
|
+
return;
|
|
25260
|
+
}
|
|
25261
|
+
const err = await authManager.registerAsync(username, password);
|
|
25262
|
+
if (err) {
|
|
25263
|
+
ws.send(JSON.stringify({ type: "auth_result", success: false, error: err }));
|
|
25264
|
+
return;
|
|
25265
|
+
}
|
|
25266
|
+
activeToken = await authManager.loginAsync(username, password) ?? "";
|
|
25267
|
+
if (ws.readyState !== ws.OPEN || authManager.verifyToken(activeToken) !== username) return;
|
|
25268
|
+
authenticatedUser = username;
|
|
25269
|
+
const userShared = getUserShared(username);
|
|
25270
|
+
handler = attachHandler(userShared);
|
|
25271
|
+
clearPreAuthTimer();
|
|
25272
|
+
invalidateConnections();
|
|
25273
|
+
console.log(` \u2713 User registered & connected: ${username} (tab: ${tabId.slice(0, 12)})`);
|
|
25274
|
+
ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
|
|
25191
25275
|
return;
|
|
25192
25276
|
}
|
|
25193
|
-
|
|
25194
|
-
|
|
25195
|
-
|
|
25196
|
-
|
|
25197
|
-
|
|
25198
|
-
|
|
25199
|
-
|
|
25200
|
-
|
|
25201
|
-
|
|
25202
|
-
|
|
25203
|
-
|
|
25204
|
-
|
|
25205
|
-
|
|
25206
|
-
ws.send(JSON.stringify({ type: "auth_result", success:
|
|
25277
|
+
if (action === "token") {
|
|
25278
|
+
const { token: clientToken } = parsed;
|
|
25279
|
+
const verifiedUser = authManager.verifyToken(clientToken);
|
|
25280
|
+
if (!verifiedUser) {
|
|
25281
|
+
ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Invalid or expired token" }));
|
|
25282
|
+
return;
|
|
25283
|
+
}
|
|
25284
|
+
activeToken = clientToken;
|
|
25285
|
+
authenticatedUser = verifiedUser;
|
|
25286
|
+
const userShared = getUserShared(verifiedUser);
|
|
25287
|
+
handler = attachHandler(userShared);
|
|
25288
|
+
clearPreAuthTimer();
|
|
25289
|
+
console.log(` \u2713 Token auth: ${verifiedUser} (tab: ${tabId.slice(0, 12)})`);
|
|
25290
|
+
ws.send(JSON.stringify({ type: "auth_result", success: true, username: verifiedUser }));
|
|
25207
25291
|
return;
|
|
25208
25292
|
}
|
|
25209
|
-
|
|
25210
|
-
|
|
25211
|
-
|
|
25212
|
-
|
|
25213
|
-
|
|
25214
|
-
|
|
25215
|
-
|
|
25216
|
-
|
|
25217
|
-
|
|
25218
|
-
|
|
25219
|
-
|
|
25220
|
-
|
|
25293
|
+
if (action === "login") {
|
|
25294
|
+
const loginToken = await authManager.loginAsync(username, password);
|
|
25295
|
+
if (!loginToken) {
|
|
25296
|
+
ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Invalid username or password" }));
|
|
25297
|
+
return;
|
|
25298
|
+
}
|
|
25299
|
+
if (ws.readyState !== ws.OPEN || authManager.verifyToken(loginToken) !== username) return;
|
|
25300
|
+
activeToken = loginToken;
|
|
25301
|
+
authenticatedUser = username;
|
|
25302
|
+
const userShared = getUserShared(username);
|
|
25303
|
+
handler = attachHandler(userShared);
|
|
25304
|
+
clearPreAuthTimer();
|
|
25305
|
+
console.log(` \u2713 User logged in: ${username} (tab: ${tabId.slice(0, 12)})`);
|
|
25306
|
+
ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
|
|
25221
25307
|
return;
|
|
25222
25308
|
}
|
|
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 }));
|
|
25309
|
+
ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
|
|
25229
25310
|
return;
|
|
25311
|
+
} finally {
|
|
25312
|
+
authBusy = false;
|
|
25230
25313
|
}
|
|
25231
|
-
ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
|
|
25232
|
-
return;
|
|
25233
25314
|
}
|
|
25234
25315
|
if (!handler) {
|
|
25235
25316
|
if (ws.readyState === ws.OPEN) {
|
|
@@ -25275,13 +25356,15 @@ async function startWebServer(options = {}) {
|
|
|
25275
25356
|
}
|
|
25276
25357
|
});
|
|
25277
25358
|
server.listen(actualPort, host, () => {
|
|
25278
|
-
|
|
25359
|
+
actualPort = server.address().port;
|
|
25360
|
+
const address = host.includes(":") ? `[${host}]` : host === "0.0.0.0" ? "localhost" : host;
|
|
25361
|
+
const url = `http://${address}:${actualPort}` + (!authManager.isEnabled() ? `#token=${access.localToken}` : "");
|
|
25279
25362
|
console.log(`
|
|
25280
25363
|
\u{1F310} Web UI ready: ${url}`);
|
|
25281
25364
|
if (actualPort !== port) console.log(` (port ${port} was in use, using ${actualPort})`);
|
|
25282
25365
|
if (host === "0.0.0.0" || host === "::") {
|
|
25283
25366
|
try {
|
|
25284
|
-
const nets =
|
|
25367
|
+
const nets = networkInterfaces2();
|
|
25285
25368
|
for (const name of Object.keys(nets)) {
|
|
25286
25369
|
for (const net2 of nets[name] ?? []) {
|
|
25287
25370
|
if (net2.family === "IPv4" && !net2.internal) {
|
|
@@ -25314,27 +25397,24 @@ function resolveProjectMcpPath() {
|
|
|
25314
25397
|
const cwd = process.cwd();
|
|
25315
25398
|
const gitRoot = getGitRoot(cwd);
|
|
25316
25399
|
const projectRoot = gitRoot ?? cwd;
|
|
25317
|
-
const configPath =
|
|
25318
|
-
return
|
|
25400
|
+
const configPath = join25(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
25401
|
+
return existsSync31(configPath) ? configPath : null;
|
|
25319
25402
|
}
|
|
25320
25403
|
function loadProjectMcpConfig() {
|
|
25321
25404
|
const cwd = process.cwd();
|
|
25322
25405
|
const gitRoot = getGitRoot(cwd);
|
|
25323
25406
|
const projectRoot = gitRoot ?? cwd;
|
|
25324
|
-
const configPath =
|
|
25325
|
-
if (!
|
|
25407
|
+
const configPath = join25(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
25408
|
+
if (!existsSync31(configPath)) return null;
|
|
25326
25409
|
try {
|
|
25327
|
-
const raw = JSON.parse(
|
|
25410
|
+
const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
|
|
25328
25411
|
return raw.mcpServers ?? raw;
|
|
25329
25412
|
} catch {
|
|
25330
25413
|
return null;
|
|
25331
25414
|
}
|
|
25332
25415
|
}
|
|
25333
25416
|
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";
|
|
25417
|
+
return req.secure || req.res?.locals.externalSecure === true;
|
|
25338
25418
|
}
|
|
25339
25419
|
function authCookieOptions(req) {
|
|
25340
25420
|
return {
|
|
@@ -25361,13 +25441,13 @@ function parseCookie(cookie) {
|
|
|
25361
25441
|
}
|
|
25362
25442
|
async function openBrowser(url) {
|
|
25363
25443
|
try {
|
|
25364
|
-
const { spawn:
|
|
25444
|
+
const { spawn: spawn6 } = await import("child_process");
|
|
25365
25445
|
if (process.platform === "win32") {
|
|
25366
|
-
|
|
25446
|
+
spawn6("cmd", ["/c", "start", '""', url], { detached: true, stdio: "ignore" }).unref();
|
|
25367
25447
|
} else if (process.platform === "darwin") {
|
|
25368
|
-
|
|
25448
|
+
spawn6("open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
25369
25449
|
} else {
|
|
25370
|
-
|
|
25450
|
+
spawn6("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
25371
25451
|
}
|
|
25372
25452
|
} catch {
|
|
25373
25453
|
}
|