jinzd-ai-cli 0.4.216 → 0.4.218
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 +6 -3
- package/dist/{batch-C6HCXZIG.js → batch-7V7OTMUP.js} +2 -2
- package/dist/{chat-index-R2E27VXN.js → chat-index-PS274XM7.js} +1 -1
- package/dist/{chunk-TJ6GGN4B.js → chunk-5CA2TJ5F.js} +1 -1
- package/dist/{chunk-OLN7VUZA.js → chunk-C2Z42DI5.js} +3 -1
- package/dist/{chunk-TCOC4AUI.js → chunk-GX3HSGJX.js} +690 -169
- package/dist/{chunk-PNTLA3MN.js → chunk-H2UIHGHH.js} +23 -22
- package/dist/{chunk-GY3C3C2Y.js → chunk-L4UREAID.js} +3 -3
- package/dist/{chunk-77HDCGTN.js → chunk-MWKE2TNS.js} +1 -1
- package/dist/{chunk-7YWS3JLU.js → chunk-NTCB7CMT.js} +3 -1
- package/dist/{chunk-6HLKCSB3.js → chunk-OUC75QCF.js} +1 -1
- package/dist/{chunk-WAI3WPV2.js → chunk-P4VBLXKS.js} +1 -1
- package/dist/{chunk-VW7Y27WW.js → chunk-VGFTM3XT.js} +1 -1
- package/dist/{chunk-OQGVGPEK.js → chunk-VHY6NVMQ.js} +2 -0
- package/dist/{ci-6ZTFO3LX.js → ci-L6GH2WVC.js} +4 -4
- package/dist/{ci-format-CLZ6QJRL.js → ci-format-WW7454AY.js} +2 -2
- package/dist/{constants-APSORFOH.js → constants-NCTFSHDU.js} +3 -1
- package/dist/{doctor-cli-GUIX4X5F.js → doctor-cli-EWMFBP5Q.js} +4 -4
- package/dist/electron-server.js +965 -377
- package/dist/{hub-ZGHQWNWE.js → hub-CDL6T7CP.js} +1 -1
- package/dist/index.js +259 -97
- package/dist/{pr-JOL3IAGV.js → pr-D6PEKEGK.js} +4 -4
- package/dist/{run-tests-4PKSIVK5.js → run-tests-NXVVKAK2.js} +1 -1
- package/dist/{run-tests-ZRK4TQUN.js → run-tests-SWU2XEV7.js} +2 -2
- package/dist/{server-FXUF5P64.js → server-LHYSS6CK.js} +207 -100
- package/dist/{server-O3XHT56X.js → server-WUT7VYTD.js} +5 -4
- package/dist/{task-orchestrator-TUMDTOAX.js → task-orchestrator-C5AA2BI5.js} +5 -4
- package/dist/{usage-X7MJX4YD.js → usage-6ZUUJBI2.js} +2 -2
- package/dist/web/client/app.js +53 -2
- package/dist/web/client/index.html +11 -1
- package/package.json +1 -1
package/dist/electron-server.js
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
MCP_TOOL_PREFIX,
|
|
28
28
|
MEMORY_FILE_NAME,
|
|
29
29
|
MEMORY_MAX_CHARS,
|
|
30
|
+
MEMORY_STORE_FILE_NAME,
|
|
30
31
|
PLAN_MODE_READONLY_TOOLS,
|
|
31
32
|
PLAN_MODE_SYSTEM_ADDON,
|
|
32
33
|
PLUGINS_DIR_NAME,
|
|
@@ -37,7 +38,7 @@ import {
|
|
|
37
38
|
VERSION,
|
|
38
39
|
buildUserIdentityPrompt,
|
|
39
40
|
runTestsTool
|
|
40
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-C2Z42DI5.js";
|
|
41
42
|
import {
|
|
42
43
|
hasSemanticIndex,
|
|
43
44
|
semanticSearch
|
|
@@ -47,11 +48,13 @@ import {
|
|
|
47
48
|
} from "./chunk-RWM2GFRC.js";
|
|
48
49
|
import "./chunk-IEQAE3QG.js";
|
|
49
50
|
import {
|
|
51
|
+
DEFAULT_PATTERNS,
|
|
50
52
|
loadChatIndex,
|
|
51
53
|
redactJson,
|
|
54
|
+
redactString,
|
|
52
55
|
scanString,
|
|
53
56
|
searchChatMemory
|
|
54
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-VHY6NVMQ.js";
|
|
55
58
|
import "./chunk-JV5N65KN.js";
|
|
56
59
|
import {
|
|
57
60
|
atomicWriteFileSync
|
|
@@ -61,8 +64,8 @@ import {
|
|
|
61
64
|
import express from "express";
|
|
62
65
|
import { createServer } from "http";
|
|
63
66
|
import { WebSocketServer } from "ws";
|
|
64
|
-
import { join as
|
|
65
|
-
import { existsSync as existsSync28, readFileSync as readFileSync22, readdirSync as
|
|
67
|
+
import { join as join23, dirname as dirname8, resolve as resolve11, relative as relative4, sep as sep3 } from "path";
|
|
68
|
+
import { existsSync as existsSync28, readFileSync as readFileSync22, readdirSync as readdirSync14, statSync as statSync10, realpathSync } from "fs";
|
|
66
69
|
import { networkInterfaces } from "os";
|
|
67
70
|
|
|
68
71
|
// src/config/config-manager.ts
|
|
@@ -1844,12 +1847,12 @@ function findPhantomClaims(content, extraMessages) {
|
|
|
1844
1847
|
const claimed = extractClaimedFilePaths(content);
|
|
1845
1848
|
if (claimed.length === 0) return [];
|
|
1846
1849
|
const normalize = (p) => p.replace(/\\/g, "/").toLowerCase().replace(/^\.\//, "");
|
|
1847
|
-
const
|
|
1850
|
+
const basename7 = (p) => {
|
|
1848
1851
|
const parts = normalize(p).split("/");
|
|
1849
1852
|
return parts[parts.length - 1] ?? "";
|
|
1850
1853
|
};
|
|
1851
1854
|
const written = extractWrittenFilePaths(extraMessages).map(normalize);
|
|
1852
|
-
const writtenBases = new Set(written.map(
|
|
1855
|
+
const writtenBases = new Set(written.map(basename7));
|
|
1853
1856
|
const writtenFull = new Set(written);
|
|
1854
1857
|
const bashText = extractBashCommands(extraMessages).map((c) => c.replace(/\\/g, "/").toLowerCase()).join("\n");
|
|
1855
1858
|
return claimed.filter((raw) => {
|
|
@@ -1858,8 +1861,8 @@ function findPhantomClaims(content, extraMessages) {
|
|
|
1858
1861
|
for (const w of writtenFull) {
|
|
1859
1862
|
if (w.endsWith("/" + norm2) || norm2.endsWith("/" + w)) return false;
|
|
1860
1863
|
}
|
|
1861
|
-
if (writtenBases.has(
|
|
1862
|
-
const base =
|
|
1864
|
+
if (writtenBases.has(basename7(norm2))) return false;
|
|
1865
|
+
const base = basename7(norm2);
|
|
1863
1866
|
if (base && bashText.includes(base)) return false;
|
|
1864
1867
|
return true;
|
|
1865
1868
|
});
|
|
@@ -7179,7 +7182,7 @@ var ToolExecutor = class {
|
|
|
7179
7182
|
rl.resume();
|
|
7180
7183
|
process.stdout.write(prompt);
|
|
7181
7184
|
this.confirming = true;
|
|
7182
|
-
return new Promise((
|
|
7185
|
+
return new Promise((resolve12) => {
|
|
7183
7186
|
let completed = false;
|
|
7184
7187
|
const cleanup = (result) => {
|
|
7185
7188
|
if (completed) return;
|
|
@@ -7189,7 +7192,7 @@ var ToolExecutor = class {
|
|
|
7189
7192
|
rl.pause();
|
|
7190
7193
|
rlAny.output = savedOutput;
|
|
7191
7194
|
this.confirming = false;
|
|
7192
|
-
|
|
7195
|
+
resolve12(result);
|
|
7193
7196
|
};
|
|
7194
7197
|
const onLine = (line) => {
|
|
7195
7198
|
const trimmed = line.trim();
|
|
@@ -7363,7 +7366,7 @@ var ToolExecutor = class {
|
|
|
7363
7366
|
rl.resume();
|
|
7364
7367
|
process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
|
|
7365
7368
|
this.confirming = true;
|
|
7366
|
-
return new Promise((
|
|
7369
|
+
return new Promise((resolve12) => {
|
|
7367
7370
|
let completed = false;
|
|
7368
7371
|
const cleanup = (answer) => {
|
|
7369
7372
|
if (completed) return;
|
|
@@ -7373,7 +7376,7 @@ var ToolExecutor = class {
|
|
|
7373
7376
|
rl.pause();
|
|
7374
7377
|
rlAny.output = savedOutput;
|
|
7375
7378
|
this.confirming = false;
|
|
7376
|
-
|
|
7379
|
+
resolve12(answer === "y");
|
|
7377
7380
|
};
|
|
7378
7381
|
const onLine = (line) => {
|
|
7379
7382
|
const trimmed = line.trim();
|
|
@@ -8622,7 +8625,7 @@ var runInteractiveTool = {
|
|
|
8622
8625
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
8623
8626
|
};
|
|
8624
8627
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
8625
|
-
return new Promise((
|
|
8628
|
+
return new Promise((resolve12) => {
|
|
8626
8629
|
const child = spawn2(executable, cmdArgs.map(String), {
|
|
8627
8630
|
cwd: process.cwd(),
|
|
8628
8631
|
env,
|
|
@@ -8655,22 +8658,22 @@ var runInteractiveTool = {
|
|
|
8655
8658
|
setTimeout(writeNextLine, 400);
|
|
8656
8659
|
const timer = setTimeout(() => {
|
|
8657
8660
|
child.kill();
|
|
8658
|
-
|
|
8661
|
+
resolve12(`${prefixWarnings}[Timeout after ${timeout}ms]
|
|
8659
8662
|
${buildOutput(stdout, stderr)}`);
|
|
8660
8663
|
}, timeout);
|
|
8661
8664
|
child.on("close", (code) => {
|
|
8662
8665
|
clearTimeout(timer);
|
|
8663
8666
|
const output = buildOutput(stdout, stderr);
|
|
8664
8667
|
if (code !== 0 && code !== null) {
|
|
8665
|
-
|
|
8668
|
+
resolve12(`${prefixWarnings}Exit code ${code}:
|
|
8666
8669
|
${output}`);
|
|
8667
8670
|
} else {
|
|
8668
|
-
|
|
8671
|
+
resolve12(`${prefixWarnings}${output || "(no output)"}`);
|
|
8669
8672
|
}
|
|
8670
8673
|
});
|
|
8671
8674
|
child.on("error", (err) => {
|
|
8672
8675
|
clearTimeout(timer);
|
|
8673
|
-
|
|
8676
|
+
resolve12(
|
|
8674
8677
|
`${prefixWarnings}Failed to start process "${executable}": ${err.message}
|
|
8675
8678
|
Hint: On Windows, use the full path to the executable, e.g.:
|
|
8676
8679
|
C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
|
|
@@ -9338,26 +9341,328 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
9338
9341
|
};
|
|
9339
9342
|
|
|
9340
9343
|
// src/tools/builtin/save-memory.ts
|
|
9341
|
-
import {
|
|
9342
|
-
import { join as join9 } from "path";
|
|
9344
|
+
import { join as join11 } from "path";
|
|
9343
9345
|
import { homedir as homedir5 } from "os";
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
+
|
|
9347
|
+
// src/memory/persistent-memory.ts
|
|
9348
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync11 } from "fs";
|
|
9349
|
+
import { randomUUID, createHash as createHash2 } from "crypto";
|
|
9350
|
+
import { dirname as dirname5, join as join10, resolve as resolve6 } from "path";
|
|
9351
|
+
|
|
9352
|
+
// src/tools/git-context.ts
|
|
9353
|
+
import { execSync as execSync2 } from "child_process";
|
|
9354
|
+
import { existsSync as existsSync14 } from "fs";
|
|
9355
|
+
import { join as join9 } from "path";
|
|
9356
|
+
function runGit(cmd, cwd) {
|
|
9357
|
+
try {
|
|
9358
|
+
return execSync2(`git ${cmd}`, {
|
|
9359
|
+
cwd,
|
|
9360
|
+
encoding: "utf-8",
|
|
9361
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
9362
|
+
timeout: 5e3
|
|
9363
|
+
}).trim();
|
|
9364
|
+
} catch {
|
|
9365
|
+
return null;
|
|
9366
|
+
}
|
|
9346
9367
|
}
|
|
9347
|
-
function
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9368
|
+
function getGitRoot(cwd = process.cwd()) {
|
|
9369
|
+
return runGit("rev-parse --show-toplevel", cwd);
|
|
9370
|
+
}
|
|
9371
|
+
function getGitContext(cwd = process.cwd()) {
|
|
9372
|
+
if (!existsSync14(join9(cwd, ".git"))) {
|
|
9373
|
+
const result = runGit("rev-parse --git-dir", cwd);
|
|
9374
|
+
if (!result) return null;
|
|
9375
|
+
}
|
|
9376
|
+
const branch = runGit("rev-parse --abbrev-ref HEAD", cwd);
|
|
9377
|
+
if (!branch) return null;
|
|
9378
|
+
const statusOutput = runGit("status --porcelain", cwd) ?? "";
|
|
9379
|
+
const statusLines = statusOutput ? statusOutput.split("\n").filter(Boolean) : [];
|
|
9380
|
+
const stagedFiles = [];
|
|
9381
|
+
const changedFiles = [];
|
|
9382
|
+
for (const line of statusLines) {
|
|
9383
|
+
const xy = line.slice(0, 2);
|
|
9384
|
+
const file = line.slice(3).trim();
|
|
9385
|
+
const indexStatus = xy[0];
|
|
9386
|
+
const workStatus = xy[1];
|
|
9387
|
+
if (indexStatus && indexStatus !== " " && indexStatus !== "?") {
|
|
9388
|
+
stagedFiles.push(`${indexStatus} ${file}`);
|
|
9389
|
+
}
|
|
9390
|
+
if (workStatus && workStatus !== " ") {
|
|
9391
|
+
changedFiles.push(`${workStatus} ${file}`);
|
|
9392
|
+
}
|
|
9393
|
+
}
|
|
9394
|
+
const logOutput = runGit("log --oneline -3", cwd) ?? "";
|
|
9395
|
+
const recentCommits = logOutput ? logOutput.split("\n").filter(Boolean) : [];
|
|
9396
|
+
const unpushedOutput = runGit("log @{u}..HEAD --oneline", cwd);
|
|
9397
|
+
const hasUnpushed = unpushedOutput !== null && unpushedOutput.trim().length > 0;
|
|
9398
|
+
return {
|
|
9399
|
+
branch,
|
|
9400
|
+
changedFiles,
|
|
9401
|
+
stagedFiles,
|
|
9402
|
+
recentCommits,
|
|
9403
|
+
hasUnpushed
|
|
9404
|
+
};
|
|
9405
|
+
}
|
|
9406
|
+
function formatGitContextForPrompt(ctx) {
|
|
9407
|
+
const lines = ["# Git Repository Status", ""];
|
|
9408
|
+
lines.push(`- **Branch**: \`${ctx.branch}\``);
|
|
9409
|
+
if (ctx.stagedFiles.length > 0) {
|
|
9410
|
+
lines.push(`- **Staged** (${ctx.stagedFiles.length} files):`);
|
|
9411
|
+
for (const f of ctx.stagedFiles.slice(0, 10)) {
|
|
9412
|
+
lines.push(` - ${f}`);
|
|
9413
|
+
}
|
|
9414
|
+
if (ctx.stagedFiles.length > 10) {
|
|
9415
|
+
lines.push(` - ... and ${ctx.stagedFiles.length - 10} more`);
|
|
9416
|
+
}
|
|
9417
|
+
}
|
|
9418
|
+
if (ctx.changedFiles.length > 0) {
|
|
9419
|
+
lines.push(`- **Modified** (${ctx.changedFiles.length} files):`);
|
|
9420
|
+
for (const f of ctx.changedFiles.slice(0, 10)) {
|
|
9421
|
+
lines.push(` - ${f}`);
|
|
9422
|
+
}
|
|
9423
|
+
if (ctx.changedFiles.length > 10) {
|
|
9424
|
+
lines.push(` - ... and ${ctx.changedFiles.length - 10} more`);
|
|
9425
|
+
}
|
|
9426
|
+
}
|
|
9427
|
+
if (ctx.stagedFiles.length === 0 && ctx.changedFiles.length === 0) {
|
|
9428
|
+
lines.push("- **Working tree**: clean");
|
|
9429
|
+
}
|
|
9430
|
+
if (ctx.recentCommits.length > 0) {
|
|
9431
|
+
lines.push("- **Recent commits**:");
|
|
9432
|
+
for (const c of ctx.recentCommits) {
|
|
9433
|
+
lines.push(` - ${c}`);
|
|
9434
|
+
}
|
|
9435
|
+
}
|
|
9436
|
+
if (ctx.hasUnpushed) {
|
|
9437
|
+
lines.push("- \u26A0\uFE0F Has unpushed commits");
|
|
9438
|
+
}
|
|
9439
|
+
return lines.join("\n");
|
|
9440
|
+
}
|
|
9441
|
+
|
|
9442
|
+
// src/memory/persistent-memory.ts
|
|
9443
|
+
function memoryStorePath(configDir) {
|
|
9444
|
+
return join10(configDir, MEMORY_STORE_FILE_NAME);
|
|
9445
|
+
}
|
|
9446
|
+
function memoryMarkdownPath(configDir) {
|
|
9447
|
+
return join10(configDir, MEMORY_FILE_NAME);
|
|
9448
|
+
}
|
|
9449
|
+
function getProjectKey(cwd = process.cwd()) {
|
|
9450
|
+
return resolve6(getGitRoot(cwd) ?? cwd);
|
|
9451
|
+
}
|
|
9452
|
+
function nowIso() {
|
|
9453
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
9454
|
+
}
|
|
9455
|
+
function isValidScope(value) {
|
|
9456
|
+
return value === "personal" || value === "project" || value === "session" || value === "team";
|
|
9457
|
+
}
|
|
9458
|
+
function isValidSensitivity(value) {
|
|
9459
|
+
return value === "low" || value === "medium" || value === "high";
|
|
9460
|
+
}
|
|
9461
|
+
function normalizeEntry(value) {
|
|
9462
|
+
if (!value || typeof value !== "object") return null;
|
|
9463
|
+
const raw = value;
|
|
9464
|
+
const content = typeof raw.content === "string" ? raw.content.trim() : "";
|
|
9465
|
+
if (!content) return null;
|
|
9466
|
+
const createdAt = typeof raw.createdAt === "string" ? raw.createdAt : nowIso();
|
|
9467
|
+
return {
|
|
9468
|
+
id: typeof raw.id === "string" && raw.id ? raw.id : randomUUID(),
|
|
9469
|
+
content,
|
|
9470
|
+
scope: isValidScope(raw.scope) ? raw.scope : "personal",
|
|
9471
|
+
sourceSession: typeof raw.sourceSession === "string" && raw.sourceSession ? raw.sourceSession : void 0,
|
|
9472
|
+
sourceProject: typeof raw.sourceProject === "string" && raw.sourceProject ? raw.sourceProject : void 0,
|
|
9473
|
+
createdAt,
|
|
9474
|
+
updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : createdAt,
|
|
9475
|
+
expiresAt: typeof raw.expiresAt === "string" && raw.expiresAt ? raw.expiresAt : void 0,
|
|
9476
|
+
sensitivity: isValidSensitivity(raw.sensitivity) ? raw.sensitivity : "low",
|
|
9477
|
+
approved: raw.approved === true,
|
|
9478
|
+
redactedKinds: Array.isArray(raw.redactedKinds) ? raw.redactedKinds.filter((x) => typeof x === "string") : void 0
|
|
9479
|
+
};
|
|
9480
|
+
}
|
|
9481
|
+
function legacyEntriesFromMarkdown(configDir) {
|
|
9482
|
+
const file = memoryMarkdownPath(configDir);
|
|
9483
|
+
if (!existsSync15(file)) return [];
|
|
9484
|
+
const content = readFileSync11(file, "utf-8").trim();
|
|
9485
|
+
if (!content) return [];
|
|
9486
|
+
const parts = content.split(/\n(?=##\s+\d{4}-\d{2}-\d{2})/g);
|
|
9487
|
+
const chunks = parts.length > 1 ? parts : [content];
|
|
9488
|
+
return chunks.map((chunk, index) => {
|
|
9489
|
+
const match = chunk.match(/^##\s+([^\n]+)\n([\s\S]*)$/);
|
|
9490
|
+
const body = (match ? match[2] : chunk).trim();
|
|
9491
|
+
const parsed = match ? Date.parse(match[1].replace(" ", "T")) : NaN;
|
|
9492
|
+
const createdAt = Number.isFinite(parsed) ? new Date(parsed).toISOString() : nowIso();
|
|
9493
|
+
const id = "legacy-" + createHash2("sha1").update(`${index}
|
|
9494
|
+
${chunk}`).digest("hex").slice(0, 12);
|
|
9495
|
+
return normalizeEntry({ id, content: body, scope: "personal", createdAt, updatedAt: createdAt, sensitivity: "low", approved: true });
|
|
9496
|
+
}).filter((entry) => Boolean(entry));
|
|
9497
|
+
}
|
|
9498
|
+
function loadMemoryEntries(configDir) {
|
|
9499
|
+
const file = memoryStorePath(configDir);
|
|
9500
|
+
if (!existsSync15(file)) return legacyEntriesFromMarkdown(configDir);
|
|
9501
|
+
const text = readFileSync11(file, "utf-8").trim();
|
|
9502
|
+
if (!text) return [];
|
|
9503
|
+
const entries = [];
|
|
9504
|
+
for (const line of text.split("\n")) {
|
|
9505
|
+
const trimmed = line.trim();
|
|
9506
|
+
if (!trimmed) continue;
|
|
9507
|
+
try {
|
|
9508
|
+
const entry = normalizeEntry(JSON.parse(trimmed));
|
|
9509
|
+
if (entry) entries.push(entry);
|
|
9510
|
+
} catch {
|
|
9511
|
+
}
|
|
9512
|
+
}
|
|
9513
|
+
return entries;
|
|
9514
|
+
}
|
|
9515
|
+
function saveMemoryEntries(configDir, entries) {
|
|
9516
|
+
mkdirSync7(configDir, { recursive: true });
|
|
9517
|
+
const jsonl = entries.map((entry) => JSON.stringify(entry)).join("\n");
|
|
9518
|
+
atomicWriteFileSync(memoryStorePath(configDir), jsonl ? jsonl + "\n" : "");
|
|
9519
|
+
syncLegacyMarkdown(configDir, entries);
|
|
9520
|
+
}
|
|
9521
|
+
function isMemoryExpired(entry, at = /* @__PURE__ */ new Date()) {
|
|
9522
|
+
return Boolean(entry.expiresAt && Date.parse(entry.expiresAt) <= at.getTime());
|
|
9523
|
+
}
|
|
9524
|
+
function isMemoryVisibleInProject(entry, cwd = process.cwd()) {
|
|
9525
|
+
if (entry.scope !== "project") return true;
|
|
9526
|
+
return entry.sourceProject === getProjectKey(cwd);
|
|
9527
|
+
}
|
|
9528
|
+
function listMemoryEntries(configDir, options = {}) {
|
|
9529
|
+
const cwd = options.cwd ?? process.cwd();
|
|
9530
|
+
return loadMemoryEntries(configDir).filter((entry) => options.includeRejected || entry.approved).filter((entry) => options.includeExpired || !isMemoryExpired(entry)).filter((entry) => isMemoryVisibleInProject(entry, cwd));
|
|
9531
|
+
}
|
|
9532
|
+
function addMemoryEntry(configDir, content, options = {}) {
|
|
9533
|
+
const original = content.trim();
|
|
9534
|
+
if (!original) throw new Error("memory content is required");
|
|
9535
|
+
const redact = options.redact ?? true;
|
|
9536
|
+
const redacted = redact ? redactString(original, { enabled: true, patterns: options.patterns ?? DEFAULT_PATTERNS, customRegexes: options.customPatterns }) : { redacted: original, hits: [] };
|
|
9537
|
+
const sensitivity = options.sensitivity ?? (redacted.hits.length > 0 ? "high" : "low");
|
|
9538
|
+
const approved = options.approved ?? (sensitivity === "low" && redacted.hits.length === 0);
|
|
9539
|
+
const scope = options.scope ?? "personal";
|
|
9540
|
+
const timestamp = nowIso();
|
|
9541
|
+
const entry = {
|
|
9542
|
+
id: randomUUID(),
|
|
9543
|
+
content: redacted.redacted.trim(),
|
|
9544
|
+
scope,
|
|
9545
|
+
sourceSession: options.sourceSession,
|
|
9546
|
+
sourceProject: scope === "project" ? getProjectKey(options.cwd) : void 0,
|
|
9547
|
+
createdAt: timestamp,
|
|
9548
|
+
updatedAt: timestamp,
|
|
9549
|
+
expiresAt: options.expiresAt,
|
|
9550
|
+
sensitivity,
|
|
9551
|
+
approved,
|
|
9552
|
+
redactedKinds: [...new Set(redacted.hits.map((hit) => hit.kind))]
|
|
9553
|
+
};
|
|
9554
|
+
const entries = loadMemoryEntries(configDir);
|
|
9555
|
+
entries.push(entry);
|
|
9556
|
+
saveMemoryEntries(configDir, entries);
|
|
9557
|
+
return entry;
|
|
9558
|
+
}
|
|
9559
|
+
function updateMemoryApproval(configDir, idPrefix, approved) {
|
|
9560
|
+
const entries = loadMemoryEntries(configDir);
|
|
9561
|
+
const entry = findUnique(entries, idPrefix);
|
|
9562
|
+
entry.approved = approved;
|
|
9563
|
+
entry.updatedAt = nowIso();
|
|
9564
|
+
saveMemoryEntries(configDir, entries);
|
|
9565
|
+
return entry;
|
|
9566
|
+
}
|
|
9567
|
+
function deleteMemoryEntry(configDir, idPrefix) {
|
|
9568
|
+
const entries = loadMemoryEntries(configDir);
|
|
9569
|
+
const entry = findUnique(entries, idPrefix);
|
|
9570
|
+
saveMemoryEntries(configDir, entries.filter((candidate) => candidate.id !== entry.id));
|
|
9571
|
+
return entry;
|
|
9572
|
+
}
|
|
9573
|
+
function expireMemoryEntry(configDir, idPrefix, expiresAt = nowIso()) {
|
|
9574
|
+
const entries = loadMemoryEntries(configDir);
|
|
9575
|
+
const entry = findUnique(entries, idPrefix);
|
|
9576
|
+
entry.expiresAt = expiresAt;
|
|
9577
|
+
entry.updatedAt = nowIso();
|
|
9578
|
+
saveMemoryEntries(configDir, entries);
|
|
9579
|
+
return entry;
|
|
9580
|
+
}
|
|
9581
|
+
function exportMemoryEntries(configDir, cwd = process.cwd()) {
|
|
9582
|
+
return JSON.stringify(listMemoryEntries(configDir, { cwd, includeExpired: true, includeRejected: true }), null, 2);
|
|
9583
|
+
}
|
|
9584
|
+
function findUnique(entries, idPrefix) {
|
|
9585
|
+
const matches = entries.filter((entry) => entry.id.startsWith(idPrefix));
|
|
9586
|
+
if (matches.length === 0) throw new Error(`memory id not found: ${idPrefix}`);
|
|
9587
|
+
if (matches.length > 1) throw new Error(`memory id is ambiguous: ${idPrefix}`);
|
|
9588
|
+
return matches[0];
|
|
9589
|
+
}
|
|
9590
|
+
function syncLegacyMarkdown(configDir, entries = loadMemoryEntries(configDir)) {
|
|
9591
|
+
mkdirSync7(dirname5(memoryMarkdownPath(configDir)), { recursive: true });
|
|
9592
|
+
const active = entries.filter((entry) => entry.approved && !isMemoryExpired(entry));
|
|
9593
|
+
const markdown = active.map((entry) => {
|
|
9594
|
+
const date = entry.createdAt.replace("T", " ").slice(0, 19);
|
|
9595
|
+
const source = [
|
|
9596
|
+
`id:${entry.id.slice(0, 8)}`,
|
|
9597
|
+
`scope:${entry.scope}`,
|
|
9598
|
+
entry.sourceSession ? `session:${entry.sourceSession.slice(0, 8)}` : "",
|
|
9599
|
+
entry.sourceProject ? `project:${entry.sourceProject}` : "",
|
|
9600
|
+
`sensitivity:${entry.sensitivity}`
|
|
9601
|
+
].filter(Boolean).join(" \xB7 ");
|
|
9602
|
+
return `## ${date}
|
|
9603
|
+
<!-- ${source} -->
|
|
9604
|
+
${entry.content}
|
|
9605
|
+
`;
|
|
9606
|
+
}).join("\n");
|
|
9607
|
+
atomicWriteFileSync(memoryMarkdownPath(configDir), markdown);
|
|
9608
|
+
}
|
|
9609
|
+
function formatMemoryForPrompt(configDir, cwd = process.cwd()) {
|
|
9610
|
+
const entries = listMemoryEntries(configDir, { cwd }).filter((entry) => entry.approved && !isMemoryExpired(entry));
|
|
9611
|
+
if (entries.length === 0) return null;
|
|
9612
|
+
let content = entries.map((entry) => {
|
|
9613
|
+
const source = [
|
|
9614
|
+
`id ${entry.id.slice(0, 8)}`,
|
|
9615
|
+
`scope ${entry.scope}`,
|
|
9616
|
+
entry.sourceSession ? `source session ${entry.sourceSession.slice(0, 8)}` : "source manual/tool",
|
|
9617
|
+
entry.sourceProject ? `project ${entry.sourceProject}` : "",
|
|
9618
|
+
`sensitivity ${entry.sensitivity}`
|
|
9619
|
+
].filter(Boolean).join(" \xB7 ");
|
|
9620
|
+
return `## ${entry.createdAt.replace("T", " ").slice(0, 19)}
|
|
9621
|
+
Source: ${source}
|
|
9622
|
+
${entry.content}`;
|
|
9623
|
+
}).join("\n\n");
|
|
9624
|
+
if (content.length > MEMORY_MAX_CHARS) {
|
|
9625
|
+
content = content.slice(-MEMORY_MAX_CHARS);
|
|
9626
|
+
const firstEntry = content.indexOf("\n## ");
|
|
9627
|
+
if (firstEntry !== -1) content = content.slice(firstEntry + 1);
|
|
9628
|
+
}
|
|
9629
|
+
return { content, entryCount: entries.length };
|
|
9630
|
+
}
|
|
9631
|
+
|
|
9632
|
+
// src/tools/builtin/save-memory.ts
|
|
9633
|
+
function getConfigDir() {
|
|
9634
|
+
return join11(homedir5(), CONFIG_DIR_NAME);
|
|
9635
|
+
}
|
|
9636
|
+
function parseScope(value) {
|
|
9637
|
+
return value === "personal" || value === "project" || value === "session" || value === "team" ? value : void 0;
|
|
9638
|
+
}
|
|
9639
|
+
function parseSensitivity(value) {
|
|
9640
|
+
return value === "low" || value === "medium" || value === "high" ? value : void 0;
|
|
9351
9641
|
}
|
|
9352
9642
|
var saveMemoryTool = {
|
|
9353
9643
|
definition: {
|
|
9354
9644
|
name: "save_memory",
|
|
9355
|
-
description: "Save important information to persistent memory
|
|
9645
|
+
description: "Save important information to governed persistent memory. Low-risk memories are approved automatically; sensitive entries are stored as pending and require /memory approve <id>. Use concise entries for user preferences, project decisions, recurring patterns, and key findings.",
|
|
9356
9646
|
parameters: {
|
|
9357
9647
|
content: {
|
|
9358
9648
|
type: "string",
|
|
9359
9649
|
description: "The information to save to persistent memory. Keep it concise and actionable.",
|
|
9360
9650
|
required: true
|
|
9651
|
+
},
|
|
9652
|
+
scope: {
|
|
9653
|
+
type: "string",
|
|
9654
|
+
description: "Memory scope: personal, project, session, or team. Defaults to personal. Project memories are only injected in the same project.",
|
|
9655
|
+
required: false
|
|
9656
|
+
},
|
|
9657
|
+
sensitivity: {
|
|
9658
|
+
type: "string",
|
|
9659
|
+
description: "Sensitivity: low, medium, or high. Low-risk entries may be auto-approved; medium/high remain pending unless explicitly approved.",
|
|
9660
|
+
required: false
|
|
9661
|
+
},
|
|
9662
|
+
expiresAt: {
|
|
9663
|
+
type: "string",
|
|
9664
|
+
description: "Optional ISO timestamp after which this memory should no longer be injected.",
|
|
9665
|
+
required: false
|
|
9361
9666
|
}
|
|
9362
9667
|
},
|
|
9363
9668
|
dangerous: false
|
|
@@ -9365,20 +9670,16 @@ var saveMemoryTool = {
|
|
|
9365
9670
|
async execute(args) {
|
|
9366
9671
|
const content = String(args["content"] ?? "").trim();
|
|
9367
9672
|
if (!content) throw new ToolError("save_memory", "content is required");
|
|
9368
|
-
const
|
|
9369
|
-
|
|
9370
|
-
|
|
9371
|
-
|
|
9372
|
-
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
|
|
9376
|
-
${
|
|
9377
|
-
`;
|
|
9378
|
-
const previous = existsSync14(memoryPath) ? readFileSync11(memoryPath, "utf-8") : "";
|
|
9379
|
-
atomicWriteFileSync(memoryPath, previous + entry);
|
|
9380
|
-
const byteSize = statSync6(memoryPath).size;
|
|
9381
|
-
return `Memory saved successfully. File size: ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
|
|
9673
|
+
const entry = addMemoryEntry(getConfigDir(), content, {
|
|
9674
|
+
scope: parseScope(args["scope"]) ?? "personal",
|
|
9675
|
+
sensitivity: parseSensitivity(args["sensitivity"]),
|
|
9676
|
+
expiresAt: typeof args["expiresAt"] === "string" ? args["expiresAt"] : void 0,
|
|
9677
|
+
cwd: process.cwd(),
|
|
9678
|
+
redact: true
|
|
9679
|
+
});
|
|
9680
|
+
const status = entry.approved ? "approved and active" : "pending approval";
|
|
9681
|
+
const redacted = entry.redactedKinds?.length ? ` Redacted: ${entry.redactedKinds.join(", ")}.` : "";
|
|
9682
|
+
return `Memory saved (${status}). id=${entry.id.slice(0, 8)} scope=${entry.scope} sensitivity=${entry.sensitivity}.${redacted}`;
|
|
9382
9683
|
}
|
|
9383
9684
|
};
|
|
9384
9685
|
|
|
@@ -9422,7 +9723,7 @@ function promptUser(rl, question) {
|
|
|
9422
9723
|
console.log();
|
|
9423
9724
|
console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
|
|
9424
9725
|
process.stdout.write(chalk4.cyan("> "));
|
|
9425
|
-
return new Promise((
|
|
9726
|
+
return new Promise((resolve12) => {
|
|
9426
9727
|
let completed = false;
|
|
9427
9728
|
const cleanup = (answer) => {
|
|
9428
9729
|
if (completed) return;
|
|
@@ -9432,7 +9733,7 @@ function promptUser(rl, question) {
|
|
|
9432
9733
|
rl.pause();
|
|
9433
9734
|
rlAny.output = savedOutput;
|
|
9434
9735
|
askUserContext.prompting = false;
|
|
9435
|
-
|
|
9736
|
+
resolve12(answer);
|
|
9436
9737
|
};
|
|
9437
9738
|
const onLine = (line) => {
|
|
9438
9739
|
cleanup(line);
|
|
@@ -9664,9 +9965,292 @@ function formatResults2(query, data, _requested) {
|
|
|
9664
9965
|
}
|
|
9665
9966
|
|
|
9666
9967
|
// src/agents/agent-config.ts
|
|
9667
|
-
import { existsSync as
|
|
9668
|
-
import { join as
|
|
9968
|
+
import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync13 } from "fs";
|
|
9969
|
+
import { join as join13 } from "path";
|
|
9669
9970
|
import { homedir as homedir6 } from "os";
|
|
9971
|
+
|
|
9972
|
+
// src/plugins/plugin-manager.ts
|
|
9973
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync12, cpSync, rmSync, statSync as statSync6 } from "fs";
|
|
9974
|
+
import { basename as basename5, dirname as dirname6, join as join12, resolve as resolve7 } from "path";
|
|
9975
|
+
import { createHash as createHash3 } from "crypto";
|
|
9976
|
+
import { z as z2 } from "zod";
|
|
9977
|
+
var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
|
|
9978
|
+
var STATE_FILE = "plugin-state.json";
|
|
9979
|
+
var HookCommandSchema = z2.union([
|
|
9980
|
+
z2.string(),
|
|
9981
|
+
z2.object({
|
|
9982
|
+
command: z2.string(),
|
|
9983
|
+
source: z2.enum(["user", "project", "managed"]).optional(),
|
|
9984
|
+
description: z2.string().optional(),
|
|
9985
|
+
required: z2.boolean().optional(),
|
|
9986
|
+
timeoutMs: z2.number().int().min(100).max(3e4).optional(),
|
|
9987
|
+
disabled: z2.boolean().optional()
|
|
9988
|
+
}),
|
|
9989
|
+
z2.array(z2.union([
|
|
9990
|
+
z2.string(),
|
|
9991
|
+
z2.object({
|
|
9992
|
+
command: z2.string(),
|
|
9993
|
+
source: z2.enum(["user", "project", "managed"]).optional(),
|
|
9994
|
+
description: z2.string().optional(),
|
|
9995
|
+
required: z2.boolean().optional(),
|
|
9996
|
+
timeoutMs: z2.number().int().min(100).max(3e4).optional(),
|
|
9997
|
+
disabled: z2.boolean().optional()
|
|
9998
|
+
})
|
|
9999
|
+
]))
|
|
10000
|
+
]);
|
|
10001
|
+
var HookEventNameSchema = z2.enum([
|
|
10002
|
+
"SessionStart",
|
|
10003
|
+
"UserPromptSubmit",
|
|
10004
|
+
"PreToolUse",
|
|
10005
|
+
"PermissionRequest",
|
|
10006
|
+
"PostToolUse",
|
|
10007
|
+
"PreCompact",
|
|
10008
|
+
"PostCompact",
|
|
10009
|
+
"Stop",
|
|
10010
|
+
"SubagentStart",
|
|
10011
|
+
"SubagentStop"
|
|
10012
|
+
]);
|
|
10013
|
+
var McpServerSchema = z2.object({
|
|
10014
|
+
command: z2.string(),
|
|
10015
|
+
args: z2.array(z2.string()).default([]),
|
|
10016
|
+
env: z2.record(z2.string()).optional(),
|
|
10017
|
+
timeout: z2.number().default(3e4)
|
|
10018
|
+
});
|
|
10019
|
+
var PluginManifestSchema = z2.object({
|
|
10020
|
+
name: z2.string().regex(/^[a-zA-Z0-9._-]{1,80}$/),
|
|
10021
|
+
version: z2.string().min(1),
|
|
10022
|
+
description: z2.string().optional(),
|
|
10023
|
+
skills: z2.array(z2.string()).default([]),
|
|
10024
|
+
hooks: z2.object({
|
|
10025
|
+
enabled: z2.boolean().optional(),
|
|
10026
|
+
events: z2.record(HookEventNameSchema, HookCommandSchema).default({})
|
|
10027
|
+
}).default({ events: {} }),
|
|
10028
|
+
commands: z2.array(z2.string()).default([]),
|
|
10029
|
+
mcpServers: z2.record(McpServerSchema).default({}),
|
|
10030
|
+
agents: z2.array(z2.string()).default([]),
|
|
10031
|
+
permissionHints: z2.array(z2.string()).default([])
|
|
10032
|
+
}).strict();
|
|
10033
|
+
function pluginRoot(configDir) {
|
|
10034
|
+
return join12(configDir, PLUGINS_DIR_NAME);
|
|
10035
|
+
}
|
|
10036
|
+
function pluginManifestPath(pluginDir) {
|
|
10037
|
+
return join12(pluginDir, MANIFEST_RELATIVE);
|
|
10038
|
+
}
|
|
10039
|
+
function statePath(configDir) {
|
|
10040
|
+
return join12(pluginRoot(configDir), STATE_FILE);
|
|
10041
|
+
}
|
|
10042
|
+
function loadState(configDir) {
|
|
10043
|
+
const file = statePath(configDir);
|
|
10044
|
+
if (!existsSync16(file)) return { version: 1, plugins: [] };
|
|
10045
|
+
try {
|
|
10046
|
+
const parsed = JSON.parse(readFileSync12(file, "utf-8"));
|
|
10047
|
+
return { version: 1, plugins: Array.isArray(parsed.plugins) ? parsed.plugins : [] };
|
|
10048
|
+
} catch {
|
|
10049
|
+
return { version: 1, plugins: [] };
|
|
10050
|
+
}
|
|
10051
|
+
}
|
|
10052
|
+
function saveState(configDir, state2) {
|
|
10053
|
+
mkdirSync8(pluginRoot(configDir), { recursive: true });
|
|
10054
|
+
atomicWriteFileSync(statePath(configDir), JSON.stringify(state2, null, 2));
|
|
10055
|
+
}
|
|
10056
|
+
function hashPluginManifest(manifestPath) {
|
|
10057
|
+
return createHash3("sha256").update(readFileSync12(manifestPath)).digest("hex");
|
|
10058
|
+
}
|
|
10059
|
+
function readPluginManifest(manifestPath) {
|
|
10060
|
+
return PluginManifestSchema.parse(JSON.parse(readFileSync12(manifestPath, "utf-8")));
|
|
10061
|
+
}
|
|
10062
|
+
function findPluginSourceDir(inputPath) {
|
|
10063
|
+
const abs = resolve7(inputPath);
|
|
10064
|
+
const direct = statSync6(abs).isDirectory() ? abs : dirname6(abs);
|
|
10065
|
+
if (existsSync16(pluginManifestPath(direct))) return direct;
|
|
10066
|
+
const nested = join12(direct, ".aicli-plugin");
|
|
10067
|
+
if (existsSync16(join12(nested, "plugin.json"))) return direct;
|
|
10068
|
+
throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
|
|
10069
|
+
}
|
|
10070
|
+
function installPlugin(configDir, inputPath) {
|
|
10071
|
+
const sourceDir = findPluginSourceDir(inputPath);
|
|
10072
|
+
const manifest = readPluginManifest(pluginManifestPath(sourceDir));
|
|
10073
|
+
const targetDir = join12(pluginRoot(configDir), manifest.name);
|
|
10074
|
+
mkdirSync8(pluginRoot(configDir), { recursive: true });
|
|
10075
|
+
if (existsSync16(targetDir)) rmSync(targetDir, { recursive: true, force: true });
|
|
10076
|
+
cpSync(sourceDir, targetDir, { recursive: true });
|
|
10077
|
+
const manifestPath = pluginManifestPath(targetDir);
|
|
10078
|
+
const hash = hashPluginManifest(manifestPath);
|
|
10079
|
+
const state2 = loadState(configDir);
|
|
10080
|
+
const previous = state2.plugins.find((p) => p.name === manifest.name);
|
|
10081
|
+
const next = {
|
|
10082
|
+
name: manifest.name,
|
|
10083
|
+
enabled: previous?.enabled ?? false,
|
|
10084
|
+
trusted: false,
|
|
10085
|
+
hash,
|
|
10086
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10087
|
+
};
|
|
10088
|
+
state2.plugins = [...state2.plugins.filter((p) => p.name !== manifest.name), next];
|
|
10089
|
+
saveState(configDir, state2);
|
|
10090
|
+
return { name: manifest.name, dir: targetDir, manifestPath, manifest, hash, enabled: next.enabled, trusted: false, valid: true };
|
|
10091
|
+
}
|
|
10092
|
+
function setPluginEnabled(configDir, name, enabled) {
|
|
10093
|
+
const plugin = getInstalledPlugin(configDir, name);
|
|
10094
|
+
if (!plugin.valid) throw new Error(plugin.error ?? `invalid plugin: ${name}`);
|
|
10095
|
+
const state2 = loadState(configDir);
|
|
10096
|
+
const current = state2.plugins.find((p) => p.name === plugin.name) ?? {
|
|
10097
|
+
name: plugin.name,
|
|
10098
|
+
enabled: false,
|
|
10099
|
+
trusted: false,
|
|
10100
|
+
hash: plugin.hash,
|
|
10101
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10102
|
+
};
|
|
10103
|
+
const next = { ...current, enabled, hash: plugin.hash, enabledAt: enabled ? (/* @__PURE__ */ new Date()).toISOString() : current.enabledAt };
|
|
10104
|
+
state2.plugins = [...state2.plugins.filter((p) => p.name !== plugin.name), next];
|
|
10105
|
+
saveState(configDir, state2);
|
|
10106
|
+
return next;
|
|
10107
|
+
}
|
|
10108
|
+
function trustPlugin(configDir, name) {
|
|
10109
|
+
const plugin = getInstalledPlugin(configDir, name);
|
|
10110
|
+
if (!plugin.valid) throw new Error(plugin.error ?? `invalid plugin: ${name}`);
|
|
10111
|
+
const state2 = loadState(configDir);
|
|
10112
|
+
const current = state2.plugins.find((p) => p.name === plugin.name) ?? {
|
|
10113
|
+
name: plugin.name,
|
|
10114
|
+
enabled: false,
|
|
10115
|
+
trusted: false,
|
|
10116
|
+
hash: plugin.hash,
|
|
10117
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10118
|
+
};
|
|
10119
|
+
const next = { ...current, trusted: true, hash: plugin.hash, trustedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
10120
|
+
state2.plugins = [...state2.plugins.filter((p) => p.name !== plugin.name), next];
|
|
10121
|
+
saveState(configDir, state2);
|
|
10122
|
+
return next;
|
|
10123
|
+
}
|
|
10124
|
+
function getInstalledPlugin(configDir, name) {
|
|
10125
|
+
const plugins = listInstalledPlugins(configDir);
|
|
10126
|
+
const matches = plugins.filter((p) => p.name === name || p.name.startsWith(name));
|
|
10127
|
+
if (matches.length === 0) throw new Error(`plugin not found: ${name}`);
|
|
10128
|
+
if (matches.length > 1) throw new Error(`plugin name is ambiguous: ${name}`);
|
|
10129
|
+
return matches[0];
|
|
10130
|
+
}
|
|
10131
|
+
function listInstalledPlugins(configDir) {
|
|
10132
|
+
const root = pluginRoot(configDir);
|
|
10133
|
+
if (!existsSync16(root)) return [];
|
|
10134
|
+
const state2 = loadState(configDir);
|
|
10135
|
+
const out = [];
|
|
10136
|
+
for (const entry of readdirSync7(root)) {
|
|
10137
|
+
const dir = join12(root, entry);
|
|
10138
|
+
try {
|
|
10139
|
+
if (!statSync6(dir).isDirectory()) continue;
|
|
10140
|
+
} catch {
|
|
10141
|
+
continue;
|
|
10142
|
+
}
|
|
10143
|
+
const manifestPath = pluginManifestPath(dir);
|
|
10144
|
+
if (!existsSync16(manifestPath)) continue;
|
|
10145
|
+
try {
|
|
10146
|
+
const manifest = readPluginManifest(manifestPath);
|
|
10147
|
+
const hash = hashPluginManifest(manifestPath);
|
|
10148
|
+
const st = state2.plugins.find((p) => p.name === manifest.name);
|
|
10149
|
+
const trusted = st?.trusted === true && st.hash === hash;
|
|
10150
|
+
out.push({
|
|
10151
|
+
name: manifest.name,
|
|
10152
|
+
dir,
|
|
10153
|
+
manifestPath,
|
|
10154
|
+
manifest,
|
|
10155
|
+
hash,
|
|
10156
|
+
enabled: st?.enabled === true,
|
|
10157
|
+
trusted,
|
|
10158
|
+
valid: true
|
|
10159
|
+
});
|
|
10160
|
+
} catch (err) {
|
|
10161
|
+
out.push({
|
|
10162
|
+
name: entry,
|
|
10163
|
+
dir,
|
|
10164
|
+
manifestPath,
|
|
10165
|
+
manifest: { name: entry, version: "invalid", skills: [], commands: [], agents: [], hooks: { events: {} }, mcpServers: {}, permissionHints: [] },
|
|
10166
|
+
hash: "",
|
|
10167
|
+
enabled: false,
|
|
10168
|
+
trusted: false,
|
|
10169
|
+
valid: false,
|
|
10170
|
+
error: err instanceof Error ? err.message : String(err)
|
|
10171
|
+
});
|
|
10172
|
+
}
|
|
10173
|
+
}
|
|
10174
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
10175
|
+
}
|
|
10176
|
+
function activePlugins(configDir) {
|
|
10177
|
+
return listInstalledPlugins(configDir).filter((p) => p.valid && p.enabled && p.trusted);
|
|
10178
|
+
}
|
|
10179
|
+
function uniqueDirs(plugin, rels) {
|
|
10180
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
10181
|
+
for (const rel of rels) {
|
|
10182
|
+
const abs = resolve7(plugin.dir, rel);
|
|
10183
|
+
if (!abs.startsWith(resolve7(plugin.dir))) continue;
|
|
10184
|
+
if (existsSync16(abs)) dirs.add(statSync6(abs).isDirectory() ? abs : dirname6(abs));
|
|
10185
|
+
}
|
|
10186
|
+
return [...dirs];
|
|
10187
|
+
}
|
|
10188
|
+
function prefixCommand(plugin, command) {
|
|
10189
|
+
return command.replaceAll("{pluginDir}", plugin.dir).replaceAll("$PLUGIN_DIR", plugin.dir);
|
|
10190
|
+
}
|
|
10191
|
+
function pluginHooks(plugin) {
|
|
10192
|
+
const events = plugin.manifest.hooks?.events ?? {};
|
|
10193
|
+
const out = {};
|
|
10194
|
+
for (const [event, raw] of Object.entries(events)) {
|
|
10195
|
+
const normalize = (item) => {
|
|
10196
|
+
if (typeof item === "string") return { command: prefixCommand(plugin, item), source: "project", description: `plugin:${plugin.name}` };
|
|
10197
|
+
if (item && typeof item === "object") {
|
|
10198
|
+
const obj = item;
|
|
10199
|
+
return { ...obj, command: prefixCommand(plugin, String(obj.command ?? "")), source: "project", description: obj.description ?? `plugin:${plugin.name}` };
|
|
10200
|
+
}
|
|
10201
|
+
return item;
|
|
10202
|
+
};
|
|
10203
|
+
out[event] = Array.isArray(raw) ? raw.map(normalize) : normalize(raw);
|
|
10204
|
+
}
|
|
10205
|
+
return Object.keys(out).length > 0 ? { enabled: plugin.manifest.hooks?.enabled ?? true, events: out } : void 0;
|
|
10206
|
+
}
|
|
10207
|
+
function mergePluginHooks(base, configDir) {
|
|
10208
|
+
const plugins = activePlugins(configDir);
|
|
10209
|
+
if (plugins.length === 0) return base;
|
|
10210
|
+
const merged = { ...base ?? {}, enabled: base?.enabled ?? true, events: { ...base?.events ?? {} } };
|
|
10211
|
+
for (const plugin of plugins) {
|
|
10212
|
+
const hooks = pluginHooks(plugin);
|
|
10213
|
+
if (!hooks?.events) continue;
|
|
10214
|
+
for (const [event, entry] of Object.entries(hooks.events)) {
|
|
10215
|
+
const key = event;
|
|
10216
|
+
const existing = merged.events?.[key];
|
|
10217
|
+
const existingItems = existing === void 0 ? [] : Array.isArray(existing) ? existing : [existing];
|
|
10218
|
+
const newItems = Array.isArray(entry) ? entry : [entry];
|
|
10219
|
+
merged.events[key] = [...existingItems, ...newItems];
|
|
10220
|
+
}
|
|
10221
|
+
}
|
|
10222
|
+
return merged;
|
|
10223
|
+
}
|
|
10224
|
+
function getActivePluginAssets(configDir) {
|
|
10225
|
+
const plugins = activePlugins(configDir);
|
|
10226
|
+
const mcpServers = {};
|
|
10227
|
+
const skillDirs = [];
|
|
10228
|
+
const commandDirs = [];
|
|
10229
|
+
const agentDirs = [];
|
|
10230
|
+
for (const plugin of plugins) {
|
|
10231
|
+
skillDirs.push(...uniqueDirs(plugin, plugin.manifest.skills));
|
|
10232
|
+
commandDirs.push(...uniqueDirs(plugin, plugin.manifest.commands));
|
|
10233
|
+
agentDirs.push(...uniqueDirs(plugin, plugin.manifest.agents));
|
|
10234
|
+
for (const [serverName, server] of Object.entries(plugin.manifest.mcpServers)) {
|
|
10235
|
+
mcpServers[`plugin_${plugin.name}_${serverName}`] = server;
|
|
10236
|
+
}
|
|
10237
|
+
}
|
|
10238
|
+
return { plugins, skillDirs, commandDirs, agentDirs, hooks: mergePluginHooks(void 0, configDir), mcpServers };
|
|
10239
|
+
}
|
|
10240
|
+
function describePlugin(plugin) {
|
|
10241
|
+
const m = plugin.manifest;
|
|
10242
|
+
return [
|
|
10243
|
+
`${plugin.name}@${m.version}`,
|
|
10244
|
+
` status: ${plugin.enabled ? "enabled" : "disabled"} \xB7 ${plugin.trusted ? "trusted" : "untrusted"}${plugin.valid ? "" : " \xB7 invalid"}`,
|
|
10245
|
+
` dir: ${plugin.dir}`,
|
|
10246
|
+
m.description ? ` description: ${m.description}` : void 0,
|
|
10247
|
+
` assets: ${m.skills.length} skill(s), ${m.commands.length} command(s), ${m.agents.length} agent(s), ${Object.keys(m.mcpServers).length} MCP server(s), ${Object.keys(m.hooks?.events ?? {}).length} hook event(s)`,
|
|
10248
|
+
m.permissionHints.length ? ` permission hints: ${m.permissionHints.join("; ")}` : void 0,
|
|
10249
|
+
plugin.error ? ` error: ${plugin.error}` : void 0
|
|
10250
|
+
].filter((line) => Boolean(line));
|
|
10251
|
+
}
|
|
10252
|
+
|
|
10253
|
+
// src/agents/agent-config.ts
|
|
9670
10254
|
var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
|
|
9671
10255
|
"read_file",
|
|
9672
10256
|
"list_dir",
|
|
@@ -9760,13 +10344,13 @@ function parseAgentConfig(raw, source, path3) {
|
|
|
9760
10344
|
return cfg;
|
|
9761
10345
|
}
|
|
9762
10346
|
function loadAgentDir(dir, source) {
|
|
9763
|
-
if (!
|
|
10347
|
+
if (!existsSync17(dir)) return [];
|
|
9764
10348
|
const out = [];
|
|
9765
|
-
for (const file of
|
|
10349
|
+
for (const file of readdirSync8(dir)) {
|
|
9766
10350
|
if (!file.endsWith(".json")) continue;
|
|
9767
|
-
const path3 =
|
|
10351
|
+
const path3 = join13(dir, file);
|
|
9768
10352
|
try {
|
|
9769
|
-
const parsed = JSON.parse(
|
|
10353
|
+
const parsed = JSON.parse(readFileSync13(path3, "utf-8"));
|
|
9770
10354
|
const cfg = parseAgentConfig(parsed, source, path3);
|
|
9771
10355
|
if (cfg) out.push(cfg);
|
|
9772
10356
|
} catch {
|
|
@@ -9776,8 +10360,8 @@ function loadAgentDir(dir, source) {
|
|
|
9776
10360
|
}
|
|
9777
10361
|
function getAgentDirs(configDir, cwd = process.cwd()) {
|
|
9778
10362
|
return {
|
|
9779
|
-
user:
|
|
9780
|
-
project:
|
|
10363
|
+
user: join13(configDir ?? join13(homedir6(), CONFIG_DIR_NAME), "agents"),
|
|
10364
|
+
project: join13(cwd, CONFIG_DIR_NAME, "agents")
|
|
9781
10365
|
};
|
|
9782
10366
|
}
|
|
9783
10367
|
function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
@@ -9785,6 +10369,11 @@ function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
|
9785
10369
|
const byName = /* @__PURE__ */ new Map();
|
|
9786
10370
|
for (const cfg of BUILTIN_AGENTS) byName.set(normalizeName(cfg.name), { ...cfg });
|
|
9787
10371
|
for (const cfg of loadAgentDir(dirs.user, "user")) byName.set(normalizeName(cfg.name), cfg);
|
|
10372
|
+
if (configDir) {
|
|
10373
|
+
for (const dir of getActivePluginAssets(configDir).agentDirs) {
|
|
10374
|
+
for (const cfg of loadAgentDir(dir, "plugin")) byName.set(normalizeName(cfg.name), cfg);
|
|
10375
|
+
}
|
|
10376
|
+
}
|
|
9788
10377
|
for (const cfg of loadAgentDir(dirs.project, "project")) byName.set(normalizeName(cfg.name), cfg);
|
|
9789
10378
|
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
9790
10379
|
}
|
|
@@ -9819,7 +10408,7 @@ var nextId = 1;
|
|
|
9819
10408
|
var preferredAgentName = "worker";
|
|
9820
10409
|
var runs = [];
|
|
9821
10410
|
var MAX_RUNS = 50;
|
|
9822
|
-
function
|
|
10411
|
+
function nowIso2() {
|
|
9823
10412
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
9824
10413
|
}
|
|
9825
10414
|
function startAgentRun(input) {
|
|
@@ -9828,7 +10417,7 @@ function startAgentRun(input) {
|
|
|
9828
10417
|
agentName: input.agentName,
|
|
9829
10418
|
task: input.task,
|
|
9830
10419
|
status: "running",
|
|
9831
|
-
startedAt:
|
|
10420
|
+
startedAt: nowIso2(),
|
|
9832
10421
|
toolNames: [],
|
|
9833
10422
|
agentIndex: input.agentIndex,
|
|
9834
10423
|
stopRequested: false
|
|
@@ -9846,7 +10435,7 @@ function finishAgentRun(runId, status, summary, error) {
|
|
|
9846
10435
|
const run = runs.find((r) => r.id === runId);
|
|
9847
10436
|
if (!run) return;
|
|
9848
10437
|
run.status = status;
|
|
9849
|
-
run.finishedAt =
|
|
10438
|
+
run.finishedAt = nowIso2();
|
|
9850
10439
|
if (summary) run.summary = summary;
|
|
9851
10440
|
if (error) run.error = error;
|
|
9852
10441
|
}
|
|
@@ -10272,7 +10861,7 @@ var spawnAgentTool = {
|
|
|
10272
10861
|
|
|
10273
10862
|
// src/tools/builtin/task-manager.ts
|
|
10274
10863
|
import { spawn as spawn3 } from "child_process";
|
|
10275
|
-
import { randomUUID } from "crypto";
|
|
10864
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
10276
10865
|
import { platform as platform4 } from "os";
|
|
10277
10866
|
var MAX_OUTPUT_CHARS = 1e4;
|
|
10278
10867
|
var MAX_OUTPUT_BYTES = 64 * 1024;
|
|
@@ -10305,7 +10894,7 @@ function createTask(command, description) {
|
|
|
10305
10894
|
}
|
|
10306
10895
|
}
|
|
10307
10896
|
}
|
|
10308
|
-
const id =
|
|
10897
|
+
const id = randomUUID2().slice(0, 8);
|
|
10309
10898
|
const { shell, args } = shellInvocation(command);
|
|
10310
10899
|
const proc = spawn3(shell, args, {
|
|
10311
10900
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -10487,20 +11076,20 @@ var taskStopTool = {
|
|
|
10487
11076
|
|
|
10488
11077
|
// src/tools/builtin/git-tools.ts
|
|
10489
11078
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
10490
|
-
import { existsSync as
|
|
10491
|
-
import { join as
|
|
11079
|
+
import { existsSync as existsSync18 } from "fs";
|
|
11080
|
+
import { join as join14 } from "path";
|
|
10492
11081
|
function assertGitRepo(cwd) {
|
|
10493
11082
|
let dir = cwd;
|
|
10494
11083
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
10495
11084
|
while (dir && dir !== root) {
|
|
10496
|
-
if (
|
|
10497
|
-
const parent =
|
|
11085
|
+
if (existsSync18(join14(dir, ".git"))) return;
|
|
11086
|
+
const parent = join14(dir, "..");
|
|
10498
11087
|
if (parent === dir) break;
|
|
10499
11088
|
dir = parent;
|
|
10500
11089
|
}
|
|
10501
11090
|
throw new ToolError("git", "Not inside a git repository. Run this command from a directory under a git-tracked project.");
|
|
10502
11091
|
}
|
|
10503
|
-
function
|
|
11092
|
+
function runGit2(args, cwd, maxBuffer = 10 * 1024 * 1024) {
|
|
10504
11093
|
try {
|
|
10505
11094
|
const output = execFileSync2("git", args, {
|
|
10506
11095
|
cwd,
|
|
@@ -10535,11 +11124,11 @@ var gitStatusTool = {
|
|
|
10535
11124
|
assertGitRepo(cwd);
|
|
10536
11125
|
let branch = "(unknown)";
|
|
10537
11126
|
try {
|
|
10538
|
-
branch =
|
|
11127
|
+
branch = runGit2(["symbolic-ref", "--short", "HEAD"], cwd).trim();
|
|
10539
11128
|
} catch {
|
|
10540
11129
|
branch = "(detached)";
|
|
10541
11130
|
}
|
|
10542
|
-
const porcelain =
|
|
11131
|
+
const porcelain = runGit2(["status", "--porcelain=v1", "-b"], cwd);
|
|
10543
11132
|
const lines = porcelain.split("\n").filter((l) => l.length > 0);
|
|
10544
11133
|
const branchLine = lines[0]?.startsWith("##") ? lines[0].slice(3) : `${branch}`;
|
|
10545
11134
|
const fileLines = lines.slice(lines[0]?.startsWith("##") ? 1 : 0);
|
|
@@ -10626,7 +11215,7 @@ var gitDiffTool = {
|
|
|
10626
11215
|
if (args["file"]) {
|
|
10627
11216
|
gitArgs.push("--", String(args["file"]));
|
|
10628
11217
|
}
|
|
10629
|
-
const output =
|
|
11218
|
+
const output = runGit2(gitArgs, cwd);
|
|
10630
11219
|
if (output.trim().length === 0) {
|
|
10631
11220
|
const label = args["staged"] ? "staged" : args["commit_range"] ? String(args["commit_range"]) : "unstaged";
|
|
10632
11221
|
return `No ${label} changes.`;
|
|
@@ -10688,7 +11277,7 @@ var gitLogTool = {
|
|
|
10688
11277
|
if (args["file"]) {
|
|
10689
11278
|
gitArgs.push("--", String(args["file"]));
|
|
10690
11279
|
}
|
|
10691
|
-
const output =
|
|
11280
|
+
const output = runGit2(gitArgs, cwd);
|
|
10692
11281
|
if (output.trim().length === 0) {
|
|
10693
11282
|
return "No commits found matching the filter.";
|
|
10694
11283
|
}
|
|
@@ -10735,9 +11324,9 @@ var gitCommitTool = {
|
|
|
10735
11324
|
throw new ToolError("git_commit", "Cannot use both stage_all and files at the same time.");
|
|
10736
11325
|
}
|
|
10737
11326
|
if (files.length > 0) {
|
|
10738
|
-
|
|
11327
|
+
runGit2(["add", "--", ...files], cwd);
|
|
10739
11328
|
}
|
|
10740
|
-
const statusBefore =
|
|
11329
|
+
const statusBefore = runGit2(["status", "--porcelain"], cwd);
|
|
10741
11330
|
const hasStaged = statusBefore.split("\n").some((l) => l.length > 0 && l[0] !== " " && l[0] !== "?");
|
|
10742
11331
|
const hasUnstaged = statusBefore.split("\n").some((l) => l.length > 1 && l[1] !== " " && l[1] !== "?");
|
|
10743
11332
|
if (!hasStaged && !stageAll) {
|
|
@@ -10748,9 +11337,9 @@ var gitCommitTool = {
|
|
|
10748
11337
|
}
|
|
10749
11338
|
const commitArgs = ["commit", "-m", message];
|
|
10750
11339
|
if (stageAll) commitArgs.splice(1, 0, "-a");
|
|
10751
|
-
const commitOutput =
|
|
10752
|
-
const hash =
|
|
10753
|
-
const summary =
|
|
11340
|
+
const commitOutput = runGit2(commitArgs, cwd);
|
|
11341
|
+
const hash = runGit2(["rev-parse", "--short", "HEAD"], cwd).trim();
|
|
11342
|
+
const summary = runGit2(["log", "-1", "--pretty=format:%h %s"], cwd).trim();
|
|
10754
11343
|
return `\u2713 Committed ${hash}
|
|
10755
11344
|
${summary}
|
|
10756
11345
|
|
|
@@ -10759,9 +11348,9 @@ ${commitOutput.trim()}`;
|
|
|
10759
11348
|
};
|
|
10760
11349
|
|
|
10761
11350
|
// src/tools/builtin/notebook-edit.ts
|
|
10762
|
-
import { readFileSync as
|
|
11351
|
+
import { readFileSync as readFileSync14, existsSync as existsSync19 } from "fs";
|
|
10763
11352
|
import { writeFile } from "fs/promises";
|
|
10764
|
-
import { resolve as
|
|
11353
|
+
import { resolve as resolve8, extname as extname2 } from "path";
|
|
10765
11354
|
var notebookEditTool = {
|
|
10766
11355
|
definition: {
|
|
10767
11356
|
name: "notebook_edit",
|
|
@@ -10810,14 +11399,14 @@ var notebookEditTool = {
|
|
|
10810
11399
|
if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
|
|
10811
11400
|
throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
|
|
10812
11401
|
}
|
|
10813
|
-
const absPath =
|
|
11402
|
+
const absPath = resolve8(filePath);
|
|
10814
11403
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
10815
11404
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
10816
11405
|
}
|
|
10817
|
-
if (!
|
|
11406
|
+
if (!existsSync19(absPath)) {
|
|
10818
11407
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
10819
11408
|
}
|
|
10820
|
-
const raw =
|
|
11409
|
+
const raw = readFileSync14(absPath, "utf-8");
|
|
10821
11410
|
const nb = parseNotebook(raw);
|
|
10822
11411
|
const cellIdx0 = cellIndexRaw - 1;
|
|
10823
11412
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -11234,8 +11823,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
11234
11823
|
|
|
11235
11824
|
// src/tools/registry.ts
|
|
11236
11825
|
import { pathToFileURL } from "url";
|
|
11237
|
-
import { existsSync as
|
|
11238
|
-
import { join as
|
|
11826
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
|
|
11827
|
+
import { join as join15 } from "path";
|
|
11239
11828
|
var ToolRegistry = class {
|
|
11240
11829
|
tools = /* @__PURE__ */ new Map();
|
|
11241
11830
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -11396,16 +11985,16 @@ var ToolRegistry = class {
|
|
|
11396
11985
|
* Returns the number of successfully loaded plugins.
|
|
11397
11986
|
*/
|
|
11398
11987
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
11399
|
-
if (!
|
|
11988
|
+
if (!existsSync20(pluginsDir)) {
|
|
11400
11989
|
try {
|
|
11401
|
-
|
|
11990
|
+
mkdirSync9(pluginsDir, { recursive: true });
|
|
11402
11991
|
} catch {
|
|
11403
11992
|
}
|
|
11404
11993
|
return 0;
|
|
11405
11994
|
}
|
|
11406
11995
|
let files;
|
|
11407
11996
|
try {
|
|
11408
|
-
files =
|
|
11997
|
+
files = readdirSync9(pluginsDir).filter((f) => f.endsWith(".js"));
|
|
11409
11998
|
} catch {
|
|
11410
11999
|
return 0;
|
|
11411
12000
|
}
|
|
@@ -11422,12 +12011,12 @@ var ToolRegistry = class {
|
|
|
11422
12011
|
process.stderr.write(
|
|
11423
12012
|
`
|
|
11424
12013
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
11425
|
-
` + files.map((f) => ` + ${
|
|
12014
|
+
` + files.map((f) => ` + ${join15(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
11426
12015
|
);
|
|
11427
12016
|
let loaded = 0;
|
|
11428
12017
|
for (const file of files) {
|
|
11429
12018
|
try {
|
|
11430
|
-
const fileUrl = pathToFileURL(
|
|
12019
|
+
const fileUrl = pathToFileURL(join15(pluginsDir, file)).href;
|
|
11431
12020
|
const mod = await import(fileUrl);
|
|
11432
12021
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
11433
12022
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -11589,7 +12178,7 @@ var McpClient = class {
|
|
|
11589
12178
|
// 内部方法:JSON-RPC 通信
|
|
11590
12179
|
// ══════════════════════════════════════════════════════════════════
|
|
11591
12180
|
sendRequest(method, params) {
|
|
11592
|
-
return new Promise((
|
|
12181
|
+
return new Promise((resolve12, reject) => {
|
|
11593
12182
|
if (!this.process?.stdin?.writable) {
|
|
11594
12183
|
return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
|
|
11595
12184
|
}
|
|
@@ -11613,7 +12202,7 @@ var McpClient = class {
|
|
|
11613
12202
|
this.pendingRequests.set(id, {
|
|
11614
12203
|
resolve: (result) => {
|
|
11615
12204
|
cleanup();
|
|
11616
|
-
|
|
12205
|
+
resolve12(result);
|
|
11617
12206
|
},
|
|
11618
12207
|
reject: (error) => {
|
|
11619
12208
|
cleanup();
|
|
@@ -11690,13 +12279,13 @@ var McpClient = class {
|
|
|
11690
12279
|
}
|
|
11691
12280
|
/** Promise 超时包装 */
|
|
11692
12281
|
withTimeout(promise, ms, label) {
|
|
11693
|
-
return new Promise((
|
|
12282
|
+
return new Promise((resolve12, reject) => {
|
|
11694
12283
|
const timer = setTimeout(() => {
|
|
11695
12284
|
reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
|
|
11696
12285
|
}, ms);
|
|
11697
12286
|
promise.then((val) => {
|
|
11698
12287
|
clearTimeout(timer);
|
|
11699
|
-
|
|
12288
|
+
resolve12(val);
|
|
11700
12289
|
}).catch((err) => {
|
|
11701
12290
|
clearTimeout(timer);
|
|
11702
12291
|
reject(err);
|
|
@@ -11973,12 +12562,12 @@ var McpManager = class {
|
|
|
11973
12562
|
};
|
|
11974
12563
|
|
|
11975
12564
|
// src/skills/manager.ts
|
|
11976
|
-
import { existsSync as
|
|
11977
|
-
import { join as
|
|
12565
|
+
import { existsSync as existsSync21, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync7 } from "fs";
|
|
12566
|
+
import { join as join16 } from "path";
|
|
11978
12567
|
|
|
11979
12568
|
// src/skills/types.ts
|
|
11980
|
-
import { readFileSync as
|
|
11981
|
-
import { basename as
|
|
12569
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
12570
|
+
import { basename as basename6 } from "path";
|
|
11982
12571
|
function parseSimpleYaml(yaml) {
|
|
11983
12572
|
const result = {};
|
|
11984
12573
|
for (const line of yaml.split("\n")) {
|
|
@@ -11999,7 +12588,7 @@ function parseYamlArray(value) {
|
|
|
11999
12588
|
function parseSkillFile(filePath) {
|
|
12000
12589
|
let raw;
|
|
12001
12590
|
try {
|
|
12002
|
-
raw =
|
|
12591
|
+
raw = readFileSync15(filePath, "utf-8");
|
|
12003
12592
|
} catch {
|
|
12004
12593
|
return null;
|
|
12005
12594
|
}
|
|
@@ -12007,7 +12596,7 @@ function parseSkillFile(filePath) {
|
|
|
12007
12596
|
if (!frontmatterMatch) {
|
|
12008
12597
|
return {
|
|
12009
12598
|
meta: {
|
|
12010
|
-
name:
|
|
12599
|
+
name: basename6(filePath, ".md"),
|
|
12011
12600
|
description: ""
|
|
12012
12601
|
},
|
|
12013
12602
|
content: raw.trim(),
|
|
@@ -12018,7 +12607,7 @@ function parseSkillFile(filePath) {
|
|
|
12018
12607
|
const parsed = parseSimpleYaml(yaml);
|
|
12019
12608
|
return {
|
|
12020
12609
|
meta: {
|
|
12021
|
-
name: parsed["name"] ??
|
|
12610
|
+
name: parsed["name"] ?? basename6(filePath, ".md"),
|
|
12022
12611
|
description: parsed["description"] ?? "",
|
|
12023
12612
|
tools: parsed["tools"] ? parseYamlArray(parsed["tools"]) : void 0
|
|
12024
12613
|
},
|
|
@@ -12039,36 +12628,40 @@ var SkillManager = class {
|
|
|
12039
12628
|
this.skillsDir = skillsDir;
|
|
12040
12629
|
this.warnThreshold = warnThreshold ?? SKILL_CONTENT_WARN_CHARS_DEFAULT;
|
|
12041
12630
|
}
|
|
12042
|
-
/** 发现并加载 skillsDir
|
|
12043
|
-
loadSkills() {
|
|
12631
|
+
/** 发现并加载 skillsDir 以及额外插件目录下的技能,返回加载数量 */
|
|
12632
|
+
loadSkills(extraDirs = []) {
|
|
12044
12633
|
this.skills.clear();
|
|
12045
|
-
|
|
12046
|
-
|
|
12047
|
-
|
|
12048
|
-
|
|
12634
|
+
this.loadSkillDir(this.skillsDir, true);
|
|
12635
|
+
for (const dir of extraDirs) this.loadSkillDir(dir, false);
|
|
12636
|
+
return this.skills.size;
|
|
12637
|
+
}
|
|
12638
|
+
loadSkillDir(dir, createIfMissing) {
|
|
12639
|
+
if (!existsSync21(dir)) {
|
|
12640
|
+
if (createIfMissing) {
|
|
12641
|
+
try {
|
|
12642
|
+
mkdirSync10(dir, { recursive: true });
|
|
12643
|
+
} catch {
|
|
12644
|
+
}
|
|
12049
12645
|
}
|
|
12050
|
-
return
|
|
12646
|
+
return;
|
|
12051
12647
|
}
|
|
12052
12648
|
let entries;
|
|
12053
12649
|
try {
|
|
12054
|
-
entries =
|
|
12650
|
+
entries = readdirSync10(dir);
|
|
12055
12651
|
} catch {
|
|
12056
|
-
return
|
|
12652
|
+
return;
|
|
12057
12653
|
}
|
|
12058
12654
|
for (const entry of entries) {
|
|
12059
12655
|
let filePath;
|
|
12060
|
-
const fullPath =
|
|
12656
|
+
const fullPath = join16(dir, entry);
|
|
12061
12657
|
if (entry.endsWith(".md")) {
|
|
12062
12658
|
filePath = fullPath;
|
|
12063
12659
|
} else {
|
|
12064
12660
|
try {
|
|
12065
12661
|
if (statSync7(fullPath).isDirectory()) {
|
|
12066
|
-
const skillMd =
|
|
12067
|
-
if (
|
|
12068
|
-
|
|
12069
|
-
} else {
|
|
12070
|
-
continue;
|
|
12071
|
-
}
|
|
12662
|
+
const skillMd = join16(fullPath, "SKILL.md");
|
|
12663
|
+
if (existsSync21(skillMd)) filePath = skillMd;
|
|
12664
|
+
else continue;
|
|
12072
12665
|
} else {
|
|
12073
12666
|
continue;
|
|
12074
12667
|
}
|
|
@@ -12078,13 +12671,10 @@ var SkillManager = class {
|
|
|
12078
12671
|
}
|
|
12079
12672
|
const skill = parseSkillFile(filePath);
|
|
12080
12673
|
if (skill) {
|
|
12081
|
-
if (skill.meta.name === "SKILL" && !entry.endsWith(".md"))
|
|
12082
|
-
|
|
12083
|
-
}
|
|
12084
|
-
this.skills.set(skill.meta.name, skill);
|
|
12674
|
+
if (skill.meta.name === "SKILL" && !entry.endsWith(".md")) skill.meta.name = entry;
|
|
12675
|
+
if (!this.skills.has(skill.meta.name)) this.skills.set(skill.meta.name, skill);
|
|
12085
12676
|
}
|
|
12086
12677
|
}
|
|
12087
|
-
return this.skills.size;
|
|
12088
12678
|
}
|
|
12089
12679
|
/** 列出所有已加载的技能 */
|
|
12090
12680
|
listSkills() {
|
|
@@ -12127,9 +12717,9 @@ var SkillManager = class {
|
|
|
12127
12717
|
};
|
|
12128
12718
|
|
|
12129
12719
|
// src/web/tool-executor-web.ts
|
|
12130
|
-
import { randomUUID as
|
|
12720
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
12131
12721
|
import { tmpdir as tmpdir2 } from "os";
|
|
12132
|
-
import { existsSync as
|
|
12722
|
+
import { existsSync as existsSync22, readFileSync as readFileSync16 } from "fs";
|
|
12133
12723
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
12134
12724
|
constructor(registry, ws) {
|
|
12135
12725
|
this.registry = registry;
|
|
@@ -12199,33 +12789,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12199
12789
|
}
|
|
12200
12790
|
/** Resolve a pending confirm from client response */
|
|
12201
12791
|
resolveConfirm(requestId, approved) {
|
|
12202
|
-
const
|
|
12203
|
-
if (
|
|
12792
|
+
const resolve12 = this.pendingConfirms.get(requestId);
|
|
12793
|
+
if (resolve12) {
|
|
12204
12794
|
this.clearPendingTimer(requestId);
|
|
12205
12795
|
this.pendingConfirms.delete(requestId);
|
|
12206
12796
|
this.confirming = false;
|
|
12207
|
-
|
|
12797
|
+
resolve12(approved);
|
|
12208
12798
|
}
|
|
12209
12799
|
}
|
|
12210
12800
|
/** Resolve a pending batch confirm from client response */
|
|
12211
12801
|
resolveBatchConfirm(requestId, decision) {
|
|
12212
|
-
const
|
|
12213
|
-
if (
|
|
12802
|
+
const resolve12 = this.pendingBatchConfirms.get(requestId);
|
|
12803
|
+
if (resolve12) {
|
|
12214
12804
|
this.clearPendingTimer(requestId);
|
|
12215
12805
|
this.pendingBatchConfirms.delete(requestId);
|
|
12216
12806
|
this.confirming = false;
|
|
12217
12807
|
if (decision === "all" || decision === "none") {
|
|
12218
|
-
|
|
12808
|
+
resolve12(decision);
|
|
12219
12809
|
} else {
|
|
12220
|
-
|
|
12810
|
+
resolve12(new Set(decision));
|
|
12221
12811
|
}
|
|
12222
12812
|
}
|
|
12223
12813
|
}
|
|
12224
12814
|
/** Cancel all pending confirms (e.g., on disconnect) */
|
|
12225
12815
|
cancelAll() {
|
|
12226
|
-
for (const
|
|
12816
|
+
for (const resolve12 of this.pendingConfirms.values()) resolve12(false);
|
|
12227
12817
|
this.pendingConfirms.clear();
|
|
12228
|
-
for (const
|
|
12818
|
+
for (const resolve12 of this.pendingBatchConfirms.values()) resolve12("none");
|
|
12229
12819
|
this.pendingBatchConfirms.clear();
|
|
12230
12820
|
this.confirming = false;
|
|
12231
12821
|
}
|
|
@@ -12268,9 +12858,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12268
12858
|
if (call.name === "write_file") {
|
|
12269
12859
|
const filePath = String(call.arguments["path"] ?? "");
|
|
12270
12860
|
const newContent = String(call.arguments["content"] ?? "");
|
|
12271
|
-
if (filePath &&
|
|
12861
|
+
if (filePath && existsSync22(filePath)) {
|
|
12272
12862
|
try {
|
|
12273
|
-
const old =
|
|
12863
|
+
const old = readFileSync16(filePath, "utf-8");
|
|
12274
12864
|
return renderDiff(old, newContent, { filePath });
|
|
12275
12865
|
} catch {
|
|
12276
12866
|
}
|
|
@@ -12290,7 +12880,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12290
12880
|
}
|
|
12291
12881
|
/** WebSocket-based confirm — sends request, returns Promise resolved by client */
|
|
12292
12882
|
confirm(call, level) {
|
|
12293
|
-
const requestId =
|
|
12883
|
+
const requestId = randomUUID3();
|
|
12294
12884
|
this.confirming = true;
|
|
12295
12885
|
const msg = {
|
|
12296
12886
|
type: "confirm_request",
|
|
@@ -12301,8 +12891,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12301
12891
|
diff: this.getDiffPreview(call)
|
|
12302
12892
|
};
|
|
12303
12893
|
this.send(msg);
|
|
12304
|
-
return new Promise((
|
|
12305
|
-
this.pendingConfirms.set(requestId,
|
|
12894
|
+
return new Promise((resolve12) => {
|
|
12895
|
+
this.pendingConfirms.set(requestId, resolve12);
|
|
12306
12896
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
12307
12897
|
if (this.pendingConfirms.has(requestId)) {
|
|
12308
12898
|
this.resolveConfirm(requestId, false);
|
|
@@ -12312,7 +12902,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12312
12902
|
}
|
|
12313
12903
|
/** WebSocket-based batch confirm */
|
|
12314
12904
|
batchConfirm(calls) {
|
|
12315
|
-
const requestId =
|
|
12905
|
+
const requestId = randomUUID3();
|
|
12316
12906
|
this.confirming = true;
|
|
12317
12907
|
const files = calls.map((call, i) => ({
|
|
12318
12908
|
index: i + 1,
|
|
@@ -12326,8 +12916,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12326
12916
|
files
|
|
12327
12917
|
};
|
|
12328
12918
|
this.send(msg);
|
|
12329
|
-
return new Promise((
|
|
12330
|
-
this.pendingBatchConfirms.set(requestId,
|
|
12919
|
+
return new Promise((resolve12) => {
|
|
12920
|
+
this.pendingBatchConfirms.set(requestId, resolve12);
|
|
12331
12921
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
12332
12922
|
if (this.pendingBatchConfirms.has(requestId)) {
|
|
12333
12923
|
this.resolveBatchConfirm(requestId, "none");
|
|
@@ -12572,21 +13162,17 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12572
13162
|
}
|
|
12573
13163
|
};
|
|
12574
13164
|
|
|
12575
|
-
// src/core/system-prompt-builder.ts
|
|
12576
|
-
import { existsSync as existsSync22, readFileSync as readFileSync17 } from "fs";
|
|
12577
|
-
import { join as join15 } from "path";
|
|
12578
|
-
|
|
12579
13165
|
// src/repl/dev-state.ts
|
|
12580
|
-
import { existsSync as
|
|
12581
|
-
import { join as
|
|
13166
|
+
import { existsSync as existsSync23, readFileSync as readFileSync17, unlinkSync as unlinkSync4, mkdirSync as mkdirSync11 } from "fs";
|
|
13167
|
+
import { join as join17 } from "path";
|
|
12582
13168
|
import { homedir as homedir7 } from "os";
|
|
12583
13169
|
function getDevStatePath() {
|
|
12584
|
-
return
|
|
13170
|
+
return join17(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
12585
13171
|
}
|
|
12586
13172
|
function loadDevState() {
|
|
12587
13173
|
const path3 = getDevStatePath();
|
|
12588
|
-
if (!
|
|
12589
|
-
const content =
|
|
13174
|
+
if (!existsSync23(path3)) return null;
|
|
13175
|
+
const content = readFileSync17(path3, "utf-8").trim();
|
|
12590
13176
|
return content || null;
|
|
12591
13177
|
}
|
|
12592
13178
|
|
|
@@ -12644,19 +13230,7 @@ ${ctx.activeSkill.content}`);
|
|
|
12644
13230
|
return { stable: stableParts.join("\n\n---\n\n"), volatile };
|
|
12645
13231
|
}
|
|
12646
13232
|
function loadMemoryContent(configDir) {
|
|
12647
|
-
|
|
12648
|
-
if (!existsSync22(memoryPath)) return null;
|
|
12649
|
-
let content = readFileSync17(memoryPath, "utf-8").trim();
|
|
12650
|
-
if (!content) return null;
|
|
12651
|
-
if (content.length > MEMORY_MAX_CHARS) {
|
|
12652
|
-
content = content.slice(-MEMORY_MAX_CHARS);
|
|
12653
|
-
const firstEntry = content.indexOf("\n## ");
|
|
12654
|
-
if (firstEntry !== -1) {
|
|
12655
|
-
content = content.slice(firstEntry + 1);
|
|
12656
|
-
}
|
|
12657
|
-
}
|
|
12658
|
-
const entryCount = (content.match(/^## \d{4}-\d{2}-\d{2}/gm) ?? []).length;
|
|
12659
|
-
return { content, entryCount };
|
|
13233
|
+
return formatMemoryForPrompt(configDir, process.cwd());
|
|
12660
13234
|
}
|
|
12661
13235
|
|
|
12662
13236
|
// src/core/pricing.ts
|
|
@@ -12855,8 +13429,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
12855
13429
|
}
|
|
12856
13430
|
|
|
12857
13431
|
// src/core/context-files.ts
|
|
12858
|
-
import { existsSync as
|
|
12859
|
-
import { join as
|
|
13432
|
+
import { existsSync as existsSync24, readFileSync as readFileSync18 } from "fs";
|
|
13433
|
+
import { join as join18, relative as relative3, resolve as resolve9 } from "path";
|
|
12860
13434
|
function uniqueNonEmpty(values) {
|
|
12861
13435
|
const seen = /* @__PURE__ */ new Set();
|
|
12862
13436
|
const out = [];
|
|
@@ -12879,7 +13453,7 @@ function displayPath(filePath, cwd) {
|
|
|
12879
13453
|
return filePath;
|
|
12880
13454
|
}
|
|
12881
13455
|
function isInsideOrEqual(parent, child) {
|
|
12882
|
-
const rel = relative3(
|
|
13456
|
+
const rel = relative3(resolve9(parent), resolve9(child));
|
|
12883
13457
|
return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
|
|
12884
13458
|
}
|
|
12885
13459
|
function readContextFile(level, filePath, cwd, maxBytes) {
|
|
@@ -12933,8 +13507,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
12933
13507
|
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
12934
13508
|
const skipped = [];
|
|
12935
13509
|
for (const candidate of candidates) {
|
|
12936
|
-
const filePath =
|
|
12937
|
-
if (!
|
|
13510
|
+
const filePath = join18(dir, candidate);
|
|
13511
|
+
if (!existsSync24(filePath)) continue;
|
|
12938
13512
|
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
12939
13513
|
if (result.skipped) skipped.push(result.skipped);
|
|
12940
13514
|
if (result.layer) return { layer: result.layer, skipped };
|
|
@@ -12942,7 +13516,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
|
12942
13516
|
return { layer: null, skipped };
|
|
12943
13517
|
}
|
|
12944
13518
|
function loadContextFiles(options) {
|
|
12945
|
-
const cwd =
|
|
13519
|
+
const cwd = resolve9(options.cwd);
|
|
12946
13520
|
const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
|
|
12947
13521
|
const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
|
|
12948
13522
|
const skipped = [];
|
|
@@ -12950,7 +13524,7 @@ function loadContextFiles(options) {
|
|
|
12950
13524
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
12951
13525
|
}
|
|
12952
13526
|
if (options.setting && options.setting !== "auto") {
|
|
12953
|
-
const filePath =
|
|
13527
|
+
const filePath = resolve9(cwd, String(options.setting));
|
|
12954
13528
|
if (!isInsideOrEqual(cwd, filePath)) {
|
|
12955
13529
|
skipped.push({
|
|
12956
13530
|
level: "single",
|
|
@@ -12961,7 +13535,7 @@ function loadContextFiles(options) {
|
|
|
12961
13535
|
});
|
|
12962
13536
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
12963
13537
|
}
|
|
12964
|
-
if (!
|
|
13538
|
+
if (!existsSync24(filePath)) {
|
|
12965
13539
|
skipped.push({
|
|
12966
13540
|
level: "single",
|
|
12967
13541
|
filePath,
|
|
@@ -12994,7 +13568,7 @@ function loadContextFiles(options) {
|
|
|
12994
13568
|
skipped.push(...result.skipped);
|
|
12995
13569
|
if (result.layer) layers.push(result.layer);
|
|
12996
13570
|
}
|
|
12997
|
-
if (
|
|
13571
|
+
if (resolve9(options.cwd) !== resolve9(options.projectRoot)) {
|
|
12998
13572
|
const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
|
|
12999
13573
|
skipped.push(...result.skipped);
|
|
13000
13574
|
if (result.layer) layers.push(result.layer);
|
|
@@ -13012,98 +13586,8 @@ function loadContextFiles(options) {
|
|
|
13012
13586
|
}
|
|
13013
13587
|
|
|
13014
13588
|
// src/web/session-handler.ts
|
|
13015
|
-
import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync2, mkdirSync as
|
|
13016
|
-
import { join as
|
|
13017
|
-
|
|
13018
|
-
// src/tools/git-context.ts
|
|
13019
|
-
import { execSync as execSync2 } from "child_process";
|
|
13020
|
-
import { existsSync as existsSync24 } from "fs";
|
|
13021
|
-
import { join as join17 } from "path";
|
|
13022
|
-
function runGit2(cmd, cwd) {
|
|
13023
|
-
try {
|
|
13024
|
-
return execSync2(`git ${cmd}`, {
|
|
13025
|
-
cwd,
|
|
13026
|
-
encoding: "utf-8",
|
|
13027
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
13028
|
-
timeout: 5e3
|
|
13029
|
-
}).trim();
|
|
13030
|
-
} catch {
|
|
13031
|
-
return null;
|
|
13032
|
-
}
|
|
13033
|
-
}
|
|
13034
|
-
function getGitRoot(cwd = process.cwd()) {
|
|
13035
|
-
return runGit2("rev-parse --show-toplevel", cwd);
|
|
13036
|
-
}
|
|
13037
|
-
function getGitContext(cwd = process.cwd()) {
|
|
13038
|
-
if (!existsSync24(join17(cwd, ".git"))) {
|
|
13039
|
-
const result = runGit2("rev-parse --git-dir", cwd);
|
|
13040
|
-
if (!result) return null;
|
|
13041
|
-
}
|
|
13042
|
-
const branch = runGit2("rev-parse --abbrev-ref HEAD", cwd);
|
|
13043
|
-
if (!branch) return null;
|
|
13044
|
-
const statusOutput = runGit2("status --porcelain", cwd) ?? "";
|
|
13045
|
-
const statusLines = statusOutput ? statusOutput.split("\n").filter(Boolean) : [];
|
|
13046
|
-
const stagedFiles = [];
|
|
13047
|
-
const changedFiles = [];
|
|
13048
|
-
for (const line of statusLines) {
|
|
13049
|
-
const xy = line.slice(0, 2);
|
|
13050
|
-
const file = line.slice(3).trim();
|
|
13051
|
-
const indexStatus = xy[0];
|
|
13052
|
-
const workStatus = xy[1];
|
|
13053
|
-
if (indexStatus && indexStatus !== " " && indexStatus !== "?") {
|
|
13054
|
-
stagedFiles.push(`${indexStatus} ${file}`);
|
|
13055
|
-
}
|
|
13056
|
-
if (workStatus && workStatus !== " ") {
|
|
13057
|
-
changedFiles.push(`${workStatus} ${file}`);
|
|
13058
|
-
}
|
|
13059
|
-
}
|
|
13060
|
-
const logOutput = runGit2("log --oneline -3", cwd) ?? "";
|
|
13061
|
-
const recentCommits = logOutput ? logOutput.split("\n").filter(Boolean) : [];
|
|
13062
|
-
const unpushedOutput = runGit2("log @{u}..HEAD --oneline", cwd);
|
|
13063
|
-
const hasUnpushed = unpushedOutput !== null && unpushedOutput.trim().length > 0;
|
|
13064
|
-
return {
|
|
13065
|
-
branch,
|
|
13066
|
-
changedFiles,
|
|
13067
|
-
stagedFiles,
|
|
13068
|
-
recentCommits,
|
|
13069
|
-
hasUnpushed
|
|
13070
|
-
};
|
|
13071
|
-
}
|
|
13072
|
-
function formatGitContextForPrompt(ctx) {
|
|
13073
|
-
const lines = ["# Git Repository Status", ""];
|
|
13074
|
-
lines.push(`- **Branch**: \`${ctx.branch}\``);
|
|
13075
|
-
if (ctx.stagedFiles.length > 0) {
|
|
13076
|
-
lines.push(`- **Staged** (${ctx.stagedFiles.length} files):`);
|
|
13077
|
-
for (const f of ctx.stagedFiles.slice(0, 10)) {
|
|
13078
|
-
lines.push(` - ${f}`);
|
|
13079
|
-
}
|
|
13080
|
-
if (ctx.stagedFiles.length > 10) {
|
|
13081
|
-
lines.push(` - ... and ${ctx.stagedFiles.length - 10} more`);
|
|
13082
|
-
}
|
|
13083
|
-
}
|
|
13084
|
-
if (ctx.changedFiles.length > 0) {
|
|
13085
|
-
lines.push(`- **Modified** (${ctx.changedFiles.length} files):`);
|
|
13086
|
-
for (const f of ctx.changedFiles.slice(0, 10)) {
|
|
13087
|
-
lines.push(` - ${f}`);
|
|
13088
|
-
}
|
|
13089
|
-
if (ctx.changedFiles.length > 10) {
|
|
13090
|
-
lines.push(` - ... and ${ctx.changedFiles.length - 10} more`);
|
|
13091
|
-
}
|
|
13092
|
-
}
|
|
13093
|
-
if (ctx.stagedFiles.length === 0 && ctx.changedFiles.length === 0) {
|
|
13094
|
-
lines.push("- **Working tree**: clean");
|
|
13095
|
-
}
|
|
13096
|
-
if (ctx.recentCommits.length > 0) {
|
|
13097
|
-
lines.push("- **Recent commits**:");
|
|
13098
|
-
for (const c of ctx.recentCommits) {
|
|
13099
|
-
lines.push(` - ${c}`);
|
|
13100
|
-
}
|
|
13101
|
-
}
|
|
13102
|
-
if (ctx.hasUnpushed) {
|
|
13103
|
-
lines.push("- \u26A0\uFE0F Has unpushed commits");
|
|
13104
|
-
}
|
|
13105
|
-
return lines.join("\n");
|
|
13106
|
-
}
|
|
13589
|
+
import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync2, mkdirSync as mkdirSync12, readdirSync as readdirSync12, statSync as statSync9, createWriteStream } from "fs";
|
|
13590
|
+
import { join as join21, resolve as resolve10, dirname as dirname7 } from "path";
|
|
13107
13591
|
|
|
13108
13592
|
// src/cli/review-prompts.ts
|
|
13109
13593
|
function buildReviewPrompt(diff, gitContextStr, detailed) {
|
|
@@ -13164,8 +13648,8 @@ If no security issues found, state "\u2705 No security vulnerabilities detected"
|
|
|
13164
13648
|
}
|
|
13165
13649
|
|
|
13166
13650
|
// src/repl/commands/project-init.ts
|
|
13167
|
-
import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as
|
|
13168
|
-
import { join as
|
|
13651
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
|
|
13652
|
+
import { join as join19 } from "path";
|
|
13169
13653
|
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
13170
13654
|
"node_modules",
|
|
13171
13655
|
".git",
|
|
@@ -13201,7 +13685,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
13201
13685
|
if (depth > maxDepth || count >= maxEntries) return;
|
|
13202
13686
|
let entries;
|
|
13203
13687
|
try {
|
|
13204
|
-
entries =
|
|
13688
|
+
entries = readdirSync11(d);
|
|
13205
13689
|
} catch {
|
|
13206
13690
|
return;
|
|
13207
13691
|
}
|
|
@@ -13209,11 +13693,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
13209
13693
|
const sorted = filtered.sort((a, b) => {
|
|
13210
13694
|
let aIsDir = false, bIsDir = false;
|
|
13211
13695
|
try {
|
|
13212
|
-
aIsDir = statSync8(
|
|
13696
|
+
aIsDir = statSync8(join19(d, a)).isDirectory();
|
|
13213
13697
|
} catch {
|
|
13214
13698
|
}
|
|
13215
13699
|
try {
|
|
13216
|
-
bIsDir = statSync8(
|
|
13700
|
+
bIsDir = statSync8(join19(d, b)).isDirectory();
|
|
13217
13701
|
} catch {
|
|
13218
13702
|
}
|
|
13219
13703
|
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
@@ -13221,7 +13705,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
13221
13705
|
});
|
|
13222
13706
|
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
13223
13707
|
const name = sorted[i];
|
|
13224
|
-
const fullPath =
|
|
13708
|
+
const fullPath = join19(d, name);
|
|
13225
13709
|
const isLast = i === sorted.length - 1;
|
|
13226
13710
|
const connector = isLast ? "+-- " : "|-- ";
|
|
13227
13711
|
let isDir;
|
|
@@ -13248,7 +13732,7 @@ function scanProject(cwd) {
|
|
|
13248
13732
|
configFiles: [],
|
|
13249
13733
|
directoryStructure: ""
|
|
13250
13734
|
};
|
|
13251
|
-
const check = (file) => existsSync25(
|
|
13735
|
+
const check = (file) => existsSync25(join19(cwd, file));
|
|
13252
13736
|
const configCandidates = [
|
|
13253
13737
|
"package.json",
|
|
13254
13738
|
"tsconfig.json",
|
|
@@ -13275,7 +13759,7 @@ function scanProject(cwd) {
|
|
|
13275
13759
|
info.type = "node";
|
|
13276
13760
|
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
13277
13761
|
try {
|
|
13278
|
-
const pkg = JSON.parse(readFileSync19(
|
|
13762
|
+
const pkg = JSON.parse(readFileSync19(join19(cwd, "package.json"), "utf-8"));
|
|
13279
13763
|
const scripts = pkg.scripts ?? {};
|
|
13280
13764
|
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
13281
13765
|
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
@@ -13935,7 +14419,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
|
|
|
13935
14419
|
}
|
|
13936
14420
|
|
|
13937
14421
|
// src/hub/persist.ts
|
|
13938
|
-
import { join as
|
|
14422
|
+
import { join as join20 } from "path";
|
|
13939
14423
|
function discussionToMessages(state2) {
|
|
13940
14424
|
const out = [];
|
|
13941
14425
|
const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -13973,7 +14457,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
|
|
|
13973
14457
|
session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
|
|
13974
14458
|
session.titleAiGenerated = true;
|
|
13975
14459
|
await sm.save();
|
|
13976
|
-
return { id: session.id, path:
|
|
14460
|
+
return { id: session.id, path: join20(config.getHistoryDir(), `${session.id}.json`) };
|
|
13977
14461
|
}
|
|
13978
14462
|
|
|
13979
14463
|
// src/web/session-handler.ts
|
|
@@ -14069,7 +14553,7 @@ var SessionHandler = class {
|
|
|
14069
14553
|
}
|
|
14070
14554
|
this.updateContextWindow();
|
|
14071
14555
|
this.activeSystemPrompt = this.loadContextFiles();
|
|
14072
|
-
const hooks = this.config.get("hooks");
|
|
14556
|
+
const hooks = mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir());
|
|
14073
14557
|
const permissionRules = this.config.get("permissionRules");
|
|
14074
14558
|
const defaultPermission = this.config.get("defaultPermission");
|
|
14075
14559
|
const permissionProfileName = this.config.get("defaultPermissionProfile") ?? "legacy";
|
|
@@ -14169,10 +14653,10 @@ var SessionHandler = class {
|
|
|
14169
14653
|
return;
|
|
14170
14654
|
}
|
|
14171
14655
|
case "ask_user_response": {
|
|
14172
|
-
const
|
|
14173
|
-
if (
|
|
14656
|
+
const resolve12 = this.pendingAskUser.get(msg.requestId);
|
|
14657
|
+
if (resolve12) {
|
|
14174
14658
|
this.pendingAskUser.delete(msg.requestId);
|
|
14175
|
-
|
|
14659
|
+
resolve12(msg.answer);
|
|
14176
14660
|
}
|
|
14177
14661
|
return;
|
|
14178
14662
|
}
|
|
@@ -14182,11 +14666,13 @@ var SessionHandler = class {
|
|
|
14182
14666
|
return this.handleMemoryStatus();
|
|
14183
14667
|
case "memory_rebuild":
|
|
14184
14668
|
return this.handleMemoryRebuild(Boolean(msg.full));
|
|
14669
|
+
case "memory_manage":
|
|
14670
|
+
return this.handleMemoryManage(msg.action, msg.id, msg.expiresAt);
|
|
14185
14671
|
case "auto_pause_response": {
|
|
14186
|
-
const
|
|
14187
|
-
if (
|
|
14672
|
+
const resolve12 = this.pendingAutoPause.get(msg.requestId);
|
|
14673
|
+
if (resolve12) {
|
|
14188
14674
|
this.pendingAutoPause.delete(msg.requestId);
|
|
14189
|
-
|
|
14675
|
+
resolve12({ action: msg.action, message: msg.message });
|
|
14190
14676
|
}
|
|
14191
14677
|
return;
|
|
14192
14678
|
}
|
|
@@ -14201,10 +14687,10 @@ var SessionHandler = class {
|
|
|
14201
14687
|
this.hubOrchestrator?.abort();
|
|
14202
14688
|
return;
|
|
14203
14689
|
case "hub_steer": {
|
|
14204
|
-
const
|
|
14205
|
-
if (
|
|
14690
|
+
const resolve12 = this.pendingHubReview.get(msg.requestId);
|
|
14691
|
+
if (resolve12) {
|
|
14206
14692
|
this.pendingHubReview.delete(msg.requestId);
|
|
14207
|
-
|
|
14693
|
+
resolve12({ action: msg.action, message: msg.message });
|
|
14208
14694
|
}
|
|
14209
14695
|
return;
|
|
14210
14696
|
}
|
|
@@ -14214,12 +14700,12 @@ var SessionHandler = class {
|
|
|
14214
14700
|
}
|
|
14215
14701
|
}
|
|
14216
14702
|
onDisconnect() {
|
|
14217
|
-
runLifecycleHooks(this.config.get("hooks") ?? void 0, "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
|
|
14703
|
+
runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
|
|
14218
14704
|
this.toolExecutor.cancelAll();
|
|
14219
14705
|
if (this.abortController) this.abortController.abort();
|
|
14220
|
-
for (const
|
|
14706
|
+
for (const resolve12 of this.pendingAskUser.values()) resolve12(null);
|
|
14221
14707
|
this.pendingAskUser.clear();
|
|
14222
|
-
for (const
|
|
14708
|
+
for (const resolve12 of this.pendingAutoPause.values()) resolve12({ action: "stop" });
|
|
14223
14709
|
this.pendingAutoPause.clear();
|
|
14224
14710
|
this.saveIfNeeded();
|
|
14225
14711
|
}
|
|
@@ -14353,9 +14839,9 @@ var SessionHandler = class {
|
|
|
14353
14839
|
this.hubOrchestrator = orchestrator;
|
|
14354
14840
|
orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
|
|
14355
14841
|
if (config.humanSteer) {
|
|
14356
|
-
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((
|
|
14842
|
+
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve12) => {
|
|
14357
14843
|
const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
14358
|
-
this.pendingHubReview.set(requestId,
|
|
14844
|
+
this.pendingHubReview.set(requestId, resolve12);
|
|
14359
14845
|
this.send({ type: "hub_review", requestId, round, maxRounds });
|
|
14360
14846
|
});
|
|
14361
14847
|
}
|
|
@@ -14393,7 +14879,7 @@ var SessionHandler = class {
|
|
|
14393
14879
|
this.ensureSession();
|
|
14394
14880
|
const session = this.sessions.current;
|
|
14395
14881
|
const promptDecisions = runLifecycleHooks(
|
|
14396
|
-
this.config.get("hooks") ?? void 0,
|
|
14882
|
+
mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
|
|
14397
14883
|
"UserPromptSubmit",
|
|
14398
14884
|
{ sessionId: session.id, prompt: content },
|
|
14399
14885
|
{ configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
@@ -14688,7 +15174,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
14688
15174
|
this.send({ type: "response_done", content, usage });
|
|
14689
15175
|
if (pendingTeeSave && isCleanDocumentBody(content)) {
|
|
14690
15176
|
try {
|
|
14691
|
-
|
|
15177
|
+
mkdirSync12(dirname7(pendingTeeSave), { recursive: true });
|
|
14692
15178
|
const bodyToSave = stripOuterCodeFence(content);
|
|
14693
15179
|
atomicWriteFileSync(pendingTeeSave, bodyToSave);
|
|
14694
15180
|
undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
|
|
@@ -14713,8 +15199,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
14713
15199
|
onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
|
|
14714
15200
|
requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
|
|
14715
15201
|
const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
14716
|
-
const pauseResp = await new Promise((
|
|
14717
|
-
this.pendingAutoPause.set(requestId,
|
|
15202
|
+
const pauseResp = await new Promise((resolve12) => {
|
|
15203
|
+
this.pendingAutoPause.set(requestId, resolve12);
|
|
14718
15204
|
this.send({
|
|
14719
15205
|
type: "auto_pause_request",
|
|
14720
15206
|
requestId,
|
|
@@ -14864,7 +15350,7 @@ ${summaryContent}`,
|
|
|
14864
15350
|
let isError = false;
|
|
14865
15351
|
let summary;
|
|
14866
15352
|
try {
|
|
14867
|
-
|
|
15353
|
+
mkdirSync12(dirname7(saveToFile), { recursive: true });
|
|
14868
15354
|
fileStream = createWriteStream(saveToFile);
|
|
14869
15355
|
const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
|
|
14870
15356
|
const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
|
|
@@ -14893,8 +15379,8 @@ ${summaryContent}`,
|
|
|
14893
15379
|
}
|
|
14894
15380
|
if (chunk.done) break;
|
|
14895
15381
|
}
|
|
14896
|
-
await new Promise((
|
|
14897
|
-
fileStream.end((err) => err ? reject(err) :
|
|
15382
|
+
await new Promise((resolve12, reject) => {
|
|
15383
|
+
fileStream.end((err) => err ? reject(err) : resolve12());
|
|
14898
15384
|
});
|
|
14899
15385
|
const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
|
|
14900
15386
|
if (verdict.kind === "reject") {
|
|
@@ -14920,7 +15406,7 @@ ${summaryContent}`,
|
|
|
14920
15406
|
} catch (err) {
|
|
14921
15407
|
if (fileStream) {
|
|
14922
15408
|
try {
|
|
14923
|
-
await new Promise((
|
|
15409
|
+
await new Promise((resolve12) => fileStream.end(() => resolve12()));
|
|
14924
15410
|
} catch {
|
|
14925
15411
|
}
|
|
14926
15412
|
}
|
|
@@ -15235,6 +15721,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
|
|
|
15235
15721
|
" /add-dir [path|remove] \u2014 Add/remove directory from AI context",
|
|
15236
15722
|
" /mcp [reconnect] \u2014 Show/manage MCP servers",
|
|
15237
15723
|
" /plugins \u2014 Show loaded plugins",
|
|
15724
|
+
" /plugin <cmd> \u2014 Manage package plugins",
|
|
15238
15725
|
" /commands \u2014 List custom commands",
|
|
15239
15726
|
" /doctor \u2014 Health check (API keys, config, MCP)",
|
|
15240
15727
|
" /bug \u2014 Generate bug report template",
|
|
@@ -15333,7 +15820,7 @@ ${lines.join("\n")}` });
|
|
|
15333
15820
|
this.sendToolsList();
|
|
15334
15821
|
this.sendStatus();
|
|
15335
15822
|
} else if (sub === "reload") {
|
|
15336
|
-
this.skillManager.loadSkills();
|
|
15823
|
+
this.skillManager.loadSkills(getActivePluginAssets(this.config.getConfigDir()).skillDirs);
|
|
15337
15824
|
this.send({ type: "info", message: `\u{1F504} Reloaded ${this.skillManager.listSkills().length} skill(s).` });
|
|
15338
15825
|
this.sendToolsList();
|
|
15339
15826
|
} else {
|
|
@@ -15655,7 +16142,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15655
16142
|
break;
|
|
15656
16143
|
}
|
|
15657
16144
|
const sub = args[0]?.toLowerCase();
|
|
15658
|
-
const
|
|
16145
|
+
const resolve12 = (ref) => {
|
|
15659
16146
|
const r = session.resolveBranchRef(ref);
|
|
15660
16147
|
if (r.ok) return r.id;
|
|
15661
16148
|
if (r.reason === "ambiguous") {
|
|
@@ -15709,7 +16196,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15709
16196
|
this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
|
|
15710
16197
|
break;
|
|
15711
16198
|
}
|
|
15712
|
-
const id =
|
|
16199
|
+
const id = resolve12(ref);
|
|
15713
16200
|
if (!id) break;
|
|
15714
16201
|
const ok = session.switchBranch(id);
|
|
15715
16202
|
if (ok) {
|
|
@@ -15729,7 +16216,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15729
16216
|
this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
|
|
15730
16217
|
break;
|
|
15731
16218
|
}
|
|
15732
|
-
const id =
|
|
16219
|
+
const id = resolve12(ref);
|
|
15733
16220
|
if (!id) break;
|
|
15734
16221
|
const ok = session.deleteBranch(id);
|
|
15735
16222
|
if (ok) {
|
|
@@ -15748,7 +16235,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15748
16235
|
this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
|
|
15749
16236
|
break;
|
|
15750
16237
|
}
|
|
15751
|
-
const id =
|
|
16238
|
+
const id = resolve12(ref);
|
|
15752
16239
|
if (!id) break;
|
|
15753
16240
|
const ok = session.renameBranch(id, title);
|
|
15754
16241
|
if (ok) {
|
|
@@ -15766,7 +16253,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15766
16253
|
this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
|
|
15767
16254
|
break;
|
|
15768
16255
|
}
|
|
15769
|
-
const id =
|
|
16256
|
+
const id = resolve12(ref);
|
|
15770
16257
|
if (!id) break;
|
|
15771
16258
|
const d = session.diffBranches(id);
|
|
15772
16259
|
if (!d) {
|
|
@@ -15808,7 +16295,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15808
16295
|
this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
|
|
15809
16296
|
break;
|
|
15810
16297
|
}
|
|
15811
|
-
const id =
|
|
16298
|
+
const id = resolve12(ref);
|
|
15812
16299
|
if (!id) break;
|
|
15813
16300
|
const idx = parseInt(idxStr, 10);
|
|
15814
16301
|
if (Number.isNaN(idx)) {
|
|
@@ -16070,7 +16557,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
16070
16557
|
case "test": {
|
|
16071
16558
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
16072
16559
|
try {
|
|
16073
|
-
const { executeTests } = await import("./run-tests-
|
|
16560
|
+
const { executeTests } = await import("./run-tests-NXVVKAK2.js");
|
|
16074
16561
|
const argStr = args.join(" ").trim();
|
|
16075
16562
|
let testArgs = {};
|
|
16076
16563
|
if (argStr) {
|
|
@@ -16087,7 +16574,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
16087
16574
|
// ── /init ───────────────────────────────────────────────────────
|
|
16088
16575
|
case "init": {
|
|
16089
16576
|
const cwd = process.cwd();
|
|
16090
|
-
const targetPath =
|
|
16577
|
+
const targetPath = join21(cwd, "AICLI.md");
|
|
16091
16578
|
const force = args.includes("--force");
|
|
16092
16579
|
if (existsSync26(targetPath) && !force) {
|
|
16093
16580
|
this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
@@ -16132,9 +16619,9 @@ Use /context reload to load it.` });
|
|
|
16132
16619
|
}
|
|
16133
16620
|
lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
|
|
16134
16621
|
};
|
|
16135
|
-
checkFile("config.json",
|
|
16136
|
-
checkFile("memory.md",
|
|
16137
|
-
checkFile("dev-state.md",
|
|
16622
|
+
checkFile("config.json", join21(configDir, "config.json"));
|
|
16623
|
+
checkFile("memory.md", join21(configDir, MEMORY_FILE_NAME));
|
|
16624
|
+
checkFile("dev-state.md", join21(configDir, "dev-state.md"));
|
|
16138
16625
|
lines.push("");
|
|
16139
16626
|
if (this.mcpManager) {
|
|
16140
16627
|
lines.push("**MCP Servers:**");
|
|
@@ -16237,7 +16724,7 @@ ${this.config.toFormattedJSON()}
|
|
|
16237
16724
|
const result = this.contextLoadResult;
|
|
16238
16725
|
const layers = ["\u{1F4DA} **Context Layers:**", ""];
|
|
16239
16726
|
if (result && result.layers.length > 0) {
|
|
16240
|
-
const labels = { global: "Global ", project: "Project", local: "Local " };
|
|
16727
|
+
const labels = { global: "Global ", project: "Project", plugin: "Plugin ", local: "Local " };
|
|
16241
16728
|
for (const layer of result.layers) {
|
|
16242
16729
|
layers.push(` \u2713 ${labels[layer.level] ?? layer.level}: ${layer.displayPath} (${layer.charCount} chars${layer.truncated ? ", truncated" : ""})`);
|
|
16243
16730
|
}
|
|
@@ -16321,7 +16808,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
16321
16808
|
this.send({ type: "info", message: "\u2713 All added directories removed from context." });
|
|
16322
16809
|
break;
|
|
16323
16810
|
}
|
|
16324
|
-
const dirPath =
|
|
16811
|
+
const dirPath = resolve10(sub);
|
|
16325
16812
|
if (!existsSync26(dirPath)) {
|
|
16326
16813
|
this.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
16327
16814
|
break;
|
|
@@ -16338,14 +16825,14 @@ It will be included in AI context for subsequent messages.` });
|
|
|
16338
16825
|
// ── /commands ───────────────────────────────────────────────────
|
|
16339
16826
|
case "commands": {
|
|
16340
16827
|
const configDir = this.config.getConfigDir();
|
|
16341
|
-
const commandsDir =
|
|
16828
|
+
const commandsDir = join21(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
16342
16829
|
if (!existsSync26(commandsDir)) {
|
|
16343
16830
|
this.send({ type: "info", message: `No custom commands directory.
|
|
16344
16831
|
Create: ${commandsDir}/ with .md files.` });
|
|
16345
16832
|
break;
|
|
16346
16833
|
}
|
|
16347
16834
|
try {
|
|
16348
|
-
const files =
|
|
16835
|
+
const files = readdirSync12(commandsDir).filter((f) => f.endsWith(".md"));
|
|
16349
16836
|
if (files.length === 0) {
|
|
16350
16837
|
this.send({ type: "info", message: `No custom commands found in ${commandsDir}
|
|
16351
16838
|
Add .md files to create commands.` });
|
|
@@ -16362,23 +16849,75 @@ Add .md files to create commands.` });
|
|
|
16362
16849
|
}
|
|
16363
16850
|
break;
|
|
16364
16851
|
}
|
|
16365
|
-
// ── /plugins
|
|
16852
|
+
// ── /plugins / /plugin ──────────────────────────────────────────
|
|
16366
16853
|
case "plugins": {
|
|
16367
16854
|
const configDir = this.config.getConfigDir();
|
|
16368
|
-
const pluginsDir =
|
|
16855
|
+
const pluginsDir = join21(configDir, PLUGINS_DIR_NAME);
|
|
16369
16856
|
const pluginTools = this.toolRegistry.listPluginTools();
|
|
16857
|
+
const pluginPackages = listInstalledPlugins(configDir);
|
|
16370
16858
|
const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
|
|
16371
|
-
if (pluginTools.length === 0) {
|
|
16859
|
+
if (pluginTools.length === 0 && pluginPackages.length === 0) {
|
|
16372
16860
|
lines.push("No plugins loaded.");
|
|
16373
|
-
lines.push("
|
|
16861
|
+
lines.push("Use /plugin install <path>, /plugin trust <name>, then /plugin enable <name>.");
|
|
16374
16862
|
} else {
|
|
16375
|
-
|
|
16376
|
-
lines.push(`
|
|
16863
|
+
if (pluginPackages.length > 0) {
|
|
16864
|
+
lines.push(`Package plugins (${pluginPackages.length}):`);
|
|
16865
|
+
for (const p of pluginPackages) lines.push(` \u2022 ${p.name}@${p.manifest.version} \u2014 ${p.enabled ? "enabled" : "disabled"} / ${p.trusted ? "trusted" : "untrusted"}`);
|
|
16866
|
+
lines.push("");
|
|
16867
|
+
}
|
|
16868
|
+
if (pluginTools.length > 0) {
|
|
16869
|
+
lines.push(`Legacy JS plugin tools (${pluginTools.length}):`);
|
|
16870
|
+
for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
|
|
16377
16871
|
}
|
|
16378
16872
|
}
|
|
16379
16873
|
this.send({ type: "info", message: lines.join("\n") });
|
|
16380
16874
|
break;
|
|
16381
16875
|
}
|
|
16876
|
+
case "plugin": {
|
|
16877
|
+
const configDir = this.config.getConfigDir();
|
|
16878
|
+
const sub = args[0] ?? "list";
|
|
16879
|
+
try {
|
|
16880
|
+
if (sub === "list") {
|
|
16881
|
+
const plugins = listInstalledPlugins(configDir);
|
|
16882
|
+
const lines = [`\u{1F50C} **Package Plugins**`, `Dir: ${pluginRoot(configDir)}`, ""];
|
|
16883
|
+
if (plugins.length === 0) lines.push("No package plugins installed.");
|
|
16884
|
+
for (const p of plugins) lines.push(` \u2022 ${p.name}@${p.manifest.version} \u2014 ${p.enabled ? "enabled" : "disabled"} / ${p.trusted ? "trusted" : "untrusted"}`);
|
|
16885
|
+
this.send({ type: "info", message: lines.join("\n") });
|
|
16886
|
+
break;
|
|
16887
|
+
}
|
|
16888
|
+
if (sub === "install") {
|
|
16889
|
+
const input = args[1];
|
|
16890
|
+
if (!input) throw new Error("Usage: /plugin install <path>");
|
|
16891
|
+
const plugin = installPlugin(configDir, input);
|
|
16892
|
+
this.send({ type: "info", message: `Installed plugin ${plugin.name}@${plugin.manifest.version}. Run /plugin trust ${plugin.name}, then /plugin enable ${plugin.name}.` });
|
|
16893
|
+
break;
|
|
16894
|
+
}
|
|
16895
|
+
if (sub === "trust") {
|
|
16896
|
+
const name2 = args[1];
|
|
16897
|
+
if (!name2) throw new Error("Usage: /plugin trust <name>");
|
|
16898
|
+
const state2 = trustPlugin(configDir, name2);
|
|
16899
|
+
this.send({ type: "info", message: `Trusted plugin ${state2.name}.` });
|
|
16900
|
+
break;
|
|
16901
|
+
}
|
|
16902
|
+
if (sub === "enable" || sub === "disable") {
|
|
16903
|
+
const name2 = args[1];
|
|
16904
|
+
if (!name2) throw new Error(`Usage: /plugin ${sub} <name>`);
|
|
16905
|
+
const state2 = setPluginEnabled(configDir, name2, sub === "enable");
|
|
16906
|
+
this.send({ type: "info", message: `${sub === "enable" ? "Enabled" : "Disabled"} plugin ${state2.name}. Restart Web UI for changes to take effect.` });
|
|
16907
|
+
break;
|
|
16908
|
+
}
|
|
16909
|
+
if (sub === "inspect") {
|
|
16910
|
+
const name2 = args[1];
|
|
16911
|
+
if (!name2) throw new Error("Usage: /plugin inspect <name>");
|
|
16912
|
+
this.send({ type: "info", message: describePlugin(getInstalledPlugin(configDir, name2)).join("\n") });
|
|
16913
|
+
break;
|
|
16914
|
+
}
|
|
16915
|
+
throw new Error("Usage: /plugin [list|install|enable|disable|inspect|trust]");
|
|
16916
|
+
} catch (err) {
|
|
16917
|
+
this.send({ type: "error", message: err instanceof Error ? err.message : String(err) });
|
|
16918
|
+
}
|
|
16919
|
+
break;
|
|
16920
|
+
}
|
|
16382
16921
|
// ── /bug ────────────────────────────────────────────────────────
|
|
16383
16922
|
case "bug": {
|
|
16384
16923
|
const session = this.sessions.current;
|
|
@@ -16421,7 +16960,7 @@ Add .md files to create commands.` });
|
|
|
16421
16960
|
return;
|
|
16422
16961
|
}
|
|
16423
16962
|
const preDecisions = runLifecycleHooks(
|
|
16424
|
-
this.config.get("hooks") ?? void 0,
|
|
16963
|
+
mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
|
|
16425
16964
|
"PreCompact",
|
|
16426
16965
|
{ sessionId: session.id, messageCount: session.messages.length, instruction },
|
|
16427
16966
|
{ configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
@@ -16458,7 +16997,7 @@ Add .md files to create commands.` });
|
|
|
16458
16997
|
const removedCount = session.compact(summaryMsg, ackMsg, keepLast);
|
|
16459
16998
|
const afterCount = session.messages.length;
|
|
16460
16999
|
runLifecycleHooks(
|
|
16461
|
-
this.config.get("hooks") ?? void 0,
|
|
17000
|
+
mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
|
|
16462
17001
|
"PostCompact",
|
|
16463
17002
|
{ sessionId: session.id, beforeCount, afterCount, removedCount, keepLast, instruction },
|
|
16464
17003
|
{ configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
@@ -16562,48 +17101,95 @@ Add .md files to create commands.` });
|
|
|
16562
17101
|
}
|
|
16563
17102
|
memoryShow() {
|
|
16564
17103
|
const configDir = this.config.getConfigDir();
|
|
16565
|
-
const
|
|
16566
|
-
|
|
16567
|
-
|
|
16568
|
-
|
|
16569
|
-
|
|
16570
|
-
|
|
16571
|
-
|
|
16572
|
-
|
|
16573
|
-
`);
|
|
16574
|
-
}
|
|
17104
|
+
const entries = listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
|
|
17105
|
+
const content = entries.length === 0 ? "(empty \u2014 no persistent memory entries yet)" : entries.map((entry) => {
|
|
17106
|
+
const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
|
|
17107
|
+
const source = entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : entry.sourceProject ? `project ${entry.sourceProject}` : "manual/tool";
|
|
17108
|
+
return `## ${entry.id.slice(0, 8)} \xB7 ${status} \xB7 scope:${entry.scope} \xB7 sensitivity:${entry.sensitivity}
|
|
17109
|
+
Source: ${source}
|
|
17110
|
+
${entry.content}`;
|
|
17111
|
+
}).join("\n\n");
|
|
16575
17112
|
this.send({
|
|
16576
17113
|
type: "memory_content",
|
|
16577
|
-
content
|
|
16578
|
-
filePath:
|
|
17114
|
+
content,
|
|
17115
|
+
filePath: memoryStorePath(configDir)
|
|
16579
17116
|
});
|
|
17117
|
+
this.sendMemoryEntries();
|
|
16580
17118
|
}
|
|
16581
17119
|
memoryAdd(text) {
|
|
16582
|
-
const configDir = this.config.getConfigDir();
|
|
16583
|
-
const memPath = join20(configDir, MEMORY_FILE_NAME);
|
|
16584
17120
|
try {
|
|
16585
|
-
|
|
16586
|
-
|
|
16587
|
-
|
|
16588
|
-
|
|
16589
|
-
|
|
16590
|
-
|
|
16591
|
-
|
|
16592
|
-
this.
|
|
17121
|
+
const entry = addMemoryEntry(this.config.getConfigDir(), text, {
|
|
17122
|
+
scope: "personal",
|
|
17123
|
+
cwd: process.cwd(),
|
|
17124
|
+
redact: true
|
|
17125
|
+
});
|
|
17126
|
+
const status = entry.approved ? "approved" : "pending approval";
|
|
17127
|
+
this.send({ type: "info", message: `\u{1F4DD} Memory entry ${entry.id.slice(0, 8)} added (${status}).` });
|
|
17128
|
+
this.sendMemoryEntries();
|
|
16593
17129
|
} catch (err) {
|
|
16594
17130
|
this.send({ type: "error", message: `Failed to write memory: ${err.message}` });
|
|
16595
17131
|
}
|
|
16596
17132
|
}
|
|
16597
17133
|
memoryClear() {
|
|
16598
17134
|
const configDir = this.config.getConfigDir();
|
|
16599
|
-
const memPath = join20(configDir, MEMORY_FILE_NAME);
|
|
16600
17135
|
try {
|
|
16601
|
-
atomicWriteFileSync(
|
|
17136
|
+
atomicWriteFileSync(memoryStorePath(configDir), "");
|
|
17137
|
+
atomicWriteFileSync(join21(configDir, MEMORY_FILE_NAME), "");
|
|
16602
17138
|
this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
|
|
17139
|
+
this.sendMemoryEntries();
|
|
16603
17140
|
} catch (err) {
|
|
16604
17141
|
this.send({ type: "error", message: `Failed to clear memory: ${err.message}` });
|
|
16605
17142
|
}
|
|
16606
17143
|
}
|
|
17144
|
+
sendMemoryEntries() {
|
|
17145
|
+
const configDir = this.config.getConfigDir();
|
|
17146
|
+
const entries = listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
|
|
17147
|
+
this.send({
|
|
17148
|
+
type: "memory_entries",
|
|
17149
|
+
storePath: memoryStorePath(configDir),
|
|
17150
|
+
entries: entries.map((entry) => ({
|
|
17151
|
+
id: entry.id,
|
|
17152
|
+
content: entry.content,
|
|
17153
|
+
scope: entry.scope,
|
|
17154
|
+
sourceSession: entry.sourceSession,
|
|
17155
|
+
sourceProject: entry.sourceProject,
|
|
17156
|
+
createdAt: entry.createdAt,
|
|
17157
|
+
updatedAt: entry.updatedAt,
|
|
17158
|
+
expiresAt: entry.expiresAt,
|
|
17159
|
+
sensitivity: entry.sensitivity,
|
|
17160
|
+
approved: entry.approved,
|
|
17161
|
+
expired: isMemoryExpired(entry),
|
|
17162
|
+
redactedKinds: entry.redactedKinds
|
|
17163
|
+
}))
|
|
17164
|
+
});
|
|
17165
|
+
}
|
|
17166
|
+
handleMemoryManage(action, id, expiresAt) {
|
|
17167
|
+
const configDir = this.config.getConfigDir();
|
|
17168
|
+
try {
|
|
17169
|
+
if (action === "approve" || action === "reject") {
|
|
17170
|
+
if (!id) throw new Error(`${action} requires id`);
|
|
17171
|
+
updateMemoryApproval(configDir, id, action === "approve");
|
|
17172
|
+
} else if (action === "delete") {
|
|
17173
|
+
if (!id) throw new Error("delete requires id");
|
|
17174
|
+
deleteMemoryEntry(configDir, id);
|
|
17175
|
+
} else if (action === "expire") {
|
|
17176
|
+
if (!id) throw new Error("expire requires id");
|
|
17177
|
+
expireMemoryEntry(configDir, id, expiresAt);
|
|
17178
|
+
} else if (action === "export") {
|
|
17179
|
+
this.send({
|
|
17180
|
+
type: "export_data",
|
|
17181
|
+
format: "json",
|
|
17182
|
+
filename: `aicli-memory-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`,
|
|
17183
|
+
content: exportMemoryEntries(configDir, process.cwd())
|
|
17184
|
+
});
|
|
17185
|
+
} else if (action !== "list") {
|
|
17186
|
+
throw new Error(`unknown memory action: ${action}`);
|
|
17187
|
+
}
|
|
17188
|
+
this.sendMemoryEntries();
|
|
17189
|
+
} catch (err) {
|
|
17190
|
+
this.send({ type: "error", message: `Memory management failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
17191
|
+
}
|
|
17192
|
+
}
|
|
16607
17193
|
// ── B4 chat memory recall (v0.4.90+) ──────────────────────────────
|
|
16608
17194
|
// Lazy-imported so the 117 MB embedder stays out of the load path for
|
|
16609
17195
|
// clients that never open the Memory panel.
|
|
@@ -16614,7 +17200,7 @@ Add .md files to create commands.` });
|
|
|
16614
17200
|
return;
|
|
16615
17201
|
}
|
|
16616
17202
|
try {
|
|
16617
|
-
const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-
|
|
17203
|
+
const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-PS274XM7.js");
|
|
16618
17204
|
const loaded = loadChatIndex2();
|
|
16619
17205
|
if (!loaded || loaded.idx.chunks.length === 0) {
|
|
16620
17206
|
this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
|
|
@@ -16650,7 +17236,7 @@ Add .md files to create commands.` });
|
|
|
16650
17236
|
}
|
|
16651
17237
|
async handleMemoryStatus() {
|
|
16652
17238
|
try {
|
|
16653
|
-
const { getChatIndexStatus } = await import("./chat-index-
|
|
17239
|
+
const { getChatIndexStatus } = await import("./chat-index-PS274XM7.js");
|
|
16654
17240
|
const s = getChatIndexStatus();
|
|
16655
17241
|
this.send({
|
|
16656
17242
|
type: "memory_status",
|
|
@@ -16675,7 +17261,7 @@ Add .md files to create commands.` });
|
|
|
16675
17261
|
type: "info",
|
|
16676
17262
|
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"
|
|
16677
17263
|
});
|
|
16678
|
-
const { buildChatIndex } = await import("./chat-index-
|
|
17264
|
+
const { buildChatIndex } = await import("./chat-index-PS274XM7.js");
|
|
16679
17265
|
const stats = await buildChatIndex({
|
|
16680
17266
|
full,
|
|
16681
17267
|
onProgress: (p) => {
|
|
@@ -16830,8 +17416,8 @@ async function setupProxy(configProxy) {
|
|
|
16830
17416
|
}
|
|
16831
17417
|
|
|
16832
17418
|
// src/web/auth.ts
|
|
16833
|
-
import { existsSync as existsSync27, readFileSync as readFileSync21, writeFileSync as writeFileSync3, mkdirSync as
|
|
16834
|
-
import { join as
|
|
17419
|
+
import { existsSync as existsSync27, readFileSync as readFileSync21, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, copyFileSync } from "fs";
|
|
17420
|
+
import { join as join22 } from "path";
|
|
16835
17421
|
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
16836
17422
|
var USERS_FILE = "users.json";
|
|
16837
17423
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
@@ -16846,7 +17432,7 @@ var AuthManager = class {
|
|
|
16846
17432
|
db;
|
|
16847
17433
|
constructor(baseDir) {
|
|
16848
17434
|
this.baseDir = baseDir;
|
|
16849
|
-
this.usersFile =
|
|
17435
|
+
this.usersFile = join22(baseDir, USERS_FILE);
|
|
16850
17436
|
this.db = this.loadOrCreate();
|
|
16851
17437
|
}
|
|
16852
17438
|
// ── Public API ─────────────────────────────────────────────────
|
|
@@ -16875,9 +17461,9 @@ var AuthManager = class {
|
|
|
16875
17461
|
}
|
|
16876
17462
|
const salt = randomBytes(16).toString("hex");
|
|
16877
17463
|
const passwordHash = this.hashPassword(password, salt);
|
|
16878
|
-
const dataDir =
|
|
16879
|
-
const fullDataDir =
|
|
16880
|
-
|
|
17464
|
+
const dataDir = join22(USERS_DIR, username);
|
|
17465
|
+
const fullDataDir = join22(this.baseDir, dataDir);
|
|
17466
|
+
mkdirSync13(join22(fullDataDir, "history"), { recursive: true });
|
|
16881
17467
|
const user = {
|
|
16882
17468
|
username,
|
|
16883
17469
|
passwordHash,
|
|
@@ -16992,7 +17578,7 @@ var AuthManager = class {
|
|
|
16992
17578
|
getUserDataDir(username) {
|
|
16993
17579
|
const user = this.db.users.find((u) => u.username === username);
|
|
16994
17580
|
if (!user) throw new Error(`User not found: ${username}`);
|
|
16995
|
-
return
|
|
17581
|
+
return join22(this.baseDir, user.dataDir);
|
|
16996
17582
|
}
|
|
16997
17583
|
/** List all usernames */
|
|
16998
17584
|
listUsers() {
|
|
@@ -17028,30 +17614,30 @@ var AuthManager = class {
|
|
|
17028
17614
|
const err = this.register(username, password);
|
|
17029
17615
|
if (err) return err;
|
|
17030
17616
|
const userDir = this.getUserDataDir(username);
|
|
17031
|
-
const globalConfig =
|
|
17617
|
+
const globalConfig = join22(this.baseDir, "config.json");
|
|
17032
17618
|
if (existsSync27(globalConfig)) {
|
|
17033
17619
|
try {
|
|
17034
17620
|
const content = readFileSync21(globalConfig, "utf-8");
|
|
17035
|
-
writeFileSync3(
|
|
17621
|
+
writeFileSync3(join22(userDir, "config.json"), content, "utf-8");
|
|
17036
17622
|
} catch {
|
|
17037
17623
|
}
|
|
17038
17624
|
}
|
|
17039
|
-
const globalMemory =
|
|
17625
|
+
const globalMemory = join22(this.baseDir, "memory.md");
|
|
17040
17626
|
if (existsSync27(globalMemory)) {
|
|
17041
17627
|
try {
|
|
17042
17628
|
const content = readFileSync21(globalMemory, "utf-8");
|
|
17043
|
-
writeFileSync3(
|
|
17629
|
+
writeFileSync3(join22(userDir, "memory.md"), content, "utf-8");
|
|
17044
17630
|
} catch {
|
|
17045
17631
|
}
|
|
17046
17632
|
}
|
|
17047
|
-
const globalHistory =
|
|
17633
|
+
const globalHistory = join22(this.baseDir, "history");
|
|
17048
17634
|
if (existsSync27(globalHistory)) {
|
|
17049
17635
|
try {
|
|
17050
|
-
const files =
|
|
17051
|
-
const userHistory =
|
|
17636
|
+
const files = readdirSync13(globalHistory).filter((f) => f.endsWith(".json"));
|
|
17637
|
+
const userHistory = join22(userDir, "history");
|
|
17052
17638
|
for (const f of files) {
|
|
17053
17639
|
try {
|
|
17054
|
-
copyFileSync(
|
|
17640
|
+
copyFileSync(join22(globalHistory, f), join22(userHistory, f));
|
|
17055
17641
|
} catch {
|
|
17056
17642
|
}
|
|
17057
17643
|
}
|
|
@@ -17080,7 +17666,7 @@ var AuthManager = class {
|
|
|
17080
17666
|
this.saveDB(this.db);
|
|
17081
17667
|
}
|
|
17082
17668
|
saveDB(db) {
|
|
17083
|
-
|
|
17669
|
+
mkdirSync13(this.baseDir, { recursive: true });
|
|
17084
17670
|
atomicWriteFileSync(this.usersFile, JSON.stringify(db, null, 2));
|
|
17085
17671
|
}
|
|
17086
17672
|
/** Legacy hash — kept only for migrating old users (v0.2.x) */
|
|
@@ -17112,11 +17698,11 @@ function getModuleDir() {
|
|
|
17112
17698
|
if (typeof import.meta?.url === "string") {
|
|
17113
17699
|
const url = new URL(import.meta.url);
|
|
17114
17700
|
const filePath = url.pathname.replace(/^\/([A-Z]:)/i, "$1");
|
|
17115
|
-
return
|
|
17701
|
+
return dirname8(filePath);
|
|
17116
17702
|
}
|
|
17117
17703
|
} catch {
|
|
17118
17704
|
}
|
|
17119
|
-
return
|
|
17705
|
+
return resolve11(".");
|
|
17120
17706
|
}
|
|
17121
17707
|
async function startWebServer(options = {}) {
|
|
17122
17708
|
const port = options.port ?? 3e3;
|
|
@@ -17151,6 +17737,8 @@ async function startWebServer(options = {}) {
|
|
|
17151
17737
|
let mcpManager = null;
|
|
17152
17738
|
const mcpEnabled = config.get("mcpEnabled") !== false;
|
|
17153
17739
|
const globalMcpServers = mcpEnabled ? config.get("mcpServers") ?? {} : {};
|
|
17740
|
+
const pluginAssets = getActivePluginAssets(config.getConfigDir());
|
|
17741
|
+
const pluginMcpServers = mcpEnabled ? pluginAssets.mcpServers : {};
|
|
17154
17742
|
const projectMcpResolved = mcpEnabled ? resolveProjectMcpPath() : null;
|
|
17155
17743
|
let projectMcpServers = {};
|
|
17156
17744
|
if (projectMcpResolved) {
|
|
@@ -17163,7 +17751,7 @@ async function startWebServer(options = {}) {
|
|
|
17163
17751
|
console.log(` Skipped to prevent unintended RCE. Run "aicli" then "/mcp trust-project" to approve.`);
|
|
17164
17752
|
}
|
|
17165
17753
|
}
|
|
17166
|
-
const mergedMcpServers = { ...globalMcpServers, ...projectMcpServers };
|
|
17754
|
+
const mergedMcpServers = { ...globalMcpServers, ...pluginMcpServers, ...projectMcpServers };
|
|
17167
17755
|
if (!mcpEnabled) {
|
|
17168
17756
|
console.log(" \u{1F50C} MCP: disabled (config.mcpEnabled=false)");
|
|
17169
17757
|
}
|
|
@@ -17181,10 +17769,10 @@ async function startWebServer(options = {}) {
|
|
|
17181
17769
|
}
|
|
17182
17770
|
}
|
|
17183
17771
|
let skillManager = null;
|
|
17184
|
-
const skillsDir =
|
|
17772
|
+
const skillsDir = join23(config.getConfigDir(), SKILLS_DIR_NAME);
|
|
17185
17773
|
if (existsSync28(skillsDir)) {
|
|
17186
17774
|
skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
|
|
17187
|
-
skillManager.loadSkills();
|
|
17775
|
+
skillManager.loadSkills(pluginAssets.skillDirs);
|
|
17188
17776
|
const count = skillManager.listSkills().length;
|
|
17189
17777
|
if (count > 0) {
|
|
17190
17778
|
console.log(` Skills: ${count} available`);
|
|
@@ -17253,18 +17841,18 @@ async function startWebServer(options = {}) {
|
|
|
17253
17841
|
next();
|
|
17254
17842
|
};
|
|
17255
17843
|
const moduleDir = getModuleDir();
|
|
17256
|
-
let clientDir =
|
|
17844
|
+
let clientDir = join23(moduleDir, "web", "client");
|
|
17257
17845
|
if (!existsSync28(clientDir)) {
|
|
17258
|
-
clientDir =
|
|
17846
|
+
clientDir = join23(moduleDir, "client");
|
|
17259
17847
|
}
|
|
17260
17848
|
if (!existsSync28(clientDir)) {
|
|
17261
|
-
clientDir =
|
|
17849
|
+
clientDir = join23(moduleDir, "..", "..", "src", "web", "client");
|
|
17262
17850
|
}
|
|
17263
17851
|
if (!existsSync28(clientDir)) {
|
|
17264
|
-
clientDir =
|
|
17852
|
+
clientDir = join23(process.cwd(), "src", "web", "client");
|
|
17265
17853
|
}
|
|
17266
17854
|
console.log(` Static files: ${clientDir}`);
|
|
17267
|
-
app.use("/vendor", express.static(
|
|
17855
|
+
app.use("/vendor", express.static(join23(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
|
|
17268
17856
|
app.use(express.static(clientDir));
|
|
17269
17857
|
app.get("/api/status", (_req, res) => {
|
|
17270
17858
|
res.json({
|
|
@@ -17331,10 +17919,10 @@ async function startWebServer(options = {}) {
|
|
|
17331
17919
|
app.get("/api/files", requireAuth, (req, res) => {
|
|
17332
17920
|
const cwd = process.cwd();
|
|
17333
17921
|
const prefix = req.query.prefix || "";
|
|
17334
|
-
const targetDir =
|
|
17922
|
+
const targetDir = join23(cwd, prefix);
|
|
17335
17923
|
try {
|
|
17336
|
-
const canonicalTarget = realpathSync(
|
|
17337
|
-
const canonicalCwd = realpathSync(
|
|
17924
|
+
const canonicalTarget = realpathSync(resolve11(targetDir));
|
|
17925
|
+
const canonicalCwd = realpathSync(resolve11(cwd));
|
|
17338
17926
|
if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
|
|
17339
17927
|
res.json({ files: [] });
|
|
17340
17928
|
return;
|
|
@@ -17345,10 +17933,10 @@ async function startWebServer(options = {}) {
|
|
|
17345
17933
|
}
|
|
17346
17934
|
try {
|
|
17347
17935
|
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", "__pycache__", ".next", ".nuxt", "coverage", ".cache"]);
|
|
17348
|
-
const entries =
|
|
17936
|
+
const entries = readdirSync14(targetDir, { withFileTypes: true });
|
|
17349
17937
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
17350
17938
|
name: e.name,
|
|
17351
|
-
path: relative4(cwd,
|
|
17939
|
+
path: relative4(cwd, join23(targetDir, e.name)).replace(/\\/g, "/"),
|
|
17352
17940
|
isDir: e.isDirectory()
|
|
17353
17941
|
}));
|
|
17354
17942
|
res.json({ files });
|
|
@@ -17384,7 +17972,7 @@ async function startWebServer(options = {}) {
|
|
|
17384
17972
|
try {
|
|
17385
17973
|
const authUser = req._authUser;
|
|
17386
17974
|
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
17387
|
-
const filePath =
|
|
17975
|
+
const filePath = join23(histDir, `${id}.json`);
|
|
17388
17976
|
if (!existsSync28(filePath)) {
|
|
17389
17977
|
res.status(404).json({ error: "Session not found" });
|
|
17390
17978
|
return;
|
|
@@ -17413,10 +18001,10 @@ async function startWebServer(options = {}) {
|
|
|
17413
18001
|
return;
|
|
17414
18002
|
}
|
|
17415
18003
|
const cwd = process.cwd();
|
|
17416
|
-
const fullPath =
|
|
18004
|
+
const fullPath = resolve11(join23(cwd, filePath));
|
|
17417
18005
|
try {
|
|
17418
18006
|
const canonicalFull = realpathSync(fullPath);
|
|
17419
|
-
const canonicalCwd = realpathSync(
|
|
18007
|
+
const canonicalCwd = realpathSync(resolve11(cwd));
|
|
17420
18008
|
if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
|
|
17421
18009
|
res.json({ error: "Access denied" });
|
|
17422
18010
|
return;
|
|
@@ -17635,7 +18223,7 @@ async function startWebServer(options = {}) {
|
|
|
17635
18223
|
});
|
|
17636
18224
|
const MAX_PORT_ATTEMPTS = 10;
|
|
17637
18225
|
let actualPort = port;
|
|
17638
|
-
const result = await new Promise((
|
|
18226
|
+
const result = await new Promise((resolve12, reject) => {
|
|
17639
18227
|
const tryListen = (attempt) => {
|
|
17640
18228
|
server.once("error", (err) => {
|
|
17641
18229
|
if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
|
|
@@ -17666,7 +18254,7 @@ async function startWebServer(options = {}) {
|
|
|
17666
18254
|
}
|
|
17667
18255
|
console.log(` Press Ctrl+C to stop
|
|
17668
18256
|
`);
|
|
17669
|
-
|
|
18257
|
+
resolve12({ port: actualPort, host, url });
|
|
17670
18258
|
});
|
|
17671
18259
|
};
|
|
17672
18260
|
tryListen(1);
|
|
@@ -17686,14 +18274,14 @@ function resolveProjectMcpPath() {
|
|
|
17686
18274
|
const cwd = process.cwd();
|
|
17687
18275
|
const gitRoot = getGitRoot(cwd);
|
|
17688
18276
|
const projectRoot = gitRoot ?? cwd;
|
|
17689
|
-
const configPath =
|
|
18277
|
+
const configPath = join23(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
17690
18278
|
return existsSync28(configPath) ? configPath : null;
|
|
17691
18279
|
}
|
|
17692
18280
|
function loadProjectMcpConfig() {
|
|
17693
18281
|
const cwd = process.cwd();
|
|
17694
18282
|
const gitRoot = getGitRoot(cwd);
|
|
17695
18283
|
const projectRoot = gitRoot ?? cwd;
|
|
17696
|
-
const configPath =
|
|
18284
|
+
const configPath = join23(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
17697
18285
|
if (!existsSync28(configPath)) return null;
|
|
17698
18286
|
try {
|
|
17699
18287
|
const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
|