jinzd-ai-cli 0.4.241 → 0.4.242
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 -2
- package/dist/{batch-K66BLTLJ.js → batch-D3UHL432.js} +2 -2
- package/dist/{chat-index-7HXBWQFH.js → chat-index-ALUHFJKI.js} +3 -2
- package/dist/{chat-index-WYXYD7YP.js → chat-index-ML5FF5L6.js} +2 -1
- package/dist/{chunk-NAQPP3GZ.js → chunk-6ZWNXHNH.js} +1 -1
- package/dist/chunk-B5TYJO7V.js +123 -0
- package/dist/chunk-D27TMEOV.js +327 -0
- package/dist/{chunk-2QQKS56X.js → chunk-DT77O33T.js} +1 -1
- package/dist/{chunk-GORPFFV4.js → chunk-FEMPCNX3.js} +79 -252
- package/dist/{chunk-3AIR7N5M.js → chunk-FRNWQIG6.js} +5 -5
- package/dist/{chunk-5MYPIQ3Y.js → chunk-IGPFYNZA.js} +4 -2
- package/dist/{chunk-2LLQOMMW.js → chunk-J363BGOH.js} +1 -1
- package/dist/{chunk-JMP3LJC2.js → chunk-KWQKQVPH.js} +4 -2
- package/dist/{chunk-PGHTAKIA.js → chunk-LHINXQZF.js} +1 -1
- package/dist/{chunk-ZPXMT2B6.js → chunk-MC2ZNPBC.js} +4 -163
- package/dist/chunk-MKLPAQT6.js +164 -0
- package/dist/{chunk-UO3XWZFE.js → chunk-NV63EYFL.js} +3 -119
- package/dist/{chunk-CHFFEVVT.js → chunk-OO2PHOXD.js} +3 -3
- package/dist/{chunk-KCEO2XJ4.js → chunk-PJ3ONBWZ.js} +0 -119
- package/dist/{chunk-LXSUPLCK.js → chunk-PSP6UIKL.js} +2 -2
- package/dist/chunk-UWV2NBCO.js +418 -0
- package/dist/{chunk-IV6GCDVR.js → chunk-WMAXYDCB.js} +7 -1
- package/dist/{chunk-SMQHEJSH.js → chunk-Y4CMNXZH.js} +1 -1
- package/dist/chunk-YVTASHS5.js +121 -0
- package/dist/{ci-T2ABDMMQ.js → ci-WSED3Z6V.js} +4 -4
- package/dist/{ci-format-S2PPU27O.js → ci-format-6AYL2X2J.js} +2 -2
- package/dist/{constants-RS7OUU7X.js → constants-FJ3L5GYO.js} +1 -1
- package/dist/{doctor-cli-TGAW6RAP.js → doctor-cli-HCP56MKH.js} +4 -4
- package/dist/electron-server.js +423 -572
- package/dist/{hub-GL5ZU6WS.js → hub-K5MYSVEF.js} +2 -2
- package/dist/index.js +203 -58
- package/dist/{persist-AMH5SQ4W.js → persist-WGIJ6RXY.js} +4 -3
- package/dist/persistent-memory-EYAKL2XA.js +55 -0
- package/dist/persistent-memory-HWFNDQFQ.js +53 -0
- package/dist/{pr-TJFKUZ4B.js → pr-OICMUN6R.js} +5 -5
- package/dist/{run-tests-EWMU34A4.js → run-tests-B3FSE5NV.js} +2 -1
- package/dist/{run-tests-KEI7IXTB.js → run-tests-DSEW5HED.js} +2 -2
- package/dist/{server-UVS2D4M6.js → server-2FS65CQZ.js} +130 -47
- package/dist/{server-E545UIX7.js → server-EDCGPEGC.js} +8 -6
- package/dist/{task-orchestrator-PWXV2EBN.js → task-orchestrator-WCZYV3TE.js} +8 -6
- package/dist/{usage-EDJ6QOWB.js → usage-WVUYNLOD.js} +2 -2
- package/package.json +1 -1
package/dist/electron-server.js
CHANGED
|
@@ -1,9 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
listFromIndex,
|
|
3
|
+
loadChatIndex,
|
|
4
|
+
readSessionMeta,
|
|
5
|
+
removeSessionFromIndex,
|
|
6
|
+
safeDate,
|
|
7
|
+
searchCandidates,
|
|
8
|
+
searchChatMemory,
|
|
9
|
+
updateSessionInIndex
|
|
10
|
+
} from "./chunk-NV63EYFL.js";
|
|
1
11
|
import {
|
|
2
12
|
fileCheckpoints
|
|
3
13
|
} from "./chunk-SKET65WZ.js";
|
|
4
14
|
import {
|
|
5
15
|
indexProject
|
|
6
16
|
} from "./chunk-BNERFXVO.js";
|
|
17
|
+
import {
|
|
18
|
+
addMemoryEntry,
|
|
19
|
+
clearMemoryEntries,
|
|
20
|
+
deleteMemoryEntry,
|
|
21
|
+
expireMemoryEntry,
|
|
22
|
+
exportMemoryEntries,
|
|
23
|
+
findSimilarMemory,
|
|
24
|
+
formatGitContextForPrompt,
|
|
25
|
+
formatMemoryForPrompt,
|
|
26
|
+
getGitContext,
|
|
27
|
+
getGitRoot,
|
|
28
|
+
isMemoryExpired,
|
|
29
|
+
listMemoryEntries,
|
|
30
|
+
loadMemoryEntries,
|
|
31
|
+
memoryStorePath,
|
|
32
|
+
parseWritableScope,
|
|
33
|
+
updateMemoryApproval,
|
|
34
|
+
updateMemoryEntry
|
|
35
|
+
} from "./chunk-UWV2NBCO.js";
|
|
36
|
+
import {
|
|
37
|
+
redactJson,
|
|
38
|
+
scanString
|
|
39
|
+
} from "./chunk-YVTASHS5.js";
|
|
40
|
+
import {
|
|
41
|
+
runTestsTool
|
|
42
|
+
} from "./chunk-MC2ZNPBC.js";
|
|
7
43
|
import {
|
|
8
44
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
9
45
|
APP_NAME,
|
|
@@ -26,8 +62,6 @@ import {
|
|
|
26
62
|
MCP_PROTOCOL_VERSION,
|
|
27
63
|
MCP_TOOL_PREFIX,
|
|
28
64
|
MEMORY_FILE_NAME,
|
|
29
|
-
MEMORY_MAX_CHARS,
|
|
30
|
-
MEMORY_STORE_FILE_NAME,
|
|
31
65
|
PLAN_MODE_READONLY_TOOLS,
|
|
32
66
|
PLAN_MODE_SYSTEM_ADDON,
|
|
33
67
|
PLUGINS_DIR_NAME,
|
|
@@ -36,9 +70,8 @@ import {
|
|
|
36
70
|
SUBAGENT_DEFAULT_MAX_ROUNDS,
|
|
37
71
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
38
72
|
VERSION,
|
|
39
|
-
buildUserIdentityPrompt
|
|
40
|
-
|
|
41
|
-
} from "./chunk-ZPXMT2B6.js";
|
|
73
|
+
buildUserIdentityPrompt
|
|
74
|
+
} from "./chunk-MKLPAQT6.js";
|
|
42
75
|
import {
|
|
43
76
|
hasSemanticIndex,
|
|
44
77
|
semanticSearch
|
|
@@ -47,20 +80,6 @@ import {
|
|
|
47
80
|
loadIndex
|
|
48
81
|
} from "./chunk-RWM2GFRC.js";
|
|
49
82
|
import "./chunk-IEQAE3QG.js";
|
|
50
|
-
import {
|
|
51
|
-
DEFAULT_PATTERNS,
|
|
52
|
-
listFromIndex,
|
|
53
|
-
loadChatIndex,
|
|
54
|
-
readSessionMeta,
|
|
55
|
-
redactJson,
|
|
56
|
-
redactString,
|
|
57
|
-
removeSessionFromIndex,
|
|
58
|
-
safeDate,
|
|
59
|
-
scanString,
|
|
60
|
-
searchCandidates,
|
|
61
|
-
searchChatMemory,
|
|
62
|
-
updateSessionInIndex
|
|
63
|
-
} from "./chunk-UO3XWZFE.js";
|
|
64
83
|
import "./chunk-JV5N65KN.js";
|
|
65
84
|
import {
|
|
66
85
|
atomicWriteFileSync
|
|
@@ -70,8 +89,8 @@ import {
|
|
|
70
89
|
import express from "express";
|
|
71
90
|
import { createServer } from "http";
|
|
72
91
|
import { WebSocketServer } from "ws";
|
|
73
|
-
import { join as
|
|
74
|
-
import { existsSync as
|
|
92
|
+
import { join as join26, dirname as dirname10, resolve as resolve12, relative as relative6, sep as sep4 } from "path";
|
|
93
|
+
import { existsSync as existsSync31, readFileSync as readFileSync24, readdirSync as readdirSync16, statSync as statSync14, realpathSync as realpathSync3 } from "fs";
|
|
75
94
|
import { networkInterfaces } from "os";
|
|
76
95
|
|
|
77
96
|
// src/config/config-manager.ts
|
|
@@ -192,6 +211,12 @@ var ConfigSchema = z.object({
|
|
|
192
211
|
projectDocMaxBytes: z.number().int().positive().default(32 * 1024),
|
|
193
212
|
showLoadedContextSources: z.boolean().default(false)
|
|
194
213
|
}).default({}),
|
|
214
|
+
// 持久记忆注入配置(MEM-005)
|
|
215
|
+
// maxChars: 注入 system prompt 的字符预算,超出丢最老条目并显式标注截断。
|
|
216
|
+
// 注意 CJK ≈ 1 字符/token——中文记忆的 token 开销约等于字符数。
|
|
217
|
+
memory: z.object({
|
|
218
|
+
maxChars: z.number().int().positive().max(2e5).default(1e4)
|
|
219
|
+
}).default({}),
|
|
195
220
|
// Google Custom Search API 的 Search Engine ID (cx 参数)
|
|
196
221
|
// API Key 通过 apiKeys['google-search'] 或 AICLI_API_KEY_GOOGLESEARCH 环境变量配置
|
|
197
222
|
// CX 也可通过 AICLI_GOOGLE_CX 环境变量覆盖
|
|
@@ -4927,7 +4952,7 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
|
|
|
4927
4952
|
const markerStart = `AICLI_MARKER_START:${id}`;
|
|
4928
4953
|
const markerEnd = `AICLI_MARKER_END:${id}`;
|
|
4929
4954
|
const wrappedCmd = IS_WINDOWS2 ? `${WIN_UTF8_PREAMBLE} echo "${markerStart}"; ${command}; echo "${markerEnd}:0"` : `echo "${markerStart}"; ${command}; echo "${markerEnd}:$?"`;
|
|
4930
|
-
return new Promise((
|
|
4955
|
+
return new Promise((resolve13) => {
|
|
4931
4956
|
let stdout = "";
|
|
4932
4957
|
let stderr = "";
|
|
4933
4958
|
let timedOut = false;
|
|
@@ -4943,7 +4968,7 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
|
|
|
4943
4968
|
stdout = stripMarkerOutput(stdout, markerStartPattern, markerEndPattern);
|
|
4944
4969
|
resolved = true;
|
|
4945
4970
|
cleanup();
|
|
4946
|
-
|
|
4971
|
+
resolve13({ stdout, stderr, exitCode, timedOut, aborted });
|
|
4947
4972
|
}
|
|
4948
4973
|
};
|
|
4949
4974
|
const onErr = (chunk) => {
|
|
@@ -4958,7 +4983,7 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
|
|
|
4958
4983
|
resolved = true;
|
|
4959
4984
|
stdout = stripMarkerOutput(stdout, markerStartPattern, markerEndPattern);
|
|
4960
4985
|
cleanup();
|
|
4961
|
-
|
|
4986
|
+
resolve13({ stdout, stderr, exitCode: null, timedOut, aborted });
|
|
4962
4987
|
try {
|
|
4963
4988
|
proc.kill();
|
|
4964
4989
|
} catch {
|
|
@@ -4971,7 +4996,7 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
|
|
|
4971
4996
|
resolved = true;
|
|
4972
4997
|
cleanup();
|
|
4973
4998
|
stdout = stripMarkerOutput(stdout, markerStartPattern, markerEndPattern);
|
|
4974
|
-
|
|
4999
|
+
resolve13({ stdout, stderr, exitCode: null, timedOut, aborted });
|
|
4975
5000
|
try {
|
|
4976
5001
|
proc.kill();
|
|
4977
5002
|
} catch {
|
|
@@ -4989,7 +5014,7 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
|
|
|
4989
5014
|
stderr += `
|
|
4990
5015
|
[pool error] ${err.message}`;
|
|
4991
5016
|
cleanup();
|
|
4992
|
-
|
|
5017
|
+
resolve13({ stdout, stderr, exitCode: null, timedOut, aborted });
|
|
4993
5018
|
pool.delete(sessionKey);
|
|
4994
5019
|
};
|
|
4995
5020
|
proc.on("error", onError);
|
|
@@ -7384,7 +7409,7 @@ var ToolExecutor = class {
|
|
|
7384
7409
|
rl.resume();
|
|
7385
7410
|
process.stdout.write(prompt);
|
|
7386
7411
|
this.confirming = true;
|
|
7387
|
-
return new Promise((
|
|
7412
|
+
return new Promise((resolve13) => {
|
|
7388
7413
|
let completed = false;
|
|
7389
7414
|
const cleanup = (result) => {
|
|
7390
7415
|
if (completed) return;
|
|
@@ -7394,7 +7419,7 @@ var ToolExecutor = class {
|
|
|
7394
7419
|
rl.pause();
|
|
7395
7420
|
rlAny.output = savedOutput;
|
|
7396
7421
|
this.confirming = false;
|
|
7397
|
-
|
|
7422
|
+
resolve13(result);
|
|
7398
7423
|
};
|
|
7399
7424
|
const onLine = (line) => {
|
|
7400
7425
|
const trimmed = line.trim();
|
|
@@ -7568,7 +7593,7 @@ var ToolExecutor = class {
|
|
|
7568
7593
|
rl.resume();
|
|
7569
7594
|
process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
|
|
7570
7595
|
this.confirming = true;
|
|
7571
|
-
return new Promise((
|
|
7596
|
+
return new Promise((resolve13) => {
|
|
7572
7597
|
let completed = false;
|
|
7573
7598
|
const cleanup = (answer) => {
|
|
7574
7599
|
if (completed) return;
|
|
@@ -7578,7 +7603,7 @@ var ToolExecutor = class {
|
|
|
7578
7603
|
rl.pause();
|
|
7579
7604
|
rlAny.output = savedOutput;
|
|
7580
7605
|
this.confirming = false;
|
|
7581
|
-
|
|
7606
|
+
resolve13(answer === "y");
|
|
7582
7607
|
};
|
|
7583
7608
|
const onLine = (line) => {
|
|
7584
7609
|
const trimmed = line.trim();
|
|
@@ -8827,7 +8852,7 @@ var runInteractiveTool = {
|
|
|
8827
8852
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
8828
8853
|
};
|
|
8829
8854
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
8830
|
-
return new Promise((
|
|
8855
|
+
return new Promise((resolve13) => {
|
|
8831
8856
|
const child = spawn3(executable, cmdArgs.map(String), {
|
|
8832
8857
|
cwd: process.cwd(),
|
|
8833
8858
|
env,
|
|
@@ -8860,22 +8885,22 @@ var runInteractiveTool = {
|
|
|
8860
8885
|
setTimeout(writeNextLine, 400);
|
|
8861
8886
|
const timer = setTimeout(() => {
|
|
8862
8887
|
child.kill();
|
|
8863
|
-
|
|
8888
|
+
resolve13(`${prefixWarnings}[Timeout after ${timeout}ms]
|
|
8864
8889
|
${buildOutput(stdout, stderr)}`);
|
|
8865
8890
|
}, timeout);
|
|
8866
8891
|
child.on("close", (code) => {
|
|
8867
8892
|
clearTimeout(timer);
|
|
8868
8893
|
const output = buildOutput(stdout, stderr);
|
|
8869
8894
|
if (code !== 0 && code !== null) {
|
|
8870
|
-
|
|
8895
|
+
resolve13(`${prefixWarnings}Exit code ${code}:
|
|
8871
8896
|
${output}`);
|
|
8872
8897
|
} else {
|
|
8873
|
-
|
|
8898
|
+
resolve13(`${prefixWarnings}${output || "(no output)"}`);
|
|
8874
8899
|
}
|
|
8875
8900
|
});
|
|
8876
8901
|
child.on("error", (err) => {
|
|
8877
8902
|
clearTimeout(timer);
|
|
8878
|
-
|
|
8903
|
+
resolve13(
|
|
8879
8904
|
`${prefixWarnings}Failed to start process "${executable}": ${err.message}
|
|
8880
8905
|
Hint: On Windows, use the full path to the executable, e.g.:
|
|
8881
8906
|
C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
|
|
@@ -9587,280 +9612,10 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
9587
9612
|
};
|
|
9588
9613
|
|
|
9589
9614
|
// src/tools/builtin/save-memory.ts
|
|
9590
|
-
import { join as join12 } from "path";
|
|
9591
|
-
import { homedir as homedir5 } from "os";
|
|
9592
|
-
|
|
9593
|
-
// src/memory/persistent-memory.ts
|
|
9594
|
-
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync12 } from "fs";
|
|
9595
|
-
import { randomUUID, createHash as createHash2 } from "crypto";
|
|
9596
|
-
import { dirname as dirname7, join as join11, resolve as resolve6 } from "path";
|
|
9597
|
-
|
|
9598
|
-
// src/tools/git-context.ts
|
|
9599
|
-
import { execSync as execSync2 } from "child_process";
|
|
9600
|
-
import { existsSync as existsSync16 } from "fs";
|
|
9601
9615
|
import { join as join10 } from "path";
|
|
9602
|
-
|
|
9603
|
-
try {
|
|
9604
|
-
return execSync2(`git ${cmd}`, {
|
|
9605
|
-
cwd,
|
|
9606
|
-
encoding: "utf-8",
|
|
9607
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
9608
|
-
timeout: 5e3
|
|
9609
|
-
}).trim();
|
|
9610
|
-
} catch {
|
|
9611
|
-
return null;
|
|
9612
|
-
}
|
|
9613
|
-
}
|
|
9614
|
-
function getGitRoot(cwd = process.cwd()) {
|
|
9615
|
-
return runGit("rev-parse --show-toplevel", cwd);
|
|
9616
|
-
}
|
|
9617
|
-
function getGitContext(cwd = process.cwd()) {
|
|
9618
|
-
if (!existsSync16(join10(cwd, ".git"))) {
|
|
9619
|
-
const result = runGit("rev-parse --git-dir", cwd);
|
|
9620
|
-
if (!result) return null;
|
|
9621
|
-
}
|
|
9622
|
-
const branch = runGit("rev-parse --abbrev-ref HEAD", cwd);
|
|
9623
|
-
if (!branch) return null;
|
|
9624
|
-
const statusOutput = runGit("status --porcelain", cwd) ?? "";
|
|
9625
|
-
const statusLines = statusOutput ? statusOutput.split("\n").filter(Boolean) : [];
|
|
9626
|
-
const stagedFiles = [];
|
|
9627
|
-
const changedFiles = [];
|
|
9628
|
-
for (const line of statusLines) {
|
|
9629
|
-
const xy = line.slice(0, 2);
|
|
9630
|
-
const file = line.slice(3).trim();
|
|
9631
|
-
const indexStatus = xy[0];
|
|
9632
|
-
const workStatus = xy[1];
|
|
9633
|
-
if (indexStatus && indexStatus !== " " && indexStatus !== "?") {
|
|
9634
|
-
stagedFiles.push(`${indexStatus} ${file}`);
|
|
9635
|
-
}
|
|
9636
|
-
if (workStatus && workStatus !== " ") {
|
|
9637
|
-
changedFiles.push(`${workStatus} ${file}`);
|
|
9638
|
-
}
|
|
9639
|
-
}
|
|
9640
|
-
const logOutput = runGit("log --oneline -3", cwd) ?? "";
|
|
9641
|
-
const recentCommits = logOutput ? logOutput.split("\n").filter(Boolean) : [];
|
|
9642
|
-
const unpushedOutput = runGit("log @{u}..HEAD --oneline", cwd);
|
|
9643
|
-
const hasUnpushed = unpushedOutput !== null && unpushedOutput.trim().length > 0;
|
|
9644
|
-
return {
|
|
9645
|
-
branch,
|
|
9646
|
-
changedFiles,
|
|
9647
|
-
stagedFiles,
|
|
9648
|
-
recentCommits,
|
|
9649
|
-
hasUnpushed
|
|
9650
|
-
};
|
|
9651
|
-
}
|
|
9652
|
-
function formatGitContextForPrompt(ctx) {
|
|
9653
|
-
const lines = ["# Git Repository Status", ""];
|
|
9654
|
-
lines.push(`- **Branch**: \`${ctx.branch}\``);
|
|
9655
|
-
if (ctx.stagedFiles.length > 0) {
|
|
9656
|
-
lines.push(`- **Staged** (${ctx.stagedFiles.length} files):`);
|
|
9657
|
-
for (const f of ctx.stagedFiles.slice(0, 10)) {
|
|
9658
|
-
lines.push(` - ${f}`);
|
|
9659
|
-
}
|
|
9660
|
-
if (ctx.stagedFiles.length > 10) {
|
|
9661
|
-
lines.push(` - ... and ${ctx.stagedFiles.length - 10} more`);
|
|
9662
|
-
}
|
|
9663
|
-
}
|
|
9664
|
-
if (ctx.changedFiles.length > 0) {
|
|
9665
|
-
lines.push(`- **Modified** (${ctx.changedFiles.length} files):`);
|
|
9666
|
-
for (const f of ctx.changedFiles.slice(0, 10)) {
|
|
9667
|
-
lines.push(` - ${f}`);
|
|
9668
|
-
}
|
|
9669
|
-
if (ctx.changedFiles.length > 10) {
|
|
9670
|
-
lines.push(` - ... and ${ctx.changedFiles.length - 10} more`);
|
|
9671
|
-
}
|
|
9672
|
-
}
|
|
9673
|
-
if (ctx.stagedFiles.length === 0 && ctx.changedFiles.length === 0) {
|
|
9674
|
-
lines.push("- **Working tree**: clean");
|
|
9675
|
-
}
|
|
9676
|
-
if (ctx.recentCommits.length > 0) {
|
|
9677
|
-
lines.push("- **Recent commits**:");
|
|
9678
|
-
for (const c of ctx.recentCommits) {
|
|
9679
|
-
lines.push(` - ${c}`);
|
|
9680
|
-
}
|
|
9681
|
-
}
|
|
9682
|
-
if (ctx.hasUnpushed) {
|
|
9683
|
-
lines.push("- \u26A0\uFE0F Has unpushed commits");
|
|
9684
|
-
}
|
|
9685
|
-
return lines.join("\n");
|
|
9686
|
-
}
|
|
9687
|
-
|
|
9688
|
-
// src/memory/persistent-memory.ts
|
|
9689
|
-
function memoryStorePath(configDir) {
|
|
9690
|
-
return join11(configDir, MEMORY_STORE_FILE_NAME);
|
|
9691
|
-
}
|
|
9692
|
-
function memoryMarkdownPath(configDir) {
|
|
9693
|
-
return join11(configDir, MEMORY_FILE_NAME);
|
|
9694
|
-
}
|
|
9695
|
-
function getProjectKey(cwd = process.cwd()) {
|
|
9696
|
-
return resolve6(getGitRoot(cwd) ?? cwd);
|
|
9697
|
-
}
|
|
9698
|
-
function nowIso() {
|
|
9699
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
9700
|
-
}
|
|
9701
|
-
function isValidScope(value) {
|
|
9702
|
-
return value === "personal" || value === "project" || value === "session" || value === "team";
|
|
9703
|
-
}
|
|
9704
|
-
function isValidSensitivity(value) {
|
|
9705
|
-
return value === "low" || value === "medium" || value === "high";
|
|
9706
|
-
}
|
|
9707
|
-
function normalizeEntry(value) {
|
|
9708
|
-
if (!value || typeof value !== "object") return null;
|
|
9709
|
-
const raw = value;
|
|
9710
|
-
const content = typeof raw.content === "string" ? raw.content.trim() : "";
|
|
9711
|
-
if (!content) return null;
|
|
9712
|
-
const createdAt = typeof raw.createdAt === "string" ? raw.createdAt : nowIso();
|
|
9713
|
-
return {
|
|
9714
|
-
id: typeof raw.id === "string" && raw.id ? raw.id : randomUUID(),
|
|
9715
|
-
content,
|
|
9716
|
-
scope: isValidScope(raw.scope) ? raw.scope : "personal",
|
|
9717
|
-
sourceSession: typeof raw.sourceSession === "string" && raw.sourceSession ? raw.sourceSession : void 0,
|
|
9718
|
-
sourceProject: typeof raw.sourceProject === "string" && raw.sourceProject ? raw.sourceProject : void 0,
|
|
9719
|
-
createdAt,
|
|
9720
|
-
updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : createdAt,
|
|
9721
|
-
expiresAt: typeof raw.expiresAt === "string" && raw.expiresAt ? raw.expiresAt : void 0,
|
|
9722
|
-
sensitivity: isValidSensitivity(raw.sensitivity) ? raw.sensitivity : "low",
|
|
9723
|
-
approved: raw.approved === true,
|
|
9724
|
-
redactedKinds: Array.isArray(raw.redactedKinds) ? raw.redactedKinds.filter((x) => typeof x === "string") : void 0
|
|
9725
|
-
};
|
|
9726
|
-
}
|
|
9727
|
-
function legacyEntriesFromMarkdown(configDir) {
|
|
9728
|
-
const file = memoryMarkdownPath(configDir);
|
|
9729
|
-
if (!existsSync17(file)) return [];
|
|
9730
|
-
const content = readFileSync12(file, "utf-8").trim();
|
|
9731
|
-
if (!content) return [];
|
|
9732
|
-
const parts = content.split(/\n(?=##\s+\d{4}-\d{2}-\d{2})/g);
|
|
9733
|
-
const chunks = parts.length > 1 ? parts : [content];
|
|
9734
|
-
return chunks.map((chunk, index) => {
|
|
9735
|
-
const match = chunk.match(/^##\s+([^\n]+)\n([\s\S]*)$/);
|
|
9736
|
-
const body = (match ? match[2] : chunk).trim();
|
|
9737
|
-
const parsed = match ? Date.parse(match[1].replace(" ", "T")) : NaN;
|
|
9738
|
-
const createdAt = Number.isFinite(parsed) ? new Date(parsed).toISOString() : nowIso();
|
|
9739
|
-
const id = "legacy-" + createHash2("sha1").update(`${index}
|
|
9740
|
-
${chunk}`).digest("hex").slice(0, 12);
|
|
9741
|
-
return normalizeEntry({ id, content: body, scope: "personal", createdAt, updatedAt: createdAt, sensitivity: "low", approved: true });
|
|
9742
|
-
}).filter((entry) => Boolean(entry));
|
|
9743
|
-
}
|
|
9744
|
-
function loadMemoryEntries(configDir) {
|
|
9745
|
-
const file = memoryStorePath(configDir);
|
|
9746
|
-
if (!existsSync17(file)) return legacyEntriesFromMarkdown(configDir);
|
|
9747
|
-
const text = readFileSync12(file, "utf-8").trim();
|
|
9748
|
-
if (!text) return [];
|
|
9749
|
-
const entries = [];
|
|
9750
|
-
for (const line of text.split("\n")) {
|
|
9751
|
-
const trimmed = line.trim();
|
|
9752
|
-
if (!trimmed) continue;
|
|
9753
|
-
try {
|
|
9754
|
-
const entry = normalizeEntry(JSON.parse(trimmed));
|
|
9755
|
-
if (entry) entries.push(entry);
|
|
9756
|
-
} catch {
|
|
9757
|
-
}
|
|
9758
|
-
}
|
|
9759
|
-
return entries;
|
|
9760
|
-
}
|
|
9761
|
-
function saveMemoryEntries(configDir, entries) {
|
|
9762
|
-
mkdirSync8(configDir, { recursive: true });
|
|
9763
|
-
const jsonl = entries.map((entry) => JSON.stringify(entry)).join("\n");
|
|
9764
|
-
atomicWriteFileSync(memoryStorePath(configDir), jsonl ? jsonl + "\n" : "");
|
|
9765
|
-
}
|
|
9766
|
-
function isMemoryExpired(entry, at = /* @__PURE__ */ new Date()) {
|
|
9767
|
-
return Boolean(entry.expiresAt && Date.parse(entry.expiresAt) <= at.getTime());
|
|
9768
|
-
}
|
|
9769
|
-
function isMemoryVisibleInProject(entry, cwd = process.cwd()) {
|
|
9770
|
-
if (entry.scope !== "project") return true;
|
|
9771
|
-
return entry.sourceProject === getProjectKey(cwd);
|
|
9772
|
-
}
|
|
9773
|
-
function listMemoryEntries(configDir, options = {}) {
|
|
9774
|
-
const cwd = options.cwd ?? process.cwd();
|
|
9775
|
-
return loadMemoryEntries(configDir).filter((entry) => options.includeRejected || entry.approved).filter((entry) => options.includeExpired || !isMemoryExpired(entry)).filter((entry) => isMemoryVisibleInProject(entry, cwd));
|
|
9776
|
-
}
|
|
9777
|
-
function addMemoryEntry(configDir, content, options = {}) {
|
|
9778
|
-
const original = content.trim();
|
|
9779
|
-
if (!original) throw new Error("memory content is required");
|
|
9780
|
-
const redact = options.redact ?? true;
|
|
9781
|
-
const redacted = redact ? redactString(original, { enabled: true, patterns: options.patterns ?? DEFAULT_PATTERNS, customRegexes: options.customPatterns }) : { redacted: original, hits: [] };
|
|
9782
|
-
const sensitivity = options.sensitivity ?? (redacted.hits.length > 0 ? "high" : "low");
|
|
9783
|
-
const approved = options.approved ?? (sensitivity === "low" && redacted.hits.length === 0);
|
|
9784
|
-
const scope = options.scope ?? "personal";
|
|
9785
|
-
const timestamp = nowIso();
|
|
9786
|
-
const entry = {
|
|
9787
|
-
id: randomUUID(),
|
|
9788
|
-
content: redacted.redacted.trim(),
|
|
9789
|
-
scope,
|
|
9790
|
-
sourceSession: options.sourceSession,
|
|
9791
|
-
sourceProject: scope === "project" ? getProjectKey(options.cwd) : void 0,
|
|
9792
|
-
createdAt: timestamp,
|
|
9793
|
-
updatedAt: timestamp,
|
|
9794
|
-
expiresAt: options.expiresAt,
|
|
9795
|
-
sensitivity,
|
|
9796
|
-
approved,
|
|
9797
|
-
redactedKinds: [...new Set(redacted.hits.map((hit) => hit.kind))]
|
|
9798
|
-
};
|
|
9799
|
-
const entries = loadMemoryEntries(configDir);
|
|
9800
|
-
entries.push(entry);
|
|
9801
|
-
saveMemoryEntries(configDir, entries);
|
|
9802
|
-
return entry;
|
|
9803
|
-
}
|
|
9804
|
-
function updateMemoryApproval(configDir, idPrefix, approved) {
|
|
9805
|
-
const entries = loadMemoryEntries(configDir);
|
|
9806
|
-
const entry = findUnique(entries, idPrefix);
|
|
9807
|
-
entry.approved = approved;
|
|
9808
|
-
entry.updatedAt = nowIso();
|
|
9809
|
-
saveMemoryEntries(configDir, entries);
|
|
9810
|
-
return entry;
|
|
9811
|
-
}
|
|
9812
|
-
function deleteMemoryEntry(configDir, idPrefix) {
|
|
9813
|
-
const entries = loadMemoryEntries(configDir);
|
|
9814
|
-
const entry = findUnique(entries, idPrefix);
|
|
9815
|
-
saveMemoryEntries(configDir, entries.filter((candidate) => candidate.id !== entry.id));
|
|
9816
|
-
return entry;
|
|
9817
|
-
}
|
|
9818
|
-
function expireMemoryEntry(configDir, idPrefix, expiresAt = nowIso()) {
|
|
9819
|
-
const entries = loadMemoryEntries(configDir);
|
|
9820
|
-
const entry = findUnique(entries, idPrefix);
|
|
9821
|
-
entry.expiresAt = expiresAt;
|
|
9822
|
-
entry.updatedAt = nowIso();
|
|
9823
|
-
saveMemoryEntries(configDir, entries);
|
|
9824
|
-
return entry;
|
|
9825
|
-
}
|
|
9826
|
-
function exportMemoryEntries(configDir, cwd = process.cwd()) {
|
|
9827
|
-
return JSON.stringify(listMemoryEntries(configDir, { cwd, includeExpired: true, includeRejected: true }), null, 2);
|
|
9828
|
-
}
|
|
9829
|
-
function findUnique(entries, idPrefix) {
|
|
9830
|
-
const matches = entries.filter((entry) => entry.id.startsWith(idPrefix));
|
|
9831
|
-
if (matches.length === 0) throw new Error(`memory id not found: ${idPrefix}`);
|
|
9832
|
-
if (matches.length > 1) throw new Error(`memory id is ambiguous: ${idPrefix}`);
|
|
9833
|
-
return matches[0];
|
|
9834
|
-
}
|
|
9835
|
-
function formatMemoryForPrompt(configDir, cwd = process.cwd()) {
|
|
9836
|
-
const entries = listMemoryEntries(configDir, { cwd }).filter((entry) => entry.approved && !isMemoryExpired(entry));
|
|
9837
|
-
if (entries.length === 0) return null;
|
|
9838
|
-
let content = entries.map((entry) => {
|
|
9839
|
-
const source = [
|
|
9840
|
-
`id ${entry.id.slice(0, 8)}`,
|
|
9841
|
-
`scope ${entry.scope}`,
|
|
9842
|
-
entry.sourceSession ? `source session ${entry.sourceSession.slice(0, 8)}` : "source manual/tool",
|
|
9843
|
-
entry.sourceProject ? `project ${entry.sourceProject}` : "",
|
|
9844
|
-
`sensitivity ${entry.sensitivity}`
|
|
9845
|
-
].filter(Boolean).join(" \xB7 ");
|
|
9846
|
-
return `## ${entry.createdAt.replace("T", " ").slice(0, 19)}
|
|
9847
|
-
Source: ${source}
|
|
9848
|
-
${entry.content}`;
|
|
9849
|
-
}).join("\n\n");
|
|
9850
|
-
if (content.length > MEMORY_MAX_CHARS) {
|
|
9851
|
-
content = content.slice(-MEMORY_MAX_CHARS);
|
|
9852
|
-
const firstEntry = content.indexOf("\n## ");
|
|
9853
|
-
if (firstEntry !== -1) content = content.slice(firstEntry + 1);
|
|
9854
|
-
}
|
|
9855
|
-
return { content, entryCount: entries.length };
|
|
9856
|
-
}
|
|
9857
|
-
|
|
9858
|
-
// src/tools/builtin/save-memory.ts
|
|
9616
|
+
import { homedir as homedir5 } from "os";
|
|
9859
9617
|
function getConfigDir() {
|
|
9860
|
-
return
|
|
9861
|
-
}
|
|
9862
|
-
function parseScope(value) {
|
|
9863
|
-
return value === "personal" || value === "project" || value === "session" || value === "team" ? value : void 0;
|
|
9618
|
+
return join10(homedir5(), CONFIG_DIR_NAME);
|
|
9864
9619
|
}
|
|
9865
9620
|
function parseSensitivity(value) {
|
|
9866
9621
|
return value === "low" || value === "medium" || value === "high" ? value : void 0;
|
|
@@ -9868,7 +9623,7 @@ function parseSensitivity(value) {
|
|
|
9868
9623
|
var saveMemoryTool = {
|
|
9869
9624
|
definition: {
|
|
9870
9625
|
name: "save_memory",
|
|
9871
|
-
description: "Save important information to governed persistent memory.
|
|
9626
|
+
description: "Save important information to governed persistent memory. Entries saved by this tool are stored as PENDING and only take effect after the user runs /memory approve <id> \u2014 tell the user the returned id so they can approve it. Use concise entries for user preferences, project decisions, recurring patterns, and key findings.",
|
|
9872
9627
|
parameters: {
|
|
9873
9628
|
content: {
|
|
9874
9629
|
type: "string",
|
|
@@ -9877,7 +9632,7 @@ var saveMemoryTool = {
|
|
|
9877
9632
|
},
|
|
9878
9633
|
scope: {
|
|
9879
9634
|
type: "string",
|
|
9880
|
-
description: "Memory scope: personal
|
|
9635
|
+
description: "Memory scope: personal (default, injected everywhere) or project (only injected inside the current project).",
|
|
9881
9636
|
required: false
|
|
9882
9637
|
},
|
|
9883
9638
|
sensitivity: {
|
|
@@ -9889,6 +9644,11 @@ var saveMemoryTool = {
|
|
|
9889
9644
|
type: "string",
|
|
9890
9645
|
description: "Optional ISO timestamp after which this memory should no longer be injected.",
|
|
9891
9646
|
required: false
|
|
9647
|
+
},
|
|
9648
|
+
updateId: {
|
|
9649
|
+
type: "string",
|
|
9650
|
+
description: "Optional id (or unique id prefix) of an existing memory entry to update in place instead of creating a new one. Use this when a previously saved memory is outdated \u2014 do not save a contradicting duplicate. Updated entries go back to pending and need user re-approval.",
|
|
9651
|
+
required: false
|
|
9892
9652
|
}
|
|
9893
9653
|
},
|
|
9894
9654
|
dangerous: false
|
|
@@ -9896,16 +9656,29 @@ var saveMemoryTool = {
|
|
|
9896
9656
|
async execute(args) {
|
|
9897
9657
|
const content = String(args["content"] ?? "").trim();
|
|
9898
9658
|
if (!content) throw new ToolError("save_memory", "content is required");
|
|
9659
|
+
const updateId = typeof args["updateId"] === "string" ? args["updateId"].trim() : "";
|
|
9660
|
+
if (updateId) {
|
|
9661
|
+
try {
|
|
9662
|
+
const updated = updateMemoryEntry(getConfigDir(), updateId, content, { source: "tool", redact: true });
|
|
9663
|
+
const redactedNote = updated.redactedKinds?.length ? ` Redacted: ${updated.redactedKinds.join(", ")}.` : "";
|
|
9664
|
+
return `Memory ${updated.id.slice(0, 8)} updated (pending approval \u2014 the user must run \`/memory approve ${updated.id.slice(0, 8)}\` before the new content is injected; mention this to the user). scope=${updated.scope} sensitivity=${updated.sensitivity}.${redactedNote}`;
|
|
9665
|
+
} catch (err) {
|
|
9666
|
+
throw new ToolError("save_memory", err instanceof Error ? err.message : String(err));
|
|
9667
|
+
}
|
|
9668
|
+
}
|
|
9669
|
+
const similar = findSimilarMemory(loadMemoryEntries(getConfigDir()), content);
|
|
9899
9670
|
const entry = addMemoryEntry(getConfigDir(), content, {
|
|
9900
|
-
scope:
|
|
9671
|
+
scope: parseWritableScope(args["scope"]) ?? "personal",
|
|
9672
|
+
source: "tool",
|
|
9901
9673
|
sensitivity: parseSensitivity(args["sensitivity"]),
|
|
9902
9674
|
expiresAt: typeof args["expiresAt"] === "string" ? args["expiresAt"] : void 0,
|
|
9903
9675
|
cwd: process.cwd(),
|
|
9904
9676
|
redact: true
|
|
9905
9677
|
});
|
|
9906
|
-
const status = entry.approved ? "approved and active" :
|
|
9678
|
+
const status = entry.approved ? "approved and active" : `pending approval \u2014 the user must run \`/memory approve ${entry.id.slice(0, 8)}\` before it is injected; mention this to the user`;
|
|
9907
9679
|
const redacted = entry.redactedKinds?.length ? ` Redacted: ${entry.redactedKinds.join(", ")}.` : "";
|
|
9908
|
-
|
|
9680
|
+
const similarNote = similar ? ` Warning: a similar entry already exists (id=${similar.id.slice(0, 8)}) \u2014 next time pass updateId to revise it instead of duplicating.` : "";
|
|
9681
|
+
return `Memory saved (${status}). id=${entry.id.slice(0, 8)} scope=${entry.scope} sensitivity=${entry.sensitivity}.${redacted}${similarNote}`;
|
|
9909
9682
|
}
|
|
9910
9683
|
};
|
|
9911
9684
|
|
|
@@ -9949,7 +9722,7 @@ function promptUser(rl, question) {
|
|
|
9949
9722
|
console.log();
|
|
9950
9723
|
console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
|
|
9951
9724
|
process.stdout.write(chalk4.cyan("> "));
|
|
9952
|
-
return new Promise((
|
|
9725
|
+
return new Promise((resolve13) => {
|
|
9953
9726
|
let completed = false;
|
|
9954
9727
|
const cleanup = (answer) => {
|
|
9955
9728
|
if (completed) return;
|
|
@@ -9959,7 +9732,7 @@ function promptUser(rl, question) {
|
|
|
9959
9732
|
rl.pause();
|
|
9960
9733
|
rlAny.output = savedOutput;
|
|
9961
9734
|
askUserContext.prompting = false;
|
|
9962
|
-
|
|
9735
|
+
resolve13(answer);
|
|
9963
9736
|
};
|
|
9964
9737
|
const onLine = (line) => {
|
|
9965
9738
|
cleanup(line);
|
|
@@ -10191,14 +9964,14 @@ function formatResults2(query, data, _requested) {
|
|
|
10191
9964
|
}
|
|
10192
9965
|
|
|
10193
9966
|
// src/agents/agent-config.ts
|
|
10194
|
-
import { existsSync as
|
|
10195
|
-
import { join as
|
|
9967
|
+
import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync13 } from "fs";
|
|
9968
|
+
import { join as join12 } from "path";
|
|
10196
9969
|
import { homedir as homedir6 } from "os";
|
|
10197
9970
|
|
|
10198
9971
|
// src/plugins/plugin-manager.ts
|
|
10199
|
-
import { existsSync as
|
|
10200
|
-
import { basename as basename6, dirname as
|
|
10201
|
-
import { createHash as
|
|
9972
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync12, realpathSync as realpathSync2, cpSync, rmSync, statSync as statSync7 } from "fs";
|
|
9973
|
+
import { basename as basename6, dirname as dirname7, join as join11, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
|
|
9974
|
+
import { createHash as createHash2 } from "crypto";
|
|
10202
9975
|
import { z as z2 } from "zod";
|
|
10203
9976
|
var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
|
|
10204
9977
|
var STATE_FILE = "plugin-state.json";
|
|
@@ -10257,19 +10030,19 @@ var PluginManifestSchema = z2.object({
|
|
|
10257
10030
|
permissionHints: z2.array(z2.string()).default([])
|
|
10258
10031
|
}).strict();
|
|
10259
10032
|
function pluginRoot(configDir) {
|
|
10260
|
-
return
|
|
10033
|
+
return join11(configDir, PLUGINS_DIR_NAME);
|
|
10261
10034
|
}
|
|
10262
10035
|
function pluginManifestPath(pluginDir) {
|
|
10263
|
-
return
|
|
10036
|
+
return join11(pluginDir, MANIFEST_RELATIVE);
|
|
10264
10037
|
}
|
|
10265
10038
|
function statePath(configDir) {
|
|
10266
|
-
return
|
|
10039
|
+
return join11(pluginRoot(configDir), STATE_FILE);
|
|
10267
10040
|
}
|
|
10268
10041
|
function loadState(configDir) {
|
|
10269
10042
|
const file = statePath(configDir);
|
|
10270
|
-
if (!
|
|
10043
|
+
if (!existsSync16(file)) return { version: 2, plugins: [] };
|
|
10271
10044
|
try {
|
|
10272
|
-
const parsed = JSON.parse(
|
|
10045
|
+
const parsed = JSON.parse(readFileSync12(file, "utf-8"));
|
|
10273
10046
|
const rawPlugins = Array.isArray(parsed.plugins) ? parsed.plugins : [];
|
|
10274
10047
|
return { version: 2, plugins: rawPlugins.map((p) => migrateEntry(p)) };
|
|
10275
10048
|
} catch {
|
|
@@ -10292,7 +10065,7 @@ function migrateEntry(entry) {
|
|
|
10292
10065
|
};
|
|
10293
10066
|
}
|
|
10294
10067
|
function saveState(configDir, state2) {
|
|
10295
|
-
|
|
10068
|
+
mkdirSync8(pluginRoot(configDir), { recursive: true });
|
|
10296
10069
|
atomicWriteFileSync(statePath(configDir), JSON.stringify(state2, null, 2));
|
|
10297
10070
|
}
|
|
10298
10071
|
function normalizeForHash(buf) {
|
|
@@ -10300,7 +10073,7 @@ function normalizeForHash(buf) {
|
|
|
10300
10073
|
return Buffer.from(str, "utf-8");
|
|
10301
10074
|
}
|
|
10302
10075
|
function extractCommandPaths(command, pluginDir) {
|
|
10303
|
-
const resolvedDir =
|
|
10076
|
+
const resolvedDir = resolve6(pluginDir);
|
|
10304
10077
|
const refs = [];
|
|
10305
10078
|
for (const m of command.matchAll(/\$PLUGIN_DIR[\\/]([^"'\s]+)/g)) {
|
|
10306
10079
|
refs.push(m[1]);
|
|
@@ -10317,9 +10090,9 @@ function extractCommandPaths(command, pluginDir) {
|
|
|
10317
10090
|
if (seen.has(ref)) continue;
|
|
10318
10091
|
seen.add(ref);
|
|
10319
10092
|
try {
|
|
10320
|
-
const abs =
|
|
10093
|
+
const abs = resolve6(resolvedDir, ref);
|
|
10321
10094
|
if (!abs.startsWith(resolvedDir + sep3)) continue;
|
|
10322
|
-
if (
|
|
10095
|
+
if (existsSync16(abs)) {
|
|
10323
10096
|
const real = realpathSync2(abs);
|
|
10324
10097
|
if (!real.startsWith(resolvedDir + sep3)) continue;
|
|
10325
10098
|
valid.push(relative3(resolvedDir, real));
|
|
@@ -10330,7 +10103,7 @@ function extractCommandPaths(command, pluginDir) {
|
|
|
10330
10103
|
return valid;
|
|
10331
10104
|
}
|
|
10332
10105
|
function hashPluginContent(pluginDir, manifest) {
|
|
10333
|
-
const resolvedDir =
|
|
10106
|
+
const resolvedDir = resolve6(pluginDir);
|
|
10334
10107
|
const assetRels = /* @__PURE__ */ new Set(
|
|
10335
10108
|
[...manifest.skills, ...manifest.commands, ...manifest.agents]
|
|
10336
10109
|
);
|
|
@@ -10350,11 +10123,11 @@ function hashPluginContent(pluginDir, manifest) {
|
|
|
10350
10123
|
}
|
|
10351
10124
|
}
|
|
10352
10125
|
const sorted = [...assetRels].sort();
|
|
10353
|
-
const hasher =
|
|
10354
|
-
const manifestPath =
|
|
10355
|
-
hasher.update(normalizeForHash(
|
|
10126
|
+
const hasher = createHash2("sha256");
|
|
10127
|
+
const manifestPath = join11(resolvedDir, MANIFEST_RELATIVE);
|
|
10128
|
+
hasher.update(normalizeForHash(readFileSync12(manifestPath)));
|
|
10356
10129
|
for (const rel of sorted) {
|
|
10357
|
-
const abs =
|
|
10130
|
+
const abs = resolve6(resolvedDir, rel);
|
|
10358
10131
|
if (!abs.startsWith(resolvedDir + sep3)) {
|
|
10359
10132
|
throw new Error(`plugin asset escapes plugin dir: ${rel}`);
|
|
10360
10133
|
}
|
|
@@ -10372,7 +10145,7 @@ function hashPluginContent(pluginDir, manifest) {
|
|
|
10372
10145
|
if (!realPath.startsWith(resolvedDir + sep3)) {
|
|
10373
10146
|
throw new Error(`plugin asset symlink escapes plugin dir: ${rel} \u2192 ${realPath}`);
|
|
10374
10147
|
}
|
|
10375
|
-
hasher.update(normalizeForHash(
|
|
10148
|
+
hasher.update(normalizeForHash(readFileSync12(abs)));
|
|
10376
10149
|
}
|
|
10377
10150
|
return hasher.digest("hex");
|
|
10378
10151
|
}
|
|
@@ -10380,22 +10153,22 @@ function effectiveHash(entry) {
|
|
|
10380
10153
|
return entry.contentHash ?? entry.hash ?? "";
|
|
10381
10154
|
}
|
|
10382
10155
|
function readPluginManifest(manifestPath) {
|
|
10383
|
-
return PluginManifestSchema.parse(JSON.parse(
|
|
10156
|
+
return PluginManifestSchema.parse(JSON.parse(readFileSync12(manifestPath, "utf-8")));
|
|
10384
10157
|
}
|
|
10385
10158
|
function findPluginSourceDir(inputPath) {
|
|
10386
|
-
const abs =
|
|
10387
|
-
const direct = statSync7(abs).isDirectory() ? abs :
|
|
10388
|
-
if (
|
|
10389
|
-
const nested =
|
|
10390
|
-
if (
|
|
10159
|
+
const abs = resolve6(inputPath);
|
|
10160
|
+
const direct = statSync7(abs).isDirectory() ? abs : dirname7(abs);
|
|
10161
|
+
if (existsSync16(pluginManifestPath(direct))) return direct;
|
|
10162
|
+
const nested = join11(direct, ".aicli-plugin");
|
|
10163
|
+
if (existsSync16(join11(nested, "plugin.json"))) return direct;
|
|
10391
10164
|
throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
|
|
10392
10165
|
}
|
|
10393
10166
|
function installPlugin(configDir, inputPath) {
|
|
10394
10167
|
const sourceDir = findPluginSourceDir(inputPath);
|
|
10395
10168
|
const manifest = readPluginManifest(pluginManifestPath(sourceDir));
|
|
10396
|
-
const targetDir =
|
|
10397
|
-
|
|
10398
|
-
if (
|
|
10169
|
+
const targetDir = join11(pluginRoot(configDir), manifest.name);
|
|
10170
|
+
mkdirSync8(pluginRoot(configDir), { recursive: true });
|
|
10171
|
+
if (existsSync16(targetDir)) rmSync(targetDir, { recursive: true, force: true });
|
|
10399
10172
|
cpSync(sourceDir, targetDir, { recursive: true });
|
|
10400
10173
|
const manifestPath = pluginManifestPath(targetDir);
|
|
10401
10174
|
const contentHash = hashPluginContent(targetDir, manifest);
|
|
@@ -10453,18 +10226,18 @@ function getInstalledPlugin(configDir, name) {
|
|
|
10453
10226
|
}
|
|
10454
10227
|
function listInstalledPlugins(configDir) {
|
|
10455
10228
|
const root = pluginRoot(configDir);
|
|
10456
|
-
if (!
|
|
10229
|
+
if (!existsSync16(root)) return [];
|
|
10457
10230
|
const state2 = loadState(configDir);
|
|
10458
10231
|
const out = [];
|
|
10459
10232
|
for (const entry of readdirSync7(root)) {
|
|
10460
|
-
const dir =
|
|
10233
|
+
const dir = join11(root, entry);
|
|
10461
10234
|
try {
|
|
10462
10235
|
if (!statSync7(dir).isDirectory()) continue;
|
|
10463
10236
|
} catch {
|
|
10464
10237
|
continue;
|
|
10465
10238
|
}
|
|
10466
10239
|
const manifestPath = pluginManifestPath(dir);
|
|
10467
|
-
if (!
|
|
10240
|
+
if (!existsSync16(manifestPath)) continue;
|
|
10468
10241
|
try {
|
|
10469
10242
|
const manifest = readPluginManifest(manifestPath);
|
|
10470
10243
|
const contentHash = hashPluginContent(dir, manifest);
|
|
@@ -10502,9 +10275,9 @@ function activePlugins(configDir) {
|
|
|
10502
10275
|
function uniqueDirs(plugin, rels) {
|
|
10503
10276
|
const dirs = /* @__PURE__ */ new Set();
|
|
10504
10277
|
for (const rel of rels) {
|
|
10505
|
-
const abs =
|
|
10506
|
-
if (!abs.startsWith(
|
|
10507
|
-
if (
|
|
10278
|
+
const abs = resolve6(plugin.dir, rel);
|
|
10279
|
+
if (!abs.startsWith(resolve6(plugin.dir))) continue;
|
|
10280
|
+
if (existsSync16(abs)) dirs.add(statSync7(abs).isDirectory() ? abs : dirname7(abs));
|
|
10508
10281
|
}
|
|
10509
10282
|
return [...dirs];
|
|
10510
10283
|
}
|
|
@@ -10667,13 +10440,13 @@ function parseAgentConfig(raw, source, path3) {
|
|
|
10667
10440
|
return cfg;
|
|
10668
10441
|
}
|
|
10669
10442
|
function loadAgentDir(dir, source) {
|
|
10670
|
-
if (!
|
|
10443
|
+
if (!existsSync17(dir)) return [];
|
|
10671
10444
|
const out = [];
|
|
10672
10445
|
for (const file of readdirSync8(dir)) {
|
|
10673
10446
|
if (!file.endsWith(".json")) continue;
|
|
10674
|
-
const path3 =
|
|
10447
|
+
const path3 = join12(dir, file);
|
|
10675
10448
|
try {
|
|
10676
|
-
const parsed = JSON.parse(
|
|
10449
|
+
const parsed = JSON.parse(readFileSync13(path3, "utf-8"));
|
|
10677
10450
|
const cfg = parseAgentConfig(parsed, source, path3);
|
|
10678
10451
|
if (cfg) out.push(cfg);
|
|
10679
10452
|
} catch {
|
|
@@ -10683,8 +10456,8 @@ function loadAgentDir(dir, source) {
|
|
|
10683
10456
|
}
|
|
10684
10457
|
function getAgentDirs(configDir, cwd = process.cwd()) {
|
|
10685
10458
|
return {
|
|
10686
|
-
user:
|
|
10687
|
-
project:
|
|
10459
|
+
user: join12(configDir ?? join12(homedir6(), CONFIG_DIR_NAME), "agents"),
|
|
10460
|
+
project: join12(cwd, CONFIG_DIR_NAME, "agents")
|
|
10688
10461
|
};
|
|
10689
10462
|
}
|
|
10690
10463
|
function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
@@ -10731,7 +10504,7 @@ var nextId = 1;
|
|
|
10731
10504
|
var preferredAgentName = "worker";
|
|
10732
10505
|
var runs = [];
|
|
10733
10506
|
var MAX_RUNS = 50;
|
|
10734
|
-
function
|
|
10507
|
+
function nowIso() {
|
|
10735
10508
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
10736
10509
|
}
|
|
10737
10510
|
function startAgentRun(input) {
|
|
@@ -10740,7 +10513,7 @@ function startAgentRun(input) {
|
|
|
10740
10513
|
agentName: input.agentName,
|
|
10741
10514
|
task: input.task,
|
|
10742
10515
|
status: "running",
|
|
10743
|
-
startedAt:
|
|
10516
|
+
startedAt: nowIso(),
|
|
10744
10517
|
toolNames: [],
|
|
10745
10518
|
agentIndex: input.agentIndex,
|
|
10746
10519
|
stopRequested: false
|
|
@@ -10758,7 +10531,7 @@ function finishAgentRun(runId, status, summary, error) {
|
|
|
10758
10531
|
const run = runs.find((r) => r.id === runId);
|
|
10759
10532
|
if (!run) return;
|
|
10760
10533
|
run.status = status;
|
|
10761
|
-
run.finishedAt =
|
|
10534
|
+
run.finishedAt = nowIso();
|
|
10762
10535
|
if (summary) run.summary = summary;
|
|
10763
10536
|
if (error) run.error = error;
|
|
10764
10537
|
}
|
|
@@ -11184,7 +10957,7 @@ var spawnAgentTool = {
|
|
|
11184
10957
|
|
|
11185
10958
|
// src/tools/builtin/task-manager.ts
|
|
11186
10959
|
import { spawn as spawn4 } from "child_process";
|
|
11187
|
-
import { randomUUID
|
|
10960
|
+
import { randomUUID } from "crypto";
|
|
11188
10961
|
import { platform as platform5 } from "os";
|
|
11189
10962
|
var MAX_OUTPUT_CHARS = 1e4;
|
|
11190
10963
|
var MAX_OUTPUT_BYTES = 64 * 1024;
|
|
@@ -11217,7 +10990,7 @@ function createTask(command, description) {
|
|
|
11217
10990
|
}
|
|
11218
10991
|
}
|
|
11219
10992
|
}
|
|
11220
|
-
const id =
|
|
10993
|
+
const id = randomUUID().slice(0, 8);
|
|
11221
10994
|
const { shell, args } = shellInvocation(command);
|
|
11222
10995
|
const proc = spawn4(shell, args, {
|
|
11223
10996
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -11399,20 +11172,20 @@ var taskStopTool = {
|
|
|
11399
11172
|
|
|
11400
11173
|
// src/tools/builtin/git-tools.ts
|
|
11401
11174
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
11402
|
-
import { existsSync as
|
|
11403
|
-
import { join as
|
|
11175
|
+
import { existsSync as existsSync18 } from "fs";
|
|
11176
|
+
import { join as join13 } from "path";
|
|
11404
11177
|
function assertGitRepo(cwd) {
|
|
11405
11178
|
let dir = cwd;
|
|
11406
11179
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
11407
11180
|
while (dir && dir !== root) {
|
|
11408
|
-
if (
|
|
11409
|
-
const parent =
|
|
11181
|
+
if (existsSync18(join13(dir, ".git"))) return;
|
|
11182
|
+
const parent = join13(dir, "..");
|
|
11410
11183
|
if (parent === dir) break;
|
|
11411
11184
|
dir = parent;
|
|
11412
11185
|
}
|
|
11413
11186
|
throw new ToolError("git", "Not inside a git repository. Run this command from a directory under a git-tracked project.");
|
|
11414
11187
|
}
|
|
11415
|
-
function
|
|
11188
|
+
function runGit(args, cwd, maxBuffer = 10 * 1024 * 1024) {
|
|
11416
11189
|
try {
|
|
11417
11190
|
const output = execFileSync2("git", args, {
|
|
11418
11191
|
cwd,
|
|
@@ -11447,11 +11220,11 @@ var gitStatusTool = {
|
|
|
11447
11220
|
assertGitRepo(cwd);
|
|
11448
11221
|
let branch = "(unknown)";
|
|
11449
11222
|
try {
|
|
11450
|
-
branch =
|
|
11223
|
+
branch = runGit(["symbolic-ref", "--short", "HEAD"], cwd).trim();
|
|
11451
11224
|
} catch {
|
|
11452
11225
|
branch = "(detached)";
|
|
11453
11226
|
}
|
|
11454
|
-
const porcelain =
|
|
11227
|
+
const porcelain = runGit(["status", "--porcelain=v1", "-b"], cwd);
|
|
11455
11228
|
const lines = porcelain.split("\n").filter((l) => l.length > 0);
|
|
11456
11229
|
const branchLine = lines[0]?.startsWith("##") ? lines[0].slice(3) : `${branch}`;
|
|
11457
11230
|
const fileLines = lines.slice(lines[0]?.startsWith("##") ? 1 : 0);
|
|
@@ -11538,7 +11311,7 @@ var gitDiffTool = {
|
|
|
11538
11311
|
if (args["file"]) {
|
|
11539
11312
|
gitArgs.push("--", String(args["file"]));
|
|
11540
11313
|
}
|
|
11541
|
-
const output =
|
|
11314
|
+
const output = runGit(gitArgs, cwd);
|
|
11542
11315
|
if (output.trim().length === 0) {
|
|
11543
11316
|
const label = args["staged"] ? "staged" : args["commit_range"] ? String(args["commit_range"]) : "unstaged";
|
|
11544
11317
|
return `No ${label} changes.`;
|
|
@@ -11600,7 +11373,7 @@ var gitLogTool = {
|
|
|
11600
11373
|
if (args["file"]) {
|
|
11601
11374
|
gitArgs.push("--", String(args["file"]));
|
|
11602
11375
|
}
|
|
11603
|
-
const output =
|
|
11376
|
+
const output = runGit(gitArgs, cwd);
|
|
11604
11377
|
if (output.trim().length === 0) {
|
|
11605
11378
|
return "No commits found matching the filter.";
|
|
11606
11379
|
}
|
|
@@ -11647,9 +11420,9 @@ var gitCommitTool = {
|
|
|
11647
11420
|
throw new ToolError("git_commit", "Cannot use both stage_all and files at the same time.");
|
|
11648
11421
|
}
|
|
11649
11422
|
if (files.length > 0) {
|
|
11650
|
-
|
|
11423
|
+
runGit(["add", "--", ...files], cwd);
|
|
11651
11424
|
}
|
|
11652
|
-
const statusBefore =
|
|
11425
|
+
const statusBefore = runGit(["status", "--porcelain"], cwd);
|
|
11653
11426
|
const hasStaged = statusBefore.split("\n").some((l) => l.length > 0 && l[0] !== " " && l[0] !== "?");
|
|
11654
11427
|
const hasUnstaged = statusBefore.split("\n").some((l) => l.length > 1 && l[1] !== " " && l[1] !== "?");
|
|
11655
11428
|
if (!hasStaged && !stageAll) {
|
|
@@ -11660,9 +11433,9 @@ var gitCommitTool = {
|
|
|
11660
11433
|
}
|
|
11661
11434
|
const commitArgs = ["commit", "-m", message];
|
|
11662
11435
|
if (stageAll) commitArgs.splice(1, 0, "-a");
|
|
11663
|
-
const commitOutput =
|
|
11664
|
-
const hash =
|
|
11665
|
-
const summary =
|
|
11436
|
+
const commitOutput = runGit(commitArgs, cwd);
|
|
11437
|
+
const hash = runGit(["rev-parse", "--short", "HEAD"], cwd).trim();
|
|
11438
|
+
const summary = runGit(["log", "-1", "--pretty=format:%h %s"], cwd).trim();
|
|
11666
11439
|
return `\u2713 Committed ${hash}
|
|
11667
11440
|
${summary}
|
|
11668
11441
|
|
|
@@ -11671,9 +11444,9 @@ ${commitOutput.trim()}`;
|
|
|
11671
11444
|
};
|
|
11672
11445
|
|
|
11673
11446
|
// src/tools/builtin/notebook-edit.ts
|
|
11674
|
-
import { readFileSync as
|
|
11447
|
+
import { readFileSync as readFileSync14, existsSync as existsSync19 } from "fs";
|
|
11675
11448
|
import { writeFile } from "fs/promises";
|
|
11676
|
-
import { resolve as
|
|
11449
|
+
import { resolve as resolve7, extname as extname2 } from "path";
|
|
11677
11450
|
var notebookEditTool = {
|
|
11678
11451
|
definition: {
|
|
11679
11452
|
name: "notebook_edit",
|
|
@@ -11722,14 +11495,14 @@ var notebookEditTool = {
|
|
|
11722
11495
|
if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
|
|
11723
11496
|
throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
|
|
11724
11497
|
}
|
|
11725
|
-
const absPath =
|
|
11498
|
+
const absPath = resolve7(filePath);
|
|
11726
11499
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
11727
11500
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
11728
11501
|
}
|
|
11729
|
-
if (!
|
|
11502
|
+
if (!existsSync19(absPath)) {
|
|
11730
11503
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
11731
11504
|
}
|
|
11732
|
-
const raw =
|
|
11505
|
+
const raw = readFileSync14(absPath, "utf-8");
|
|
11733
11506
|
const nb = parseNotebook(raw);
|
|
11734
11507
|
const cellIdx0 = cellIndexRaw - 1;
|
|
11735
11508
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -12146,8 +11919,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
12146
11919
|
|
|
12147
11920
|
// src/tools/registry.ts
|
|
12148
11921
|
import { pathToFileURL } from "url";
|
|
12149
|
-
import { existsSync as
|
|
12150
|
-
import { join as
|
|
11922
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
|
|
11923
|
+
import { join as join14 } from "path";
|
|
12151
11924
|
var ToolRegistry = class {
|
|
12152
11925
|
tools = /* @__PURE__ */ new Map();
|
|
12153
11926
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -12308,9 +12081,9 @@ var ToolRegistry = class {
|
|
|
12308
12081
|
* Returns the number of successfully loaded plugins.
|
|
12309
12082
|
*/
|
|
12310
12083
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
12311
|
-
if (!
|
|
12084
|
+
if (!existsSync20(pluginsDir)) {
|
|
12312
12085
|
try {
|
|
12313
|
-
|
|
12086
|
+
mkdirSync9(pluginsDir, { recursive: true });
|
|
12314
12087
|
} catch {
|
|
12315
12088
|
}
|
|
12316
12089
|
return 0;
|
|
@@ -12334,12 +12107,12 @@ var ToolRegistry = class {
|
|
|
12334
12107
|
process.stderr.write(
|
|
12335
12108
|
`
|
|
12336
12109
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
12337
|
-
` + files.map((f) => ` + ${
|
|
12110
|
+
` + files.map((f) => ` + ${join14(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
12338
12111
|
);
|
|
12339
12112
|
let loaded = 0;
|
|
12340
12113
|
for (const file of files) {
|
|
12341
12114
|
try {
|
|
12342
|
-
const fileUrl = pathToFileURL(
|
|
12115
|
+
const fileUrl = pathToFileURL(join14(pluginsDir, file)).href;
|
|
12343
12116
|
const mod = await import(fileUrl);
|
|
12344
12117
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
12345
12118
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -12501,7 +12274,7 @@ var McpClient = class {
|
|
|
12501
12274
|
// 内部方法:JSON-RPC 通信
|
|
12502
12275
|
// ══════════════════════════════════════════════════════════════════
|
|
12503
12276
|
sendRequest(method, params) {
|
|
12504
|
-
return new Promise((
|
|
12277
|
+
return new Promise((resolve13, reject) => {
|
|
12505
12278
|
if (!this.process?.stdin?.writable) {
|
|
12506
12279
|
return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
|
|
12507
12280
|
}
|
|
@@ -12525,7 +12298,7 @@ var McpClient = class {
|
|
|
12525
12298
|
this.pendingRequests.set(id, {
|
|
12526
12299
|
resolve: (result) => {
|
|
12527
12300
|
cleanup();
|
|
12528
|
-
|
|
12301
|
+
resolve13(result);
|
|
12529
12302
|
},
|
|
12530
12303
|
reject: (error) => {
|
|
12531
12304
|
cleanup();
|
|
@@ -12602,13 +12375,13 @@ var McpClient = class {
|
|
|
12602
12375
|
}
|
|
12603
12376
|
/** Promise 超时包装 */
|
|
12604
12377
|
withTimeout(promise, ms, label) {
|
|
12605
|
-
return new Promise((
|
|
12378
|
+
return new Promise((resolve13, reject) => {
|
|
12606
12379
|
const timer = setTimeout(() => {
|
|
12607
12380
|
reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
|
|
12608
12381
|
}, ms);
|
|
12609
12382
|
promise.then((val) => {
|
|
12610
12383
|
clearTimeout(timer);
|
|
12611
|
-
|
|
12384
|
+
resolve13(val);
|
|
12612
12385
|
}).catch((err) => {
|
|
12613
12386
|
clearTimeout(timer);
|
|
12614
12387
|
reject(err);
|
|
@@ -12885,11 +12658,11 @@ var McpManager = class {
|
|
|
12885
12658
|
};
|
|
12886
12659
|
|
|
12887
12660
|
// src/skills/manager.ts
|
|
12888
|
-
import { existsSync as
|
|
12889
|
-
import { join as
|
|
12661
|
+
import { existsSync as existsSync21, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync8 } from "fs";
|
|
12662
|
+
import { join as join15 } from "path";
|
|
12890
12663
|
|
|
12891
12664
|
// src/skills/types.ts
|
|
12892
|
-
import { readFileSync as
|
|
12665
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
12893
12666
|
import { basename as basename7 } from "path";
|
|
12894
12667
|
function parseSimpleYaml(yaml) {
|
|
12895
12668
|
const result = {};
|
|
@@ -12911,7 +12684,7 @@ function parseYamlArray(value) {
|
|
|
12911
12684
|
function parseSkillFile(filePath) {
|
|
12912
12685
|
let raw;
|
|
12913
12686
|
try {
|
|
12914
|
-
raw =
|
|
12687
|
+
raw = readFileSync15(filePath, "utf-8");
|
|
12915
12688
|
} catch {
|
|
12916
12689
|
return null;
|
|
12917
12690
|
}
|
|
@@ -12959,10 +12732,10 @@ var SkillManager = class {
|
|
|
12959
12732
|
return this.skills.size;
|
|
12960
12733
|
}
|
|
12961
12734
|
loadSkillDir(dir, createIfMissing) {
|
|
12962
|
-
if (!
|
|
12735
|
+
if (!existsSync21(dir)) {
|
|
12963
12736
|
if (createIfMissing) {
|
|
12964
12737
|
try {
|
|
12965
|
-
|
|
12738
|
+
mkdirSync10(dir, { recursive: true });
|
|
12966
12739
|
} catch {
|
|
12967
12740
|
}
|
|
12968
12741
|
}
|
|
@@ -12976,14 +12749,14 @@ var SkillManager = class {
|
|
|
12976
12749
|
}
|
|
12977
12750
|
for (const entry of entries) {
|
|
12978
12751
|
let filePath;
|
|
12979
|
-
const fullPath =
|
|
12752
|
+
const fullPath = join15(dir, entry);
|
|
12980
12753
|
if (entry.endsWith(".md")) {
|
|
12981
12754
|
filePath = fullPath;
|
|
12982
12755
|
} else {
|
|
12983
12756
|
try {
|
|
12984
12757
|
if (statSync8(fullPath).isDirectory()) {
|
|
12985
|
-
const skillMd =
|
|
12986
|
-
if (
|
|
12758
|
+
const skillMd = join15(fullPath, "SKILL.md");
|
|
12759
|
+
if (existsSync21(skillMd)) filePath = skillMd;
|
|
12987
12760
|
else continue;
|
|
12988
12761
|
} else {
|
|
12989
12762
|
continue;
|
|
@@ -13040,9 +12813,9 @@ var SkillManager = class {
|
|
|
13040
12813
|
};
|
|
13041
12814
|
|
|
13042
12815
|
// src/web/tool-executor-web.ts
|
|
13043
|
-
import { randomUUID as
|
|
12816
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
13044
12817
|
import { tmpdir as tmpdir2 } from "os";
|
|
13045
|
-
import { existsSync as
|
|
12818
|
+
import { existsSync as existsSync22, readFileSync as readFileSync16 } from "fs";
|
|
13046
12819
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
13047
12820
|
constructor(registry, ws) {
|
|
13048
12821
|
this.registry = registry;
|
|
@@ -13112,33 +12885,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13112
12885
|
}
|
|
13113
12886
|
/** Resolve a pending confirm from client response */
|
|
13114
12887
|
resolveConfirm(requestId, approved) {
|
|
13115
|
-
const
|
|
13116
|
-
if (
|
|
12888
|
+
const resolve13 = this.pendingConfirms.get(requestId);
|
|
12889
|
+
if (resolve13) {
|
|
13117
12890
|
this.clearPendingTimer(requestId);
|
|
13118
12891
|
this.pendingConfirms.delete(requestId);
|
|
13119
12892
|
this.confirming = false;
|
|
13120
|
-
|
|
12893
|
+
resolve13(approved);
|
|
13121
12894
|
}
|
|
13122
12895
|
}
|
|
13123
12896
|
/** Resolve a pending batch confirm from client response */
|
|
13124
12897
|
resolveBatchConfirm(requestId, decision) {
|
|
13125
|
-
const
|
|
13126
|
-
if (
|
|
12898
|
+
const resolve13 = this.pendingBatchConfirms.get(requestId);
|
|
12899
|
+
if (resolve13) {
|
|
13127
12900
|
this.clearPendingTimer(requestId);
|
|
13128
12901
|
this.pendingBatchConfirms.delete(requestId);
|
|
13129
12902
|
this.confirming = false;
|
|
13130
12903
|
if (decision === "all" || decision === "none") {
|
|
13131
|
-
|
|
12904
|
+
resolve13(decision);
|
|
13132
12905
|
} else {
|
|
13133
|
-
|
|
12906
|
+
resolve13(new Set(decision));
|
|
13134
12907
|
}
|
|
13135
12908
|
}
|
|
13136
12909
|
}
|
|
13137
12910
|
/** Cancel all pending confirms (e.g., on disconnect) */
|
|
13138
12911
|
cancelAll() {
|
|
13139
|
-
for (const
|
|
12912
|
+
for (const resolve13 of this.pendingConfirms.values()) resolve13(false);
|
|
13140
12913
|
this.pendingConfirms.clear();
|
|
13141
|
-
for (const
|
|
12914
|
+
for (const resolve13 of this.pendingBatchConfirms.values()) resolve13("none");
|
|
13142
12915
|
this.pendingBatchConfirms.clear();
|
|
13143
12916
|
this.confirming = false;
|
|
13144
12917
|
}
|
|
@@ -13181,9 +12954,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13181
12954
|
if (call.name === "write_file") {
|
|
13182
12955
|
const filePath = String(call.arguments["path"] ?? "");
|
|
13183
12956
|
const newContent = String(call.arguments["content"] ?? "");
|
|
13184
|
-
if (filePath &&
|
|
12957
|
+
if (filePath && existsSync22(filePath)) {
|
|
13185
12958
|
try {
|
|
13186
|
-
const old =
|
|
12959
|
+
const old = readFileSync16(filePath, "utf-8");
|
|
13187
12960
|
return renderDiff(old, newContent, { filePath });
|
|
13188
12961
|
} catch {
|
|
13189
12962
|
}
|
|
@@ -13203,7 +12976,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13203
12976
|
}
|
|
13204
12977
|
/** WebSocket-based confirm — sends request, returns Promise resolved by client */
|
|
13205
12978
|
confirm(call, level) {
|
|
13206
|
-
const requestId =
|
|
12979
|
+
const requestId = randomUUID2();
|
|
13207
12980
|
this.confirming = true;
|
|
13208
12981
|
const msg = {
|
|
13209
12982
|
type: "confirm_request",
|
|
@@ -13214,8 +12987,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13214
12987
|
diff: this.getDiffPreview(call)
|
|
13215
12988
|
};
|
|
13216
12989
|
this.send(msg);
|
|
13217
|
-
return new Promise((
|
|
13218
|
-
this.pendingConfirms.set(requestId,
|
|
12990
|
+
return new Promise((resolve13) => {
|
|
12991
|
+
this.pendingConfirms.set(requestId, resolve13);
|
|
13219
12992
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
13220
12993
|
if (this.pendingConfirms.has(requestId)) {
|
|
13221
12994
|
this.resolveConfirm(requestId, false);
|
|
@@ -13225,7 +12998,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13225
12998
|
}
|
|
13226
12999
|
/** WebSocket-based batch confirm */
|
|
13227
13000
|
batchConfirm(calls) {
|
|
13228
|
-
const requestId =
|
|
13001
|
+
const requestId = randomUUID2();
|
|
13229
13002
|
this.confirming = true;
|
|
13230
13003
|
const files = calls.map((call, i) => ({
|
|
13231
13004
|
index: i + 1,
|
|
@@ -13239,8 +13012,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13239
13012
|
files
|
|
13240
13013
|
};
|
|
13241
13014
|
this.send(msg);
|
|
13242
|
-
return new Promise((
|
|
13243
|
-
this.pendingBatchConfirms.set(requestId,
|
|
13015
|
+
return new Promise((resolve13) => {
|
|
13016
|
+
this.pendingBatchConfirms.set(requestId, resolve13);
|
|
13244
13017
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
13245
13018
|
if (this.pendingBatchConfirms.has(requestId)) {
|
|
13246
13019
|
this.resolveBatchConfirm(requestId, "none");
|
|
@@ -13487,16 +13260,16 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13487
13260
|
};
|
|
13488
13261
|
|
|
13489
13262
|
// src/repl/dev-state.ts
|
|
13490
|
-
import { existsSync as
|
|
13491
|
-
import { join as
|
|
13263
|
+
import { existsSync as existsSync23, readFileSync as readFileSync17, unlinkSync as unlinkSync5, mkdirSync as mkdirSync11 } from "fs";
|
|
13264
|
+
import { join as join16 } from "path";
|
|
13492
13265
|
import { homedir as homedir7 } from "os";
|
|
13493
13266
|
function getDevStatePath() {
|
|
13494
|
-
return
|
|
13267
|
+
return join16(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
13495
13268
|
}
|
|
13496
13269
|
function loadDevState() {
|
|
13497
13270
|
const path3 = getDevStatePath();
|
|
13498
|
-
if (!
|
|
13499
|
-
const content =
|
|
13271
|
+
if (!existsSync23(path3)) return null;
|
|
13272
|
+
const content = readFileSync17(path3, "utf-8").trim();
|
|
13500
13273
|
return content || null;
|
|
13501
13274
|
}
|
|
13502
13275
|
|
|
@@ -13521,7 +13294,7 @@ Working directory: ${process.cwd()}`;
|
|
|
13521
13294
|
}
|
|
13522
13295
|
stableParts.push(AGENTIC_BEHAVIOR_GUIDELINE);
|
|
13523
13296
|
if (ctx.configDir) {
|
|
13524
|
-
const memory = loadMemoryContent(ctx.configDir);
|
|
13297
|
+
const memory = loadMemoryContent(ctx.configDir, ctx.memoryMaxChars);
|
|
13525
13298
|
if (memory) stableParts.push(`# Persistent Memory
|
|
13526
13299
|
|
|
13527
13300
|
${memory.content}`);
|
|
@@ -13553,8 +13326,8 @@ ${ctx.activeSkill.content}`);
|
|
|
13553
13326
|
if (ctx.planMode) stableParts.push(PLAN_MODE_SYSTEM_ADDON);
|
|
13554
13327
|
return { stable: stableParts.join("\n\n---\n\n"), volatile };
|
|
13555
13328
|
}
|
|
13556
|
-
function loadMemoryContent(configDir) {
|
|
13557
|
-
return formatMemoryForPrompt(configDir, process.cwd());
|
|
13329
|
+
function loadMemoryContent(configDir, maxChars) {
|
|
13330
|
+
return formatMemoryForPrompt(configDir, process.cwd(), maxChars);
|
|
13558
13331
|
}
|
|
13559
13332
|
|
|
13560
13333
|
// src/core/pricing.ts
|
|
@@ -13753,8 +13526,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
13753
13526
|
}
|
|
13754
13527
|
|
|
13755
13528
|
// src/core/context-files.ts
|
|
13756
|
-
import { existsSync as
|
|
13757
|
-
import { join as
|
|
13529
|
+
import { existsSync as existsSync24, readFileSync as readFileSync18 } from "fs";
|
|
13530
|
+
import { join as join17, relative as relative4, resolve as resolve8 } from "path";
|
|
13758
13531
|
function uniqueNonEmpty(values) {
|
|
13759
13532
|
const seen = /* @__PURE__ */ new Set();
|
|
13760
13533
|
const out = [];
|
|
@@ -13777,14 +13550,14 @@ function displayPath(filePath, cwd) {
|
|
|
13777
13550
|
return filePath;
|
|
13778
13551
|
}
|
|
13779
13552
|
function isInsideOrEqual(parent, child) {
|
|
13780
|
-
const rel = relative4(
|
|
13553
|
+
const rel = relative4(resolve8(parent), resolve8(child));
|
|
13781
13554
|
return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
|
|
13782
13555
|
}
|
|
13783
13556
|
function readContextFile(level, filePath, cwd, maxBytes) {
|
|
13784
13557
|
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
13785
13558
|
const shown = displayPath(filePath, cwd);
|
|
13786
13559
|
try {
|
|
13787
|
-
const raw =
|
|
13560
|
+
const raw = readFileSync18(filePath);
|
|
13788
13561
|
const byteCount = raw.byteLength;
|
|
13789
13562
|
if (byteCount === 0) {
|
|
13790
13563
|
return {
|
|
@@ -13831,8 +13604,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
13831
13604
|
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
13832
13605
|
const skipped = [];
|
|
13833
13606
|
for (const candidate of candidates) {
|
|
13834
|
-
const filePath =
|
|
13835
|
-
if (!
|
|
13607
|
+
const filePath = join17(dir, candidate);
|
|
13608
|
+
if (!existsSync24(filePath)) continue;
|
|
13836
13609
|
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
13837
13610
|
if (result.skipped) skipped.push(result.skipped);
|
|
13838
13611
|
if (result.layer) return { layer: result.layer, skipped };
|
|
@@ -13840,7 +13613,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
|
13840
13613
|
return { layer: null, skipped };
|
|
13841
13614
|
}
|
|
13842
13615
|
function loadContextFiles(options) {
|
|
13843
|
-
const cwd =
|
|
13616
|
+
const cwd = resolve8(options.cwd);
|
|
13844
13617
|
const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
|
|
13845
13618
|
const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
|
|
13846
13619
|
const skipped = [];
|
|
@@ -13848,7 +13621,7 @@ function loadContextFiles(options) {
|
|
|
13848
13621
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
13849
13622
|
}
|
|
13850
13623
|
if (options.setting && options.setting !== "auto") {
|
|
13851
|
-
const filePath =
|
|
13624
|
+
const filePath = resolve8(cwd, String(options.setting));
|
|
13852
13625
|
if (!isInsideOrEqual(cwd, filePath)) {
|
|
13853
13626
|
skipped.push({
|
|
13854
13627
|
level: "single",
|
|
@@ -13859,7 +13632,7 @@ function loadContextFiles(options) {
|
|
|
13859
13632
|
});
|
|
13860
13633
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
13861
13634
|
}
|
|
13862
|
-
if (!
|
|
13635
|
+
if (!existsSync24(filePath)) {
|
|
13863
13636
|
skipped.push({
|
|
13864
13637
|
level: "single",
|
|
13865
13638
|
filePath,
|
|
@@ -13892,7 +13665,7 @@ function loadContextFiles(options) {
|
|
|
13892
13665
|
skipped.push(...result.skipped);
|
|
13893
13666
|
if (result.layer) layers.push(result.layer);
|
|
13894
13667
|
}
|
|
13895
|
-
if (
|
|
13668
|
+
if (resolve8(options.cwd) !== resolve8(options.projectRoot)) {
|
|
13896
13669
|
const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
|
|
13897
13670
|
skipped.push(...result.skipped);
|
|
13898
13671
|
if (result.layer) layers.push(result.layer);
|
|
@@ -13910,12 +13683,12 @@ function loadContextFiles(options) {
|
|
|
13910
13683
|
}
|
|
13911
13684
|
|
|
13912
13685
|
// src/web/session-handler.ts
|
|
13913
|
-
import { mkdirSync as
|
|
13914
|
-
import {
|
|
13686
|
+
import { mkdirSync as mkdirSync14, createWriteStream as createWriteStream2 } from "fs";
|
|
13687
|
+
import { dirname as dirname9 } from "path";
|
|
13915
13688
|
|
|
13916
13689
|
// src/repl/commands/project-init.ts
|
|
13917
|
-
import { existsSync as
|
|
13918
|
-
import { join as
|
|
13690
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync11, statSync as statSync9 } from "fs";
|
|
13691
|
+
import { join as join18 } from "path";
|
|
13919
13692
|
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
13920
13693
|
"node_modules",
|
|
13921
13694
|
".git",
|
|
@@ -13959,11 +13732,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
13959
13732
|
const sorted = filtered.sort((a, b) => {
|
|
13960
13733
|
let aIsDir = false, bIsDir = false;
|
|
13961
13734
|
try {
|
|
13962
|
-
aIsDir = statSync9(
|
|
13735
|
+
aIsDir = statSync9(join18(d, a)).isDirectory();
|
|
13963
13736
|
} catch {
|
|
13964
13737
|
}
|
|
13965
13738
|
try {
|
|
13966
|
-
bIsDir = statSync9(
|
|
13739
|
+
bIsDir = statSync9(join18(d, b)).isDirectory();
|
|
13967
13740
|
} catch {
|
|
13968
13741
|
}
|
|
13969
13742
|
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
@@ -13971,7 +13744,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
13971
13744
|
});
|
|
13972
13745
|
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
13973
13746
|
const name = sorted[i];
|
|
13974
|
-
const fullPath =
|
|
13747
|
+
const fullPath = join18(d, name);
|
|
13975
13748
|
const isLast = i === sorted.length - 1;
|
|
13976
13749
|
const connector = isLast ? "+-- " : "|-- ";
|
|
13977
13750
|
let isDir;
|
|
@@ -13998,7 +13771,7 @@ function scanProject(cwd) {
|
|
|
13998
13771
|
configFiles: [],
|
|
13999
13772
|
directoryStructure: ""
|
|
14000
13773
|
};
|
|
14001
|
-
const check = (file) =>
|
|
13774
|
+
const check = (file) => existsSync25(join18(cwd, file));
|
|
14002
13775
|
const configCandidates = [
|
|
14003
13776
|
"package.json",
|
|
14004
13777
|
"tsconfig.json",
|
|
@@ -14025,7 +13798,7 @@ function scanProject(cwd) {
|
|
|
14025
13798
|
info.type = "node";
|
|
14026
13799
|
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
14027
13800
|
try {
|
|
14028
|
-
const pkg = JSON.parse(
|
|
13801
|
+
const pkg = JSON.parse(readFileSync19(join18(cwd, "package.json"), "utf-8"));
|
|
14029
13802
|
const scripts = pkg.scripts ?? {};
|
|
14030
13803
|
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
14031
13804
|
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
@@ -14674,7 +14447,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
|
|
|
14674
14447
|
}
|
|
14675
14448
|
|
|
14676
14449
|
// src/hub/persist.ts
|
|
14677
|
-
import { join as
|
|
14450
|
+
import { join as join19 } from "path";
|
|
14678
14451
|
function discussionToMessages(state2) {
|
|
14679
14452
|
const out = [];
|
|
14680
14453
|
const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -14712,7 +14485,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
|
|
|
14712
14485
|
session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
|
|
14713
14486
|
session.titleAiGenerated = true;
|
|
14714
14487
|
await sm.save();
|
|
14715
|
-
return { id: session.id, path:
|
|
14488
|
+
return { id: session.id, path: join19(config.getHistoryDir(), `${session.id}.json`) };
|
|
14716
14489
|
}
|
|
14717
14490
|
|
|
14718
14491
|
// src/web/commands/provider-commands.ts
|
|
@@ -14800,8 +14573,8 @@ async function handlePlan(args, ctx) {
|
|
|
14800
14573
|
}
|
|
14801
14574
|
|
|
14802
14575
|
// src/web/commands/_shared.ts
|
|
14803
|
-
import { existsSync as
|
|
14804
|
-
import { join as
|
|
14576
|
+
import { existsSync as existsSync28, readFileSync as readFileSync21, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, statSync as statSync12, createWriteStream } from "fs";
|
|
14577
|
+
import { join as join22, resolve as resolve9, dirname as dirname8 } from "path";
|
|
14805
14578
|
|
|
14806
14579
|
// src/cli/review-prompts.ts
|
|
14807
14580
|
function buildReviewPrompt(diff, gitContextStr, detailed) {
|
|
@@ -14873,34 +14646,34 @@ function readGitDiff(options = {}) {
|
|
|
14873
14646
|
}
|
|
14874
14647
|
|
|
14875
14648
|
// src/diagnostics/doctor-report.ts
|
|
14876
|
-
import { existsSync as
|
|
14877
|
-
import { join as
|
|
14649
|
+
import { existsSync as existsSync27, statSync as statSync11 } from "fs";
|
|
14650
|
+
import { join as join21 } from "path";
|
|
14878
14651
|
import { arch as arch2, platform as platform7, release as release2 } from "os";
|
|
14879
14652
|
|
|
14880
14653
|
// src/diagnostics/crash-log.ts
|
|
14881
14654
|
import {
|
|
14882
|
-
existsSync as
|
|
14883
|
-
mkdirSync as
|
|
14655
|
+
existsSync as existsSync26,
|
|
14656
|
+
mkdirSync as mkdirSync12,
|
|
14884
14657
|
readdirSync as readdirSync12,
|
|
14885
|
-
readFileSync as
|
|
14658
|
+
readFileSync as readFileSync20,
|
|
14886
14659
|
statSync as statSync10,
|
|
14887
14660
|
unlinkSync as unlinkSync6,
|
|
14888
14661
|
writeFileSync as writeFileSync2
|
|
14889
14662
|
} from "fs";
|
|
14890
|
-
import { join as
|
|
14663
|
+
import { join as join20 } from "path";
|
|
14891
14664
|
import { homedir as homedir8, platform as platform6, release, arch } from "os";
|
|
14892
14665
|
var LOGS_DIR_NAME = "logs";
|
|
14893
14666
|
var CRASH_LOG_PREFIX = "crash-";
|
|
14894
14667
|
function getLogsDir(configDir) {
|
|
14895
|
-
const base = configDir ??
|
|
14896
|
-
return
|
|
14668
|
+
const base = configDir ?? join20(homedir8(), CONFIG_DIR_NAME);
|
|
14669
|
+
return join20(base, LOGS_DIR_NAME);
|
|
14897
14670
|
}
|
|
14898
14671
|
function truncate(s, n) {
|
|
14899
14672
|
return s.length <= n ? s : s.slice(0, n) + "\u2026";
|
|
14900
14673
|
}
|
|
14901
14674
|
function listRecentCrashes(limit = 10, configDir) {
|
|
14902
14675
|
const dir = getLogsDir(configDir);
|
|
14903
|
-
if (!
|
|
14676
|
+
if (!existsSync26(dir)) return [];
|
|
14904
14677
|
const files = [];
|
|
14905
14678
|
let names;
|
|
14906
14679
|
try {
|
|
@@ -14910,10 +14683,10 @@ function listRecentCrashes(limit = 10, configDir) {
|
|
|
14910
14683
|
}
|
|
14911
14684
|
for (const name of names) {
|
|
14912
14685
|
if (!name.startsWith(CRASH_LOG_PREFIX) || !name.endsWith(".log")) continue;
|
|
14913
|
-
const full =
|
|
14686
|
+
const full = join20(dir, name);
|
|
14914
14687
|
try {
|
|
14915
14688
|
const st = statSync10(full);
|
|
14916
|
-
const head =
|
|
14689
|
+
const head = readFileSync20(full, "utf-8").split("\n").slice(0, 20);
|
|
14917
14690
|
const tsLine = head.find((l) => l.startsWith("timestamp:"));
|
|
14918
14691
|
const kindLine = head.find((l) => l.startsWith("kind:"));
|
|
14919
14692
|
const msgIdx = head.findIndex((l) => l.trim() === "## message");
|
|
@@ -14932,8 +14705,8 @@ function listRecentCrashes(limit = 10, configDir) {
|
|
|
14932
14705
|
return files.slice(0, limit);
|
|
14933
14706
|
}
|
|
14934
14707
|
function getConfigDirUsage(configDir) {
|
|
14935
|
-
const base = configDir ??
|
|
14936
|
-
if (!
|
|
14708
|
+
const base = configDir ?? join20(homedir8(), CONFIG_DIR_NAME);
|
|
14709
|
+
if (!existsSync26(base)) return { totalBytes: 0, entries: [] };
|
|
14937
14710
|
const entries = [];
|
|
14938
14711
|
let total = 0;
|
|
14939
14712
|
let names;
|
|
@@ -14943,7 +14716,7 @@ function getConfigDirUsage(configDir) {
|
|
|
14943
14716
|
return { totalBytes: 0, entries: [] };
|
|
14944
14717
|
}
|
|
14945
14718
|
for (const name of names) {
|
|
14946
|
-
const full =
|
|
14719
|
+
const full = join20(base, name);
|
|
14947
14720
|
const bytes = dirSize(full);
|
|
14948
14721
|
entries.push({ name, bytes });
|
|
14949
14722
|
total += bytes;
|
|
@@ -14958,7 +14731,7 @@ function dirSize(path3) {
|
|
|
14958
14731
|
if (!st.isDirectory()) return 0;
|
|
14959
14732
|
let sum = 0;
|
|
14960
14733
|
for (const name of readdirSync12(path3)) {
|
|
14961
|
-
sum += dirSize(
|
|
14734
|
+
sum += dirSize(join20(path3, name));
|
|
14962
14735
|
}
|
|
14963
14736
|
return sum;
|
|
14964
14737
|
} catch {
|
|
@@ -14968,7 +14741,7 @@ function dirSize(path3) {
|
|
|
14968
14741
|
|
|
14969
14742
|
// src/diagnostics/doctor-report.ts
|
|
14970
14743
|
function checkFile(label, path3) {
|
|
14971
|
-
const exists =
|
|
14744
|
+
const exists = existsSync27(path3);
|
|
14972
14745
|
let sizeBytes = null;
|
|
14973
14746
|
if (exists) {
|
|
14974
14747
|
try {
|
|
@@ -15011,12 +14784,12 @@ function buildDoctorReport(options) {
|
|
|
15011
14784
|
configDir,
|
|
15012
14785
|
providers: options.providers.listAll().map((p) => ({ id: p.id, displayName: p.displayName, configured: p.configured })),
|
|
15013
14786
|
files: [
|
|
15014
|
-
checkFile("config.json",
|
|
15015
|
-
checkFile("memory.md",
|
|
15016
|
-
checkFile("memory.jsonl",
|
|
15017
|
-
checkFile("dev-state.md",
|
|
15018
|
-
checkFile("hooks-trust.json",
|
|
15019
|
-
checkFile("plugin-state.json",
|
|
14787
|
+
checkFile("config.json", join21(configDir, "config.json")),
|
|
14788
|
+
checkFile("memory.md", join21(configDir, MEMORY_FILE_NAME)),
|
|
14789
|
+
checkFile("memory.jsonl", join21(configDir, "memory.jsonl")),
|
|
14790
|
+
checkFile("dev-state.md", join21(configDir, DEV_STATE_FILE_NAME)),
|
|
14791
|
+
checkFile("hooks-trust.json", join21(configDir, "hooks-trust.json")),
|
|
14792
|
+
checkFile("plugin-state.json", join21(configDir, "plugins", "plugin-state.json"))
|
|
15020
14793
|
],
|
|
15021
14794
|
context: {
|
|
15022
14795
|
enabled: contextSetting !== false,
|
|
@@ -15051,7 +14824,7 @@ function buildDoctorReport(options) {
|
|
|
15051
14824
|
disabled: hooks.filter((h) => h.disabled).length
|
|
15052
14825
|
},
|
|
15053
14826
|
plugins: {
|
|
15054
|
-
root:
|
|
14827
|
+
root: join21(configDir, "plugins"),
|
|
15055
14828
|
installed: plugins.length,
|
|
15056
14829
|
active: activeAssets.plugins.length,
|
|
15057
14830
|
invalid: plugins.filter((p) => !p.valid).length,
|
|
@@ -15066,7 +14839,7 @@ function buildDoctorReport(options) {
|
|
|
15066
14839
|
mcp: {
|
|
15067
14840
|
enabled: config.get("mcpEnabled"),
|
|
15068
14841
|
configured: Object.keys(mcpServers).length,
|
|
15069
|
-
projectConfigExists:
|
|
14842
|
+
projectConfigExists: existsSync27(join21(projectRoot, MCP_PROJECT_CONFIG_NAME)),
|
|
15070
14843
|
statuses: options.mcpStatuses ?? []
|
|
15071
14844
|
},
|
|
15072
14845
|
recentCrashes: listRecentCrashes(5, configDir),
|
|
@@ -15292,12 +15065,20 @@ async function handleStatus(_args, ctx) {
|
|
|
15292
15065
|
Cache: write=${cacheCreate} read=${cacheRead}` : "";
|
|
15293
15066
|
const costLine = cost != null ? `
|
|
15294
15067
|
Cost: ${formatCost(cost)}` : "";
|
|
15068
|
+
let memoryLine = "";
|
|
15069
|
+
try {
|
|
15070
|
+
const { countPendingMemories } = await import("./persistent-memory-HWFNDQFQ.js");
|
|
15071
|
+
const pending = countPendingMemories(ctx.config.getConfigDir());
|
|
15072
|
+
if (pending > 0) memoryLine = `
|
|
15073
|
+
Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
|
|
15074
|
+
} catch {
|
|
15075
|
+
}
|
|
15295
15076
|
ctx.send({
|
|
15296
15077
|
type: "info",
|
|
15297
15078
|
message: `Provider: ${ctx.currentProvider}
|
|
15298
15079
|
Model: ${ctx.currentModel}
|
|
15299
15080
|
Session: ${session?.id ?? "none"} (${session?.messages.length ?? 0} messages)
|
|
15300
|
-
Tokens: in=${ctx.sessionTokenUsage.inputTokens} out=${ctx.sessionTokenUsage.outputTokens}${cacheLine}${costLine}`
|
|
15081
|
+
Tokens: in=${ctx.sessionTokenUsage.inputTokens} out=${ctx.sessionTokenUsage.outputTokens}${cacheLine}${costLine}${memoryLine}`
|
|
15301
15082
|
});
|
|
15302
15083
|
}
|
|
15303
15084
|
async function handleSession(args, ctx) {
|
|
@@ -15593,7 +15374,7 @@ async function handleBranch(args, ctx) {
|
|
|
15593
15374
|
return;
|
|
15594
15375
|
}
|
|
15595
15376
|
const sub = args[0]?.toLowerCase();
|
|
15596
|
-
const
|
|
15377
|
+
const resolve13 = (ref) => {
|
|
15597
15378
|
const r = session.resolveBranchRef(ref);
|
|
15598
15379
|
if (r.ok) return r.id;
|
|
15599
15380
|
if (r.reason === "ambiguous") {
|
|
@@ -15647,7 +15428,7 @@ async function handleBranch(args, ctx) {
|
|
|
15647
15428
|
ctx.send({ type: "error", message: "Usage: /branch switch <id|title>" });
|
|
15648
15429
|
return;
|
|
15649
15430
|
}
|
|
15650
|
-
const id =
|
|
15431
|
+
const id = resolve13(ref);
|
|
15651
15432
|
if (!id) return;
|
|
15652
15433
|
const ok = session.switchBranch(id);
|
|
15653
15434
|
if (ok) {
|
|
@@ -15667,7 +15448,7 @@ async function handleBranch(args, ctx) {
|
|
|
15667
15448
|
ctx.send({ type: "error", message: "Usage: /branch delete <id|title>" });
|
|
15668
15449
|
return;
|
|
15669
15450
|
}
|
|
15670
|
-
const id =
|
|
15451
|
+
const id = resolve13(ref);
|
|
15671
15452
|
if (!id) return;
|
|
15672
15453
|
const ok = session.deleteBranch(id);
|
|
15673
15454
|
if (ok) {
|
|
@@ -15686,7 +15467,7 @@ async function handleBranch(args, ctx) {
|
|
|
15686
15467
|
ctx.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
|
|
15687
15468
|
return;
|
|
15688
15469
|
}
|
|
15689
|
-
const id =
|
|
15470
|
+
const id = resolve13(ref);
|
|
15690
15471
|
if (!id) return;
|
|
15691
15472
|
const ok = session.renameBranch(id, title);
|
|
15692
15473
|
if (ok) {
|
|
@@ -15704,7 +15485,7 @@ async function handleBranch(args, ctx) {
|
|
|
15704
15485
|
ctx.send({ type: "error", message: "Usage: /branch diff <id|title>" });
|
|
15705
15486
|
return;
|
|
15706
15487
|
}
|
|
15707
|
-
const id =
|
|
15488
|
+
const id = resolve13(ref);
|
|
15708
15489
|
if (!id) return;
|
|
15709
15490
|
const d = session.diffBranches(id);
|
|
15710
15491
|
if (!d) {
|
|
@@ -15746,7 +15527,7 @@ async function handleBranch(args, ctx) {
|
|
|
15746
15527
|
ctx.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
|
|
15747
15528
|
return;
|
|
15748
15529
|
}
|
|
15749
|
-
const id =
|
|
15530
|
+
const id = resolve13(ref);
|
|
15750
15531
|
if (!id) return;
|
|
15751
15532
|
const idx = parseInt(idxStr, 10);
|
|
15752
15533
|
if (Number.isNaN(idx)) {
|
|
@@ -15870,7 +15651,7 @@ async function handlePlugin(args, ctx) {
|
|
|
15870
15651
|
lines.push("");
|
|
15871
15652
|
}
|
|
15872
15653
|
if (pluginTools.length > 0) {
|
|
15873
|
-
const legacyDir =
|
|
15654
|
+
const legacyDir = join22(configDir, PLUGINS_DIR_NAME);
|
|
15874
15655
|
lines.push(`**Legacy JS tools** (${pluginTools.length}):`);
|
|
15875
15656
|
lines.push(`Dir: ${legacyDir}`);
|
|
15876
15657
|
for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
|
|
@@ -15994,8 +15775,8 @@ ${activated.meta.description || ""}` });
|
|
|
15994
15775
|
}
|
|
15995
15776
|
async function handleCommands(_args, ctx) {
|
|
15996
15777
|
const configDir = ctx.config.getConfigDir();
|
|
15997
|
-
const commandsDir =
|
|
15998
|
-
if (!
|
|
15778
|
+
const commandsDir = join22(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
15779
|
+
if (!existsSync28(commandsDir)) {
|
|
15999
15780
|
ctx.send({ type: "info", message: `No custom commands directory.
|
|
16000
15781
|
Create: ${commandsDir}/ with .md files.` });
|
|
16001
15782
|
return;
|
|
@@ -16095,7 +15876,7 @@ async function handleSecurityReview(args, ctx) {
|
|
|
16095
15876
|
async function handleTest(args, ctx) {
|
|
16096
15877
|
ctx.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
16097
15878
|
try {
|
|
16098
|
-
const { executeTests } = await import("./run-tests-
|
|
15879
|
+
const { executeTests } = await import("./run-tests-B3FSE5NV.js");
|
|
16099
15880
|
const argStr = args.join(" ").trim();
|
|
16100
15881
|
let testArgs = {};
|
|
16101
15882
|
if (argStr) {
|
|
@@ -16125,9 +15906,9 @@ async function handleDiff(args, ctx) {
|
|
|
16125
15906
|
let modifiedFiles = 0;
|
|
16126
15907
|
const diffLines2 = [];
|
|
16127
15908
|
for (const [filePath, { earliest }] of fileMap) {
|
|
16128
|
-
const currentContent =
|
|
15909
|
+
const currentContent = existsSync28(filePath) ? (() => {
|
|
16129
15910
|
try {
|
|
16130
|
-
return
|
|
15911
|
+
return readFileSync21(filePath, "utf-8");
|
|
16131
15912
|
} catch {
|
|
16132
15913
|
return null;
|
|
16133
15914
|
}
|
|
@@ -16162,9 +15943,9 @@ async function handleDiff(args, ctx) {
|
|
|
16162
15943
|
}
|
|
16163
15944
|
async function handleInit(args, ctx) {
|
|
16164
15945
|
const cwd = process.cwd();
|
|
16165
|
-
const targetPath =
|
|
15946
|
+
const targetPath = join22(cwd, "AICLI.md");
|
|
16166
15947
|
const force = args.includes("--force");
|
|
16167
|
-
if (
|
|
15948
|
+
if (existsSync28(targetPath) && !force) {
|
|
16168
15949
|
ctx.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
16169
15950
|
Use /init --force to overwrite.` });
|
|
16170
15951
|
return;
|
|
@@ -16326,9 +16107,58 @@ async function handleIndex(args, ctx) {
|
|
|
16326
16107
|
async function handleMemory(args, ctx) {
|
|
16327
16108
|
const sub = args[0];
|
|
16328
16109
|
if (sub === "add" && args.length > 1) {
|
|
16329
|
-
|
|
16110
|
+
const rest = args.slice(1);
|
|
16111
|
+
const opts = {};
|
|
16112
|
+
const text = [];
|
|
16113
|
+
for (let i = 0; i < rest.length; i++) {
|
|
16114
|
+
if (rest[i] === "--scope" && rest[i + 1]) {
|
|
16115
|
+
opts.scope = rest[++i];
|
|
16116
|
+
} else if (rest[i] === "--sensitivity" && rest[i + 1]) {
|
|
16117
|
+
opts.sensitivity = rest[++i];
|
|
16118
|
+
} else if (rest[i] === "--expires-at" && rest[i + 1]) {
|
|
16119
|
+
opts.expiresAt = rest[++i];
|
|
16120
|
+
} else text.push(rest[i]);
|
|
16121
|
+
}
|
|
16122
|
+
if (text.length === 0) {
|
|
16123
|
+
ctx.send({ type: "info", message: "Usage: /memory add [--scope personal|project] [--sensitivity low|medium|high] [--expires-at ISO] <text>" });
|
|
16124
|
+
return;
|
|
16125
|
+
}
|
|
16126
|
+
ctx.memoryAdd(text.join(" "), opts);
|
|
16127
|
+
} else if (sub === "edit") {
|
|
16128
|
+
const id = args[1];
|
|
16129
|
+
const text = args.slice(2).join(" ").trim();
|
|
16130
|
+
if (!id || !text) {
|
|
16131
|
+
ctx.send({ type: "info", message: "Usage: /memory edit <id> <new text>" });
|
|
16132
|
+
return;
|
|
16133
|
+
}
|
|
16134
|
+
ctx.memoryEdit(id, text);
|
|
16135
|
+
} else if (sub === "approve" || sub === "reject" || sub === "delete" || sub === "expire") {
|
|
16136
|
+
if (!args[1]) {
|
|
16137
|
+
ctx.send({ type: "info", message: `Usage: /memory ${sub} <id>` });
|
|
16138
|
+
return;
|
|
16139
|
+
}
|
|
16140
|
+
ctx.handleMemoryManage(sub, args[1], sub === "expire" ? args[2] : void 0);
|
|
16141
|
+
} else if (sub === "export") {
|
|
16142
|
+
if (args[1] === "md") {
|
|
16143
|
+
const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-HWFNDQFQ.js");
|
|
16144
|
+
ctx.send({
|
|
16145
|
+
type: "export_data",
|
|
16146
|
+
format: "md",
|
|
16147
|
+
filename: `aicli-memory-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.md`,
|
|
16148
|
+
content: exportMemoryEntries2(ctx.config.getConfigDir(), process.cwd(), "md")
|
|
16149
|
+
});
|
|
16150
|
+
} else {
|
|
16151
|
+
ctx.handleMemoryManage("export");
|
|
16152
|
+
}
|
|
16330
16153
|
} else if (sub === "clear") {
|
|
16331
|
-
|
|
16154
|
+
if (args[1] === "confirm") {
|
|
16155
|
+
ctx.memoryClear();
|
|
16156
|
+
} else {
|
|
16157
|
+
ctx.send({
|
|
16158
|
+
type: "info",
|
|
16159
|
+
message: "\u26A0 This permanently clears ALL persistent memory (a timestamped .bak backup is kept next to memory.jsonl).\nRun `/memory clear confirm` to proceed, or manage individual entries in the Memory panel."
|
|
16160
|
+
});
|
|
16161
|
+
}
|
|
16332
16162
|
} else {
|
|
16333
16163
|
ctx.memoryShow();
|
|
16334
16164
|
}
|
|
@@ -16347,8 +16177,8 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
16347
16177
|
ctx.send({ type: "info", message: "\u2713 All added directories removed from context." });
|
|
16348
16178
|
return;
|
|
16349
16179
|
}
|
|
16350
|
-
const dirPath =
|
|
16351
|
-
if (!
|
|
16180
|
+
const dirPath = resolve9(sub);
|
|
16181
|
+
if (!existsSync28(dirPath)) {
|
|
16352
16182
|
ctx.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
16353
16183
|
return;
|
|
16354
16184
|
}
|
|
@@ -16913,10 +16743,10 @@ var SessionHandler = class {
|
|
|
16913
16743
|
return;
|
|
16914
16744
|
}
|
|
16915
16745
|
case "ask_user_response": {
|
|
16916
|
-
const
|
|
16917
|
-
if (
|
|
16746
|
+
const resolve13 = this.pendingAskUser.get(msg.requestId);
|
|
16747
|
+
if (resolve13) {
|
|
16918
16748
|
this.pendingAskUser.delete(msg.requestId);
|
|
16919
|
-
|
|
16749
|
+
resolve13(msg.answer);
|
|
16920
16750
|
}
|
|
16921
16751
|
return;
|
|
16922
16752
|
}
|
|
@@ -16929,10 +16759,10 @@ var SessionHandler = class {
|
|
|
16929
16759
|
case "memory_manage":
|
|
16930
16760
|
return this.handleMemoryManage(msg.action, msg.id, msg.expiresAt);
|
|
16931
16761
|
case "auto_pause_response": {
|
|
16932
|
-
const
|
|
16933
|
-
if (
|
|
16762
|
+
const resolve13 = this.pendingAutoPause.get(msg.requestId);
|
|
16763
|
+
if (resolve13) {
|
|
16934
16764
|
this.pendingAutoPause.delete(msg.requestId);
|
|
16935
|
-
|
|
16765
|
+
resolve13({ action: msg.action, message: msg.message });
|
|
16936
16766
|
}
|
|
16937
16767
|
return;
|
|
16938
16768
|
}
|
|
@@ -16947,10 +16777,10 @@ var SessionHandler = class {
|
|
|
16947
16777
|
this.hubOrchestrator?.abort();
|
|
16948
16778
|
return;
|
|
16949
16779
|
case "hub_steer": {
|
|
16950
|
-
const
|
|
16951
|
-
if (
|
|
16780
|
+
const resolve13 = this.pendingHubReview.get(msg.requestId);
|
|
16781
|
+
if (resolve13) {
|
|
16952
16782
|
this.pendingHubReview.delete(msg.requestId);
|
|
16953
|
-
|
|
16783
|
+
resolve13({ action: msg.action, message: msg.message });
|
|
16954
16784
|
}
|
|
16955
16785
|
return;
|
|
16956
16786
|
}
|
|
@@ -16963,9 +16793,9 @@ var SessionHandler = class {
|
|
|
16963
16793
|
runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
|
|
16964
16794
|
this.toolExecutor.cancelAll();
|
|
16965
16795
|
if (this.abortController) this.abortController.abort();
|
|
16966
|
-
for (const
|
|
16796
|
+
for (const resolve13 of this.pendingAskUser.values()) resolve13(null);
|
|
16967
16797
|
this.pendingAskUser.clear();
|
|
16968
|
-
for (const
|
|
16798
|
+
for (const resolve13 of this.pendingAutoPause.values()) resolve13({ action: "stop" });
|
|
16969
16799
|
this.pendingAutoPause.clear();
|
|
16970
16800
|
this.saveIfNeeded();
|
|
16971
16801
|
if (this._bashSessionKey) clearBashSession(this._bashSessionKey);
|
|
@@ -17100,9 +16930,9 @@ var SessionHandler = class {
|
|
|
17100
16930
|
this.hubOrchestrator = orchestrator;
|
|
17101
16931
|
orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
|
|
17102
16932
|
if (config.humanSteer) {
|
|
17103
|
-
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((
|
|
16933
|
+
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve13) => {
|
|
17104
16934
|
const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
17105
|
-
this.pendingHubReview.set(requestId,
|
|
16935
|
+
this.pendingHubReview.set(requestId, resolve13);
|
|
17106
16936
|
this.send({ type: "hub_review", requestId, round, maxRounds });
|
|
17107
16937
|
});
|
|
17108
16938
|
}
|
|
@@ -17435,7 +17265,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
17435
17265
|
this.send({ type: "response_done", content, usage });
|
|
17436
17266
|
if (pendingTeeSave && isCleanDocumentBody(content)) {
|
|
17437
17267
|
try {
|
|
17438
|
-
|
|
17268
|
+
mkdirSync14(dirname9(pendingTeeSave), { recursive: true });
|
|
17439
17269
|
const bodyToSave = stripOuterCodeFence(content);
|
|
17440
17270
|
atomicWriteFileSync(pendingTeeSave, bodyToSave);
|
|
17441
17271
|
undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
|
|
@@ -17460,8 +17290,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
17460
17290
|
onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
|
|
17461
17291
|
requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
|
|
17462
17292
|
const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
17463
|
-
const pauseResp = await new Promise((
|
|
17464
|
-
this.pendingAutoPause.set(requestId,
|
|
17293
|
+
const pauseResp = await new Promise((resolve13) => {
|
|
17294
|
+
this.pendingAutoPause.set(requestId, resolve13);
|
|
17465
17295
|
this.send({
|
|
17466
17296
|
type: "auto_pause_request",
|
|
17467
17297
|
requestId,
|
|
@@ -17611,7 +17441,7 @@ ${summaryContent}`,
|
|
|
17611
17441
|
let isError = false;
|
|
17612
17442
|
let summary;
|
|
17613
17443
|
try {
|
|
17614
|
-
|
|
17444
|
+
mkdirSync14(dirname9(saveToFile), { recursive: true });
|
|
17615
17445
|
fileStream = createWriteStream2(saveToFile);
|
|
17616
17446
|
const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
|
|
17617
17447
|
const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
|
|
@@ -17640,8 +17470,8 @@ ${summaryContent}`,
|
|
|
17640
17470
|
}
|
|
17641
17471
|
if (chunk.done) break;
|
|
17642
17472
|
}
|
|
17643
|
-
await new Promise((
|
|
17644
|
-
fileStream.end((err) => err ? reject(err) :
|
|
17473
|
+
await new Promise((resolve13, reject) => {
|
|
17474
|
+
fileStream.end((err) => err ? reject(err) : resolve13());
|
|
17645
17475
|
});
|
|
17646
17476
|
const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
|
|
17647
17477
|
if (verdict.kind === "reject") {
|
|
@@ -17667,7 +17497,7 @@ ${summaryContent}`,
|
|
|
17667
17497
|
} catch (err) {
|
|
17668
17498
|
if (fileStream) {
|
|
17669
17499
|
try {
|
|
17670
|
-
await new Promise((
|
|
17500
|
+
await new Promise((resolve13) => fileStream.end(() => resolve13()));
|
|
17671
17501
|
} catch {
|
|
17672
17502
|
}
|
|
17673
17503
|
}
|
|
@@ -17874,7 +17704,11 @@ ${summaryContent}`,
|
|
|
17874
17704
|
const entries = listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
|
|
17875
17705
|
const content = entries.length === 0 ? "(empty \u2014 no persistent memory entries yet)" : entries.map((entry) => {
|
|
17876
17706
|
const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
|
|
17877
|
-
const source =
|
|
17707
|
+
const source = [
|
|
17708
|
+
entry.source ?? "unknown",
|
|
17709
|
+
entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : "",
|
|
17710
|
+
entry.sourceProject ? `project ${entry.sourceProject}` : ""
|
|
17711
|
+
].filter(Boolean).join(" \xB7 ");
|
|
17878
17712
|
return `## ${entry.id.slice(0, 8)} \xB7 ${status} \xB7 scope:${entry.scope} \xB7 sensitivity:${entry.sensitivity}
|
|
17879
17713
|
Source: ${source}
|
|
17880
17714
|
${entry.content}`;
|
|
@@ -17886,26 +17720,41 @@ ${entry.content}`;
|
|
|
17886
17720
|
});
|
|
17887
17721
|
this.sendMemoryEntries();
|
|
17888
17722
|
}
|
|
17889
|
-
memoryAdd(text) {
|
|
17723
|
+
memoryAdd(text, opts) {
|
|
17724
|
+
const asSensitivity = (v) => v === "low" || v === "medium" || v === "high" ? v : void 0;
|
|
17890
17725
|
try {
|
|
17891
17726
|
const entry = addMemoryEntry(this.config.getConfigDir(), text, {
|
|
17892
|
-
scope: "personal",
|
|
17727
|
+
scope: parseWritableScope(opts?.scope) ?? "personal",
|
|
17728
|
+
source: "manual",
|
|
17729
|
+
sensitivity: asSensitivity(opts?.sensitivity),
|
|
17730
|
+
expiresAt: opts?.expiresAt,
|
|
17893
17731
|
cwd: process.cwd(),
|
|
17894
17732
|
redact: true
|
|
17895
17733
|
});
|
|
17896
17734
|
const status = entry.approved ? "approved" : "pending approval";
|
|
17897
|
-
this.send({ type: "info", message: `\u{1F4DD} Memory entry ${entry.id.slice(0, 8)} added (${status}).` });
|
|
17735
|
+
this.send({ type: "info", message: `\u{1F4DD} Memory entry ${entry.id.slice(0, 8)} added (${status}, scope:${entry.scope}).` });
|
|
17898
17736
|
this.sendMemoryEntries();
|
|
17899
17737
|
} catch (err) {
|
|
17900
17738
|
this.send({ type: "error", message: `Failed to write memory: ${err.message}` });
|
|
17901
17739
|
}
|
|
17902
17740
|
}
|
|
17741
|
+
memoryEdit(id, text) {
|
|
17742
|
+
try {
|
|
17743
|
+
const entry = updateMemoryEntry(this.config.getConfigDir(), id, text, { source: "manual", redact: true });
|
|
17744
|
+
const status = entry.approved ? "approved" : `pending approval \u2014 approve it in the Memory panel or /memory approve ${entry.id.slice(0, 8)}`;
|
|
17745
|
+
this.send({ type: "info", message: `\u{1F4DD} Memory ${entry.id.slice(0, 8)} updated (${status}).` });
|
|
17746
|
+
this.sendMemoryEntries();
|
|
17747
|
+
} catch (err) {
|
|
17748
|
+
this.send({ type: "error", message: `Failed to update memory: ${err.message}` });
|
|
17749
|
+
}
|
|
17750
|
+
}
|
|
17903
17751
|
memoryClear() {
|
|
17904
17752
|
const configDir = this.config.getConfigDir();
|
|
17905
17753
|
try {
|
|
17906
|
-
|
|
17907
|
-
|
|
17908
|
-
|
|
17754
|
+
const result = clearMemoryEntries(configDir);
|
|
17755
|
+
const backupNote = result.backupPath ? `
|
|
17756
|
+
Backup saved: ${result.backupPath}` : "";
|
|
17757
|
+
this.send({ type: "info", message: `\u{1F5D1}\uFE0F Persistent memory cleared (${result.cleared} entries).${backupNote}` });
|
|
17909
17758
|
this.sendMemoryEntries();
|
|
17910
17759
|
} catch (err) {
|
|
17911
17760
|
this.send({ type: "error", message: `Failed to clear memory: ${err.message}` });
|
|
@@ -17921,6 +17770,7 @@ ${entry.content}`;
|
|
|
17921
17770
|
id: entry.id,
|
|
17922
17771
|
content: entry.content,
|
|
17923
17772
|
scope: entry.scope,
|
|
17773
|
+
source: entry.source,
|
|
17924
17774
|
sourceSession: entry.sourceSession,
|
|
17925
17775
|
sourceProject: entry.sourceProject,
|
|
17926
17776
|
createdAt: entry.createdAt,
|
|
@@ -17970,7 +17820,7 @@ ${entry.content}`;
|
|
|
17970
17820
|
return;
|
|
17971
17821
|
}
|
|
17972
17822
|
try {
|
|
17973
|
-
const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-
|
|
17823
|
+
const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-ML5FF5L6.js");
|
|
17974
17824
|
const loaded = loadChatIndex2();
|
|
17975
17825
|
if (!loaded || loaded.idx.chunks.length === 0) {
|
|
17976
17826
|
this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
|
|
@@ -18006,7 +17856,7 @@ ${entry.content}`;
|
|
|
18006
17856
|
}
|
|
18007
17857
|
async handleMemoryStatus() {
|
|
18008
17858
|
try {
|
|
18009
|
-
const { getChatIndexStatus } = await import("./chat-index-
|
|
17859
|
+
const { getChatIndexStatus } = await import("./chat-index-ML5FF5L6.js");
|
|
18010
17860
|
const s = getChatIndexStatus();
|
|
18011
17861
|
this.send({
|
|
18012
17862
|
type: "memory_status",
|
|
@@ -18031,7 +17881,7 @@ ${entry.content}`;
|
|
|
18031
17881
|
type: "info",
|
|
18032
17882
|
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"
|
|
18033
17883
|
});
|
|
18034
|
-
const { buildChatIndex } = await import("./chat-index-
|
|
17884
|
+
const { buildChatIndex } = await import("./chat-index-ML5FF5L6.js");
|
|
18035
17885
|
const stats = await buildChatIndex({
|
|
18036
17886
|
full,
|
|
18037
17887
|
onProgress: (p) => {
|
|
@@ -18105,6 +17955,7 @@ ${entry.content}`;
|
|
|
18105
17955
|
activeSkill,
|
|
18106
17956
|
planMode: this.planMode,
|
|
18107
17957
|
configDir: this.config.getConfigDir(),
|
|
17958
|
+
memoryMaxChars: this.config.get("memory")?.maxChars,
|
|
18108
17959
|
userProfile: this.config.get("userProfile")
|
|
18109
17960
|
});
|
|
18110
17961
|
let stable = baseStable;
|
|
@@ -18186,8 +18037,8 @@ async function setupProxy(configProxy) {
|
|
|
18186
18037
|
}
|
|
18187
18038
|
|
|
18188
18039
|
// src/web/auth.ts
|
|
18189
|
-
import { existsSync as
|
|
18190
|
-
import { isAbsolute as isAbsolute3, join as
|
|
18040
|
+
import { existsSync as existsSync30, readFileSync as readFileSync23, writeFileSync as writeFileSync5, mkdirSync as mkdirSync15, readdirSync as readdirSync15, copyFileSync } from "fs";
|
|
18041
|
+
import { isAbsolute as isAbsolute3, join as join25, relative as relative5 } from "path";
|
|
18191
18042
|
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
18192
18043
|
var USERS_FILE = "users.json";
|
|
18193
18044
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
@@ -18202,7 +18053,7 @@ var AuthManager = class {
|
|
|
18202
18053
|
db;
|
|
18203
18054
|
constructor(baseDir) {
|
|
18204
18055
|
this.baseDir = baseDir;
|
|
18205
|
-
this.usersFile =
|
|
18056
|
+
this.usersFile = join25(baseDir, USERS_FILE);
|
|
18206
18057
|
this.db = this.loadOrCreate();
|
|
18207
18058
|
}
|
|
18208
18059
|
// ── Public API ─────────────────────────────────────────────────
|
|
@@ -18231,9 +18082,9 @@ var AuthManager = class {
|
|
|
18231
18082
|
}
|
|
18232
18083
|
const salt = randomBytes(16).toString("hex");
|
|
18233
18084
|
const passwordHash = this.hashPassword(password, salt);
|
|
18234
|
-
const dataDir =
|
|
18235
|
-
const fullDataDir =
|
|
18236
|
-
|
|
18085
|
+
const dataDir = join25(USERS_DIR, username);
|
|
18086
|
+
const fullDataDir = join25(this.baseDir, dataDir);
|
|
18087
|
+
mkdirSync15(join25(fullDataDir, "history"), { recursive: true });
|
|
18237
18088
|
const user = {
|
|
18238
18089
|
username,
|
|
18239
18090
|
passwordHash,
|
|
@@ -18348,7 +18199,7 @@ var AuthManager = class {
|
|
|
18348
18199
|
getUserDataDir(username) {
|
|
18349
18200
|
const user = this.db.users.find((u) => u.username === username);
|
|
18350
18201
|
if (!user) throw new Error(`User not found: ${username}`);
|
|
18351
|
-
return
|
|
18202
|
+
return join25(this.baseDir, user.dataDir);
|
|
18352
18203
|
}
|
|
18353
18204
|
/** List all usernames */
|
|
18354
18205
|
listUsers() {
|
|
@@ -18384,30 +18235,30 @@ var AuthManager = class {
|
|
|
18384
18235
|
const err = this.register(username, password);
|
|
18385
18236
|
if (err) return err;
|
|
18386
18237
|
const userDir = this.getUserDataDir(username);
|
|
18387
|
-
const globalConfig =
|
|
18388
|
-
if (
|
|
18238
|
+
const globalConfig = join25(this.baseDir, "config.json");
|
|
18239
|
+
if (existsSync30(globalConfig)) {
|
|
18389
18240
|
try {
|
|
18390
|
-
const content =
|
|
18391
|
-
writeFileSync5(
|
|
18241
|
+
const content = readFileSync23(globalConfig, "utf-8");
|
|
18242
|
+
writeFileSync5(join25(userDir, "config.json"), content, "utf-8");
|
|
18392
18243
|
} catch {
|
|
18393
18244
|
}
|
|
18394
18245
|
}
|
|
18395
|
-
const globalMemory =
|
|
18396
|
-
if (
|
|
18246
|
+
const globalMemory = join25(this.baseDir, "memory.md");
|
|
18247
|
+
if (existsSync30(globalMemory)) {
|
|
18397
18248
|
try {
|
|
18398
|
-
const content =
|
|
18399
|
-
writeFileSync5(
|
|
18249
|
+
const content = readFileSync23(globalMemory, "utf-8");
|
|
18250
|
+
writeFileSync5(join25(userDir, "memory.md"), content, "utf-8");
|
|
18400
18251
|
} catch {
|
|
18401
18252
|
}
|
|
18402
18253
|
}
|
|
18403
|
-
const globalHistory =
|
|
18404
|
-
if (
|
|
18254
|
+
const globalHistory = join25(this.baseDir, "history");
|
|
18255
|
+
if (existsSync30(globalHistory)) {
|
|
18405
18256
|
try {
|
|
18406
18257
|
const files = readdirSync15(globalHistory).filter((f) => f.endsWith(".json"));
|
|
18407
|
-
const userHistory =
|
|
18258
|
+
const userHistory = join25(userDir, "history");
|
|
18408
18259
|
for (const f of files) {
|
|
18409
18260
|
try {
|
|
18410
|
-
copyFileSync(
|
|
18261
|
+
copyFileSync(join25(globalHistory, f), join25(userHistory, f));
|
|
18411
18262
|
} catch {
|
|
18412
18263
|
}
|
|
18413
18264
|
}
|
|
@@ -18418,9 +18269,9 @@ var AuthManager = class {
|
|
|
18418
18269
|
}
|
|
18419
18270
|
// ── Private methods ────────────────────────────────────────────
|
|
18420
18271
|
loadOrCreate() {
|
|
18421
|
-
if (
|
|
18272
|
+
if (existsSync30(this.usersFile)) {
|
|
18422
18273
|
try {
|
|
18423
|
-
const parsed = JSON.parse(
|
|
18274
|
+
const parsed = JSON.parse(readFileSync23(this.usersFile, "utf-8"));
|
|
18424
18275
|
if (!this.isValidUsersDB(parsed)) {
|
|
18425
18276
|
throw new Error("database structure is invalid");
|
|
18426
18277
|
}
|
|
@@ -18454,7 +18305,7 @@ var AuthManager = class {
|
|
|
18454
18305
|
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)) {
|
|
18455
18306
|
return false;
|
|
18456
18307
|
}
|
|
18457
|
-
const dataDirFromBase = relative5(this.baseDir,
|
|
18308
|
+
const dataDirFromBase = relative5(this.baseDir, join25(this.baseDir, record.dataDir));
|
|
18458
18309
|
if (dataDirFromBase.startsWith("..") || isAbsolute3(dataDirFromBase)) return false;
|
|
18459
18310
|
if (record.hashVersion !== void 0 && record.hashVersion !== 1 && record.hashVersion !== 2) return false;
|
|
18460
18311
|
if (record.tokensRevokedBefore !== void 0 && !Number.isFinite(record.tokensRevokedBefore)) return false;
|
|
@@ -18466,7 +18317,7 @@ var AuthManager = class {
|
|
|
18466
18317
|
this.saveDB(this.db);
|
|
18467
18318
|
}
|
|
18468
18319
|
saveDB(db) {
|
|
18469
|
-
|
|
18320
|
+
mkdirSync15(this.baseDir, { recursive: true });
|
|
18470
18321
|
atomicWriteFileSync(this.usersFile, JSON.stringify(db, null, 2));
|
|
18471
18322
|
}
|
|
18472
18323
|
/** Legacy hash — kept only for migrating old users (v0.2.x) */
|
|
@@ -18498,11 +18349,11 @@ function getModuleDir() {
|
|
|
18498
18349
|
if (typeof import.meta?.url === "string") {
|
|
18499
18350
|
const url = new URL(import.meta.url);
|
|
18500
18351
|
const filePath = url.pathname.replace(/^\/([A-Z]:)/i, "$1");
|
|
18501
|
-
return
|
|
18352
|
+
return dirname10(filePath);
|
|
18502
18353
|
}
|
|
18503
18354
|
} catch {
|
|
18504
18355
|
}
|
|
18505
|
-
return
|
|
18356
|
+
return resolve12(".");
|
|
18506
18357
|
}
|
|
18507
18358
|
async function startWebServer(options = {}) {
|
|
18508
18359
|
const port = options.port ?? 3e3;
|
|
@@ -18570,8 +18421,8 @@ async function startWebServer(options = {}) {
|
|
|
18570
18421
|
}
|
|
18571
18422
|
}
|
|
18572
18423
|
let skillManager = null;
|
|
18573
|
-
const skillsDir =
|
|
18574
|
-
if (
|
|
18424
|
+
const skillsDir = join26(config.getConfigDir(), SKILLS_DIR_NAME);
|
|
18425
|
+
if (existsSync31(skillsDir)) {
|
|
18575
18426
|
skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
|
|
18576
18427
|
skillManager.loadSkills(pluginAssets.skillDirs);
|
|
18577
18428
|
const count = skillManager.listSkills().length;
|
|
@@ -18642,18 +18493,18 @@ async function startWebServer(options = {}) {
|
|
|
18642
18493
|
next();
|
|
18643
18494
|
};
|
|
18644
18495
|
const moduleDir = getModuleDir();
|
|
18645
|
-
let clientDir =
|
|
18646
|
-
if (!
|
|
18647
|
-
clientDir =
|
|
18496
|
+
let clientDir = join26(moduleDir, "web", "client");
|
|
18497
|
+
if (!existsSync31(clientDir)) {
|
|
18498
|
+
clientDir = join26(moduleDir, "client");
|
|
18648
18499
|
}
|
|
18649
|
-
if (!
|
|
18650
|
-
clientDir =
|
|
18500
|
+
if (!existsSync31(clientDir)) {
|
|
18501
|
+
clientDir = join26(moduleDir, "..", "..", "src", "web", "client");
|
|
18651
18502
|
}
|
|
18652
|
-
if (!
|
|
18653
|
-
clientDir =
|
|
18503
|
+
if (!existsSync31(clientDir)) {
|
|
18504
|
+
clientDir = join26(process.cwd(), "src", "web", "client");
|
|
18654
18505
|
}
|
|
18655
18506
|
console.log(` Static files: ${clientDir}`);
|
|
18656
|
-
app.use("/vendor", express.static(
|
|
18507
|
+
app.use("/vendor", express.static(join26(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
|
|
18657
18508
|
app.use(express.static(clientDir));
|
|
18658
18509
|
app.get("/api/status", (_req, res) => {
|
|
18659
18510
|
res.json({
|
|
@@ -18720,10 +18571,10 @@ async function startWebServer(options = {}) {
|
|
|
18720
18571
|
app.get("/api/files", requireAuth, (req, res) => {
|
|
18721
18572
|
const cwd = process.cwd();
|
|
18722
18573
|
const prefix = req.query.prefix || "";
|
|
18723
|
-
const targetDir =
|
|
18574
|
+
const targetDir = join26(cwd, prefix);
|
|
18724
18575
|
try {
|
|
18725
|
-
const canonicalTarget = realpathSync3(
|
|
18726
|
-
const canonicalCwd = realpathSync3(
|
|
18576
|
+
const canonicalTarget = realpathSync3(resolve12(targetDir));
|
|
18577
|
+
const canonicalCwd = realpathSync3(resolve12(cwd));
|
|
18727
18578
|
if (!canonicalTarget.startsWith(canonicalCwd + sep4) && canonicalTarget !== canonicalCwd) {
|
|
18728
18579
|
res.json({ files: [] });
|
|
18729
18580
|
return;
|
|
@@ -18737,7 +18588,7 @@ async function startWebServer(options = {}) {
|
|
|
18737
18588
|
const entries = readdirSync16(targetDir, { withFileTypes: true });
|
|
18738
18589
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
18739
18590
|
name: e.name,
|
|
18740
|
-
path: relative6(cwd,
|
|
18591
|
+
path: relative6(cwd, join26(targetDir, e.name)).replace(/\\/g, "/"),
|
|
18741
18592
|
isDir: e.isDirectory()
|
|
18742
18593
|
}));
|
|
18743
18594
|
res.json({ files });
|
|
@@ -18777,8 +18628,8 @@ async function startWebServer(options = {}) {
|
|
|
18777
18628
|
try {
|
|
18778
18629
|
const authUser = req._authUser;
|
|
18779
18630
|
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
18780
|
-
const filePath =
|
|
18781
|
-
if (!
|
|
18631
|
+
const filePath = join26(histDir, `${id}.json`);
|
|
18632
|
+
if (!existsSync31(filePath)) {
|
|
18782
18633
|
res.status(404).json({ error: "Session not found" });
|
|
18783
18634
|
return;
|
|
18784
18635
|
}
|
|
@@ -18793,7 +18644,7 @@ async function startWebServer(options = {}) {
|
|
|
18793
18644
|
res.status(404).json({ error: "Session not found" });
|
|
18794
18645
|
return;
|
|
18795
18646
|
}
|
|
18796
|
-
const data = JSON.parse(
|
|
18647
|
+
const data = JSON.parse(readFileSync24(filePath, "utf-8"));
|
|
18797
18648
|
res.json({ session: data });
|
|
18798
18649
|
} catch (err) {
|
|
18799
18650
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -18806,10 +18657,10 @@ async function startWebServer(options = {}) {
|
|
|
18806
18657
|
return;
|
|
18807
18658
|
}
|
|
18808
18659
|
const cwd = process.cwd();
|
|
18809
|
-
const fullPath =
|
|
18660
|
+
const fullPath = resolve12(join26(cwd, filePath));
|
|
18810
18661
|
try {
|
|
18811
18662
|
const canonicalFull = realpathSync3(fullPath);
|
|
18812
|
-
const canonicalCwd = realpathSync3(
|
|
18663
|
+
const canonicalCwd = realpathSync3(resolve12(cwd));
|
|
18813
18664
|
if (!canonicalFull.startsWith(canonicalCwd + sep4) && canonicalFull !== canonicalCwd) {
|
|
18814
18665
|
res.json({ error: "Access denied" });
|
|
18815
18666
|
return;
|
|
@@ -18824,7 +18675,7 @@ async function startWebServer(options = {}) {
|
|
|
18824
18675
|
res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
|
|
18825
18676
|
return;
|
|
18826
18677
|
}
|
|
18827
|
-
const content =
|
|
18678
|
+
const content = readFileSync24(fullPath, "utf-8");
|
|
18828
18679
|
res.json({ content, size: stat.size });
|
|
18829
18680
|
} catch {
|
|
18830
18681
|
res.json({ error: "Cannot read file" });
|
|
@@ -19028,7 +18879,7 @@ async function startWebServer(options = {}) {
|
|
|
19028
18879
|
});
|
|
19029
18880
|
const MAX_PORT_ATTEMPTS = 10;
|
|
19030
18881
|
let actualPort = port;
|
|
19031
|
-
const result = await new Promise((
|
|
18882
|
+
const result = await new Promise((resolve13, reject) => {
|
|
19032
18883
|
const tryListen = (attempt) => {
|
|
19033
18884
|
server.once("error", (err) => {
|
|
19034
18885
|
if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
|
|
@@ -19059,7 +18910,7 @@ async function startWebServer(options = {}) {
|
|
|
19059
18910
|
}
|
|
19060
18911
|
console.log(` Press Ctrl+C to stop
|
|
19061
18912
|
`);
|
|
19062
|
-
|
|
18913
|
+
resolve13({ port: actualPort, host, url });
|
|
19063
18914
|
});
|
|
19064
18915
|
};
|
|
19065
18916
|
tryListen(1);
|
|
@@ -19079,17 +18930,17 @@ function resolveProjectMcpPath() {
|
|
|
19079
18930
|
const cwd = process.cwd();
|
|
19080
18931
|
const gitRoot = getGitRoot(cwd);
|
|
19081
18932
|
const projectRoot = gitRoot ?? cwd;
|
|
19082
|
-
const configPath =
|
|
19083
|
-
return
|
|
18933
|
+
const configPath = join26(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
18934
|
+
return existsSync31(configPath) ? configPath : null;
|
|
19084
18935
|
}
|
|
19085
18936
|
function loadProjectMcpConfig() {
|
|
19086
18937
|
const cwd = process.cwd();
|
|
19087
18938
|
const gitRoot = getGitRoot(cwd);
|
|
19088
18939
|
const projectRoot = gitRoot ?? cwd;
|
|
19089
|
-
const configPath =
|
|
19090
|
-
if (!
|
|
18940
|
+
const configPath = join26(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
18941
|
+
if (!existsSync31(configPath)) return null;
|
|
19091
18942
|
try {
|
|
19092
|
-
const raw = JSON.parse(
|
|
18943
|
+
const raw = JSON.parse(readFileSync24(configPath, "utf-8"));
|
|
19093
18944
|
return raw.mcpServers ?? raw;
|
|
19094
18945
|
} catch {
|
|
19095
18946
|
return null;
|