jinzd-ai-cli 0.4.217 → 0.4.219
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 +5 -2
- package/dist/{batch-YCOVKTXD.js → batch-CHAPYRVM.js} +2 -2
- package/dist/chunk-3I4RZYYO.js +638 -0
- package/dist/{chunk-KOPUCJXM.js → chunk-4ZN6L6K5.js} +3 -3
- package/dist/{chunk-VTH7BLXK.js → chunk-64NUW3WL.js} +1 -1
- package/dist/{chunk-QAYOI57M.js → chunk-EGMORH5W.js} +1 -1
- package/dist/{chunk-MGBMNCHG.js → chunk-GEN4O5QH.js} +94 -270
- package/dist/chunk-GGKQHPB3.js +604 -0
- package/dist/{chunk-WKOQ5CYC.js → chunk-KIEZBTVD.js} +1 -1
- package/dist/{chunk-OUAZOE5U.js → chunk-QUFYBR6Q.js} +47 -206
- package/dist/{chunk-XJGEQIYS.js → chunk-U7KVU25H.js} +1 -1
- package/dist/{chunk-WZ3VKLF3.js → chunk-VADHBW7T.js} +1 -1
- package/dist/{ci-52RZIYWB.js → ci-QPP66T53.js} +4 -4
- package/dist/{ci-format-73UXKE65.js → ci-format-5S3EEYRK.js} +2 -2
- package/dist/{constants-DIXAD35W.js → constants-FJOLBABC.js} +1 -1
- package/dist/doctor-cli-KB2LUDF3.js +17 -0
- package/dist/electron-server.js +921 -274
- package/dist/{hub-GIGBITZN.js → hub-DIM7SKKY.js} +1 -1
- package/dist/index.js +192 -200
- package/dist/{pr-KPQ5RPKB.js → pr-YQGH72N6.js} +4 -4
- package/dist/{run-tests-ZDSA3QES.js → run-tests-GEZSSNJM.js} +2 -2
- package/dist/{run-tests-K7QR5QN4.js → run-tests-O76JIBCW.js} +1 -1
- package/dist/{server-QT3SC2KI.js → server-5E2AIXVX.js} +108 -80
- package/dist/{server-22YF3U34.js → server-Z6O3G2LY.js} +9 -9
- package/dist/{task-orchestrator-BHQQCVTY.js → task-orchestrator-5HBW4O64.js} +9 -9
- package/dist/{usage-WZZFSFLM.js → usage-BMK6M5U3.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-524WZOKS.js +0 -166
- package/dist/chunk-SNJAOXFT.js +0 -129
- package/dist/doctor-cli-7GOQPULZ.js +0 -226
package/dist/electron-server.js
CHANGED
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
VERSION,
|
|
39
39
|
buildUserIdentityPrompt,
|
|
40
40
|
runTestsTool
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-EGMORH5W.js";
|
|
42
42
|
import {
|
|
43
43
|
hasSemanticIndex,
|
|
44
44
|
semanticSearch
|
|
@@ -64,8 +64,8 @@ import {
|
|
|
64
64
|
import express from "express";
|
|
65
65
|
import { createServer } from "http";
|
|
66
66
|
import { WebSocketServer } from "ws";
|
|
67
|
-
import { join as
|
|
68
|
-
import { existsSync as
|
|
67
|
+
import { join as join25, dirname as dirname8, resolve as resolve11, relative as relative4, sep as sep3 } from "path";
|
|
68
|
+
import { existsSync as existsSync30, readFileSync as readFileSync23, readdirSync as readdirSync15, statSync as statSync12, realpathSync } from "fs";
|
|
69
69
|
import { networkInterfaces } from "os";
|
|
70
70
|
|
|
71
71
|
// src/config/config-manager.ts
|
|
@@ -1847,12 +1847,12 @@ function findPhantomClaims(content, extraMessages) {
|
|
|
1847
1847
|
const claimed = extractClaimedFilePaths(content);
|
|
1848
1848
|
if (claimed.length === 0) return [];
|
|
1849
1849
|
const normalize = (p) => p.replace(/\\/g, "/").toLowerCase().replace(/^\.\//, "");
|
|
1850
|
-
const
|
|
1850
|
+
const basename7 = (p) => {
|
|
1851
1851
|
const parts = normalize(p).split("/");
|
|
1852
1852
|
return parts[parts.length - 1] ?? "";
|
|
1853
1853
|
};
|
|
1854
1854
|
const written = extractWrittenFilePaths(extraMessages).map(normalize);
|
|
1855
|
-
const writtenBases = new Set(written.map(
|
|
1855
|
+
const writtenBases = new Set(written.map(basename7));
|
|
1856
1856
|
const writtenFull = new Set(written);
|
|
1857
1857
|
const bashText = extractBashCommands(extraMessages).map((c) => c.replace(/\\/g, "/").toLowerCase()).join("\n");
|
|
1858
1858
|
return claimed.filter((raw) => {
|
|
@@ -1861,8 +1861,8 @@ function findPhantomClaims(content, extraMessages) {
|
|
|
1861
1861
|
for (const w of writtenFull) {
|
|
1862
1862
|
if (w.endsWith("/" + norm2) || norm2.endsWith("/" + w)) return false;
|
|
1863
1863
|
}
|
|
1864
|
-
if (writtenBases.has(
|
|
1865
|
-
const base =
|
|
1864
|
+
if (writtenBases.has(basename7(norm2))) return false;
|
|
1865
|
+
const base = basename7(norm2);
|
|
1866
1866
|
if (base && bashText.includes(base)) return false;
|
|
1867
1867
|
return true;
|
|
1868
1868
|
});
|
|
@@ -6629,6 +6629,21 @@ function flush() {
|
|
|
6629
6629
|
} catch {
|
|
6630
6630
|
}
|
|
6631
6631
|
}
|
|
6632
|
+
function getStatsSnapshot() {
|
|
6633
|
+
const s = ensureLoaded();
|
|
6634
|
+
return Object.values(s.entries).map((e) => ({ ...e }));
|
|
6635
|
+
}
|
|
6636
|
+
function getTopFailingTools(limit = 5) {
|
|
6637
|
+
return getStatsSnapshot().filter((e) => e.failures > 0).sort((a, b) => {
|
|
6638
|
+
const rateA = a.failures / Math.max(1, a.calls);
|
|
6639
|
+
const rateB = b.failures / Math.max(1, b.calls);
|
|
6640
|
+
if (rateB !== rateA) return rateB - rateA;
|
|
6641
|
+
return b.failures - a.failures;
|
|
6642
|
+
}).slice(0, limit);
|
|
6643
|
+
}
|
|
6644
|
+
function getTopUsedTools(limit = 5) {
|
|
6645
|
+
return getStatsSnapshot().sort((a, b) => b.calls - a.calls).slice(0, limit);
|
|
6646
|
+
}
|
|
6632
6647
|
|
|
6633
6648
|
// src/tools/action-classifier.ts
|
|
6634
6649
|
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
@@ -7182,7 +7197,7 @@ var ToolExecutor = class {
|
|
|
7182
7197
|
rl.resume();
|
|
7183
7198
|
process.stdout.write(prompt);
|
|
7184
7199
|
this.confirming = true;
|
|
7185
|
-
return new Promise((
|
|
7200
|
+
return new Promise((resolve12) => {
|
|
7186
7201
|
let completed = false;
|
|
7187
7202
|
const cleanup = (result) => {
|
|
7188
7203
|
if (completed) return;
|
|
@@ -7192,7 +7207,7 @@ var ToolExecutor = class {
|
|
|
7192
7207
|
rl.pause();
|
|
7193
7208
|
rlAny.output = savedOutput;
|
|
7194
7209
|
this.confirming = false;
|
|
7195
|
-
|
|
7210
|
+
resolve12(result);
|
|
7196
7211
|
};
|
|
7197
7212
|
const onLine = (line) => {
|
|
7198
7213
|
const trimmed = line.trim();
|
|
@@ -7366,7 +7381,7 @@ var ToolExecutor = class {
|
|
|
7366
7381
|
rl.resume();
|
|
7367
7382
|
process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
|
|
7368
7383
|
this.confirming = true;
|
|
7369
|
-
return new Promise((
|
|
7384
|
+
return new Promise((resolve12) => {
|
|
7370
7385
|
let completed = false;
|
|
7371
7386
|
const cleanup = (answer) => {
|
|
7372
7387
|
if (completed) return;
|
|
@@ -7376,7 +7391,7 @@ var ToolExecutor = class {
|
|
|
7376
7391
|
rl.pause();
|
|
7377
7392
|
rlAny.output = savedOutput;
|
|
7378
7393
|
this.confirming = false;
|
|
7379
|
-
|
|
7394
|
+
resolve12(answer === "y");
|
|
7380
7395
|
};
|
|
7381
7396
|
const onLine = (line) => {
|
|
7382
7397
|
const trimmed = line.trim();
|
|
@@ -8625,7 +8640,7 @@ var runInteractiveTool = {
|
|
|
8625
8640
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
8626
8641
|
};
|
|
8627
8642
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
8628
|
-
return new Promise((
|
|
8643
|
+
return new Promise((resolve12) => {
|
|
8629
8644
|
const child = spawn2(executable, cmdArgs.map(String), {
|
|
8630
8645
|
cwd: process.cwd(),
|
|
8631
8646
|
env,
|
|
@@ -8658,22 +8673,22 @@ var runInteractiveTool = {
|
|
|
8658
8673
|
setTimeout(writeNextLine, 400);
|
|
8659
8674
|
const timer = setTimeout(() => {
|
|
8660
8675
|
child.kill();
|
|
8661
|
-
|
|
8676
|
+
resolve12(`${prefixWarnings}[Timeout after ${timeout}ms]
|
|
8662
8677
|
${buildOutput(stdout, stderr)}`);
|
|
8663
8678
|
}, timeout);
|
|
8664
8679
|
child.on("close", (code) => {
|
|
8665
8680
|
clearTimeout(timer);
|
|
8666
8681
|
const output = buildOutput(stdout, stderr);
|
|
8667
8682
|
if (code !== 0 && code !== null) {
|
|
8668
|
-
|
|
8683
|
+
resolve12(`${prefixWarnings}Exit code ${code}:
|
|
8669
8684
|
${output}`);
|
|
8670
8685
|
} else {
|
|
8671
|
-
|
|
8686
|
+
resolve12(`${prefixWarnings}${output || "(no output)"}`);
|
|
8672
8687
|
}
|
|
8673
8688
|
});
|
|
8674
8689
|
child.on("error", (err) => {
|
|
8675
8690
|
clearTimeout(timer);
|
|
8676
|
-
|
|
8691
|
+
resolve12(
|
|
8677
8692
|
`${prefixWarnings}Failed to start process "${executable}": ${err.message}
|
|
8678
8693
|
Hint: On Windows, use the full path to the executable, e.g.:
|
|
8679
8694
|
C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
|
|
@@ -9723,7 +9738,7 @@ function promptUser(rl, question) {
|
|
|
9723
9738
|
console.log();
|
|
9724
9739
|
console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
|
|
9725
9740
|
process.stdout.write(chalk4.cyan("> "));
|
|
9726
|
-
return new Promise((
|
|
9741
|
+
return new Promise((resolve12) => {
|
|
9727
9742
|
let completed = false;
|
|
9728
9743
|
const cleanup = (answer) => {
|
|
9729
9744
|
if (completed) return;
|
|
@@ -9733,7 +9748,7 @@ function promptUser(rl, question) {
|
|
|
9733
9748
|
rl.pause();
|
|
9734
9749
|
rlAny.output = savedOutput;
|
|
9735
9750
|
askUserContext.prompting = false;
|
|
9736
|
-
|
|
9751
|
+
resolve12(answer);
|
|
9737
9752
|
};
|
|
9738
9753
|
const onLine = (line) => {
|
|
9739
9754
|
cleanup(line);
|
|
@@ -9965,9 +9980,292 @@ function formatResults2(query, data, _requested) {
|
|
|
9965
9980
|
}
|
|
9966
9981
|
|
|
9967
9982
|
// src/agents/agent-config.ts
|
|
9968
|
-
import { existsSync as
|
|
9969
|
-
import { join as
|
|
9983
|
+
import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync13 } from "fs";
|
|
9984
|
+
import { join as join13 } from "path";
|
|
9970
9985
|
import { homedir as homedir6 } from "os";
|
|
9986
|
+
|
|
9987
|
+
// src/plugins/plugin-manager.ts
|
|
9988
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync12, cpSync, rmSync, statSync as statSync6 } from "fs";
|
|
9989
|
+
import { basename as basename5, dirname as dirname6, join as join12, resolve as resolve7 } from "path";
|
|
9990
|
+
import { createHash as createHash3 } from "crypto";
|
|
9991
|
+
import { z as z2 } from "zod";
|
|
9992
|
+
var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
|
|
9993
|
+
var STATE_FILE = "plugin-state.json";
|
|
9994
|
+
var HookCommandSchema = z2.union([
|
|
9995
|
+
z2.string(),
|
|
9996
|
+
z2.object({
|
|
9997
|
+
command: z2.string(),
|
|
9998
|
+
source: z2.enum(["user", "project", "managed"]).optional(),
|
|
9999
|
+
description: z2.string().optional(),
|
|
10000
|
+
required: z2.boolean().optional(),
|
|
10001
|
+
timeoutMs: z2.number().int().min(100).max(3e4).optional(),
|
|
10002
|
+
disabled: z2.boolean().optional()
|
|
10003
|
+
}),
|
|
10004
|
+
z2.array(z2.union([
|
|
10005
|
+
z2.string(),
|
|
10006
|
+
z2.object({
|
|
10007
|
+
command: z2.string(),
|
|
10008
|
+
source: z2.enum(["user", "project", "managed"]).optional(),
|
|
10009
|
+
description: z2.string().optional(),
|
|
10010
|
+
required: z2.boolean().optional(),
|
|
10011
|
+
timeoutMs: z2.number().int().min(100).max(3e4).optional(),
|
|
10012
|
+
disabled: z2.boolean().optional()
|
|
10013
|
+
})
|
|
10014
|
+
]))
|
|
10015
|
+
]);
|
|
10016
|
+
var HookEventNameSchema = z2.enum([
|
|
10017
|
+
"SessionStart",
|
|
10018
|
+
"UserPromptSubmit",
|
|
10019
|
+
"PreToolUse",
|
|
10020
|
+
"PermissionRequest",
|
|
10021
|
+
"PostToolUse",
|
|
10022
|
+
"PreCompact",
|
|
10023
|
+
"PostCompact",
|
|
10024
|
+
"Stop",
|
|
10025
|
+
"SubagentStart",
|
|
10026
|
+
"SubagentStop"
|
|
10027
|
+
]);
|
|
10028
|
+
var McpServerSchema = z2.object({
|
|
10029
|
+
command: z2.string(),
|
|
10030
|
+
args: z2.array(z2.string()).default([]),
|
|
10031
|
+
env: z2.record(z2.string()).optional(),
|
|
10032
|
+
timeout: z2.number().default(3e4)
|
|
10033
|
+
});
|
|
10034
|
+
var PluginManifestSchema = z2.object({
|
|
10035
|
+
name: z2.string().regex(/^[a-zA-Z0-9._-]{1,80}$/),
|
|
10036
|
+
version: z2.string().min(1),
|
|
10037
|
+
description: z2.string().optional(),
|
|
10038
|
+
skills: z2.array(z2.string()).default([]),
|
|
10039
|
+
hooks: z2.object({
|
|
10040
|
+
enabled: z2.boolean().optional(),
|
|
10041
|
+
events: z2.record(HookEventNameSchema, HookCommandSchema).default({})
|
|
10042
|
+
}).default({ events: {} }),
|
|
10043
|
+
commands: z2.array(z2.string()).default([]),
|
|
10044
|
+
mcpServers: z2.record(McpServerSchema).default({}),
|
|
10045
|
+
agents: z2.array(z2.string()).default([]),
|
|
10046
|
+
permissionHints: z2.array(z2.string()).default([])
|
|
10047
|
+
}).strict();
|
|
10048
|
+
function pluginRoot(configDir) {
|
|
10049
|
+
return join12(configDir, PLUGINS_DIR_NAME);
|
|
10050
|
+
}
|
|
10051
|
+
function pluginManifestPath(pluginDir) {
|
|
10052
|
+
return join12(pluginDir, MANIFEST_RELATIVE);
|
|
10053
|
+
}
|
|
10054
|
+
function statePath(configDir) {
|
|
10055
|
+
return join12(pluginRoot(configDir), STATE_FILE);
|
|
10056
|
+
}
|
|
10057
|
+
function loadState(configDir) {
|
|
10058
|
+
const file = statePath(configDir);
|
|
10059
|
+
if (!existsSync16(file)) return { version: 1, plugins: [] };
|
|
10060
|
+
try {
|
|
10061
|
+
const parsed = JSON.parse(readFileSync12(file, "utf-8"));
|
|
10062
|
+
return { version: 1, plugins: Array.isArray(parsed.plugins) ? parsed.plugins : [] };
|
|
10063
|
+
} catch {
|
|
10064
|
+
return { version: 1, plugins: [] };
|
|
10065
|
+
}
|
|
10066
|
+
}
|
|
10067
|
+
function saveState(configDir, state2) {
|
|
10068
|
+
mkdirSync8(pluginRoot(configDir), { recursive: true });
|
|
10069
|
+
atomicWriteFileSync(statePath(configDir), JSON.stringify(state2, null, 2));
|
|
10070
|
+
}
|
|
10071
|
+
function hashPluginManifest(manifestPath) {
|
|
10072
|
+
return createHash3("sha256").update(readFileSync12(manifestPath)).digest("hex");
|
|
10073
|
+
}
|
|
10074
|
+
function readPluginManifest(manifestPath) {
|
|
10075
|
+
return PluginManifestSchema.parse(JSON.parse(readFileSync12(manifestPath, "utf-8")));
|
|
10076
|
+
}
|
|
10077
|
+
function findPluginSourceDir(inputPath) {
|
|
10078
|
+
const abs = resolve7(inputPath);
|
|
10079
|
+
const direct = statSync6(abs).isDirectory() ? abs : dirname6(abs);
|
|
10080
|
+
if (existsSync16(pluginManifestPath(direct))) return direct;
|
|
10081
|
+
const nested = join12(direct, ".aicli-plugin");
|
|
10082
|
+
if (existsSync16(join12(nested, "plugin.json"))) return direct;
|
|
10083
|
+
throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
|
|
10084
|
+
}
|
|
10085
|
+
function installPlugin(configDir, inputPath) {
|
|
10086
|
+
const sourceDir = findPluginSourceDir(inputPath);
|
|
10087
|
+
const manifest = readPluginManifest(pluginManifestPath(sourceDir));
|
|
10088
|
+
const targetDir = join12(pluginRoot(configDir), manifest.name);
|
|
10089
|
+
mkdirSync8(pluginRoot(configDir), { recursive: true });
|
|
10090
|
+
if (existsSync16(targetDir)) rmSync(targetDir, { recursive: true, force: true });
|
|
10091
|
+
cpSync(sourceDir, targetDir, { recursive: true });
|
|
10092
|
+
const manifestPath = pluginManifestPath(targetDir);
|
|
10093
|
+
const hash = hashPluginManifest(manifestPath);
|
|
10094
|
+
const state2 = loadState(configDir);
|
|
10095
|
+
const previous = state2.plugins.find((p) => p.name === manifest.name);
|
|
10096
|
+
const next = {
|
|
10097
|
+
name: manifest.name,
|
|
10098
|
+
enabled: previous?.enabled ?? false,
|
|
10099
|
+
trusted: false,
|
|
10100
|
+
hash,
|
|
10101
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10102
|
+
};
|
|
10103
|
+
state2.plugins = [...state2.plugins.filter((p) => p.name !== manifest.name), next];
|
|
10104
|
+
saveState(configDir, state2);
|
|
10105
|
+
return { name: manifest.name, dir: targetDir, manifestPath, manifest, hash, enabled: next.enabled, trusted: false, valid: true };
|
|
10106
|
+
}
|
|
10107
|
+
function setPluginEnabled(configDir, name, enabled) {
|
|
10108
|
+
const plugin = getInstalledPlugin(configDir, name);
|
|
10109
|
+
if (!plugin.valid) throw new Error(plugin.error ?? `invalid plugin: ${name}`);
|
|
10110
|
+
const state2 = loadState(configDir);
|
|
10111
|
+
const current = state2.plugins.find((p) => p.name === plugin.name) ?? {
|
|
10112
|
+
name: plugin.name,
|
|
10113
|
+
enabled: false,
|
|
10114
|
+
trusted: false,
|
|
10115
|
+
hash: plugin.hash,
|
|
10116
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10117
|
+
};
|
|
10118
|
+
const next = { ...current, enabled, hash: plugin.hash, enabledAt: enabled ? (/* @__PURE__ */ new Date()).toISOString() : current.enabledAt };
|
|
10119
|
+
state2.plugins = [...state2.plugins.filter((p) => p.name !== plugin.name), next];
|
|
10120
|
+
saveState(configDir, state2);
|
|
10121
|
+
return next;
|
|
10122
|
+
}
|
|
10123
|
+
function trustPlugin(configDir, name) {
|
|
10124
|
+
const plugin = getInstalledPlugin(configDir, name);
|
|
10125
|
+
if (!plugin.valid) throw new Error(plugin.error ?? `invalid plugin: ${name}`);
|
|
10126
|
+
const state2 = loadState(configDir);
|
|
10127
|
+
const current = state2.plugins.find((p) => p.name === plugin.name) ?? {
|
|
10128
|
+
name: plugin.name,
|
|
10129
|
+
enabled: false,
|
|
10130
|
+
trusted: false,
|
|
10131
|
+
hash: plugin.hash,
|
|
10132
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10133
|
+
};
|
|
10134
|
+
const next = { ...current, trusted: true, hash: plugin.hash, trustedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
10135
|
+
state2.plugins = [...state2.plugins.filter((p) => p.name !== plugin.name), next];
|
|
10136
|
+
saveState(configDir, state2);
|
|
10137
|
+
return next;
|
|
10138
|
+
}
|
|
10139
|
+
function getInstalledPlugin(configDir, name) {
|
|
10140
|
+
const plugins = listInstalledPlugins(configDir);
|
|
10141
|
+
const matches = plugins.filter((p) => p.name === name || p.name.startsWith(name));
|
|
10142
|
+
if (matches.length === 0) throw new Error(`plugin not found: ${name}`);
|
|
10143
|
+
if (matches.length > 1) throw new Error(`plugin name is ambiguous: ${name}`);
|
|
10144
|
+
return matches[0];
|
|
10145
|
+
}
|
|
10146
|
+
function listInstalledPlugins(configDir) {
|
|
10147
|
+
const root = pluginRoot(configDir);
|
|
10148
|
+
if (!existsSync16(root)) return [];
|
|
10149
|
+
const state2 = loadState(configDir);
|
|
10150
|
+
const out = [];
|
|
10151
|
+
for (const entry of readdirSync7(root)) {
|
|
10152
|
+
const dir = join12(root, entry);
|
|
10153
|
+
try {
|
|
10154
|
+
if (!statSync6(dir).isDirectory()) continue;
|
|
10155
|
+
} catch {
|
|
10156
|
+
continue;
|
|
10157
|
+
}
|
|
10158
|
+
const manifestPath = pluginManifestPath(dir);
|
|
10159
|
+
if (!existsSync16(manifestPath)) continue;
|
|
10160
|
+
try {
|
|
10161
|
+
const manifest = readPluginManifest(manifestPath);
|
|
10162
|
+
const hash = hashPluginManifest(manifestPath);
|
|
10163
|
+
const st = state2.plugins.find((p) => p.name === manifest.name);
|
|
10164
|
+
const trusted = st?.trusted === true && st.hash === hash;
|
|
10165
|
+
out.push({
|
|
10166
|
+
name: manifest.name,
|
|
10167
|
+
dir,
|
|
10168
|
+
manifestPath,
|
|
10169
|
+
manifest,
|
|
10170
|
+
hash,
|
|
10171
|
+
enabled: st?.enabled === true,
|
|
10172
|
+
trusted,
|
|
10173
|
+
valid: true
|
|
10174
|
+
});
|
|
10175
|
+
} catch (err) {
|
|
10176
|
+
out.push({
|
|
10177
|
+
name: entry,
|
|
10178
|
+
dir,
|
|
10179
|
+
manifestPath,
|
|
10180
|
+
manifest: { name: entry, version: "invalid", skills: [], commands: [], agents: [], hooks: { events: {} }, mcpServers: {}, permissionHints: [] },
|
|
10181
|
+
hash: "",
|
|
10182
|
+
enabled: false,
|
|
10183
|
+
trusted: false,
|
|
10184
|
+
valid: false,
|
|
10185
|
+
error: err instanceof Error ? err.message : String(err)
|
|
10186
|
+
});
|
|
10187
|
+
}
|
|
10188
|
+
}
|
|
10189
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
10190
|
+
}
|
|
10191
|
+
function activePlugins(configDir) {
|
|
10192
|
+
return listInstalledPlugins(configDir).filter((p) => p.valid && p.enabled && p.trusted);
|
|
10193
|
+
}
|
|
10194
|
+
function uniqueDirs(plugin, rels) {
|
|
10195
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
10196
|
+
for (const rel of rels) {
|
|
10197
|
+
const abs = resolve7(plugin.dir, rel);
|
|
10198
|
+
if (!abs.startsWith(resolve7(plugin.dir))) continue;
|
|
10199
|
+
if (existsSync16(abs)) dirs.add(statSync6(abs).isDirectory() ? abs : dirname6(abs));
|
|
10200
|
+
}
|
|
10201
|
+
return [...dirs];
|
|
10202
|
+
}
|
|
10203
|
+
function prefixCommand(plugin, command) {
|
|
10204
|
+
return command.replaceAll("{pluginDir}", plugin.dir).replaceAll("$PLUGIN_DIR", plugin.dir);
|
|
10205
|
+
}
|
|
10206
|
+
function pluginHooks(plugin) {
|
|
10207
|
+
const events = plugin.manifest.hooks?.events ?? {};
|
|
10208
|
+
const out = {};
|
|
10209
|
+
for (const [event, raw] of Object.entries(events)) {
|
|
10210
|
+
const normalize = (item) => {
|
|
10211
|
+
if (typeof item === "string") return { command: prefixCommand(plugin, item), source: "project", description: `plugin:${plugin.name}` };
|
|
10212
|
+
if (item && typeof item === "object") {
|
|
10213
|
+
const obj = item;
|
|
10214
|
+
return { ...obj, command: prefixCommand(plugin, String(obj.command ?? "")), source: "project", description: obj.description ?? `plugin:${plugin.name}` };
|
|
10215
|
+
}
|
|
10216
|
+
return item;
|
|
10217
|
+
};
|
|
10218
|
+
out[event] = Array.isArray(raw) ? raw.map(normalize) : normalize(raw);
|
|
10219
|
+
}
|
|
10220
|
+
return Object.keys(out).length > 0 ? { enabled: plugin.manifest.hooks?.enabled ?? true, events: out } : void 0;
|
|
10221
|
+
}
|
|
10222
|
+
function mergePluginHooks(base, configDir) {
|
|
10223
|
+
const plugins = activePlugins(configDir);
|
|
10224
|
+
if (plugins.length === 0) return base;
|
|
10225
|
+
const merged = { ...base ?? {}, enabled: base?.enabled ?? true, events: { ...base?.events ?? {} } };
|
|
10226
|
+
for (const plugin of plugins) {
|
|
10227
|
+
const hooks = pluginHooks(plugin);
|
|
10228
|
+
if (!hooks?.events) continue;
|
|
10229
|
+
for (const [event, entry] of Object.entries(hooks.events)) {
|
|
10230
|
+
const key = event;
|
|
10231
|
+
const existing = merged.events?.[key];
|
|
10232
|
+
const existingItems = existing === void 0 ? [] : Array.isArray(existing) ? existing : [existing];
|
|
10233
|
+
const newItems = Array.isArray(entry) ? entry : [entry];
|
|
10234
|
+
merged.events[key] = [...existingItems, ...newItems];
|
|
10235
|
+
}
|
|
10236
|
+
}
|
|
10237
|
+
return merged;
|
|
10238
|
+
}
|
|
10239
|
+
function getActivePluginAssets(configDir) {
|
|
10240
|
+
const plugins = activePlugins(configDir);
|
|
10241
|
+
const mcpServers = {};
|
|
10242
|
+
const skillDirs = [];
|
|
10243
|
+
const commandDirs = [];
|
|
10244
|
+
const agentDirs = [];
|
|
10245
|
+
for (const plugin of plugins) {
|
|
10246
|
+
skillDirs.push(...uniqueDirs(plugin, plugin.manifest.skills));
|
|
10247
|
+
commandDirs.push(...uniqueDirs(plugin, plugin.manifest.commands));
|
|
10248
|
+
agentDirs.push(...uniqueDirs(plugin, plugin.manifest.agents));
|
|
10249
|
+
for (const [serverName, server] of Object.entries(plugin.manifest.mcpServers)) {
|
|
10250
|
+
mcpServers[`plugin_${plugin.name}_${serverName}`] = server;
|
|
10251
|
+
}
|
|
10252
|
+
}
|
|
10253
|
+
return { plugins, skillDirs, commandDirs, agentDirs, hooks: mergePluginHooks(void 0, configDir), mcpServers };
|
|
10254
|
+
}
|
|
10255
|
+
function describePlugin(plugin) {
|
|
10256
|
+
const m = plugin.manifest;
|
|
10257
|
+
return [
|
|
10258
|
+
`${plugin.name}@${m.version}`,
|
|
10259
|
+
` status: ${plugin.enabled ? "enabled" : "disabled"} \xB7 ${plugin.trusted ? "trusted" : "untrusted"}${plugin.valid ? "" : " \xB7 invalid"}`,
|
|
10260
|
+
` dir: ${plugin.dir}`,
|
|
10261
|
+
m.description ? ` description: ${m.description}` : void 0,
|
|
10262
|
+
` 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)`,
|
|
10263
|
+
m.permissionHints.length ? ` permission hints: ${m.permissionHints.join("; ")}` : void 0,
|
|
10264
|
+
plugin.error ? ` error: ${plugin.error}` : void 0
|
|
10265
|
+
].filter((line) => Boolean(line));
|
|
10266
|
+
}
|
|
10267
|
+
|
|
10268
|
+
// src/agents/agent-config.ts
|
|
9971
10269
|
var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
|
|
9972
10270
|
"read_file",
|
|
9973
10271
|
"list_dir",
|
|
@@ -10061,13 +10359,13 @@ function parseAgentConfig(raw, source, path3) {
|
|
|
10061
10359
|
return cfg;
|
|
10062
10360
|
}
|
|
10063
10361
|
function loadAgentDir(dir, source) {
|
|
10064
|
-
if (!
|
|
10362
|
+
if (!existsSync17(dir)) return [];
|
|
10065
10363
|
const out = [];
|
|
10066
|
-
for (const file of
|
|
10364
|
+
for (const file of readdirSync8(dir)) {
|
|
10067
10365
|
if (!file.endsWith(".json")) continue;
|
|
10068
|
-
const path3 =
|
|
10366
|
+
const path3 = join13(dir, file);
|
|
10069
10367
|
try {
|
|
10070
|
-
const parsed = JSON.parse(
|
|
10368
|
+
const parsed = JSON.parse(readFileSync13(path3, "utf-8"));
|
|
10071
10369
|
const cfg = parseAgentConfig(parsed, source, path3);
|
|
10072
10370
|
if (cfg) out.push(cfg);
|
|
10073
10371
|
} catch {
|
|
@@ -10077,8 +10375,8 @@ function loadAgentDir(dir, source) {
|
|
|
10077
10375
|
}
|
|
10078
10376
|
function getAgentDirs(configDir, cwd = process.cwd()) {
|
|
10079
10377
|
return {
|
|
10080
|
-
user:
|
|
10081
|
-
project:
|
|
10378
|
+
user: join13(configDir ?? join13(homedir6(), CONFIG_DIR_NAME), "agents"),
|
|
10379
|
+
project: join13(cwd, CONFIG_DIR_NAME, "agents")
|
|
10082
10380
|
};
|
|
10083
10381
|
}
|
|
10084
10382
|
function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
@@ -10086,6 +10384,11 @@ function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
|
10086
10384
|
const byName = /* @__PURE__ */ new Map();
|
|
10087
10385
|
for (const cfg of BUILTIN_AGENTS) byName.set(normalizeName(cfg.name), { ...cfg });
|
|
10088
10386
|
for (const cfg of loadAgentDir(dirs.user, "user")) byName.set(normalizeName(cfg.name), cfg);
|
|
10387
|
+
if (configDir) {
|
|
10388
|
+
for (const dir of getActivePluginAssets(configDir).agentDirs) {
|
|
10389
|
+
for (const cfg of loadAgentDir(dir, "plugin")) byName.set(normalizeName(cfg.name), cfg);
|
|
10390
|
+
}
|
|
10391
|
+
}
|
|
10089
10392
|
for (const cfg of loadAgentDir(dirs.project, "project")) byName.set(normalizeName(cfg.name), cfg);
|
|
10090
10393
|
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
10091
10394
|
}
|
|
@@ -10788,14 +11091,14 @@ var taskStopTool = {
|
|
|
10788
11091
|
|
|
10789
11092
|
// src/tools/builtin/git-tools.ts
|
|
10790
11093
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
10791
|
-
import { existsSync as
|
|
10792
|
-
import { join as
|
|
11094
|
+
import { existsSync as existsSync18 } from "fs";
|
|
11095
|
+
import { join as join14 } from "path";
|
|
10793
11096
|
function assertGitRepo(cwd) {
|
|
10794
11097
|
let dir = cwd;
|
|
10795
11098
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
10796
11099
|
while (dir && dir !== root) {
|
|
10797
|
-
if (
|
|
10798
|
-
const parent =
|
|
11100
|
+
if (existsSync18(join14(dir, ".git"))) return;
|
|
11101
|
+
const parent = join14(dir, "..");
|
|
10799
11102
|
if (parent === dir) break;
|
|
10800
11103
|
dir = parent;
|
|
10801
11104
|
}
|
|
@@ -11060,9 +11363,9 @@ ${commitOutput.trim()}`;
|
|
|
11060
11363
|
};
|
|
11061
11364
|
|
|
11062
11365
|
// src/tools/builtin/notebook-edit.ts
|
|
11063
|
-
import { readFileSync as
|
|
11366
|
+
import { readFileSync as readFileSync14, existsSync as existsSync19 } from "fs";
|
|
11064
11367
|
import { writeFile } from "fs/promises";
|
|
11065
|
-
import { resolve as
|
|
11368
|
+
import { resolve as resolve8, extname as extname2 } from "path";
|
|
11066
11369
|
var notebookEditTool = {
|
|
11067
11370
|
definition: {
|
|
11068
11371
|
name: "notebook_edit",
|
|
@@ -11111,14 +11414,14 @@ var notebookEditTool = {
|
|
|
11111
11414
|
if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
|
|
11112
11415
|
throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
|
|
11113
11416
|
}
|
|
11114
|
-
const absPath =
|
|
11417
|
+
const absPath = resolve8(filePath);
|
|
11115
11418
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
11116
11419
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
11117
11420
|
}
|
|
11118
|
-
if (!
|
|
11421
|
+
if (!existsSync19(absPath)) {
|
|
11119
11422
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
11120
11423
|
}
|
|
11121
|
-
const raw =
|
|
11424
|
+
const raw = readFileSync14(absPath, "utf-8");
|
|
11122
11425
|
const nb = parseNotebook(raw);
|
|
11123
11426
|
const cellIdx0 = cellIndexRaw - 1;
|
|
11124
11427
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -11535,8 +11838,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
11535
11838
|
|
|
11536
11839
|
// src/tools/registry.ts
|
|
11537
11840
|
import { pathToFileURL } from "url";
|
|
11538
|
-
import { existsSync as
|
|
11539
|
-
import { join as
|
|
11841
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
|
|
11842
|
+
import { join as join15 } from "path";
|
|
11540
11843
|
var ToolRegistry = class {
|
|
11541
11844
|
tools = /* @__PURE__ */ new Map();
|
|
11542
11845
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -11697,16 +12000,16 @@ var ToolRegistry = class {
|
|
|
11697
12000
|
* Returns the number of successfully loaded plugins.
|
|
11698
12001
|
*/
|
|
11699
12002
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
11700
|
-
if (!
|
|
12003
|
+
if (!existsSync20(pluginsDir)) {
|
|
11701
12004
|
try {
|
|
11702
|
-
|
|
12005
|
+
mkdirSync9(pluginsDir, { recursive: true });
|
|
11703
12006
|
} catch {
|
|
11704
12007
|
}
|
|
11705
12008
|
return 0;
|
|
11706
12009
|
}
|
|
11707
12010
|
let files;
|
|
11708
12011
|
try {
|
|
11709
|
-
files =
|
|
12012
|
+
files = readdirSync9(pluginsDir).filter((f) => f.endsWith(".js"));
|
|
11710
12013
|
} catch {
|
|
11711
12014
|
return 0;
|
|
11712
12015
|
}
|
|
@@ -11723,12 +12026,12 @@ var ToolRegistry = class {
|
|
|
11723
12026
|
process.stderr.write(
|
|
11724
12027
|
`
|
|
11725
12028
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
11726
|
-
` + files.map((f) => ` + ${
|
|
12029
|
+
` + files.map((f) => ` + ${join15(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
11727
12030
|
);
|
|
11728
12031
|
let loaded = 0;
|
|
11729
12032
|
for (const file of files) {
|
|
11730
12033
|
try {
|
|
11731
|
-
const fileUrl = pathToFileURL(
|
|
12034
|
+
const fileUrl = pathToFileURL(join15(pluginsDir, file)).href;
|
|
11732
12035
|
const mod = await import(fileUrl);
|
|
11733
12036
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
11734
12037
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -11890,7 +12193,7 @@ var McpClient = class {
|
|
|
11890
12193
|
// 内部方法:JSON-RPC 通信
|
|
11891
12194
|
// ══════════════════════════════════════════════════════════════════
|
|
11892
12195
|
sendRequest(method, params) {
|
|
11893
|
-
return new Promise((
|
|
12196
|
+
return new Promise((resolve12, reject) => {
|
|
11894
12197
|
if (!this.process?.stdin?.writable) {
|
|
11895
12198
|
return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
|
|
11896
12199
|
}
|
|
@@ -11914,7 +12217,7 @@ var McpClient = class {
|
|
|
11914
12217
|
this.pendingRequests.set(id, {
|
|
11915
12218
|
resolve: (result) => {
|
|
11916
12219
|
cleanup();
|
|
11917
|
-
|
|
12220
|
+
resolve12(result);
|
|
11918
12221
|
},
|
|
11919
12222
|
reject: (error) => {
|
|
11920
12223
|
cleanup();
|
|
@@ -11991,13 +12294,13 @@ var McpClient = class {
|
|
|
11991
12294
|
}
|
|
11992
12295
|
/** Promise 超时包装 */
|
|
11993
12296
|
withTimeout(promise, ms, label) {
|
|
11994
|
-
return new Promise((
|
|
12297
|
+
return new Promise((resolve12, reject) => {
|
|
11995
12298
|
const timer = setTimeout(() => {
|
|
11996
12299
|
reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
|
|
11997
12300
|
}, ms);
|
|
11998
12301
|
promise.then((val) => {
|
|
11999
12302
|
clearTimeout(timer);
|
|
12000
|
-
|
|
12303
|
+
resolve12(val);
|
|
12001
12304
|
}).catch((err) => {
|
|
12002
12305
|
clearTimeout(timer);
|
|
12003
12306
|
reject(err);
|
|
@@ -12274,12 +12577,12 @@ var McpManager = class {
|
|
|
12274
12577
|
};
|
|
12275
12578
|
|
|
12276
12579
|
// src/skills/manager.ts
|
|
12277
|
-
import { existsSync as
|
|
12278
|
-
import { join as
|
|
12580
|
+
import { existsSync as existsSync21, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync7 } from "fs";
|
|
12581
|
+
import { join as join16 } from "path";
|
|
12279
12582
|
|
|
12280
12583
|
// src/skills/types.ts
|
|
12281
|
-
import { readFileSync as
|
|
12282
|
-
import { basename as
|
|
12584
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
12585
|
+
import { basename as basename6 } from "path";
|
|
12283
12586
|
function parseSimpleYaml(yaml) {
|
|
12284
12587
|
const result = {};
|
|
12285
12588
|
for (const line of yaml.split("\n")) {
|
|
@@ -12300,7 +12603,7 @@ function parseYamlArray(value) {
|
|
|
12300
12603
|
function parseSkillFile(filePath) {
|
|
12301
12604
|
let raw;
|
|
12302
12605
|
try {
|
|
12303
|
-
raw =
|
|
12606
|
+
raw = readFileSync15(filePath, "utf-8");
|
|
12304
12607
|
} catch {
|
|
12305
12608
|
return null;
|
|
12306
12609
|
}
|
|
@@ -12308,7 +12611,7 @@ function parseSkillFile(filePath) {
|
|
|
12308
12611
|
if (!frontmatterMatch) {
|
|
12309
12612
|
return {
|
|
12310
12613
|
meta: {
|
|
12311
|
-
name:
|
|
12614
|
+
name: basename6(filePath, ".md"),
|
|
12312
12615
|
description: ""
|
|
12313
12616
|
},
|
|
12314
12617
|
content: raw.trim(),
|
|
@@ -12319,7 +12622,7 @@ function parseSkillFile(filePath) {
|
|
|
12319
12622
|
const parsed = parseSimpleYaml(yaml);
|
|
12320
12623
|
return {
|
|
12321
12624
|
meta: {
|
|
12322
|
-
name: parsed["name"] ??
|
|
12625
|
+
name: parsed["name"] ?? basename6(filePath, ".md"),
|
|
12323
12626
|
description: parsed["description"] ?? "",
|
|
12324
12627
|
tools: parsed["tools"] ? parseYamlArray(parsed["tools"]) : void 0
|
|
12325
12628
|
},
|
|
@@ -12340,36 +12643,40 @@ var SkillManager = class {
|
|
|
12340
12643
|
this.skillsDir = skillsDir;
|
|
12341
12644
|
this.warnThreshold = warnThreshold ?? SKILL_CONTENT_WARN_CHARS_DEFAULT;
|
|
12342
12645
|
}
|
|
12343
|
-
/** 发现并加载 skillsDir
|
|
12344
|
-
loadSkills() {
|
|
12646
|
+
/** 发现并加载 skillsDir 以及额外插件目录下的技能,返回加载数量 */
|
|
12647
|
+
loadSkills(extraDirs = []) {
|
|
12345
12648
|
this.skills.clear();
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12649
|
+
this.loadSkillDir(this.skillsDir, true);
|
|
12650
|
+
for (const dir of extraDirs) this.loadSkillDir(dir, false);
|
|
12651
|
+
return this.skills.size;
|
|
12652
|
+
}
|
|
12653
|
+
loadSkillDir(dir, createIfMissing) {
|
|
12654
|
+
if (!existsSync21(dir)) {
|
|
12655
|
+
if (createIfMissing) {
|
|
12656
|
+
try {
|
|
12657
|
+
mkdirSync10(dir, { recursive: true });
|
|
12658
|
+
} catch {
|
|
12659
|
+
}
|
|
12350
12660
|
}
|
|
12351
|
-
return
|
|
12661
|
+
return;
|
|
12352
12662
|
}
|
|
12353
12663
|
let entries;
|
|
12354
12664
|
try {
|
|
12355
|
-
entries =
|
|
12665
|
+
entries = readdirSync10(dir);
|
|
12356
12666
|
} catch {
|
|
12357
|
-
return
|
|
12667
|
+
return;
|
|
12358
12668
|
}
|
|
12359
12669
|
for (const entry of entries) {
|
|
12360
12670
|
let filePath;
|
|
12361
|
-
const fullPath =
|
|
12671
|
+
const fullPath = join16(dir, entry);
|
|
12362
12672
|
if (entry.endsWith(".md")) {
|
|
12363
12673
|
filePath = fullPath;
|
|
12364
12674
|
} else {
|
|
12365
12675
|
try {
|
|
12366
|
-
if (
|
|
12367
|
-
const skillMd =
|
|
12368
|
-
if (
|
|
12369
|
-
|
|
12370
|
-
} else {
|
|
12371
|
-
continue;
|
|
12372
|
-
}
|
|
12676
|
+
if (statSync7(fullPath).isDirectory()) {
|
|
12677
|
+
const skillMd = join16(fullPath, "SKILL.md");
|
|
12678
|
+
if (existsSync21(skillMd)) filePath = skillMd;
|
|
12679
|
+
else continue;
|
|
12373
12680
|
} else {
|
|
12374
12681
|
continue;
|
|
12375
12682
|
}
|
|
@@ -12379,13 +12686,10 @@ var SkillManager = class {
|
|
|
12379
12686
|
}
|
|
12380
12687
|
const skill = parseSkillFile(filePath);
|
|
12381
12688
|
if (skill) {
|
|
12382
|
-
if (skill.meta.name === "SKILL" && !entry.endsWith(".md"))
|
|
12383
|
-
|
|
12384
|
-
}
|
|
12385
|
-
this.skills.set(skill.meta.name, skill);
|
|
12689
|
+
if (skill.meta.name === "SKILL" && !entry.endsWith(".md")) skill.meta.name = entry;
|
|
12690
|
+
if (!this.skills.has(skill.meta.name)) this.skills.set(skill.meta.name, skill);
|
|
12386
12691
|
}
|
|
12387
12692
|
}
|
|
12388
|
-
return this.skills.size;
|
|
12389
12693
|
}
|
|
12390
12694
|
/** 列出所有已加载的技能 */
|
|
12391
12695
|
listSkills() {
|
|
@@ -12430,7 +12734,7 @@ var SkillManager = class {
|
|
|
12430
12734
|
// src/web/tool-executor-web.ts
|
|
12431
12735
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
12432
12736
|
import { tmpdir as tmpdir2 } from "os";
|
|
12433
|
-
import { existsSync as
|
|
12737
|
+
import { existsSync as existsSync22, readFileSync as readFileSync16 } from "fs";
|
|
12434
12738
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
12435
12739
|
constructor(registry, ws) {
|
|
12436
12740
|
this.registry = registry;
|
|
@@ -12500,33 +12804,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12500
12804
|
}
|
|
12501
12805
|
/** Resolve a pending confirm from client response */
|
|
12502
12806
|
resolveConfirm(requestId, approved) {
|
|
12503
|
-
const
|
|
12504
|
-
if (
|
|
12807
|
+
const resolve12 = this.pendingConfirms.get(requestId);
|
|
12808
|
+
if (resolve12) {
|
|
12505
12809
|
this.clearPendingTimer(requestId);
|
|
12506
12810
|
this.pendingConfirms.delete(requestId);
|
|
12507
12811
|
this.confirming = false;
|
|
12508
|
-
|
|
12812
|
+
resolve12(approved);
|
|
12509
12813
|
}
|
|
12510
12814
|
}
|
|
12511
12815
|
/** Resolve a pending batch confirm from client response */
|
|
12512
12816
|
resolveBatchConfirm(requestId, decision) {
|
|
12513
|
-
const
|
|
12514
|
-
if (
|
|
12817
|
+
const resolve12 = this.pendingBatchConfirms.get(requestId);
|
|
12818
|
+
if (resolve12) {
|
|
12515
12819
|
this.clearPendingTimer(requestId);
|
|
12516
12820
|
this.pendingBatchConfirms.delete(requestId);
|
|
12517
12821
|
this.confirming = false;
|
|
12518
12822
|
if (decision === "all" || decision === "none") {
|
|
12519
|
-
|
|
12823
|
+
resolve12(decision);
|
|
12520
12824
|
} else {
|
|
12521
|
-
|
|
12825
|
+
resolve12(new Set(decision));
|
|
12522
12826
|
}
|
|
12523
12827
|
}
|
|
12524
12828
|
}
|
|
12525
12829
|
/** Cancel all pending confirms (e.g., on disconnect) */
|
|
12526
12830
|
cancelAll() {
|
|
12527
|
-
for (const
|
|
12831
|
+
for (const resolve12 of this.pendingConfirms.values()) resolve12(false);
|
|
12528
12832
|
this.pendingConfirms.clear();
|
|
12529
|
-
for (const
|
|
12833
|
+
for (const resolve12 of this.pendingBatchConfirms.values()) resolve12("none");
|
|
12530
12834
|
this.pendingBatchConfirms.clear();
|
|
12531
12835
|
this.confirming = false;
|
|
12532
12836
|
}
|
|
@@ -12569,9 +12873,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12569
12873
|
if (call.name === "write_file") {
|
|
12570
12874
|
const filePath = String(call.arguments["path"] ?? "");
|
|
12571
12875
|
const newContent = String(call.arguments["content"] ?? "");
|
|
12572
|
-
if (filePath &&
|
|
12876
|
+
if (filePath && existsSync22(filePath)) {
|
|
12573
12877
|
try {
|
|
12574
|
-
const old =
|
|
12878
|
+
const old = readFileSync16(filePath, "utf-8");
|
|
12575
12879
|
return renderDiff(old, newContent, { filePath });
|
|
12576
12880
|
} catch {
|
|
12577
12881
|
}
|
|
@@ -12602,8 +12906,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12602
12906
|
diff: this.getDiffPreview(call)
|
|
12603
12907
|
};
|
|
12604
12908
|
this.send(msg);
|
|
12605
|
-
return new Promise((
|
|
12606
|
-
this.pendingConfirms.set(requestId,
|
|
12909
|
+
return new Promise((resolve12) => {
|
|
12910
|
+
this.pendingConfirms.set(requestId, resolve12);
|
|
12607
12911
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
12608
12912
|
if (this.pendingConfirms.has(requestId)) {
|
|
12609
12913
|
this.resolveConfirm(requestId, false);
|
|
@@ -12627,8 +12931,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12627
12931
|
files
|
|
12628
12932
|
};
|
|
12629
12933
|
this.send(msg);
|
|
12630
|
-
return new Promise((
|
|
12631
|
-
this.pendingBatchConfirms.set(requestId,
|
|
12934
|
+
return new Promise((resolve12) => {
|
|
12935
|
+
this.pendingBatchConfirms.set(requestId, resolve12);
|
|
12632
12936
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
12633
12937
|
if (this.pendingBatchConfirms.has(requestId)) {
|
|
12634
12938
|
this.resolveBatchConfirm(requestId, "none");
|
|
@@ -12874,16 +13178,16 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12874
13178
|
};
|
|
12875
13179
|
|
|
12876
13180
|
// src/repl/dev-state.ts
|
|
12877
|
-
import { existsSync as
|
|
12878
|
-
import { join as
|
|
13181
|
+
import { existsSync as existsSync23, readFileSync as readFileSync17, unlinkSync as unlinkSync4, mkdirSync as mkdirSync11 } from "fs";
|
|
13182
|
+
import { join as join17 } from "path";
|
|
12879
13183
|
import { homedir as homedir7 } from "os";
|
|
12880
13184
|
function getDevStatePath() {
|
|
12881
|
-
return
|
|
13185
|
+
return join17(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
12882
13186
|
}
|
|
12883
13187
|
function loadDevState() {
|
|
12884
13188
|
const path3 = getDevStatePath();
|
|
12885
|
-
if (!
|
|
12886
|
-
const content =
|
|
13189
|
+
if (!existsSync23(path3)) return null;
|
|
13190
|
+
const content = readFileSync17(path3, "utf-8").trim();
|
|
12887
13191
|
return content || null;
|
|
12888
13192
|
}
|
|
12889
13193
|
|
|
@@ -13140,8 +13444,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
13140
13444
|
}
|
|
13141
13445
|
|
|
13142
13446
|
// src/core/context-files.ts
|
|
13143
|
-
import { existsSync as
|
|
13144
|
-
import { join as
|
|
13447
|
+
import { existsSync as existsSync24, readFileSync as readFileSync18 } from "fs";
|
|
13448
|
+
import { join as join18, relative as relative3, resolve as resolve9 } from "path";
|
|
13145
13449
|
function uniqueNonEmpty(values) {
|
|
13146
13450
|
const seen = /* @__PURE__ */ new Set();
|
|
13147
13451
|
const out = [];
|
|
@@ -13164,14 +13468,14 @@ function displayPath(filePath, cwd) {
|
|
|
13164
13468
|
return filePath;
|
|
13165
13469
|
}
|
|
13166
13470
|
function isInsideOrEqual(parent, child) {
|
|
13167
|
-
const rel = relative3(
|
|
13471
|
+
const rel = relative3(resolve9(parent), resolve9(child));
|
|
13168
13472
|
return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
|
|
13169
13473
|
}
|
|
13170
13474
|
function readContextFile(level, filePath, cwd, maxBytes) {
|
|
13171
13475
|
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
13172
13476
|
const shown = displayPath(filePath, cwd);
|
|
13173
13477
|
try {
|
|
13174
|
-
const raw =
|
|
13478
|
+
const raw = readFileSync18(filePath);
|
|
13175
13479
|
const byteCount = raw.byteLength;
|
|
13176
13480
|
if (byteCount === 0) {
|
|
13177
13481
|
return {
|
|
@@ -13218,8 +13522,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
13218
13522
|
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
13219
13523
|
const skipped = [];
|
|
13220
13524
|
for (const candidate of candidates) {
|
|
13221
|
-
const filePath =
|
|
13222
|
-
if (!
|
|
13525
|
+
const filePath = join18(dir, candidate);
|
|
13526
|
+
if (!existsSync24(filePath)) continue;
|
|
13223
13527
|
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
13224
13528
|
if (result.skipped) skipped.push(result.skipped);
|
|
13225
13529
|
if (result.layer) return { layer: result.layer, skipped };
|
|
@@ -13227,7 +13531,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
|
13227
13531
|
return { layer: null, skipped };
|
|
13228
13532
|
}
|
|
13229
13533
|
function loadContextFiles(options) {
|
|
13230
|
-
const cwd =
|
|
13534
|
+
const cwd = resolve9(options.cwd);
|
|
13231
13535
|
const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
|
|
13232
13536
|
const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
|
|
13233
13537
|
const skipped = [];
|
|
@@ -13235,7 +13539,7 @@ function loadContextFiles(options) {
|
|
|
13235
13539
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
13236
13540
|
}
|
|
13237
13541
|
if (options.setting && options.setting !== "auto") {
|
|
13238
|
-
const filePath =
|
|
13542
|
+
const filePath = resolve9(cwd, String(options.setting));
|
|
13239
13543
|
if (!isInsideOrEqual(cwd, filePath)) {
|
|
13240
13544
|
skipped.push({
|
|
13241
13545
|
level: "single",
|
|
@@ -13246,7 +13550,7 @@ function loadContextFiles(options) {
|
|
|
13246
13550
|
});
|
|
13247
13551
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
13248
13552
|
}
|
|
13249
|
-
if (!
|
|
13553
|
+
if (!existsSync24(filePath)) {
|
|
13250
13554
|
skipped.push({
|
|
13251
13555
|
level: "single",
|
|
13252
13556
|
filePath,
|
|
@@ -13279,7 +13583,7 @@ function loadContextFiles(options) {
|
|
|
13279
13583
|
skipped.push(...result.skipped);
|
|
13280
13584
|
if (result.layer) layers.push(result.layer);
|
|
13281
13585
|
}
|
|
13282
|
-
if (
|
|
13586
|
+
if (resolve9(options.cwd) !== resolve9(options.projectRoot)) {
|
|
13283
13587
|
const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
|
|
13284
13588
|
skipped.push(...result.skipped);
|
|
13285
13589
|
if (result.layer) layers.push(result.layer);
|
|
@@ -13297,8 +13601,8 @@ function loadContextFiles(options) {
|
|
|
13297
13601
|
}
|
|
13298
13602
|
|
|
13299
13603
|
// src/web/session-handler.ts
|
|
13300
|
-
import { existsSync as
|
|
13301
|
-
import { join as
|
|
13604
|
+
import { existsSync as existsSync28, readFileSync as readFileSync21, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, statSync as statSync11, createWriteStream } from "fs";
|
|
13605
|
+
import { join as join23, resolve as resolve10, dirname as dirname7 } from "path";
|
|
13302
13606
|
|
|
13303
13607
|
// src/cli/review-prompts.ts
|
|
13304
13608
|
function buildReviewPrompt(diff, gitContextStr, detailed) {
|
|
@@ -13359,8 +13663,8 @@ If no security issues found, state "\u2705 No security vulnerabilities detected"
|
|
|
13359
13663
|
}
|
|
13360
13664
|
|
|
13361
13665
|
// src/repl/commands/project-init.ts
|
|
13362
|
-
import { existsSync as
|
|
13363
|
-
import { join as
|
|
13666
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
|
|
13667
|
+
import { join as join19 } from "path";
|
|
13364
13668
|
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
13365
13669
|
"node_modules",
|
|
13366
13670
|
".git",
|
|
@@ -13396,7 +13700,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
13396
13700
|
if (depth > maxDepth || count >= maxEntries) return;
|
|
13397
13701
|
let entries;
|
|
13398
13702
|
try {
|
|
13399
|
-
entries =
|
|
13703
|
+
entries = readdirSync11(d);
|
|
13400
13704
|
} catch {
|
|
13401
13705
|
return;
|
|
13402
13706
|
}
|
|
@@ -13404,11 +13708,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
13404
13708
|
const sorted = filtered.sort((a, b) => {
|
|
13405
13709
|
let aIsDir = false, bIsDir = false;
|
|
13406
13710
|
try {
|
|
13407
|
-
aIsDir =
|
|
13711
|
+
aIsDir = statSync8(join19(d, a)).isDirectory();
|
|
13408
13712
|
} catch {
|
|
13409
13713
|
}
|
|
13410
13714
|
try {
|
|
13411
|
-
bIsDir =
|
|
13715
|
+
bIsDir = statSync8(join19(d, b)).isDirectory();
|
|
13412
13716
|
} catch {
|
|
13413
13717
|
}
|
|
13414
13718
|
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
@@ -13416,12 +13720,12 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
13416
13720
|
});
|
|
13417
13721
|
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
13418
13722
|
const name = sorted[i];
|
|
13419
|
-
const fullPath =
|
|
13723
|
+
const fullPath = join19(d, name);
|
|
13420
13724
|
const isLast = i === sorted.length - 1;
|
|
13421
13725
|
const connector = isLast ? "+-- " : "|-- ";
|
|
13422
13726
|
let isDir;
|
|
13423
13727
|
try {
|
|
13424
|
-
isDir =
|
|
13728
|
+
isDir = statSync8(fullPath).isDirectory();
|
|
13425
13729
|
} catch {
|
|
13426
13730
|
continue;
|
|
13427
13731
|
}
|
|
@@ -13443,7 +13747,7 @@ function scanProject(cwd) {
|
|
|
13443
13747
|
configFiles: [],
|
|
13444
13748
|
directoryStructure: ""
|
|
13445
13749
|
};
|
|
13446
|
-
const check = (file) =>
|
|
13750
|
+
const check = (file) => existsSync25(join19(cwd, file));
|
|
13447
13751
|
const configCandidates = [
|
|
13448
13752
|
"package.json",
|
|
13449
13753
|
"tsconfig.json",
|
|
@@ -13470,7 +13774,7 @@ function scanProject(cwd) {
|
|
|
13470
13774
|
info.type = "node";
|
|
13471
13775
|
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
13472
13776
|
try {
|
|
13473
|
-
const pkg = JSON.parse(
|
|
13777
|
+
const pkg = JSON.parse(readFileSync19(join19(cwd, "package.json"), "utf-8"));
|
|
13474
13778
|
const scripts = pkg.scripts ?? {};
|
|
13475
13779
|
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
13476
13780
|
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
@@ -13943,6 +14247,334 @@ var DiscussionOrchestrator = class {
|
|
|
13943
14247
|
}
|
|
13944
14248
|
};
|
|
13945
14249
|
|
|
14250
|
+
// src/diagnostics/doctor-report.ts
|
|
14251
|
+
import { existsSync as existsSync27, statSync as statSync10 } from "fs";
|
|
14252
|
+
import { join as join21 } from "path";
|
|
14253
|
+
import { arch as arch2, platform as platform6, release as release2 } from "os";
|
|
14254
|
+
|
|
14255
|
+
// src/diagnostics/crash-log.ts
|
|
14256
|
+
import {
|
|
14257
|
+
existsSync as existsSync26,
|
|
14258
|
+
mkdirSync as mkdirSync12,
|
|
14259
|
+
readdirSync as readdirSync12,
|
|
14260
|
+
readFileSync as readFileSync20,
|
|
14261
|
+
statSync as statSync9,
|
|
14262
|
+
unlinkSync as unlinkSync5,
|
|
14263
|
+
writeFileSync as writeFileSync2
|
|
14264
|
+
} from "fs";
|
|
14265
|
+
import { join as join20 } from "path";
|
|
14266
|
+
import { homedir as homedir8, platform as platform5, release, arch } from "os";
|
|
14267
|
+
var LOGS_DIR_NAME = "logs";
|
|
14268
|
+
var CRASH_LOG_PREFIX = "crash-";
|
|
14269
|
+
function getLogsDir(configDir) {
|
|
14270
|
+
const base = configDir ?? join20(homedir8(), CONFIG_DIR_NAME);
|
|
14271
|
+
return join20(base, LOGS_DIR_NAME);
|
|
14272
|
+
}
|
|
14273
|
+
function truncate(s, n) {
|
|
14274
|
+
return s.length <= n ? s : s.slice(0, n) + "\u2026";
|
|
14275
|
+
}
|
|
14276
|
+
function listRecentCrashes(limit = 10, configDir) {
|
|
14277
|
+
const dir = getLogsDir(configDir);
|
|
14278
|
+
if (!existsSync26(dir)) return [];
|
|
14279
|
+
const files = [];
|
|
14280
|
+
let names;
|
|
14281
|
+
try {
|
|
14282
|
+
names = readdirSync12(dir);
|
|
14283
|
+
} catch {
|
|
14284
|
+
return [];
|
|
14285
|
+
}
|
|
14286
|
+
for (const name of names) {
|
|
14287
|
+
if (!name.startsWith(CRASH_LOG_PREFIX) || !name.endsWith(".log")) continue;
|
|
14288
|
+
const full = join20(dir, name);
|
|
14289
|
+
try {
|
|
14290
|
+
const st = statSync9(full);
|
|
14291
|
+
const head = readFileSync20(full, "utf-8").split("\n").slice(0, 20);
|
|
14292
|
+
const tsLine = head.find((l) => l.startsWith("timestamp:"));
|
|
14293
|
+
const kindLine = head.find((l) => l.startsWith("kind:"));
|
|
14294
|
+
const msgIdx = head.findIndex((l) => l.trim() === "## message");
|
|
14295
|
+
const message = msgIdx >= 0 && head[msgIdx + 1] ? head[msgIdx + 1] : "";
|
|
14296
|
+
files.push({
|
|
14297
|
+
file: full,
|
|
14298
|
+
timestamp: tsLine ? tsLine.replace("timestamp:", "").trim() : st.mtime.toISOString(),
|
|
14299
|
+
kind: kindLine ? kindLine.replace("kind:", "").trim() : "unknown",
|
|
14300
|
+
message: truncate(message, 200),
|
|
14301
|
+
sizeBytes: st.size
|
|
14302
|
+
});
|
|
14303
|
+
} catch {
|
|
14304
|
+
}
|
|
14305
|
+
}
|
|
14306
|
+
files.sort((a, b) => a.timestamp < b.timestamp ? 1 : -1);
|
|
14307
|
+
return files.slice(0, limit);
|
|
14308
|
+
}
|
|
14309
|
+
function getConfigDirUsage(configDir) {
|
|
14310
|
+
const base = configDir ?? join20(homedir8(), CONFIG_DIR_NAME);
|
|
14311
|
+
if (!existsSync26(base)) return { totalBytes: 0, entries: [] };
|
|
14312
|
+
const entries = [];
|
|
14313
|
+
let total = 0;
|
|
14314
|
+
let names;
|
|
14315
|
+
try {
|
|
14316
|
+
names = readdirSync12(base);
|
|
14317
|
+
} catch {
|
|
14318
|
+
return { totalBytes: 0, entries: [] };
|
|
14319
|
+
}
|
|
14320
|
+
for (const name of names) {
|
|
14321
|
+
const full = join20(base, name);
|
|
14322
|
+
const bytes = dirSize(full);
|
|
14323
|
+
entries.push({ name, bytes });
|
|
14324
|
+
total += bytes;
|
|
14325
|
+
}
|
|
14326
|
+
entries.sort((a, b) => b.bytes - a.bytes);
|
|
14327
|
+
return { totalBytes: total, entries };
|
|
14328
|
+
}
|
|
14329
|
+
function dirSize(path3) {
|
|
14330
|
+
try {
|
|
14331
|
+
const st = statSync9(path3);
|
|
14332
|
+
if (st.isFile()) return st.size;
|
|
14333
|
+
if (!st.isDirectory()) return 0;
|
|
14334
|
+
let sum = 0;
|
|
14335
|
+
for (const name of readdirSync12(path3)) {
|
|
14336
|
+
sum += dirSize(join20(path3, name));
|
|
14337
|
+
}
|
|
14338
|
+
return sum;
|
|
14339
|
+
} catch {
|
|
14340
|
+
return 0;
|
|
14341
|
+
}
|
|
14342
|
+
}
|
|
14343
|
+
|
|
14344
|
+
// src/diagnostics/doctor-report.ts
|
|
14345
|
+
function checkFile(label, path3) {
|
|
14346
|
+
const exists = existsSync27(path3);
|
|
14347
|
+
let sizeBytes = null;
|
|
14348
|
+
if (exists) {
|
|
14349
|
+
try {
|
|
14350
|
+
sizeBytes = statSync10(path3).size;
|
|
14351
|
+
} catch {
|
|
14352
|
+
}
|
|
14353
|
+
}
|
|
14354
|
+
return { label, path: path3, exists, sizeBytes };
|
|
14355
|
+
}
|
|
14356
|
+
function buildDoctorReport(options) {
|
|
14357
|
+
const config = options.config;
|
|
14358
|
+
const cwd = options.cwd ?? process.cwd();
|
|
14359
|
+
const configDir = config.getConfigDir();
|
|
14360
|
+
const projectRoot = getGitRoot(cwd) ?? cwd;
|
|
14361
|
+
const contextCfg = config.get("context");
|
|
14362
|
+
const contextSetting = config.get("contextFile");
|
|
14363
|
+
const context = loadContextFiles({
|
|
14364
|
+
cwd,
|
|
14365
|
+
configDir,
|
|
14366
|
+
projectRoot,
|
|
14367
|
+
setting: contextSetting,
|
|
14368
|
+
maxBytes: contextCfg.projectDocMaxBytes,
|
|
14369
|
+
fallbackFilenames: contextCfg.projectDocFallbackFilenames
|
|
14370
|
+
});
|
|
14371
|
+
const hooksConfig = config.get("hooks");
|
|
14372
|
+
const hooks = listHooks(hooksConfig, configDir);
|
|
14373
|
+
const pendingHooks = getPendingHookTrust(hooksConfig, configDir);
|
|
14374
|
+
const plugins = listInstalledPlugins(configDir);
|
|
14375
|
+
const activeAssets = getActivePluginAssets(configDir);
|
|
14376
|
+
const network = config.get("networkPolicy");
|
|
14377
|
+
const mcpServers = config.get("mcpServers");
|
|
14378
|
+
const allStats = getStatsSnapshot();
|
|
14379
|
+
return {
|
|
14380
|
+
version: VERSION,
|
|
14381
|
+
npmCheck: options.npmCheck,
|
|
14382
|
+
node: process.version,
|
|
14383
|
+
platform: `${platform6()} ${release2()} (${arch2()})`,
|
|
14384
|
+
cwd,
|
|
14385
|
+
projectRoot,
|
|
14386
|
+
configDir,
|
|
14387
|
+
providers: options.providers.listAll().map((p) => ({ id: p.id, displayName: p.displayName, configured: p.configured })),
|
|
14388
|
+
files: [
|
|
14389
|
+
checkFile("config.json", join21(configDir, "config.json")),
|
|
14390
|
+
checkFile("memory.md", join21(configDir, MEMORY_FILE_NAME)),
|
|
14391
|
+
checkFile("memory.jsonl", join21(configDir, "memory.jsonl")),
|
|
14392
|
+
checkFile("dev-state.md", join21(configDir, DEV_STATE_FILE_NAME)),
|
|
14393
|
+
checkFile("hooks-trust.json", join21(configDir, "hooks-trust.json")),
|
|
14394
|
+
checkFile("plugin-state.json", join21(configDir, "plugins", "plugin-state.json"))
|
|
14395
|
+
],
|
|
14396
|
+
context: {
|
|
14397
|
+
enabled: contextSetting !== false,
|
|
14398
|
+
setting: contextSetting,
|
|
14399
|
+
candidates: context.candidates,
|
|
14400
|
+
maxBytes: context.maxBytes,
|
|
14401
|
+
layers: context.layers.map((l) => ({ level: l.level, fileName: l.fileName, displayPath: l.displayPath, bytes: l.byteCount, chars: l.charCount, truncated: l.truncated })),
|
|
14402
|
+
skipped: context.skipped.map((s) => ({ level: s.level, fileName: s.fileName, displayPath: s.displayPath, reason: s.reason, message: s.message }))
|
|
14403
|
+
},
|
|
14404
|
+
permissions: {
|
|
14405
|
+
defaultProfile: config.get("defaultPermissionProfile"),
|
|
14406
|
+
allowedProfiles: config.get("allowedPermissionProfiles"),
|
|
14407
|
+
customProfileCount: Object.keys(config.get("permissionProfiles")).length,
|
|
14408
|
+
legacyDefaultAction: config.get("defaultPermission"),
|
|
14409
|
+
ruleCount: config.get("permissionRules").length
|
|
14410
|
+
},
|
|
14411
|
+
networkPolicy: {
|
|
14412
|
+
enabled: network.enabled,
|
|
14413
|
+
defaultAction: network.defaultAction,
|
|
14414
|
+
allowDomains: network.allowDomains.length,
|
|
14415
|
+
denyDomains: network.denyDomains.length,
|
|
14416
|
+
allowPorts: network.allowPorts.length,
|
|
14417
|
+
denyPorts: network.denyPorts.length,
|
|
14418
|
+
allowPrivateNetwork: network.allowPrivateNetwork,
|
|
14419
|
+
toolOverrides: Object.fromEntries(Object.entries(network.tools).filter(([, v]) => v !== void 0))
|
|
14420
|
+
},
|
|
14421
|
+
hooks: {
|
|
14422
|
+
enabled: hooksConfig?.enabled !== false,
|
|
14423
|
+
total: hooks.length,
|
|
14424
|
+
project: hooks.filter((h) => h.source === "project").length,
|
|
14425
|
+
pendingTrust: pendingHooks.length,
|
|
14426
|
+
disabled: hooks.filter((h) => h.disabled).length
|
|
14427
|
+
},
|
|
14428
|
+
plugins: {
|
|
14429
|
+
root: join21(configDir, "plugins"),
|
|
14430
|
+
installed: plugins.length,
|
|
14431
|
+
active: activeAssets.plugins.length,
|
|
14432
|
+
invalid: plugins.filter((p) => !p.valid).length,
|
|
14433
|
+
untrusted: plugins.filter((p) => p.valid && !p.trusted).length,
|
|
14434
|
+
disabled: plugins.filter((p) => p.valid && !p.enabled).length,
|
|
14435
|
+
mcpServers: Object.keys(activeAssets.mcpServers).length,
|
|
14436
|
+
skillDirs: activeAssets.skillDirs.length,
|
|
14437
|
+
commandDirs: activeAssets.commandDirs.length,
|
|
14438
|
+
agentDirs: activeAssets.agentDirs.length,
|
|
14439
|
+
entries: plugins.map((p) => ({ name: p.name, version: p.manifest.version, enabled: p.enabled, trusted: p.trusted, valid: p.valid, error: p.error }))
|
|
14440
|
+
},
|
|
14441
|
+
mcp: {
|
|
14442
|
+
enabled: config.get("mcpEnabled"),
|
|
14443
|
+
configured: Object.keys(mcpServers).length,
|
|
14444
|
+
projectConfigExists: existsSync27(join21(projectRoot, MCP_PROJECT_CONFIG_NAME)),
|
|
14445
|
+
statuses: options.mcpStatuses ?? []
|
|
14446
|
+
},
|
|
14447
|
+
recentCrashes: listRecentCrashes(5, configDir),
|
|
14448
|
+
diskUsage: getConfigDirUsage(configDir),
|
|
14449
|
+
toolStats: {
|
|
14450
|
+
totalCalls: allStats.reduce((a, b) => a + b.calls, 0),
|
|
14451
|
+
totalFailures: allStats.reduce((a, b) => a + b.failures, 0),
|
|
14452
|
+
topUsed: getTopUsedTools(5),
|
|
14453
|
+
topFailing: getTopFailingTools(5)
|
|
14454
|
+
}
|
|
14455
|
+
};
|
|
14456
|
+
}
|
|
14457
|
+
|
|
14458
|
+
// src/diagnostics/doctor-cli.ts
|
|
14459
|
+
function formatBytes(n) {
|
|
14460
|
+
if (n < 1024) return `${n} B`;
|
|
14461
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
14462
|
+
if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
14463
|
+
return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
14464
|
+
}
|
|
14465
|
+
function formatDoctorReport(report, ansi = true) {
|
|
14466
|
+
const wrap = (code, s) => ansi ? `${code}${s}\x1B[0m` : s;
|
|
14467
|
+
const B = (s) => wrap("\x1B[1m", s);
|
|
14468
|
+
const D = (s) => wrap("\x1B[2m", s);
|
|
14469
|
+
const G = (s) => wrap("\x1B[32m", s);
|
|
14470
|
+
const Y = (s) => wrap("\x1B[33m", s);
|
|
14471
|
+
const R = (s) => wrap("\x1B[31m", s);
|
|
14472
|
+
const out = [];
|
|
14473
|
+
out.push("");
|
|
14474
|
+
out.push(B("\u{1FA7A} AI-CLI Health Check"));
|
|
14475
|
+
out.push("");
|
|
14476
|
+
const nc = report.npmCheck;
|
|
14477
|
+
let versionLine = ` version: ${report.version}`;
|
|
14478
|
+
if (nc?.status === "up-to-date") versionLine += ` ${D("(latest on npm)")}`;
|
|
14479
|
+
else if (nc?.status === "outdated" && nc.latest) versionLine += ` ${Y(`\u2192 ${nc.latest} available`)} ${D("(npm i -g jinzd-ai-cli@latest)")}`;
|
|
14480
|
+
else if (nc?.status === "skipped") versionLine += ` ${D(`(npm check skipped: ${nc.reason ?? "unknown"})`)}`;
|
|
14481
|
+
out.push(versionLine);
|
|
14482
|
+
out.push(` node: ${report.node}`);
|
|
14483
|
+
out.push(` platform: ${report.platform}`);
|
|
14484
|
+
out.push(` cwd: ${report.cwd}`);
|
|
14485
|
+
out.push(` project: ${report.projectRoot}`);
|
|
14486
|
+
out.push(` config: ${report.configDir}`);
|
|
14487
|
+
out.push("");
|
|
14488
|
+
out.push(B("API Keys:"));
|
|
14489
|
+
for (const p of report.providers) {
|
|
14490
|
+
out.push(` ${p.configured ? G("\u2713") : D("\u25CB")} ${p.id.padEnd(14)} ${p.configured ? G("configured") : D("not configured")}`);
|
|
14491
|
+
}
|
|
14492
|
+
out.push("");
|
|
14493
|
+
out.push(B("Context Files:"));
|
|
14494
|
+
out.push(` status: ${report.context.enabled ? G("enabled") : D("disabled")} \xB7 max ${formatBytes(report.context.maxBytes)}`);
|
|
14495
|
+
if (report.context.layers.length === 0) out.push(` ${D("(no context files loaded)")}`);
|
|
14496
|
+
for (const l of report.context.layers) {
|
|
14497
|
+
out.push(` ${G("\u2713")} ${l.level.padEnd(7)} ${l.displayPath} ${D(`(${formatBytes(l.bytes)}${l.truncated ? ", truncated" : ""})`)}`);
|
|
14498
|
+
}
|
|
14499
|
+
if (report.context.skipped.length > 0) {
|
|
14500
|
+
out.push(` ${D("skipped:")}`);
|
|
14501
|
+
for (const s of report.context.skipped.slice(0, 5)) {
|
|
14502
|
+
out.push(` ${Y("!")} ${s.level}:${s.fileName} ${s.reason}${s.message ? ` \xB7 ${s.message}` : ""}`);
|
|
14503
|
+
}
|
|
14504
|
+
}
|
|
14505
|
+
out.push("");
|
|
14506
|
+
out.push(B("Permissions / Network:"));
|
|
14507
|
+
out.push(` profile: ${report.permissions.defaultProfile} \xB7 allowed ${report.permissions.allowedProfiles.join(", ")} \xB7 rules ${report.permissions.ruleCount} \xB7 custom profiles ${report.permissions.customProfileCount}`);
|
|
14508
|
+
const np = report.networkPolicy;
|
|
14509
|
+
out.push(` networkPolicy: ${np.enabled ? G("enabled") : D("disabled")} \xB7 default ${np.defaultAction} \xB7 allow ${np.allowDomains} domain(s)/${np.allowPorts} port(s) \xB7 deny ${np.denyDomains} domain(s)/${np.denyPorts} port(s) \xB7 private ${np.allowPrivateNetwork ? "allow" : "block/confirm"}`);
|
|
14510
|
+
const overrides = Object.entries(np.toolOverrides).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
14511
|
+
if (overrides) out.push(` tool overrides: ${overrides}`);
|
|
14512
|
+
out.push("");
|
|
14513
|
+
out.push(B("Hooks / Plugins:"));
|
|
14514
|
+
out.push(` hooks: ${report.hooks.enabled ? G("enabled") : D("disabled")} \xB7 total ${report.hooks.total} \xB7 project ${report.hooks.project} \xB7 pending trust ${report.hooks.pendingTrust} \xB7 disabled ${report.hooks.disabled}`);
|
|
14515
|
+
out.push(` plugins: installed ${report.plugins.installed} \xB7 active ${report.plugins.active} \xB7 disabled ${report.plugins.disabled} \xB7 untrusted ${report.plugins.untrusted} \xB7 invalid ${report.plugins.invalid}`);
|
|
14516
|
+
out.push(` plugin assets: ${report.plugins.skillDirs} skill dir(s), ${report.plugins.commandDirs} command dir(s), ${report.plugins.agentDirs} agent dir(s), ${report.plugins.mcpServers} MCP server(s)`);
|
|
14517
|
+
if (report.plugins.entries.length > 0) {
|
|
14518
|
+
for (const p of report.plugins.entries.slice(0, 5)) {
|
|
14519
|
+
const state2 = p.valid ? `${p.enabled ? "enabled" : "disabled"} \xB7 ${p.trusted ? "trusted" : "untrusted"}` : `invalid \xB7 ${p.error ?? ""}`;
|
|
14520
|
+
out.push(` ${p.valid ? G("\u2713") : R("\u2717")} ${p.name}@${p.version} ${D(state2)}`);
|
|
14521
|
+
}
|
|
14522
|
+
}
|
|
14523
|
+
out.push("");
|
|
14524
|
+
out.push(B("MCP Servers:"));
|
|
14525
|
+
out.push(` global configured: ${report.mcp.configured} \xB7 project .mcp.json: ${report.mcp.projectConfigExists ? "yes" : "no"} \xB7 mcpEnabled: ${report.mcp.enabled ? "true" : "false"}`);
|
|
14526
|
+
if (report.mcp.statuses.length === 0) out.push(` ${D("(no connected manager status in this mode)")}`);
|
|
14527
|
+
for (const s of report.mcp.statuses) {
|
|
14528
|
+
const state2 = s.connected ? `${G("connected")} \xB7 ${s.serverName} \xB7 ${s.toolCount} tools` : `${R("disconnected")}${s.error ? D(` \xB7 ${s.error}`) : ""}`;
|
|
14529
|
+
out.push(` ${s.connected ? G("\u2713") : R("\u2717")} ${s.serverId.padEnd(16)} ${state2}`);
|
|
14530
|
+
}
|
|
14531
|
+
out.push("");
|
|
14532
|
+
out.push(B("Config Files:"));
|
|
14533
|
+
for (const f of report.files) {
|
|
14534
|
+
const extra = f.exists && f.sizeBytes !== null ? ` ${D(`(${formatBytes(f.sizeBytes)})`)}` : "";
|
|
14535
|
+
out.push(` ${f.exists ? G("\u2713") : D("\u2013")} ${f.label.padEnd(16)} ${f.exists ? f.path : D("(not found)")}${extra}`);
|
|
14536
|
+
}
|
|
14537
|
+
out.push("");
|
|
14538
|
+
out.push(B("Recent Crashes (last 5):"));
|
|
14539
|
+
if (report.recentCrashes.length === 0) out.push(` ${D("(none - clean!)")}`);
|
|
14540
|
+
for (const c of report.recentCrashes) {
|
|
14541
|
+
out.push(` ${R("\u2717")} ${c.timestamp} ${Y(c.kind)}`);
|
|
14542
|
+
out.push(` ${D(c.message)}`);
|
|
14543
|
+
out.push(` ${D(c.file)}`);
|
|
14544
|
+
}
|
|
14545
|
+
out.push("");
|
|
14546
|
+
const ts = report.toolStats;
|
|
14547
|
+
out.push(B("Tool Usage:"));
|
|
14548
|
+
out.push(` total: ${ts.totalCalls} calls \xB7 ${ts.totalFailures} failures (${ts.totalCalls ? (ts.totalFailures * 100 / ts.totalCalls).toFixed(1) : "0.0"}%)`);
|
|
14549
|
+
if (ts.topUsed.length > 0) {
|
|
14550
|
+
out.push(` ${D("top used:")}`);
|
|
14551
|
+
for (const t of ts.topUsed) out.push(` ${t.name.padEnd(20)} ${String(t.calls).padStart(6)} calls \xB7 avg ${t.calls ? (t.totalDurationMs / t.calls).toFixed(0) : "0"}ms`);
|
|
14552
|
+
}
|
|
14553
|
+
if (ts.topFailing.length > 0) {
|
|
14554
|
+
out.push(` ${D("top failing:")}`);
|
|
14555
|
+
for (const t of ts.topFailing) out.push(` ${R(t.name.padEnd(20))} ${t.failures}/${t.calls} (${(t.failures * 100 / Math.max(1, t.calls)).toFixed(1)}%) ${D(t.lastFailureMessage ?? "")}`.slice(0, 200));
|
|
14556
|
+
}
|
|
14557
|
+
out.push("");
|
|
14558
|
+
const dirAnnotations = {
|
|
14559
|
+
models: "semantic search model -> /index semantic-clear",
|
|
14560
|
+
index: "symbol index -> /index clear",
|
|
14561
|
+
history: "conversation history",
|
|
14562
|
+
logs: "crash logs",
|
|
14563
|
+
"memory-index": "chat memory index -> /memory index-clear",
|
|
14564
|
+
plugins: "installed package plugins"
|
|
14565
|
+
};
|
|
14566
|
+
out.push(B("Disk Usage (~/.aicli):"));
|
|
14567
|
+
out.push(` total: ${formatBytes(report.diskUsage.totalBytes)}`);
|
|
14568
|
+
for (const e of report.diskUsage.entries.slice(0, 10)) {
|
|
14569
|
+
const note = dirAnnotations[e.name];
|
|
14570
|
+
out.push(` ${e.name.padEnd(18)} ${formatBytes(e.bytes)}${note ? ` ${D(`\xB7 ${note}`)}` : ""}`);
|
|
14571
|
+
}
|
|
14572
|
+
out.push("");
|
|
14573
|
+
out.push(G("\u2713 Health check complete"));
|
|
14574
|
+
out.push("");
|
|
14575
|
+
return out.join("\n");
|
|
14576
|
+
}
|
|
14577
|
+
|
|
13946
14578
|
// src/hub/presets.ts
|
|
13947
14579
|
var PRESETS = [
|
|
13948
14580
|
{
|
|
@@ -14130,7 +14762,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
|
|
|
14130
14762
|
}
|
|
14131
14763
|
|
|
14132
14764
|
// src/hub/persist.ts
|
|
14133
|
-
import { join as
|
|
14765
|
+
import { join as join22 } from "path";
|
|
14134
14766
|
function discussionToMessages(state2) {
|
|
14135
14767
|
const out = [];
|
|
14136
14768
|
const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -14168,7 +14800,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
|
|
|
14168
14800
|
session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
|
|
14169
14801
|
session.titleAiGenerated = true;
|
|
14170
14802
|
await sm.save();
|
|
14171
|
-
return { id: session.id, path:
|
|
14803
|
+
return { id: session.id, path: join22(config.getHistoryDir(), `${session.id}.json`) };
|
|
14172
14804
|
}
|
|
14173
14805
|
|
|
14174
14806
|
// src/web/session-handler.ts
|
|
@@ -14264,7 +14896,7 @@ var SessionHandler = class {
|
|
|
14264
14896
|
}
|
|
14265
14897
|
this.updateContextWindow();
|
|
14266
14898
|
this.activeSystemPrompt = this.loadContextFiles();
|
|
14267
|
-
const hooks = this.config.get("hooks");
|
|
14899
|
+
const hooks = mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir());
|
|
14268
14900
|
const permissionRules = this.config.get("permissionRules");
|
|
14269
14901
|
const defaultPermission = this.config.get("defaultPermission");
|
|
14270
14902
|
const permissionProfileName = this.config.get("defaultPermissionProfile") ?? "legacy";
|
|
@@ -14364,10 +14996,10 @@ var SessionHandler = class {
|
|
|
14364
14996
|
return;
|
|
14365
14997
|
}
|
|
14366
14998
|
case "ask_user_response": {
|
|
14367
|
-
const
|
|
14368
|
-
if (
|
|
14999
|
+
const resolve12 = this.pendingAskUser.get(msg.requestId);
|
|
15000
|
+
if (resolve12) {
|
|
14369
15001
|
this.pendingAskUser.delete(msg.requestId);
|
|
14370
|
-
|
|
15002
|
+
resolve12(msg.answer);
|
|
14371
15003
|
}
|
|
14372
15004
|
return;
|
|
14373
15005
|
}
|
|
@@ -14380,10 +15012,10 @@ var SessionHandler = class {
|
|
|
14380
15012
|
case "memory_manage":
|
|
14381
15013
|
return this.handleMemoryManage(msg.action, msg.id, msg.expiresAt);
|
|
14382
15014
|
case "auto_pause_response": {
|
|
14383
|
-
const
|
|
14384
|
-
if (
|
|
15015
|
+
const resolve12 = this.pendingAutoPause.get(msg.requestId);
|
|
15016
|
+
if (resolve12) {
|
|
14385
15017
|
this.pendingAutoPause.delete(msg.requestId);
|
|
14386
|
-
|
|
15018
|
+
resolve12({ action: msg.action, message: msg.message });
|
|
14387
15019
|
}
|
|
14388
15020
|
return;
|
|
14389
15021
|
}
|
|
@@ -14398,10 +15030,10 @@ var SessionHandler = class {
|
|
|
14398
15030
|
this.hubOrchestrator?.abort();
|
|
14399
15031
|
return;
|
|
14400
15032
|
case "hub_steer": {
|
|
14401
|
-
const
|
|
14402
|
-
if (
|
|
15033
|
+
const resolve12 = this.pendingHubReview.get(msg.requestId);
|
|
15034
|
+
if (resolve12) {
|
|
14403
15035
|
this.pendingHubReview.delete(msg.requestId);
|
|
14404
|
-
|
|
15036
|
+
resolve12({ action: msg.action, message: msg.message });
|
|
14405
15037
|
}
|
|
14406
15038
|
return;
|
|
14407
15039
|
}
|
|
@@ -14411,12 +15043,12 @@ var SessionHandler = class {
|
|
|
14411
15043
|
}
|
|
14412
15044
|
}
|
|
14413
15045
|
onDisconnect() {
|
|
14414
|
-
runLifecycleHooks(this.config.get("hooks") ?? void 0, "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
|
|
15046
|
+
runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
|
|
14415
15047
|
this.toolExecutor.cancelAll();
|
|
14416
15048
|
if (this.abortController) this.abortController.abort();
|
|
14417
|
-
for (const
|
|
15049
|
+
for (const resolve12 of this.pendingAskUser.values()) resolve12(null);
|
|
14418
15050
|
this.pendingAskUser.clear();
|
|
14419
|
-
for (const
|
|
15051
|
+
for (const resolve12 of this.pendingAutoPause.values()) resolve12({ action: "stop" });
|
|
14420
15052
|
this.pendingAutoPause.clear();
|
|
14421
15053
|
this.saveIfNeeded();
|
|
14422
15054
|
}
|
|
@@ -14550,9 +15182,9 @@ var SessionHandler = class {
|
|
|
14550
15182
|
this.hubOrchestrator = orchestrator;
|
|
14551
15183
|
orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
|
|
14552
15184
|
if (config.humanSteer) {
|
|
14553
|
-
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((
|
|
15185
|
+
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve12) => {
|
|
14554
15186
|
const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
14555
|
-
this.pendingHubReview.set(requestId,
|
|
15187
|
+
this.pendingHubReview.set(requestId, resolve12);
|
|
14556
15188
|
this.send({ type: "hub_review", requestId, round, maxRounds });
|
|
14557
15189
|
});
|
|
14558
15190
|
}
|
|
@@ -14590,7 +15222,7 @@ var SessionHandler = class {
|
|
|
14590
15222
|
this.ensureSession();
|
|
14591
15223
|
const session = this.sessions.current;
|
|
14592
15224
|
const promptDecisions = runLifecycleHooks(
|
|
14593
|
-
this.config.get("hooks") ?? void 0,
|
|
15225
|
+
mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
|
|
14594
15226
|
"UserPromptSubmit",
|
|
14595
15227
|
{ sessionId: session.id, prompt: content },
|
|
14596
15228
|
{ configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
@@ -14885,7 +15517,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
14885
15517
|
this.send({ type: "response_done", content, usage });
|
|
14886
15518
|
if (pendingTeeSave && isCleanDocumentBody(content)) {
|
|
14887
15519
|
try {
|
|
14888
|
-
|
|
15520
|
+
mkdirSync13(dirname7(pendingTeeSave), { recursive: true });
|
|
14889
15521
|
const bodyToSave = stripOuterCodeFence(content);
|
|
14890
15522
|
atomicWriteFileSync(pendingTeeSave, bodyToSave);
|
|
14891
15523
|
undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
|
|
@@ -14910,8 +15542,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
14910
15542
|
onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
|
|
14911
15543
|
requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
|
|
14912
15544
|
const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
14913
|
-
const pauseResp = await new Promise((
|
|
14914
|
-
this.pendingAutoPause.set(requestId,
|
|
15545
|
+
const pauseResp = await new Promise((resolve12) => {
|
|
15546
|
+
this.pendingAutoPause.set(requestId, resolve12);
|
|
14915
15547
|
this.send({
|
|
14916
15548
|
type: "auto_pause_request",
|
|
14917
15549
|
requestId,
|
|
@@ -15061,7 +15693,7 @@ ${summaryContent}`,
|
|
|
15061
15693
|
let isError = false;
|
|
15062
15694
|
let summary;
|
|
15063
15695
|
try {
|
|
15064
|
-
|
|
15696
|
+
mkdirSync13(dirname7(saveToFile), { recursive: true });
|
|
15065
15697
|
fileStream = createWriteStream(saveToFile);
|
|
15066
15698
|
const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
|
|
15067
15699
|
const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
|
|
@@ -15090,8 +15722,8 @@ ${summaryContent}`,
|
|
|
15090
15722
|
}
|
|
15091
15723
|
if (chunk.done) break;
|
|
15092
15724
|
}
|
|
15093
|
-
await new Promise((
|
|
15094
|
-
fileStream.end((err) => err ? reject(err) :
|
|
15725
|
+
await new Promise((resolve12, reject) => {
|
|
15726
|
+
fileStream.end((err) => err ? reject(err) : resolve12());
|
|
15095
15727
|
});
|
|
15096
15728
|
const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
|
|
15097
15729
|
if (verdict.kind === "reject") {
|
|
@@ -15117,7 +15749,7 @@ ${summaryContent}`,
|
|
|
15117
15749
|
} catch (err) {
|
|
15118
15750
|
if (fileStream) {
|
|
15119
15751
|
try {
|
|
15120
|
-
await new Promise((
|
|
15752
|
+
await new Promise((resolve12) => fileStream.end(() => resolve12()));
|
|
15121
15753
|
} catch {
|
|
15122
15754
|
}
|
|
15123
15755
|
}
|
|
@@ -15432,6 +16064,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
|
|
|
15432
16064
|
" /add-dir [path|remove] \u2014 Add/remove directory from AI context",
|
|
15433
16065
|
" /mcp [reconnect] \u2014 Show/manage MCP servers",
|
|
15434
16066
|
" /plugins \u2014 Show loaded plugins",
|
|
16067
|
+
" /plugin <cmd> \u2014 Manage package plugins",
|
|
15435
16068
|
" /commands \u2014 List custom commands",
|
|
15436
16069
|
" /doctor \u2014 Health check (API keys, config, MCP)",
|
|
15437
16070
|
" /bug \u2014 Generate bug report template",
|
|
@@ -15530,7 +16163,7 @@ ${lines.join("\n")}` });
|
|
|
15530
16163
|
this.sendToolsList();
|
|
15531
16164
|
this.sendStatus();
|
|
15532
16165
|
} else if (sub === "reload") {
|
|
15533
|
-
this.skillManager.loadSkills();
|
|
16166
|
+
this.skillManager.loadSkills(getActivePluginAssets(this.config.getConfigDir()).skillDirs);
|
|
15534
16167
|
this.send({ type: "info", message: `\u{1F504} Reloaded ${this.skillManager.listSkills().length} skill(s).` });
|
|
15535
16168
|
this.sendToolsList();
|
|
15536
16169
|
} else {
|
|
@@ -15749,9 +16382,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15749
16382
|
let modifiedFiles = 0;
|
|
15750
16383
|
const diffLines2 = [];
|
|
15751
16384
|
for (const [filePath, { earliest }] of fileMap) {
|
|
15752
|
-
const currentContent =
|
|
16385
|
+
const currentContent = existsSync28(filePath) ? (() => {
|
|
15753
16386
|
try {
|
|
15754
|
-
return
|
|
16387
|
+
return readFileSync21(filePath, "utf-8");
|
|
15755
16388
|
} catch {
|
|
15756
16389
|
return null;
|
|
15757
16390
|
}
|
|
@@ -15852,7 +16485,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15852
16485
|
break;
|
|
15853
16486
|
}
|
|
15854
16487
|
const sub = args[0]?.toLowerCase();
|
|
15855
|
-
const
|
|
16488
|
+
const resolve12 = (ref) => {
|
|
15856
16489
|
const r = session.resolveBranchRef(ref);
|
|
15857
16490
|
if (r.ok) return r.id;
|
|
15858
16491
|
if (r.reason === "ambiguous") {
|
|
@@ -15906,7 +16539,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15906
16539
|
this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
|
|
15907
16540
|
break;
|
|
15908
16541
|
}
|
|
15909
|
-
const id =
|
|
16542
|
+
const id = resolve12(ref);
|
|
15910
16543
|
if (!id) break;
|
|
15911
16544
|
const ok = session.switchBranch(id);
|
|
15912
16545
|
if (ok) {
|
|
@@ -15926,7 +16559,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15926
16559
|
this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
|
|
15927
16560
|
break;
|
|
15928
16561
|
}
|
|
15929
|
-
const id =
|
|
16562
|
+
const id = resolve12(ref);
|
|
15930
16563
|
if (!id) break;
|
|
15931
16564
|
const ok = session.deleteBranch(id);
|
|
15932
16565
|
if (ok) {
|
|
@@ -15945,7 +16578,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15945
16578
|
this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
|
|
15946
16579
|
break;
|
|
15947
16580
|
}
|
|
15948
|
-
const id =
|
|
16581
|
+
const id = resolve12(ref);
|
|
15949
16582
|
if (!id) break;
|
|
15950
16583
|
const ok = session.renameBranch(id, title);
|
|
15951
16584
|
if (ok) {
|
|
@@ -15963,7 +16596,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15963
16596
|
this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
|
|
15964
16597
|
break;
|
|
15965
16598
|
}
|
|
15966
|
-
const id =
|
|
16599
|
+
const id = resolve12(ref);
|
|
15967
16600
|
if (!id) break;
|
|
15968
16601
|
const d = session.diffBranches(id);
|
|
15969
16602
|
if (!d) {
|
|
@@ -16005,7 +16638,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
16005
16638
|
this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
|
|
16006
16639
|
break;
|
|
16007
16640
|
}
|
|
16008
|
-
const id =
|
|
16641
|
+
const id = resolve12(ref);
|
|
16009
16642
|
if (!id) break;
|
|
16010
16643
|
const idx = parseInt(idxStr, 10);
|
|
16011
16644
|
if (Number.isNaN(idx)) {
|
|
@@ -16267,7 +16900,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
16267
16900
|
case "test": {
|
|
16268
16901
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
16269
16902
|
try {
|
|
16270
|
-
const { executeTests } = await import("./run-tests-
|
|
16903
|
+
const { executeTests } = await import("./run-tests-O76JIBCW.js");
|
|
16271
16904
|
const argStr = args.join(" ").trim();
|
|
16272
16905
|
let testArgs = {};
|
|
16273
16906
|
if (argStr) {
|
|
@@ -16284,9 +16917,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
16284
16917
|
// ── /init ───────────────────────────────────────────────────────
|
|
16285
16918
|
case "init": {
|
|
16286
16919
|
const cwd = process.cwd();
|
|
16287
|
-
const targetPath =
|
|
16920
|
+
const targetPath = join23(cwd, "AICLI.md");
|
|
16288
16921
|
const force = args.includes("--force");
|
|
16289
|
-
if (
|
|
16922
|
+
if (existsSync28(targetPath) && !force) {
|
|
16290
16923
|
this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
16291
16924
|
Use /init --force to overwrite.` });
|
|
16292
16925
|
break;
|
|
@@ -16296,7 +16929,7 @@ Use /init --force to overwrite.` });
|
|
|
16296
16929
|
const projectInfo = scanProject(cwd);
|
|
16297
16930
|
const prompt = buildInitPrompt(projectInfo, cwd);
|
|
16298
16931
|
const content = await this.chatOnce(prompt, { temperature: 0.3, maxTokens: 4096 });
|
|
16299
|
-
|
|
16932
|
+
writeFileSync3(targetPath, content, "utf-8");
|
|
16300
16933
|
this.send({ type: "info", message: `\u2713 Generated: ${targetPath} (${content.length} chars)
|
|
16301
16934
|
Use /context reload to load it.` });
|
|
16302
16935
|
} catch (err) {
|
|
@@ -16306,53 +16939,13 @@ Use /context reload to load it.` });
|
|
|
16306
16939
|
}
|
|
16307
16940
|
// ── /doctor ─────────────────────────────────────────────────────
|
|
16308
16941
|
case "doctor": {
|
|
16309
|
-
const
|
|
16310
|
-
|
|
16311
|
-
|
|
16312
|
-
|
|
16313
|
-
|
|
16314
|
-
|
|
16315
|
-
|
|
16316
|
-
}
|
|
16317
|
-
lines.push("");
|
|
16318
|
-
const configDir = this.config.getConfigDir();
|
|
16319
|
-
lines.push("**Config Files:**");
|
|
16320
|
-
lines.push(` Dir: ${configDir}`);
|
|
16321
|
-
const checkFile = (label, filePath) => {
|
|
16322
|
-
const exists = existsSync25(filePath);
|
|
16323
|
-
let extra = "";
|
|
16324
|
-
if (exists) {
|
|
16325
|
-
try {
|
|
16326
|
-
extra = ` (${statSync8(filePath).size} bytes)`;
|
|
16327
|
-
} catch {
|
|
16328
|
-
}
|
|
16329
|
-
}
|
|
16330
|
-
lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
|
|
16331
|
-
};
|
|
16332
|
-
checkFile("config.json", join20(configDir, "config.json"));
|
|
16333
|
-
checkFile("memory.md", join20(configDir, MEMORY_FILE_NAME));
|
|
16334
|
-
checkFile("dev-state.md", join20(configDir, "dev-state.md"));
|
|
16335
|
-
lines.push("");
|
|
16336
|
-
if (this.mcpManager) {
|
|
16337
|
-
lines.push("**MCP Servers:**");
|
|
16338
|
-
const statuses = this.mcpManager.getStatus();
|
|
16339
|
-
if (statuses.length === 0) {
|
|
16340
|
-
lines.push(" (no servers configured)");
|
|
16341
|
-
} else {
|
|
16342
|
-
for (const s of statuses) {
|
|
16343
|
-
const state2 = s.connected ? `connected \xB7 ${s.serverName} \xB7 ${s.toolCount} tools` : `disconnected${s.error ? ` \xB7 ${s.error}` : ""}`;
|
|
16344
|
-
lines.push(` ${s.connected ? "\u2713" : "\u2717"} ${s.serverId.padEnd(16)} ${state2}`);
|
|
16345
|
-
}
|
|
16346
|
-
}
|
|
16347
|
-
lines.push("");
|
|
16348
|
-
}
|
|
16349
|
-
lines.push("**Current Session:**");
|
|
16350
|
-
lines.push(` Provider: ${this.currentProvider}`);
|
|
16351
|
-
lines.push(` Model: ${this.currentModel}`);
|
|
16352
|
-
lines.push(` Version: ${VERSION}`);
|
|
16353
|
-
lines.push("");
|
|
16354
|
-
lines.push("\u2713 Health check complete");
|
|
16355
|
-
this.send({ type: "info", message: lines.join("\n") });
|
|
16942
|
+
const report = buildDoctorReport({
|
|
16943
|
+
config: this.config,
|
|
16944
|
+
providers: this.providers,
|
|
16945
|
+
cwd: process.cwd(),
|
|
16946
|
+
mcpStatuses: this.mcpManager?.getStatus() ?? []
|
|
16947
|
+
});
|
|
16948
|
+
this.send({ type: "info", message: formatDoctorReport(report, false) });
|
|
16356
16949
|
break;
|
|
16357
16950
|
}
|
|
16358
16951
|
// ── /about ──────────────────────────────────────────────────────
|
|
@@ -16434,7 +17027,7 @@ ${this.config.toFormattedJSON()}
|
|
|
16434
17027
|
const result = this.contextLoadResult;
|
|
16435
17028
|
const layers = ["\u{1F4DA} **Context Layers:**", ""];
|
|
16436
17029
|
if (result && result.layers.length > 0) {
|
|
16437
|
-
const labels = { global: "Global ", project: "Project", local: "Local " };
|
|
17030
|
+
const labels = { global: "Global ", project: "Project", plugin: "Plugin ", local: "Local " };
|
|
16438
17031
|
for (const layer of result.layers) {
|
|
16439
17032
|
layers.push(` \u2713 ${labels[layer.level] ?? layer.level}: ${layer.displayPath} (${layer.charCount} chars${layer.truncated ? ", truncated" : ""})`);
|
|
16440
17033
|
}
|
|
@@ -16518,12 +17111,12 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
16518
17111
|
this.send({ type: "info", message: "\u2713 All added directories removed from context." });
|
|
16519
17112
|
break;
|
|
16520
17113
|
}
|
|
16521
|
-
const dirPath =
|
|
16522
|
-
if (!
|
|
17114
|
+
const dirPath = resolve10(sub);
|
|
17115
|
+
if (!existsSync28(dirPath)) {
|
|
16523
17116
|
this.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
16524
17117
|
break;
|
|
16525
17118
|
}
|
|
16526
|
-
if (!
|
|
17119
|
+
if (!statSync11(dirPath).isDirectory()) {
|
|
16527
17120
|
this.send({ type: "error", message: `Not a directory: ${dirPath}` });
|
|
16528
17121
|
break;
|
|
16529
17122
|
}
|
|
@@ -16535,14 +17128,14 @@ It will be included in AI context for subsequent messages.` });
|
|
|
16535
17128
|
// ── /commands ───────────────────────────────────────────────────
|
|
16536
17129
|
case "commands": {
|
|
16537
17130
|
const configDir = this.config.getConfigDir();
|
|
16538
|
-
const commandsDir =
|
|
16539
|
-
if (!
|
|
17131
|
+
const commandsDir = join23(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
17132
|
+
if (!existsSync28(commandsDir)) {
|
|
16540
17133
|
this.send({ type: "info", message: `No custom commands directory.
|
|
16541
17134
|
Create: ${commandsDir}/ with .md files.` });
|
|
16542
17135
|
break;
|
|
16543
17136
|
}
|
|
16544
17137
|
try {
|
|
16545
|
-
const files =
|
|
17138
|
+
const files = readdirSync13(commandsDir).filter((f) => f.endsWith(".md"));
|
|
16546
17139
|
if (files.length === 0) {
|
|
16547
17140
|
this.send({ type: "info", message: `No custom commands found in ${commandsDir}
|
|
16548
17141
|
Add .md files to create commands.` });
|
|
@@ -16559,23 +17152,75 @@ Add .md files to create commands.` });
|
|
|
16559
17152
|
}
|
|
16560
17153
|
break;
|
|
16561
17154
|
}
|
|
16562
|
-
// ── /plugins
|
|
17155
|
+
// ── /plugins / /plugin ──────────────────────────────────────────
|
|
16563
17156
|
case "plugins": {
|
|
16564
17157
|
const configDir = this.config.getConfigDir();
|
|
16565
|
-
const pluginsDir =
|
|
17158
|
+
const pluginsDir = join23(configDir, PLUGINS_DIR_NAME);
|
|
16566
17159
|
const pluginTools = this.toolRegistry.listPluginTools();
|
|
17160
|
+
const pluginPackages = listInstalledPlugins(configDir);
|
|
16567
17161
|
const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
|
|
16568
|
-
if (pluginTools.length === 0) {
|
|
17162
|
+
if (pluginTools.length === 0 && pluginPackages.length === 0) {
|
|
16569
17163
|
lines.push("No plugins loaded.");
|
|
16570
|
-
lines.push("
|
|
17164
|
+
lines.push("Use /plugin install <path>, /plugin trust <name>, then /plugin enable <name>.");
|
|
16571
17165
|
} else {
|
|
16572
|
-
|
|
16573
|
-
lines.push(`
|
|
17166
|
+
if (pluginPackages.length > 0) {
|
|
17167
|
+
lines.push(`Package plugins (${pluginPackages.length}):`);
|
|
17168
|
+
for (const p of pluginPackages) lines.push(` \u2022 ${p.name}@${p.manifest.version} \u2014 ${p.enabled ? "enabled" : "disabled"} / ${p.trusted ? "trusted" : "untrusted"}`);
|
|
17169
|
+
lines.push("");
|
|
17170
|
+
}
|
|
17171
|
+
if (pluginTools.length > 0) {
|
|
17172
|
+
lines.push(`Legacy JS plugin tools (${pluginTools.length}):`);
|
|
17173
|
+
for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
|
|
16574
17174
|
}
|
|
16575
17175
|
}
|
|
16576
17176
|
this.send({ type: "info", message: lines.join("\n") });
|
|
16577
17177
|
break;
|
|
16578
17178
|
}
|
|
17179
|
+
case "plugin": {
|
|
17180
|
+
const configDir = this.config.getConfigDir();
|
|
17181
|
+
const sub = args[0] ?? "list";
|
|
17182
|
+
try {
|
|
17183
|
+
if (sub === "list") {
|
|
17184
|
+
const plugins = listInstalledPlugins(configDir);
|
|
17185
|
+
const lines = [`\u{1F50C} **Package Plugins**`, `Dir: ${pluginRoot(configDir)}`, ""];
|
|
17186
|
+
if (plugins.length === 0) lines.push("No package plugins installed.");
|
|
17187
|
+
for (const p of plugins) lines.push(` \u2022 ${p.name}@${p.manifest.version} \u2014 ${p.enabled ? "enabled" : "disabled"} / ${p.trusted ? "trusted" : "untrusted"}`);
|
|
17188
|
+
this.send({ type: "info", message: lines.join("\n") });
|
|
17189
|
+
break;
|
|
17190
|
+
}
|
|
17191
|
+
if (sub === "install") {
|
|
17192
|
+
const input = args[1];
|
|
17193
|
+
if (!input) throw new Error("Usage: /plugin install <path>");
|
|
17194
|
+
const plugin = installPlugin(configDir, input);
|
|
17195
|
+
this.send({ type: "info", message: `Installed plugin ${plugin.name}@${plugin.manifest.version}. Run /plugin trust ${plugin.name}, then /plugin enable ${plugin.name}.` });
|
|
17196
|
+
break;
|
|
17197
|
+
}
|
|
17198
|
+
if (sub === "trust") {
|
|
17199
|
+
const name2 = args[1];
|
|
17200
|
+
if (!name2) throw new Error("Usage: /plugin trust <name>");
|
|
17201
|
+
const state2 = trustPlugin(configDir, name2);
|
|
17202
|
+
this.send({ type: "info", message: `Trusted plugin ${state2.name}.` });
|
|
17203
|
+
break;
|
|
17204
|
+
}
|
|
17205
|
+
if (sub === "enable" || sub === "disable") {
|
|
17206
|
+
const name2 = args[1];
|
|
17207
|
+
if (!name2) throw new Error(`Usage: /plugin ${sub} <name>`);
|
|
17208
|
+
const state2 = setPluginEnabled(configDir, name2, sub === "enable");
|
|
17209
|
+
this.send({ type: "info", message: `${sub === "enable" ? "Enabled" : "Disabled"} plugin ${state2.name}. Restart Web UI for changes to take effect.` });
|
|
17210
|
+
break;
|
|
17211
|
+
}
|
|
17212
|
+
if (sub === "inspect") {
|
|
17213
|
+
const name2 = args[1];
|
|
17214
|
+
if (!name2) throw new Error("Usage: /plugin inspect <name>");
|
|
17215
|
+
this.send({ type: "info", message: describePlugin(getInstalledPlugin(configDir, name2)).join("\n") });
|
|
17216
|
+
break;
|
|
17217
|
+
}
|
|
17218
|
+
throw new Error("Usage: /plugin [list|install|enable|disable|inspect|trust]");
|
|
17219
|
+
} catch (err) {
|
|
17220
|
+
this.send({ type: "error", message: err instanceof Error ? err.message : String(err) });
|
|
17221
|
+
}
|
|
17222
|
+
break;
|
|
17223
|
+
}
|
|
16579
17224
|
// ── /bug ────────────────────────────────────────────────────────
|
|
16580
17225
|
case "bug": {
|
|
16581
17226
|
const session = this.sessions.current;
|
|
@@ -16618,7 +17263,7 @@ Add .md files to create commands.` });
|
|
|
16618
17263
|
return;
|
|
16619
17264
|
}
|
|
16620
17265
|
const preDecisions = runLifecycleHooks(
|
|
16621
|
-
this.config.get("hooks") ?? void 0,
|
|
17266
|
+
mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
|
|
16622
17267
|
"PreCompact",
|
|
16623
17268
|
{ sessionId: session.id, messageCount: session.messages.length, instruction },
|
|
16624
17269
|
{ configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
@@ -16655,7 +17300,7 @@ Add .md files to create commands.` });
|
|
|
16655
17300
|
const removedCount = session.compact(summaryMsg, ackMsg, keepLast);
|
|
16656
17301
|
const afterCount = session.messages.length;
|
|
16657
17302
|
runLifecycleHooks(
|
|
16658
|
-
this.config.get("hooks") ?? void 0,
|
|
17303
|
+
mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
|
|
16659
17304
|
"PostCompact",
|
|
16660
17305
|
{ sessionId: session.id, beforeCount, afterCount, removedCount, keepLast, instruction },
|
|
16661
17306
|
{ configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
@@ -16792,7 +17437,7 @@ ${entry.content}`;
|
|
|
16792
17437
|
const configDir = this.config.getConfigDir();
|
|
16793
17438
|
try {
|
|
16794
17439
|
atomicWriteFileSync(memoryStorePath(configDir), "");
|
|
16795
|
-
atomicWriteFileSync(
|
|
17440
|
+
atomicWriteFileSync(join23(configDir, MEMORY_FILE_NAME), "");
|
|
16796
17441
|
this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
|
|
16797
17442
|
this.sendMemoryEntries();
|
|
16798
17443
|
} catch (err) {
|
|
@@ -17074,8 +17719,8 @@ async function setupProxy(configProxy) {
|
|
|
17074
17719
|
}
|
|
17075
17720
|
|
|
17076
17721
|
// src/web/auth.ts
|
|
17077
|
-
import { existsSync as
|
|
17078
|
-
import { join as
|
|
17722
|
+
import { existsSync as existsSync29, readFileSync as readFileSync22, writeFileSync as writeFileSync4, mkdirSync as mkdirSync14, readdirSync as readdirSync14, copyFileSync } from "fs";
|
|
17723
|
+
import { join as join24 } from "path";
|
|
17079
17724
|
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
17080
17725
|
var USERS_FILE = "users.json";
|
|
17081
17726
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
@@ -17090,7 +17735,7 @@ var AuthManager = class {
|
|
|
17090
17735
|
db;
|
|
17091
17736
|
constructor(baseDir) {
|
|
17092
17737
|
this.baseDir = baseDir;
|
|
17093
|
-
this.usersFile =
|
|
17738
|
+
this.usersFile = join24(baseDir, USERS_FILE);
|
|
17094
17739
|
this.db = this.loadOrCreate();
|
|
17095
17740
|
}
|
|
17096
17741
|
// ── Public API ─────────────────────────────────────────────────
|
|
@@ -17119,9 +17764,9 @@ var AuthManager = class {
|
|
|
17119
17764
|
}
|
|
17120
17765
|
const salt = randomBytes(16).toString("hex");
|
|
17121
17766
|
const passwordHash = this.hashPassword(password, salt);
|
|
17122
|
-
const dataDir =
|
|
17123
|
-
const fullDataDir =
|
|
17124
|
-
|
|
17767
|
+
const dataDir = join24(USERS_DIR, username);
|
|
17768
|
+
const fullDataDir = join24(this.baseDir, dataDir);
|
|
17769
|
+
mkdirSync14(join24(fullDataDir, "history"), { recursive: true });
|
|
17125
17770
|
const user = {
|
|
17126
17771
|
username,
|
|
17127
17772
|
passwordHash,
|
|
@@ -17236,7 +17881,7 @@ var AuthManager = class {
|
|
|
17236
17881
|
getUserDataDir(username) {
|
|
17237
17882
|
const user = this.db.users.find((u) => u.username === username);
|
|
17238
17883
|
if (!user) throw new Error(`User not found: ${username}`);
|
|
17239
|
-
return
|
|
17884
|
+
return join24(this.baseDir, user.dataDir);
|
|
17240
17885
|
}
|
|
17241
17886
|
/** List all usernames */
|
|
17242
17887
|
listUsers() {
|
|
@@ -17272,30 +17917,30 @@ var AuthManager = class {
|
|
|
17272
17917
|
const err = this.register(username, password);
|
|
17273
17918
|
if (err) return err;
|
|
17274
17919
|
const userDir = this.getUserDataDir(username);
|
|
17275
|
-
const globalConfig =
|
|
17276
|
-
if (
|
|
17920
|
+
const globalConfig = join24(this.baseDir, "config.json");
|
|
17921
|
+
if (existsSync29(globalConfig)) {
|
|
17277
17922
|
try {
|
|
17278
|
-
const content =
|
|
17279
|
-
|
|
17923
|
+
const content = readFileSync22(globalConfig, "utf-8");
|
|
17924
|
+
writeFileSync4(join24(userDir, "config.json"), content, "utf-8");
|
|
17280
17925
|
} catch {
|
|
17281
17926
|
}
|
|
17282
17927
|
}
|
|
17283
|
-
const globalMemory =
|
|
17284
|
-
if (
|
|
17928
|
+
const globalMemory = join24(this.baseDir, "memory.md");
|
|
17929
|
+
if (existsSync29(globalMemory)) {
|
|
17285
17930
|
try {
|
|
17286
|
-
const content =
|
|
17287
|
-
|
|
17931
|
+
const content = readFileSync22(globalMemory, "utf-8");
|
|
17932
|
+
writeFileSync4(join24(userDir, "memory.md"), content, "utf-8");
|
|
17288
17933
|
} catch {
|
|
17289
17934
|
}
|
|
17290
17935
|
}
|
|
17291
|
-
const globalHistory =
|
|
17292
|
-
if (
|
|
17936
|
+
const globalHistory = join24(this.baseDir, "history");
|
|
17937
|
+
if (existsSync29(globalHistory)) {
|
|
17293
17938
|
try {
|
|
17294
|
-
const files =
|
|
17295
|
-
const userHistory =
|
|
17939
|
+
const files = readdirSync14(globalHistory).filter((f) => f.endsWith(".json"));
|
|
17940
|
+
const userHistory = join24(userDir, "history");
|
|
17296
17941
|
for (const f of files) {
|
|
17297
17942
|
try {
|
|
17298
|
-
copyFileSync(
|
|
17943
|
+
copyFileSync(join24(globalHistory, f), join24(userHistory, f));
|
|
17299
17944
|
} catch {
|
|
17300
17945
|
}
|
|
17301
17946
|
}
|
|
@@ -17306,9 +17951,9 @@ var AuthManager = class {
|
|
|
17306
17951
|
}
|
|
17307
17952
|
// ── Private methods ────────────────────────────────────────────
|
|
17308
17953
|
loadOrCreate() {
|
|
17309
|
-
if (
|
|
17954
|
+
if (existsSync29(this.usersFile)) {
|
|
17310
17955
|
try {
|
|
17311
|
-
return JSON.parse(
|
|
17956
|
+
return JSON.parse(readFileSync22(this.usersFile, "utf-8"));
|
|
17312
17957
|
} catch {
|
|
17313
17958
|
}
|
|
17314
17959
|
}
|
|
@@ -17324,7 +17969,7 @@ var AuthManager = class {
|
|
|
17324
17969
|
this.saveDB(this.db);
|
|
17325
17970
|
}
|
|
17326
17971
|
saveDB(db) {
|
|
17327
|
-
|
|
17972
|
+
mkdirSync14(this.baseDir, { recursive: true });
|
|
17328
17973
|
atomicWriteFileSync(this.usersFile, JSON.stringify(db, null, 2));
|
|
17329
17974
|
}
|
|
17330
17975
|
/** Legacy hash — kept only for migrating old users (v0.2.x) */
|
|
@@ -17356,11 +18001,11 @@ function getModuleDir() {
|
|
|
17356
18001
|
if (typeof import.meta?.url === "string") {
|
|
17357
18002
|
const url = new URL(import.meta.url);
|
|
17358
18003
|
const filePath = url.pathname.replace(/^\/([A-Z]:)/i, "$1");
|
|
17359
|
-
return
|
|
18004
|
+
return dirname8(filePath);
|
|
17360
18005
|
}
|
|
17361
18006
|
} catch {
|
|
17362
18007
|
}
|
|
17363
|
-
return
|
|
18008
|
+
return resolve11(".");
|
|
17364
18009
|
}
|
|
17365
18010
|
async function startWebServer(options = {}) {
|
|
17366
18011
|
const port = options.port ?? 3e3;
|
|
@@ -17395,6 +18040,8 @@ async function startWebServer(options = {}) {
|
|
|
17395
18040
|
let mcpManager = null;
|
|
17396
18041
|
const mcpEnabled = config.get("mcpEnabled") !== false;
|
|
17397
18042
|
const globalMcpServers = mcpEnabled ? config.get("mcpServers") ?? {} : {};
|
|
18043
|
+
const pluginAssets = getActivePluginAssets(config.getConfigDir());
|
|
18044
|
+
const pluginMcpServers = mcpEnabled ? pluginAssets.mcpServers : {};
|
|
17398
18045
|
const projectMcpResolved = mcpEnabled ? resolveProjectMcpPath() : null;
|
|
17399
18046
|
let projectMcpServers = {};
|
|
17400
18047
|
if (projectMcpResolved) {
|
|
@@ -17407,7 +18054,7 @@ async function startWebServer(options = {}) {
|
|
|
17407
18054
|
console.log(` Skipped to prevent unintended RCE. Run "aicli" then "/mcp trust-project" to approve.`);
|
|
17408
18055
|
}
|
|
17409
18056
|
}
|
|
17410
|
-
const mergedMcpServers = { ...globalMcpServers, ...projectMcpServers };
|
|
18057
|
+
const mergedMcpServers = { ...globalMcpServers, ...pluginMcpServers, ...projectMcpServers };
|
|
17411
18058
|
if (!mcpEnabled) {
|
|
17412
18059
|
console.log(" \u{1F50C} MCP: disabled (config.mcpEnabled=false)");
|
|
17413
18060
|
}
|
|
@@ -17425,10 +18072,10 @@ async function startWebServer(options = {}) {
|
|
|
17425
18072
|
}
|
|
17426
18073
|
}
|
|
17427
18074
|
let skillManager = null;
|
|
17428
|
-
const skillsDir =
|
|
17429
|
-
if (
|
|
18075
|
+
const skillsDir = join25(config.getConfigDir(), SKILLS_DIR_NAME);
|
|
18076
|
+
if (existsSync30(skillsDir)) {
|
|
17430
18077
|
skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
|
|
17431
|
-
skillManager.loadSkills();
|
|
18078
|
+
skillManager.loadSkills(pluginAssets.skillDirs);
|
|
17432
18079
|
const count = skillManager.listSkills().length;
|
|
17433
18080
|
if (count > 0) {
|
|
17434
18081
|
console.log(` Skills: ${count} available`);
|
|
@@ -17497,18 +18144,18 @@ async function startWebServer(options = {}) {
|
|
|
17497
18144
|
next();
|
|
17498
18145
|
};
|
|
17499
18146
|
const moduleDir = getModuleDir();
|
|
17500
|
-
let clientDir =
|
|
17501
|
-
if (!
|
|
17502
|
-
clientDir =
|
|
18147
|
+
let clientDir = join25(moduleDir, "web", "client");
|
|
18148
|
+
if (!existsSync30(clientDir)) {
|
|
18149
|
+
clientDir = join25(moduleDir, "client");
|
|
17503
18150
|
}
|
|
17504
|
-
if (!
|
|
17505
|
-
clientDir =
|
|
18151
|
+
if (!existsSync30(clientDir)) {
|
|
18152
|
+
clientDir = join25(moduleDir, "..", "..", "src", "web", "client");
|
|
17506
18153
|
}
|
|
17507
|
-
if (!
|
|
17508
|
-
clientDir =
|
|
18154
|
+
if (!existsSync30(clientDir)) {
|
|
18155
|
+
clientDir = join25(process.cwd(), "src", "web", "client");
|
|
17509
18156
|
}
|
|
17510
18157
|
console.log(` Static files: ${clientDir}`);
|
|
17511
|
-
app.use("/vendor", express.static(
|
|
18158
|
+
app.use("/vendor", express.static(join25(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
|
|
17512
18159
|
app.use(express.static(clientDir));
|
|
17513
18160
|
app.get("/api/status", (_req, res) => {
|
|
17514
18161
|
res.json({
|
|
@@ -17575,10 +18222,10 @@ async function startWebServer(options = {}) {
|
|
|
17575
18222
|
app.get("/api/files", requireAuth, (req, res) => {
|
|
17576
18223
|
const cwd = process.cwd();
|
|
17577
18224
|
const prefix = req.query.prefix || "";
|
|
17578
|
-
const targetDir =
|
|
18225
|
+
const targetDir = join25(cwd, prefix);
|
|
17579
18226
|
try {
|
|
17580
|
-
const canonicalTarget = realpathSync(
|
|
17581
|
-
const canonicalCwd = realpathSync(
|
|
18227
|
+
const canonicalTarget = realpathSync(resolve11(targetDir));
|
|
18228
|
+
const canonicalCwd = realpathSync(resolve11(cwd));
|
|
17582
18229
|
if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
|
|
17583
18230
|
res.json({ files: [] });
|
|
17584
18231
|
return;
|
|
@@ -17589,10 +18236,10 @@ async function startWebServer(options = {}) {
|
|
|
17589
18236
|
}
|
|
17590
18237
|
try {
|
|
17591
18238
|
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", "__pycache__", ".next", ".nuxt", "coverage", ".cache"]);
|
|
17592
|
-
const entries =
|
|
18239
|
+
const entries = readdirSync15(targetDir, { withFileTypes: true });
|
|
17593
18240
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
17594
18241
|
name: e.name,
|
|
17595
|
-
path: relative4(cwd,
|
|
18242
|
+
path: relative4(cwd, join25(targetDir, e.name)).replace(/\\/g, "/"),
|
|
17596
18243
|
isDir: e.isDirectory()
|
|
17597
18244
|
}));
|
|
17598
18245
|
res.json({ files });
|
|
@@ -17628,8 +18275,8 @@ async function startWebServer(options = {}) {
|
|
|
17628
18275
|
try {
|
|
17629
18276
|
const authUser = req._authUser;
|
|
17630
18277
|
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
17631
|
-
const filePath =
|
|
17632
|
-
if (!
|
|
18278
|
+
const filePath = join25(histDir, `${id}.json`);
|
|
18279
|
+
if (!existsSync30(filePath)) {
|
|
17633
18280
|
res.status(404).json({ error: "Session not found" });
|
|
17634
18281
|
return;
|
|
17635
18282
|
}
|
|
@@ -17644,7 +18291,7 @@ async function startWebServer(options = {}) {
|
|
|
17644
18291
|
res.status(404).json({ error: "Session not found" });
|
|
17645
18292
|
return;
|
|
17646
18293
|
}
|
|
17647
|
-
const data = JSON.parse(
|
|
18294
|
+
const data = JSON.parse(readFileSync23(filePath, "utf-8"));
|
|
17648
18295
|
res.json({ session: data });
|
|
17649
18296
|
} catch (err) {
|
|
17650
18297
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -17657,10 +18304,10 @@ async function startWebServer(options = {}) {
|
|
|
17657
18304
|
return;
|
|
17658
18305
|
}
|
|
17659
18306
|
const cwd = process.cwd();
|
|
17660
|
-
const fullPath =
|
|
18307
|
+
const fullPath = resolve11(join25(cwd, filePath));
|
|
17661
18308
|
try {
|
|
17662
18309
|
const canonicalFull = realpathSync(fullPath);
|
|
17663
|
-
const canonicalCwd = realpathSync(
|
|
18310
|
+
const canonicalCwd = realpathSync(resolve11(cwd));
|
|
17664
18311
|
if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
|
|
17665
18312
|
res.json({ error: "Access denied" });
|
|
17666
18313
|
return;
|
|
@@ -17670,12 +18317,12 @@ async function startWebServer(options = {}) {
|
|
|
17670
18317
|
return;
|
|
17671
18318
|
}
|
|
17672
18319
|
try {
|
|
17673
|
-
const stat =
|
|
18320
|
+
const stat = statSync12(fullPath);
|
|
17674
18321
|
if (stat.size > 512 * 1024) {
|
|
17675
18322
|
res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
|
|
17676
18323
|
return;
|
|
17677
18324
|
}
|
|
17678
|
-
const content =
|
|
18325
|
+
const content = readFileSync23(fullPath, "utf-8");
|
|
17679
18326
|
res.json({ content, size: stat.size });
|
|
17680
18327
|
} catch {
|
|
17681
18328
|
res.json({ error: "Cannot read file" });
|
|
@@ -17879,7 +18526,7 @@ async function startWebServer(options = {}) {
|
|
|
17879
18526
|
});
|
|
17880
18527
|
const MAX_PORT_ATTEMPTS = 10;
|
|
17881
18528
|
let actualPort = port;
|
|
17882
|
-
const result = await new Promise((
|
|
18529
|
+
const result = await new Promise((resolve12, reject) => {
|
|
17883
18530
|
const tryListen = (attempt) => {
|
|
17884
18531
|
server.once("error", (err) => {
|
|
17885
18532
|
if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
|
|
@@ -17910,7 +18557,7 @@ async function startWebServer(options = {}) {
|
|
|
17910
18557
|
}
|
|
17911
18558
|
console.log(` Press Ctrl+C to stop
|
|
17912
18559
|
`);
|
|
17913
|
-
|
|
18560
|
+
resolve12({ port: actualPort, host, url });
|
|
17914
18561
|
});
|
|
17915
18562
|
};
|
|
17916
18563
|
tryListen(1);
|
|
@@ -17930,17 +18577,17 @@ function resolveProjectMcpPath() {
|
|
|
17930
18577
|
const cwd = process.cwd();
|
|
17931
18578
|
const gitRoot = getGitRoot(cwd);
|
|
17932
18579
|
const projectRoot = gitRoot ?? cwd;
|
|
17933
|
-
const configPath =
|
|
17934
|
-
return
|
|
18580
|
+
const configPath = join25(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
18581
|
+
return existsSync30(configPath) ? configPath : null;
|
|
17935
18582
|
}
|
|
17936
18583
|
function loadProjectMcpConfig() {
|
|
17937
18584
|
const cwd = process.cwd();
|
|
17938
18585
|
const gitRoot = getGitRoot(cwd);
|
|
17939
18586
|
const projectRoot = gitRoot ?? cwd;
|
|
17940
|
-
const configPath =
|
|
17941
|
-
if (!
|
|
18587
|
+
const configPath = join25(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
18588
|
+
if (!existsSync30(configPath)) return null;
|
|
17942
18589
|
try {
|
|
17943
|
-
const raw = JSON.parse(
|
|
18590
|
+
const raw = JSON.parse(readFileSync23(configPath, "utf-8"));
|
|
17944
18591
|
return raw.mcpServers ?? raw;
|
|
17945
18592
|
} catch {
|
|
17946
18593
|
return null;
|