jinzd-ai-cli 0.4.217 → 0.4.218
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/{batch-YCOVKTXD.js → batch-7V7OTMUP.js} +2 -2
- package/dist/{chunk-WZ3VKLF3.js → chunk-5CA2TJ5F.js} +1 -1
- package/dist/{chunk-QAYOI57M.js → chunk-C2Z42DI5.js} +1 -1
- package/dist/{chunk-MGBMNCHG.js → chunk-GX3HSGJX.js} +452 -156
- package/dist/{chunk-OUAZOE5U.js → chunk-H2UIHGHH.js} +23 -22
- package/dist/{chunk-KOPUCJXM.js → chunk-L4UREAID.js} +3 -3
- package/dist/{chunk-SNJAOXFT.js → chunk-MWKE2TNS.js} +1 -1
- package/dist/{chunk-XJGEQIYS.js → chunk-NTCB7CMT.js} +1 -1
- package/dist/{chunk-524WZOKS.js → chunk-OUC75QCF.js} +1 -1
- package/dist/{chunk-WKOQ5CYC.js → chunk-P4VBLXKS.js} +1 -1
- package/dist/{chunk-VTH7BLXK.js → chunk-VGFTM3XT.js} +1 -1
- package/dist/{ci-52RZIYWB.js → ci-L6GH2WVC.js} +4 -4
- package/dist/{ci-format-73UXKE65.js → ci-format-WW7454AY.js} +2 -2
- package/dist/{constants-DIXAD35W.js → constants-NCTFSHDU.js} +1 -1
- package/dist/{doctor-cli-7GOQPULZ.js → doctor-cli-EWMFBP5Q.js} +4 -4
- package/dist/electron-server.js +575 -231
- package/dist/{hub-GIGBITZN.js → hub-CDL6T7CP.js} +1 -1
- package/dist/index.js +148 -46
- package/dist/{pr-KPQ5RPKB.js → pr-D6PEKEGK.js} +4 -4
- package/dist/{run-tests-K7QR5QN4.js → run-tests-NXVVKAK2.js} +1 -1
- package/dist/{run-tests-ZDSA3QES.js → run-tests-SWU2XEV7.js} +2 -2
- package/dist/{server-QT3SC2KI.js → server-LHYSS6CK.js} +85 -21
- package/dist/{server-22YF3U34.js → server-WUT7VYTD.js} +4 -4
- package/dist/{task-orchestrator-BHQQCVTY.js → task-orchestrator-C5AA2BI5.js} +4 -4
- package/dist/{usage-WZZFSFLM.js → usage-6ZUUJBI2.js} +2 -2
- package/package.json +1 -1
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-C2Z42DI5.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 join23, dirname as dirname8, resolve as resolve11, relative as relative4, sep as sep3 } from "path";
|
|
68
|
+
import { existsSync as existsSync28, readFileSync as readFileSync22, readdirSync as readdirSync14, statSync as statSync10, realpathSync } from "fs";
|
|
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
|
});
|
|
@@ -7182,7 +7182,7 @@ var ToolExecutor = class {
|
|
|
7182
7182
|
rl.resume();
|
|
7183
7183
|
process.stdout.write(prompt);
|
|
7184
7184
|
this.confirming = true;
|
|
7185
|
-
return new Promise((
|
|
7185
|
+
return new Promise((resolve12) => {
|
|
7186
7186
|
let completed = false;
|
|
7187
7187
|
const cleanup = (result) => {
|
|
7188
7188
|
if (completed) return;
|
|
@@ -7192,7 +7192,7 @@ var ToolExecutor = class {
|
|
|
7192
7192
|
rl.pause();
|
|
7193
7193
|
rlAny.output = savedOutput;
|
|
7194
7194
|
this.confirming = false;
|
|
7195
|
-
|
|
7195
|
+
resolve12(result);
|
|
7196
7196
|
};
|
|
7197
7197
|
const onLine = (line) => {
|
|
7198
7198
|
const trimmed = line.trim();
|
|
@@ -7366,7 +7366,7 @@ var ToolExecutor = class {
|
|
|
7366
7366
|
rl.resume();
|
|
7367
7367
|
process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
|
|
7368
7368
|
this.confirming = true;
|
|
7369
|
-
return new Promise((
|
|
7369
|
+
return new Promise((resolve12) => {
|
|
7370
7370
|
let completed = false;
|
|
7371
7371
|
const cleanup = (answer) => {
|
|
7372
7372
|
if (completed) return;
|
|
@@ -7376,7 +7376,7 @@ var ToolExecutor = class {
|
|
|
7376
7376
|
rl.pause();
|
|
7377
7377
|
rlAny.output = savedOutput;
|
|
7378
7378
|
this.confirming = false;
|
|
7379
|
-
|
|
7379
|
+
resolve12(answer === "y");
|
|
7380
7380
|
};
|
|
7381
7381
|
const onLine = (line) => {
|
|
7382
7382
|
const trimmed = line.trim();
|
|
@@ -8625,7 +8625,7 @@ var runInteractiveTool = {
|
|
|
8625
8625
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
8626
8626
|
};
|
|
8627
8627
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
8628
|
-
return new Promise((
|
|
8628
|
+
return new Promise((resolve12) => {
|
|
8629
8629
|
const child = spawn2(executable, cmdArgs.map(String), {
|
|
8630
8630
|
cwd: process.cwd(),
|
|
8631
8631
|
env,
|
|
@@ -8658,22 +8658,22 @@ var runInteractiveTool = {
|
|
|
8658
8658
|
setTimeout(writeNextLine, 400);
|
|
8659
8659
|
const timer = setTimeout(() => {
|
|
8660
8660
|
child.kill();
|
|
8661
|
-
|
|
8661
|
+
resolve12(`${prefixWarnings}[Timeout after ${timeout}ms]
|
|
8662
8662
|
${buildOutput(stdout, stderr)}`);
|
|
8663
8663
|
}, timeout);
|
|
8664
8664
|
child.on("close", (code) => {
|
|
8665
8665
|
clearTimeout(timer);
|
|
8666
8666
|
const output = buildOutput(stdout, stderr);
|
|
8667
8667
|
if (code !== 0 && code !== null) {
|
|
8668
|
-
|
|
8668
|
+
resolve12(`${prefixWarnings}Exit code ${code}:
|
|
8669
8669
|
${output}`);
|
|
8670
8670
|
} else {
|
|
8671
|
-
|
|
8671
|
+
resolve12(`${prefixWarnings}${output || "(no output)"}`);
|
|
8672
8672
|
}
|
|
8673
8673
|
});
|
|
8674
8674
|
child.on("error", (err) => {
|
|
8675
8675
|
clearTimeout(timer);
|
|
8676
|
-
|
|
8676
|
+
resolve12(
|
|
8677
8677
|
`${prefixWarnings}Failed to start process "${executable}": ${err.message}
|
|
8678
8678
|
Hint: On Windows, use the full path to the executable, e.g.:
|
|
8679
8679
|
C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
|
|
@@ -9723,7 +9723,7 @@ function promptUser(rl, question) {
|
|
|
9723
9723
|
console.log();
|
|
9724
9724
|
console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
|
|
9725
9725
|
process.stdout.write(chalk4.cyan("> "));
|
|
9726
|
-
return new Promise((
|
|
9726
|
+
return new Promise((resolve12) => {
|
|
9727
9727
|
let completed = false;
|
|
9728
9728
|
const cleanup = (answer) => {
|
|
9729
9729
|
if (completed) return;
|
|
@@ -9733,7 +9733,7 @@ function promptUser(rl, question) {
|
|
|
9733
9733
|
rl.pause();
|
|
9734
9734
|
rlAny.output = savedOutput;
|
|
9735
9735
|
askUserContext.prompting = false;
|
|
9736
|
-
|
|
9736
|
+
resolve12(answer);
|
|
9737
9737
|
};
|
|
9738
9738
|
const onLine = (line) => {
|
|
9739
9739
|
cleanup(line);
|
|
@@ -9965,9 +9965,292 @@ function formatResults2(query, data, _requested) {
|
|
|
9965
9965
|
}
|
|
9966
9966
|
|
|
9967
9967
|
// src/agents/agent-config.ts
|
|
9968
|
-
import { existsSync as
|
|
9969
|
-
import { join as
|
|
9968
|
+
import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync13 } from "fs";
|
|
9969
|
+
import { join as join13 } from "path";
|
|
9970
9970
|
import { homedir as homedir6 } from "os";
|
|
9971
|
+
|
|
9972
|
+
// src/plugins/plugin-manager.ts
|
|
9973
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync12, cpSync, rmSync, statSync as statSync6 } from "fs";
|
|
9974
|
+
import { basename as basename5, dirname as dirname6, join as join12, resolve as resolve7 } from "path";
|
|
9975
|
+
import { createHash as createHash3 } from "crypto";
|
|
9976
|
+
import { z as z2 } from "zod";
|
|
9977
|
+
var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
|
|
9978
|
+
var STATE_FILE = "plugin-state.json";
|
|
9979
|
+
var HookCommandSchema = z2.union([
|
|
9980
|
+
z2.string(),
|
|
9981
|
+
z2.object({
|
|
9982
|
+
command: z2.string(),
|
|
9983
|
+
source: z2.enum(["user", "project", "managed"]).optional(),
|
|
9984
|
+
description: z2.string().optional(),
|
|
9985
|
+
required: z2.boolean().optional(),
|
|
9986
|
+
timeoutMs: z2.number().int().min(100).max(3e4).optional(),
|
|
9987
|
+
disabled: z2.boolean().optional()
|
|
9988
|
+
}),
|
|
9989
|
+
z2.array(z2.union([
|
|
9990
|
+
z2.string(),
|
|
9991
|
+
z2.object({
|
|
9992
|
+
command: z2.string(),
|
|
9993
|
+
source: z2.enum(["user", "project", "managed"]).optional(),
|
|
9994
|
+
description: z2.string().optional(),
|
|
9995
|
+
required: z2.boolean().optional(),
|
|
9996
|
+
timeoutMs: z2.number().int().min(100).max(3e4).optional(),
|
|
9997
|
+
disabled: z2.boolean().optional()
|
|
9998
|
+
})
|
|
9999
|
+
]))
|
|
10000
|
+
]);
|
|
10001
|
+
var HookEventNameSchema = z2.enum([
|
|
10002
|
+
"SessionStart",
|
|
10003
|
+
"UserPromptSubmit",
|
|
10004
|
+
"PreToolUse",
|
|
10005
|
+
"PermissionRequest",
|
|
10006
|
+
"PostToolUse",
|
|
10007
|
+
"PreCompact",
|
|
10008
|
+
"PostCompact",
|
|
10009
|
+
"Stop",
|
|
10010
|
+
"SubagentStart",
|
|
10011
|
+
"SubagentStop"
|
|
10012
|
+
]);
|
|
10013
|
+
var McpServerSchema = z2.object({
|
|
10014
|
+
command: z2.string(),
|
|
10015
|
+
args: z2.array(z2.string()).default([]),
|
|
10016
|
+
env: z2.record(z2.string()).optional(),
|
|
10017
|
+
timeout: z2.number().default(3e4)
|
|
10018
|
+
});
|
|
10019
|
+
var PluginManifestSchema = z2.object({
|
|
10020
|
+
name: z2.string().regex(/^[a-zA-Z0-9._-]{1,80}$/),
|
|
10021
|
+
version: z2.string().min(1),
|
|
10022
|
+
description: z2.string().optional(),
|
|
10023
|
+
skills: z2.array(z2.string()).default([]),
|
|
10024
|
+
hooks: z2.object({
|
|
10025
|
+
enabled: z2.boolean().optional(),
|
|
10026
|
+
events: z2.record(HookEventNameSchema, HookCommandSchema).default({})
|
|
10027
|
+
}).default({ events: {} }),
|
|
10028
|
+
commands: z2.array(z2.string()).default([]),
|
|
10029
|
+
mcpServers: z2.record(McpServerSchema).default({}),
|
|
10030
|
+
agents: z2.array(z2.string()).default([]),
|
|
10031
|
+
permissionHints: z2.array(z2.string()).default([])
|
|
10032
|
+
}).strict();
|
|
10033
|
+
function pluginRoot(configDir) {
|
|
10034
|
+
return join12(configDir, PLUGINS_DIR_NAME);
|
|
10035
|
+
}
|
|
10036
|
+
function pluginManifestPath(pluginDir) {
|
|
10037
|
+
return join12(pluginDir, MANIFEST_RELATIVE);
|
|
10038
|
+
}
|
|
10039
|
+
function statePath(configDir) {
|
|
10040
|
+
return join12(pluginRoot(configDir), STATE_FILE);
|
|
10041
|
+
}
|
|
10042
|
+
function loadState(configDir) {
|
|
10043
|
+
const file = statePath(configDir);
|
|
10044
|
+
if (!existsSync16(file)) return { version: 1, plugins: [] };
|
|
10045
|
+
try {
|
|
10046
|
+
const parsed = JSON.parse(readFileSync12(file, "utf-8"));
|
|
10047
|
+
return { version: 1, plugins: Array.isArray(parsed.plugins) ? parsed.plugins : [] };
|
|
10048
|
+
} catch {
|
|
10049
|
+
return { version: 1, plugins: [] };
|
|
10050
|
+
}
|
|
10051
|
+
}
|
|
10052
|
+
function saveState(configDir, state2) {
|
|
10053
|
+
mkdirSync8(pluginRoot(configDir), { recursive: true });
|
|
10054
|
+
atomicWriteFileSync(statePath(configDir), JSON.stringify(state2, null, 2));
|
|
10055
|
+
}
|
|
10056
|
+
function hashPluginManifest(manifestPath) {
|
|
10057
|
+
return createHash3("sha256").update(readFileSync12(manifestPath)).digest("hex");
|
|
10058
|
+
}
|
|
10059
|
+
function readPluginManifest(manifestPath) {
|
|
10060
|
+
return PluginManifestSchema.parse(JSON.parse(readFileSync12(manifestPath, "utf-8")));
|
|
10061
|
+
}
|
|
10062
|
+
function findPluginSourceDir(inputPath) {
|
|
10063
|
+
const abs = resolve7(inputPath);
|
|
10064
|
+
const direct = statSync6(abs).isDirectory() ? abs : dirname6(abs);
|
|
10065
|
+
if (existsSync16(pluginManifestPath(direct))) return direct;
|
|
10066
|
+
const nested = join12(direct, ".aicli-plugin");
|
|
10067
|
+
if (existsSync16(join12(nested, "plugin.json"))) return direct;
|
|
10068
|
+
throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
|
|
10069
|
+
}
|
|
10070
|
+
function installPlugin(configDir, inputPath) {
|
|
10071
|
+
const sourceDir = findPluginSourceDir(inputPath);
|
|
10072
|
+
const manifest = readPluginManifest(pluginManifestPath(sourceDir));
|
|
10073
|
+
const targetDir = join12(pluginRoot(configDir), manifest.name);
|
|
10074
|
+
mkdirSync8(pluginRoot(configDir), { recursive: true });
|
|
10075
|
+
if (existsSync16(targetDir)) rmSync(targetDir, { recursive: true, force: true });
|
|
10076
|
+
cpSync(sourceDir, targetDir, { recursive: true });
|
|
10077
|
+
const manifestPath = pluginManifestPath(targetDir);
|
|
10078
|
+
const hash = hashPluginManifest(manifestPath);
|
|
10079
|
+
const state2 = loadState(configDir);
|
|
10080
|
+
const previous = state2.plugins.find((p) => p.name === manifest.name);
|
|
10081
|
+
const next = {
|
|
10082
|
+
name: manifest.name,
|
|
10083
|
+
enabled: previous?.enabled ?? false,
|
|
10084
|
+
trusted: false,
|
|
10085
|
+
hash,
|
|
10086
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10087
|
+
};
|
|
10088
|
+
state2.plugins = [...state2.plugins.filter((p) => p.name !== manifest.name), next];
|
|
10089
|
+
saveState(configDir, state2);
|
|
10090
|
+
return { name: manifest.name, dir: targetDir, manifestPath, manifest, hash, enabled: next.enabled, trusted: false, valid: true };
|
|
10091
|
+
}
|
|
10092
|
+
function setPluginEnabled(configDir, name, enabled) {
|
|
10093
|
+
const plugin = getInstalledPlugin(configDir, name);
|
|
10094
|
+
if (!plugin.valid) throw new Error(plugin.error ?? `invalid plugin: ${name}`);
|
|
10095
|
+
const state2 = loadState(configDir);
|
|
10096
|
+
const current = state2.plugins.find((p) => p.name === plugin.name) ?? {
|
|
10097
|
+
name: plugin.name,
|
|
10098
|
+
enabled: false,
|
|
10099
|
+
trusted: false,
|
|
10100
|
+
hash: plugin.hash,
|
|
10101
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10102
|
+
};
|
|
10103
|
+
const next = { ...current, enabled, hash: plugin.hash, enabledAt: enabled ? (/* @__PURE__ */ new Date()).toISOString() : current.enabledAt };
|
|
10104
|
+
state2.plugins = [...state2.plugins.filter((p) => p.name !== plugin.name), next];
|
|
10105
|
+
saveState(configDir, state2);
|
|
10106
|
+
return next;
|
|
10107
|
+
}
|
|
10108
|
+
function trustPlugin(configDir, name) {
|
|
10109
|
+
const plugin = getInstalledPlugin(configDir, name);
|
|
10110
|
+
if (!plugin.valid) throw new Error(plugin.error ?? `invalid plugin: ${name}`);
|
|
10111
|
+
const state2 = loadState(configDir);
|
|
10112
|
+
const current = state2.plugins.find((p) => p.name === plugin.name) ?? {
|
|
10113
|
+
name: plugin.name,
|
|
10114
|
+
enabled: false,
|
|
10115
|
+
trusted: false,
|
|
10116
|
+
hash: plugin.hash,
|
|
10117
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10118
|
+
};
|
|
10119
|
+
const next = { ...current, trusted: true, hash: plugin.hash, trustedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
10120
|
+
state2.plugins = [...state2.plugins.filter((p) => p.name !== plugin.name), next];
|
|
10121
|
+
saveState(configDir, state2);
|
|
10122
|
+
return next;
|
|
10123
|
+
}
|
|
10124
|
+
function getInstalledPlugin(configDir, name) {
|
|
10125
|
+
const plugins = listInstalledPlugins(configDir);
|
|
10126
|
+
const matches = plugins.filter((p) => p.name === name || p.name.startsWith(name));
|
|
10127
|
+
if (matches.length === 0) throw new Error(`plugin not found: ${name}`);
|
|
10128
|
+
if (matches.length > 1) throw new Error(`plugin name is ambiguous: ${name}`);
|
|
10129
|
+
return matches[0];
|
|
10130
|
+
}
|
|
10131
|
+
function listInstalledPlugins(configDir) {
|
|
10132
|
+
const root = pluginRoot(configDir);
|
|
10133
|
+
if (!existsSync16(root)) return [];
|
|
10134
|
+
const state2 = loadState(configDir);
|
|
10135
|
+
const out = [];
|
|
10136
|
+
for (const entry of readdirSync7(root)) {
|
|
10137
|
+
const dir = join12(root, entry);
|
|
10138
|
+
try {
|
|
10139
|
+
if (!statSync6(dir).isDirectory()) continue;
|
|
10140
|
+
} catch {
|
|
10141
|
+
continue;
|
|
10142
|
+
}
|
|
10143
|
+
const manifestPath = pluginManifestPath(dir);
|
|
10144
|
+
if (!existsSync16(manifestPath)) continue;
|
|
10145
|
+
try {
|
|
10146
|
+
const manifest = readPluginManifest(manifestPath);
|
|
10147
|
+
const hash = hashPluginManifest(manifestPath);
|
|
10148
|
+
const st = state2.plugins.find((p) => p.name === manifest.name);
|
|
10149
|
+
const trusted = st?.trusted === true && st.hash === hash;
|
|
10150
|
+
out.push({
|
|
10151
|
+
name: manifest.name,
|
|
10152
|
+
dir,
|
|
10153
|
+
manifestPath,
|
|
10154
|
+
manifest,
|
|
10155
|
+
hash,
|
|
10156
|
+
enabled: st?.enabled === true,
|
|
10157
|
+
trusted,
|
|
10158
|
+
valid: true
|
|
10159
|
+
});
|
|
10160
|
+
} catch (err) {
|
|
10161
|
+
out.push({
|
|
10162
|
+
name: entry,
|
|
10163
|
+
dir,
|
|
10164
|
+
manifestPath,
|
|
10165
|
+
manifest: { name: entry, version: "invalid", skills: [], commands: [], agents: [], hooks: { events: {} }, mcpServers: {}, permissionHints: [] },
|
|
10166
|
+
hash: "",
|
|
10167
|
+
enabled: false,
|
|
10168
|
+
trusted: false,
|
|
10169
|
+
valid: false,
|
|
10170
|
+
error: err instanceof Error ? err.message : String(err)
|
|
10171
|
+
});
|
|
10172
|
+
}
|
|
10173
|
+
}
|
|
10174
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
10175
|
+
}
|
|
10176
|
+
function activePlugins(configDir) {
|
|
10177
|
+
return listInstalledPlugins(configDir).filter((p) => p.valid && p.enabled && p.trusted);
|
|
10178
|
+
}
|
|
10179
|
+
function uniqueDirs(plugin, rels) {
|
|
10180
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
10181
|
+
for (const rel of rels) {
|
|
10182
|
+
const abs = resolve7(plugin.dir, rel);
|
|
10183
|
+
if (!abs.startsWith(resolve7(plugin.dir))) continue;
|
|
10184
|
+
if (existsSync16(abs)) dirs.add(statSync6(abs).isDirectory() ? abs : dirname6(abs));
|
|
10185
|
+
}
|
|
10186
|
+
return [...dirs];
|
|
10187
|
+
}
|
|
10188
|
+
function prefixCommand(plugin, command) {
|
|
10189
|
+
return command.replaceAll("{pluginDir}", plugin.dir).replaceAll("$PLUGIN_DIR", plugin.dir);
|
|
10190
|
+
}
|
|
10191
|
+
function pluginHooks(plugin) {
|
|
10192
|
+
const events = plugin.manifest.hooks?.events ?? {};
|
|
10193
|
+
const out = {};
|
|
10194
|
+
for (const [event, raw] of Object.entries(events)) {
|
|
10195
|
+
const normalize = (item) => {
|
|
10196
|
+
if (typeof item === "string") return { command: prefixCommand(plugin, item), source: "project", description: `plugin:${plugin.name}` };
|
|
10197
|
+
if (item && typeof item === "object") {
|
|
10198
|
+
const obj = item;
|
|
10199
|
+
return { ...obj, command: prefixCommand(plugin, String(obj.command ?? "")), source: "project", description: obj.description ?? `plugin:${plugin.name}` };
|
|
10200
|
+
}
|
|
10201
|
+
return item;
|
|
10202
|
+
};
|
|
10203
|
+
out[event] = Array.isArray(raw) ? raw.map(normalize) : normalize(raw);
|
|
10204
|
+
}
|
|
10205
|
+
return Object.keys(out).length > 0 ? { enabled: plugin.manifest.hooks?.enabled ?? true, events: out } : void 0;
|
|
10206
|
+
}
|
|
10207
|
+
function mergePluginHooks(base, configDir) {
|
|
10208
|
+
const plugins = activePlugins(configDir);
|
|
10209
|
+
if (plugins.length === 0) return base;
|
|
10210
|
+
const merged = { ...base ?? {}, enabled: base?.enabled ?? true, events: { ...base?.events ?? {} } };
|
|
10211
|
+
for (const plugin of plugins) {
|
|
10212
|
+
const hooks = pluginHooks(plugin);
|
|
10213
|
+
if (!hooks?.events) continue;
|
|
10214
|
+
for (const [event, entry] of Object.entries(hooks.events)) {
|
|
10215
|
+
const key = event;
|
|
10216
|
+
const existing = merged.events?.[key];
|
|
10217
|
+
const existingItems = existing === void 0 ? [] : Array.isArray(existing) ? existing : [existing];
|
|
10218
|
+
const newItems = Array.isArray(entry) ? entry : [entry];
|
|
10219
|
+
merged.events[key] = [...existingItems, ...newItems];
|
|
10220
|
+
}
|
|
10221
|
+
}
|
|
10222
|
+
return merged;
|
|
10223
|
+
}
|
|
10224
|
+
function getActivePluginAssets(configDir) {
|
|
10225
|
+
const plugins = activePlugins(configDir);
|
|
10226
|
+
const mcpServers = {};
|
|
10227
|
+
const skillDirs = [];
|
|
10228
|
+
const commandDirs = [];
|
|
10229
|
+
const agentDirs = [];
|
|
10230
|
+
for (const plugin of plugins) {
|
|
10231
|
+
skillDirs.push(...uniqueDirs(plugin, plugin.manifest.skills));
|
|
10232
|
+
commandDirs.push(...uniqueDirs(plugin, plugin.manifest.commands));
|
|
10233
|
+
agentDirs.push(...uniqueDirs(plugin, plugin.manifest.agents));
|
|
10234
|
+
for (const [serverName, server] of Object.entries(plugin.manifest.mcpServers)) {
|
|
10235
|
+
mcpServers[`plugin_${plugin.name}_${serverName}`] = server;
|
|
10236
|
+
}
|
|
10237
|
+
}
|
|
10238
|
+
return { plugins, skillDirs, commandDirs, agentDirs, hooks: mergePluginHooks(void 0, configDir), mcpServers };
|
|
10239
|
+
}
|
|
10240
|
+
function describePlugin(plugin) {
|
|
10241
|
+
const m = plugin.manifest;
|
|
10242
|
+
return [
|
|
10243
|
+
`${plugin.name}@${m.version}`,
|
|
10244
|
+
` status: ${plugin.enabled ? "enabled" : "disabled"} \xB7 ${plugin.trusted ? "trusted" : "untrusted"}${plugin.valid ? "" : " \xB7 invalid"}`,
|
|
10245
|
+
` dir: ${plugin.dir}`,
|
|
10246
|
+
m.description ? ` description: ${m.description}` : void 0,
|
|
10247
|
+
` assets: ${m.skills.length} skill(s), ${m.commands.length} command(s), ${m.agents.length} agent(s), ${Object.keys(m.mcpServers).length} MCP server(s), ${Object.keys(m.hooks?.events ?? {}).length} hook event(s)`,
|
|
10248
|
+
m.permissionHints.length ? ` permission hints: ${m.permissionHints.join("; ")}` : void 0,
|
|
10249
|
+
plugin.error ? ` error: ${plugin.error}` : void 0
|
|
10250
|
+
].filter((line) => Boolean(line));
|
|
10251
|
+
}
|
|
10252
|
+
|
|
10253
|
+
// src/agents/agent-config.ts
|
|
9971
10254
|
var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
|
|
9972
10255
|
"read_file",
|
|
9973
10256
|
"list_dir",
|
|
@@ -10061,13 +10344,13 @@ function parseAgentConfig(raw, source, path3) {
|
|
|
10061
10344
|
return cfg;
|
|
10062
10345
|
}
|
|
10063
10346
|
function loadAgentDir(dir, source) {
|
|
10064
|
-
if (!
|
|
10347
|
+
if (!existsSync17(dir)) return [];
|
|
10065
10348
|
const out = [];
|
|
10066
|
-
for (const file of
|
|
10349
|
+
for (const file of readdirSync8(dir)) {
|
|
10067
10350
|
if (!file.endsWith(".json")) continue;
|
|
10068
|
-
const path3 =
|
|
10351
|
+
const path3 = join13(dir, file);
|
|
10069
10352
|
try {
|
|
10070
|
-
const parsed = JSON.parse(
|
|
10353
|
+
const parsed = JSON.parse(readFileSync13(path3, "utf-8"));
|
|
10071
10354
|
const cfg = parseAgentConfig(parsed, source, path3);
|
|
10072
10355
|
if (cfg) out.push(cfg);
|
|
10073
10356
|
} catch {
|
|
@@ -10077,8 +10360,8 @@ function loadAgentDir(dir, source) {
|
|
|
10077
10360
|
}
|
|
10078
10361
|
function getAgentDirs(configDir, cwd = process.cwd()) {
|
|
10079
10362
|
return {
|
|
10080
|
-
user:
|
|
10081
|
-
project:
|
|
10363
|
+
user: join13(configDir ?? join13(homedir6(), CONFIG_DIR_NAME), "agents"),
|
|
10364
|
+
project: join13(cwd, CONFIG_DIR_NAME, "agents")
|
|
10082
10365
|
};
|
|
10083
10366
|
}
|
|
10084
10367
|
function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
@@ -10086,6 +10369,11 @@ function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
|
10086
10369
|
const byName = /* @__PURE__ */ new Map();
|
|
10087
10370
|
for (const cfg of BUILTIN_AGENTS) byName.set(normalizeName(cfg.name), { ...cfg });
|
|
10088
10371
|
for (const cfg of loadAgentDir(dirs.user, "user")) byName.set(normalizeName(cfg.name), cfg);
|
|
10372
|
+
if (configDir) {
|
|
10373
|
+
for (const dir of getActivePluginAssets(configDir).agentDirs) {
|
|
10374
|
+
for (const cfg of loadAgentDir(dir, "plugin")) byName.set(normalizeName(cfg.name), cfg);
|
|
10375
|
+
}
|
|
10376
|
+
}
|
|
10089
10377
|
for (const cfg of loadAgentDir(dirs.project, "project")) byName.set(normalizeName(cfg.name), cfg);
|
|
10090
10378
|
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
10091
10379
|
}
|
|
@@ -10788,14 +11076,14 @@ var taskStopTool = {
|
|
|
10788
11076
|
|
|
10789
11077
|
// src/tools/builtin/git-tools.ts
|
|
10790
11078
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
10791
|
-
import { existsSync as
|
|
10792
|
-
import { join as
|
|
11079
|
+
import { existsSync as existsSync18 } from "fs";
|
|
11080
|
+
import { join as join14 } from "path";
|
|
10793
11081
|
function assertGitRepo(cwd) {
|
|
10794
11082
|
let dir = cwd;
|
|
10795
11083
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
10796
11084
|
while (dir && dir !== root) {
|
|
10797
|
-
if (
|
|
10798
|
-
const parent =
|
|
11085
|
+
if (existsSync18(join14(dir, ".git"))) return;
|
|
11086
|
+
const parent = join14(dir, "..");
|
|
10799
11087
|
if (parent === dir) break;
|
|
10800
11088
|
dir = parent;
|
|
10801
11089
|
}
|
|
@@ -11060,9 +11348,9 @@ ${commitOutput.trim()}`;
|
|
|
11060
11348
|
};
|
|
11061
11349
|
|
|
11062
11350
|
// src/tools/builtin/notebook-edit.ts
|
|
11063
|
-
import { readFileSync as
|
|
11351
|
+
import { readFileSync as readFileSync14, existsSync as existsSync19 } from "fs";
|
|
11064
11352
|
import { writeFile } from "fs/promises";
|
|
11065
|
-
import { resolve as
|
|
11353
|
+
import { resolve as resolve8, extname as extname2 } from "path";
|
|
11066
11354
|
var notebookEditTool = {
|
|
11067
11355
|
definition: {
|
|
11068
11356
|
name: "notebook_edit",
|
|
@@ -11111,14 +11399,14 @@ var notebookEditTool = {
|
|
|
11111
11399
|
if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
|
|
11112
11400
|
throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
|
|
11113
11401
|
}
|
|
11114
|
-
const absPath =
|
|
11402
|
+
const absPath = resolve8(filePath);
|
|
11115
11403
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
11116
11404
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
11117
11405
|
}
|
|
11118
|
-
if (!
|
|
11406
|
+
if (!existsSync19(absPath)) {
|
|
11119
11407
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
11120
11408
|
}
|
|
11121
|
-
const raw =
|
|
11409
|
+
const raw = readFileSync14(absPath, "utf-8");
|
|
11122
11410
|
const nb = parseNotebook(raw);
|
|
11123
11411
|
const cellIdx0 = cellIndexRaw - 1;
|
|
11124
11412
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -11535,8 +11823,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
11535
11823
|
|
|
11536
11824
|
// src/tools/registry.ts
|
|
11537
11825
|
import { pathToFileURL } from "url";
|
|
11538
|
-
import { existsSync as
|
|
11539
|
-
import { join as
|
|
11826
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
|
|
11827
|
+
import { join as join15 } from "path";
|
|
11540
11828
|
var ToolRegistry = class {
|
|
11541
11829
|
tools = /* @__PURE__ */ new Map();
|
|
11542
11830
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -11697,16 +11985,16 @@ var ToolRegistry = class {
|
|
|
11697
11985
|
* Returns the number of successfully loaded plugins.
|
|
11698
11986
|
*/
|
|
11699
11987
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
11700
|
-
if (!
|
|
11988
|
+
if (!existsSync20(pluginsDir)) {
|
|
11701
11989
|
try {
|
|
11702
|
-
|
|
11990
|
+
mkdirSync9(pluginsDir, { recursive: true });
|
|
11703
11991
|
} catch {
|
|
11704
11992
|
}
|
|
11705
11993
|
return 0;
|
|
11706
11994
|
}
|
|
11707
11995
|
let files;
|
|
11708
11996
|
try {
|
|
11709
|
-
files =
|
|
11997
|
+
files = readdirSync9(pluginsDir).filter((f) => f.endsWith(".js"));
|
|
11710
11998
|
} catch {
|
|
11711
11999
|
return 0;
|
|
11712
12000
|
}
|
|
@@ -11723,12 +12011,12 @@ var ToolRegistry = class {
|
|
|
11723
12011
|
process.stderr.write(
|
|
11724
12012
|
`
|
|
11725
12013
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
11726
|
-
` + files.map((f) => ` + ${
|
|
12014
|
+
` + files.map((f) => ` + ${join15(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
11727
12015
|
);
|
|
11728
12016
|
let loaded = 0;
|
|
11729
12017
|
for (const file of files) {
|
|
11730
12018
|
try {
|
|
11731
|
-
const fileUrl = pathToFileURL(
|
|
12019
|
+
const fileUrl = pathToFileURL(join15(pluginsDir, file)).href;
|
|
11732
12020
|
const mod = await import(fileUrl);
|
|
11733
12021
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
11734
12022
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -11890,7 +12178,7 @@ var McpClient = class {
|
|
|
11890
12178
|
// 内部方法:JSON-RPC 通信
|
|
11891
12179
|
// ══════════════════════════════════════════════════════════════════
|
|
11892
12180
|
sendRequest(method, params) {
|
|
11893
|
-
return new Promise((
|
|
12181
|
+
return new Promise((resolve12, reject) => {
|
|
11894
12182
|
if (!this.process?.stdin?.writable) {
|
|
11895
12183
|
return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
|
|
11896
12184
|
}
|
|
@@ -11914,7 +12202,7 @@ var McpClient = class {
|
|
|
11914
12202
|
this.pendingRequests.set(id, {
|
|
11915
12203
|
resolve: (result) => {
|
|
11916
12204
|
cleanup();
|
|
11917
|
-
|
|
12205
|
+
resolve12(result);
|
|
11918
12206
|
},
|
|
11919
12207
|
reject: (error) => {
|
|
11920
12208
|
cleanup();
|
|
@@ -11991,13 +12279,13 @@ var McpClient = class {
|
|
|
11991
12279
|
}
|
|
11992
12280
|
/** Promise 超时包装 */
|
|
11993
12281
|
withTimeout(promise, ms, label) {
|
|
11994
|
-
return new Promise((
|
|
12282
|
+
return new Promise((resolve12, reject) => {
|
|
11995
12283
|
const timer = setTimeout(() => {
|
|
11996
12284
|
reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
|
|
11997
12285
|
}, ms);
|
|
11998
12286
|
promise.then((val) => {
|
|
11999
12287
|
clearTimeout(timer);
|
|
12000
|
-
|
|
12288
|
+
resolve12(val);
|
|
12001
12289
|
}).catch((err) => {
|
|
12002
12290
|
clearTimeout(timer);
|
|
12003
12291
|
reject(err);
|
|
@@ -12274,12 +12562,12 @@ var McpManager = class {
|
|
|
12274
12562
|
};
|
|
12275
12563
|
|
|
12276
12564
|
// src/skills/manager.ts
|
|
12277
|
-
import { existsSync as
|
|
12278
|
-
import { join as
|
|
12565
|
+
import { existsSync as existsSync21, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync7 } from "fs";
|
|
12566
|
+
import { join as join16 } from "path";
|
|
12279
12567
|
|
|
12280
12568
|
// src/skills/types.ts
|
|
12281
|
-
import { readFileSync as
|
|
12282
|
-
import { basename as
|
|
12569
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
12570
|
+
import { basename as basename6 } from "path";
|
|
12283
12571
|
function parseSimpleYaml(yaml) {
|
|
12284
12572
|
const result = {};
|
|
12285
12573
|
for (const line of yaml.split("\n")) {
|
|
@@ -12300,7 +12588,7 @@ function parseYamlArray(value) {
|
|
|
12300
12588
|
function parseSkillFile(filePath) {
|
|
12301
12589
|
let raw;
|
|
12302
12590
|
try {
|
|
12303
|
-
raw =
|
|
12591
|
+
raw = readFileSync15(filePath, "utf-8");
|
|
12304
12592
|
} catch {
|
|
12305
12593
|
return null;
|
|
12306
12594
|
}
|
|
@@ -12308,7 +12596,7 @@ function parseSkillFile(filePath) {
|
|
|
12308
12596
|
if (!frontmatterMatch) {
|
|
12309
12597
|
return {
|
|
12310
12598
|
meta: {
|
|
12311
|
-
name:
|
|
12599
|
+
name: basename6(filePath, ".md"),
|
|
12312
12600
|
description: ""
|
|
12313
12601
|
},
|
|
12314
12602
|
content: raw.trim(),
|
|
@@ -12319,7 +12607,7 @@ function parseSkillFile(filePath) {
|
|
|
12319
12607
|
const parsed = parseSimpleYaml(yaml);
|
|
12320
12608
|
return {
|
|
12321
12609
|
meta: {
|
|
12322
|
-
name: parsed["name"] ??
|
|
12610
|
+
name: parsed["name"] ?? basename6(filePath, ".md"),
|
|
12323
12611
|
description: parsed["description"] ?? "",
|
|
12324
12612
|
tools: parsed["tools"] ? parseYamlArray(parsed["tools"]) : void 0
|
|
12325
12613
|
},
|
|
@@ -12340,36 +12628,40 @@ var SkillManager = class {
|
|
|
12340
12628
|
this.skillsDir = skillsDir;
|
|
12341
12629
|
this.warnThreshold = warnThreshold ?? SKILL_CONTENT_WARN_CHARS_DEFAULT;
|
|
12342
12630
|
}
|
|
12343
|
-
/** 发现并加载 skillsDir
|
|
12344
|
-
loadSkills() {
|
|
12631
|
+
/** 发现并加载 skillsDir 以及额外插件目录下的技能,返回加载数量 */
|
|
12632
|
+
loadSkills(extraDirs = []) {
|
|
12345
12633
|
this.skills.clear();
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12634
|
+
this.loadSkillDir(this.skillsDir, true);
|
|
12635
|
+
for (const dir of extraDirs) this.loadSkillDir(dir, false);
|
|
12636
|
+
return this.skills.size;
|
|
12637
|
+
}
|
|
12638
|
+
loadSkillDir(dir, createIfMissing) {
|
|
12639
|
+
if (!existsSync21(dir)) {
|
|
12640
|
+
if (createIfMissing) {
|
|
12641
|
+
try {
|
|
12642
|
+
mkdirSync10(dir, { recursive: true });
|
|
12643
|
+
} catch {
|
|
12644
|
+
}
|
|
12350
12645
|
}
|
|
12351
|
-
return
|
|
12646
|
+
return;
|
|
12352
12647
|
}
|
|
12353
12648
|
let entries;
|
|
12354
12649
|
try {
|
|
12355
|
-
entries =
|
|
12650
|
+
entries = readdirSync10(dir);
|
|
12356
12651
|
} catch {
|
|
12357
|
-
return
|
|
12652
|
+
return;
|
|
12358
12653
|
}
|
|
12359
12654
|
for (const entry of entries) {
|
|
12360
12655
|
let filePath;
|
|
12361
|
-
const fullPath =
|
|
12656
|
+
const fullPath = join16(dir, entry);
|
|
12362
12657
|
if (entry.endsWith(".md")) {
|
|
12363
12658
|
filePath = fullPath;
|
|
12364
12659
|
} else {
|
|
12365
12660
|
try {
|
|
12366
|
-
if (
|
|
12367
|
-
const skillMd =
|
|
12368
|
-
if (
|
|
12369
|
-
|
|
12370
|
-
} else {
|
|
12371
|
-
continue;
|
|
12372
|
-
}
|
|
12661
|
+
if (statSync7(fullPath).isDirectory()) {
|
|
12662
|
+
const skillMd = join16(fullPath, "SKILL.md");
|
|
12663
|
+
if (existsSync21(skillMd)) filePath = skillMd;
|
|
12664
|
+
else continue;
|
|
12373
12665
|
} else {
|
|
12374
12666
|
continue;
|
|
12375
12667
|
}
|
|
@@ -12379,13 +12671,10 @@ var SkillManager = class {
|
|
|
12379
12671
|
}
|
|
12380
12672
|
const skill = parseSkillFile(filePath);
|
|
12381
12673
|
if (skill) {
|
|
12382
|
-
if (skill.meta.name === "SKILL" && !entry.endsWith(".md"))
|
|
12383
|
-
|
|
12384
|
-
}
|
|
12385
|
-
this.skills.set(skill.meta.name, skill);
|
|
12674
|
+
if (skill.meta.name === "SKILL" && !entry.endsWith(".md")) skill.meta.name = entry;
|
|
12675
|
+
if (!this.skills.has(skill.meta.name)) this.skills.set(skill.meta.name, skill);
|
|
12386
12676
|
}
|
|
12387
12677
|
}
|
|
12388
|
-
return this.skills.size;
|
|
12389
12678
|
}
|
|
12390
12679
|
/** 列出所有已加载的技能 */
|
|
12391
12680
|
listSkills() {
|
|
@@ -12430,7 +12719,7 @@ var SkillManager = class {
|
|
|
12430
12719
|
// src/web/tool-executor-web.ts
|
|
12431
12720
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
12432
12721
|
import { tmpdir as tmpdir2 } from "os";
|
|
12433
|
-
import { existsSync as
|
|
12722
|
+
import { existsSync as existsSync22, readFileSync as readFileSync16 } from "fs";
|
|
12434
12723
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
12435
12724
|
constructor(registry, ws) {
|
|
12436
12725
|
this.registry = registry;
|
|
@@ -12500,33 +12789,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12500
12789
|
}
|
|
12501
12790
|
/** Resolve a pending confirm from client response */
|
|
12502
12791
|
resolveConfirm(requestId, approved) {
|
|
12503
|
-
const
|
|
12504
|
-
if (
|
|
12792
|
+
const resolve12 = this.pendingConfirms.get(requestId);
|
|
12793
|
+
if (resolve12) {
|
|
12505
12794
|
this.clearPendingTimer(requestId);
|
|
12506
12795
|
this.pendingConfirms.delete(requestId);
|
|
12507
12796
|
this.confirming = false;
|
|
12508
|
-
|
|
12797
|
+
resolve12(approved);
|
|
12509
12798
|
}
|
|
12510
12799
|
}
|
|
12511
12800
|
/** Resolve a pending batch confirm from client response */
|
|
12512
12801
|
resolveBatchConfirm(requestId, decision) {
|
|
12513
|
-
const
|
|
12514
|
-
if (
|
|
12802
|
+
const resolve12 = this.pendingBatchConfirms.get(requestId);
|
|
12803
|
+
if (resolve12) {
|
|
12515
12804
|
this.clearPendingTimer(requestId);
|
|
12516
12805
|
this.pendingBatchConfirms.delete(requestId);
|
|
12517
12806
|
this.confirming = false;
|
|
12518
12807
|
if (decision === "all" || decision === "none") {
|
|
12519
|
-
|
|
12808
|
+
resolve12(decision);
|
|
12520
12809
|
} else {
|
|
12521
|
-
|
|
12810
|
+
resolve12(new Set(decision));
|
|
12522
12811
|
}
|
|
12523
12812
|
}
|
|
12524
12813
|
}
|
|
12525
12814
|
/** Cancel all pending confirms (e.g., on disconnect) */
|
|
12526
12815
|
cancelAll() {
|
|
12527
|
-
for (const
|
|
12816
|
+
for (const resolve12 of this.pendingConfirms.values()) resolve12(false);
|
|
12528
12817
|
this.pendingConfirms.clear();
|
|
12529
|
-
for (const
|
|
12818
|
+
for (const resolve12 of this.pendingBatchConfirms.values()) resolve12("none");
|
|
12530
12819
|
this.pendingBatchConfirms.clear();
|
|
12531
12820
|
this.confirming = false;
|
|
12532
12821
|
}
|
|
@@ -12569,9 +12858,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12569
12858
|
if (call.name === "write_file") {
|
|
12570
12859
|
const filePath = String(call.arguments["path"] ?? "");
|
|
12571
12860
|
const newContent = String(call.arguments["content"] ?? "");
|
|
12572
|
-
if (filePath &&
|
|
12861
|
+
if (filePath && existsSync22(filePath)) {
|
|
12573
12862
|
try {
|
|
12574
|
-
const old =
|
|
12863
|
+
const old = readFileSync16(filePath, "utf-8");
|
|
12575
12864
|
return renderDiff(old, newContent, { filePath });
|
|
12576
12865
|
} catch {
|
|
12577
12866
|
}
|
|
@@ -12602,8 +12891,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12602
12891
|
diff: this.getDiffPreview(call)
|
|
12603
12892
|
};
|
|
12604
12893
|
this.send(msg);
|
|
12605
|
-
return new Promise((
|
|
12606
|
-
this.pendingConfirms.set(requestId,
|
|
12894
|
+
return new Promise((resolve12) => {
|
|
12895
|
+
this.pendingConfirms.set(requestId, resolve12);
|
|
12607
12896
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
12608
12897
|
if (this.pendingConfirms.has(requestId)) {
|
|
12609
12898
|
this.resolveConfirm(requestId, false);
|
|
@@ -12627,8 +12916,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12627
12916
|
files
|
|
12628
12917
|
};
|
|
12629
12918
|
this.send(msg);
|
|
12630
|
-
return new Promise((
|
|
12631
|
-
this.pendingBatchConfirms.set(requestId,
|
|
12919
|
+
return new Promise((resolve12) => {
|
|
12920
|
+
this.pendingBatchConfirms.set(requestId, resolve12);
|
|
12632
12921
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
12633
12922
|
if (this.pendingBatchConfirms.has(requestId)) {
|
|
12634
12923
|
this.resolveBatchConfirm(requestId, "none");
|
|
@@ -12874,16 +13163,16 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12874
13163
|
};
|
|
12875
13164
|
|
|
12876
13165
|
// src/repl/dev-state.ts
|
|
12877
|
-
import { existsSync as
|
|
12878
|
-
import { join as
|
|
13166
|
+
import { existsSync as existsSync23, readFileSync as readFileSync17, unlinkSync as unlinkSync4, mkdirSync as mkdirSync11 } from "fs";
|
|
13167
|
+
import { join as join17 } from "path";
|
|
12879
13168
|
import { homedir as homedir7 } from "os";
|
|
12880
13169
|
function getDevStatePath() {
|
|
12881
|
-
return
|
|
13170
|
+
return join17(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
12882
13171
|
}
|
|
12883
13172
|
function loadDevState() {
|
|
12884
13173
|
const path3 = getDevStatePath();
|
|
12885
|
-
if (!
|
|
12886
|
-
const content =
|
|
13174
|
+
if (!existsSync23(path3)) return null;
|
|
13175
|
+
const content = readFileSync17(path3, "utf-8").trim();
|
|
12887
13176
|
return content || null;
|
|
12888
13177
|
}
|
|
12889
13178
|
|
|
@@ -13140,8 +13429,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
13140
13429
|
}
|
|
13141
13430
|
|
|
13142
13431
|
// src/core/context-files.ts
|
|
13143
|
-
import { existsSync as
|
|
13144
|
-
import { join as
|
|
13432
|
+
import { existsSync as existsSync24, readFileSync as readFileSync18 } from "fs";
|
|
13433
|
+
import { join as join18, relative as relative3, resolve as resolve9 } from "path";
|
|
13145
13434
|
function uniqueNonEmpty(values) {
|
|
13146
13435
|
const seen = /* @__PURE__ */ new Set();
|
|
13147
13436
|
const out = [];
|
|
@@ -13164,14 +13453,14 @@ function displayPath(filePath, cwd) {
|
|
|
13164
13453
|
return filePath;
|
|
13165
13454
|
}
|
|
13166
13455
|
function isInsideOrEqual(parent, child) {
|
|
13167
|
-
const rel = relative3(
|
|
13456
|
+
const rel = relative3(resolve9(parent), resolve9(child));
|
|
13168
13457
|
return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
|
|
13169
13458
|
}
|
|
13170
13459
|
function readContextFile(level, filePath, cwd, maxBytes) {
|
|
13171
13460
|
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
13172
13461
|
const shown = displayPath(filePath, cwd);
|
|
13173
13462
|
try {
|
|
13174
|
-
const raw =
|
|
13463
|
+
const raw = readFileSync18(filePath);
|
|
13175
13464
|
const byteCount = raw.byteLength;
|
|
13176
13465
|
if (byteCount === 0) {
|
|
13177
13466
|
return {
|
|
@@ -13218,8 +13507,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
13218
13507
|
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
13219
13508
|
const skipped = [];
|
|
13220
13509
|
for (const candidate of candidates) {
|
|
13221
|
-
const filePath =
|
|
13222
|
-
if (!
|
|
13510
|
+
const filePath = join18(dir, candidate);
|
|
13511
|
+
if (!existsSync24(filePath)) continue;
|
|
13223
13512
|
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
13224
13513
|
if (result.skipped) skipped.push(result.skipped);
|
|
13225
13514
|
if (result.layer) return { layer: result.layer, skipped };
|
|
@@ -13227,7 +13516,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
|
13227
13516
|
return { layer: null, skipped };
|
|
13228
13517
|
}
|
|
13229
13518
|
function loadContextFiles(options) {
|
|
13230
|
-
const cwd =
|
|
13519
|
+
const cwd = resolve9(options.cwd);
|
|
13231
13520
|
const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
|
|
13232
13521
|
const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
|
|
13233
13522
|
const skipped = [];
|
|
@@ -13235,7 +13524,7 @@ function loadContextFiles(options) {
|
|
|
13235
13524
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
13236
13525
|
}
|
|
13237
13526
|
if (options.setting && options.setting !== "auto") {
|
|
13238
|
-
const filePath =
|
|
13527
|
+
const filePath = resolve9(cwd, String(options.setting));
|
|
13239
13528
|
if (!isInsideOrEqual(cwd, filePath)) {
|
|
13240
13529
|
skipped.push({
|
|
13241
13530
|
level: "single",
|
|
@@ -13246,7 +13535,7 @@ function loadContextFiles(options) {
|
|
|
13246
13535
|
});
|
|
13247
13536
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
13248
13537
|
}
|
|
13249
|
-
if (!
|
|
13538
|
+
if (!existsSync24(filePath)) {
|
|
13250
13539
|
skipped.push({
|
|
13251
13540
|
level: "single",
|
|
13252
13541
|
filePath,
|
|
@@ -13279,7 +13568,7 @@ function loadContextFiles(options) {
|
|
|
13279
13568
|
skipped.push(...result.skipped);
|
|
13280
13569
|
if (result.layer) layers.push(result.layer);
|
|
13281
13570
|
}
|
|
13282
|
-
if (
|
|
13571
|
+
if (resolve9(options.cwd) !== resolve9(options.projectRoot)) {
|
|
13283
13572
|
const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
|
|
13284
13573
|
skipped.push(...result.skipped);
|
|
13285
13574
|
if (result.layer) layers.push(result.layer);
|
|
@@ -13297,8 +13586,8 @@ function loadContextFiles(options) {
|
|
|
13297
13586
|
}
|
|
13298
13587
|
|
|
13299
13588
|
// src/web/session-handler.ts
|
|
13300
|
-
import { existsSync as
|
|
13301
|
-
import { join as
|
|
13589
|
+
import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync2, mkdirSync as mkdirSync12, readdirSync as readdirSync12, statSync as statSync9, createWriteStream } from "fs";
|
|
13590
|
+
import { join as join21, resolve as resolve10, dirname as dirname7 } from "path";
|
|
13302
13591
|
|
|
13303
13592
|
// src/cli/review-prompts.ts
|
|
13304
13593
|
function buildReviewPrompt(diff, gitContextStr, detailed) {
|
|
@@ -13359,8 +13648,8 @@ If no security issues found, state "\u2705 No security vulnerabilities detected"
|
|
|
13359
13648
|
}
|
|
13360
13649
|
|
|
13361
13650
|
// src/repl/commands/project-init.ts
|
|
13362
|
-
import { existsSync as
|
|
13363
|
-
import { join as
|
|
13651
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
|
|
13652
|
+
import { join as join19 } from "path";
|
|
13364
13653
|
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
13365
13654
|
"node_modules",
|
|
13366
13655
|
".git",
|
|
@@ -13396,7 +13685,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
13396
13685
|
if (depth > maxDepth || count >= maxEntries) return;
|
|
13397
13686
|
let entries;
|
|
13398
13687
|
try {
|
|
13399
|
-
entries =
|
|
13688
|
+
entries = readdirSync11(d);
|
|
13400
13689
|
} catch {
|
|
13401
13690
|
return;
|
|
13402
13691
|
}
|
|
@@ -13404,11 +13693,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
13404
13693
|
const sorted = filtered.sort((a, b) => {
|
|
13405
13694
|
let aIsDir = false, bIsDir = false;
|
|
13406
13695
|
try {
|
|
13407
|
-
aIsDir =
|
|
13696
|
+
aIsDir = statSync8(join19(d, a)).isDirectory();
|
|
13408
13697
|
} catch {
|
|
13409
13698
|
}
|
|
13410
13699
|
try {
|
|
13411
|
-
bIsDir =
|
|
13700
|
+
bIsDir = statSync8(join19(d, b)).isDirectory();
|
|
13412
13701
|
} catch {
|
|
13413
13702
|
}
|
|
13414
13703
|
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
@@ -13416,12 +13705,12 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
13416
13705
|
});
|
|
13417
13706
|
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
13418
13707
|
const name = sorted[i];
|
|
13419
|
-
const fullPath =
|
|
13708
|
+
const fullPath = join19(d, name);
|
|
13420
13709
|
const isLast = i === sorted.length - 1;
|
|
13421
13710
|
const connector = isLast ? "+-- " : "|-- ";
|
|
13422
13711
|
let isDir;
|
|
13423
13712
|
try {
|
|
13424
|
-
isDir =
|
|
13713
|
+
isDir = statSync8(fullPath).isDirectory();
|
|
13425
13714
|
} catch {
|
|
13426
13715
|
continue;
|
|
13427
13716
|
}
|
|
@@ -13443,7 +13732,7 @@ function scanProject(cwd) {
|
|
|
13443
13732
|
configFiles: [],
|
|
13444
13733
|
directoryStructure: ""
|
|
13445
13734
|
};
|
|
13446
|
-
const check = (file) =>
|
|
13735
|
+
const check = (file) => existsSync25(join19(cwd, file));
|
|
13447
13736
|
const configCandidates = [
|
|
13448
13737
|
"package.json",
|
|
13449
13738
|
"tsconfig.json",
|
|
@@ -13470,7 +13759,7 @@ function scanProject(cwd) {
|
|
|
13470
13759
|
info.type = "node";
|
|
13471
13760
|
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
13472
13761
|
try {
|
|
13473
|
-
const pkg = JSON.parse(
|
|
13762
|
+
const pkg = JSON.parse(readFileSync19(join19(cwd, "package.json"), "utf-8"));
|
|
13474
13763
|
const scripts = pkg.scripts ?? {};
|
|
13475
13764
|
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
13476
13765
|
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
@@ -14130,7 +14419,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
|
|
|
14130
14419
|
}
|
|
14131
14420
|
|
|
14132
14421
|
// src/hub/persist.ts
|
|
14133
|
-
import { join as
|
|
14422
|
+
import { join as join20 } from "path";
|
|
14134
14423
|
function discussionToMessages(state2) {
|
|
14135
14424
|
const out = [];
|
|
14136
14425
|
const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -14168,7 +14457,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
|
|
|
14168
14457
|
session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
|
|
14169
14458
|
session.titleAiGenerated = true;
|
|
14170
14459
|
await sm.save();
|
|
14171
|
-
return { id: session.id, path:
|
|
14460
|
+
return { id: session.id, path: join20(config.getHistoryDir(), `${session.id}.json`) };
|
|
14172
14461
|
}
|
|
14173
14462
|
|
|
14174
14463
|
// src/web/session-handler.ts
|
|
@@ -14264,7 +14553,7 @@ var SessionHandler = class {
|
|
|
14264
14553
|
}
|
|
14265
14554
|
this.updateContextWindow();
|
|
14266
14555
|
this.activeSystemPrompt = this.loadContextFiles();
|
|
14267
|
-
const hooks = this.config.get("hooks");
|
|
14556
|
+
const hooks = mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir());
|
|
14268
14557
|
const permissionRules = this.config.get("permissionRules");
|
|
14269
14558
|
const defaultPermission = this.config.get("defaultPermission");
|
|
14270
14559
|
const permissionProfileName = this.config.get("defaultPermissionProfile") ?? "legacy";
|
|
@@ -14364,10 +14653,10 @@ var SessionHandler = class {
|
|
|
14364
14653
|
return;
|
|
14365
14654
|
}
|
|
14366
14655
|
case "ask_user_response": {
|
|
14367
|
-
const
|
|
14368
|
-
if (
|
|
14656
|
+
const resolve12 = this.pendingAskUser.get(msg.requestId);
|
|
14657
|
+
if (resolve12) {
|
|
14369
14658
|
this.pendingAskUser.delete(msg.requestId);
|
|
14370
|
-
|
|
14659
|
+
resolve12(msg.answer);
|
|
14371
14660
|
}
|
|
14372
14661
|
return;
|
|
14373
14662
|
}
|
|
@@ -14380,10 +14669,10 @@ var SessionHandler = class {
|
|
|
14380
14669
|
case "memory_manage":
|
|
14381
14670
|
return this.handleMemoryManage(msg.action, msg.id, msg.expiresAt);
|
|
14382
14671
|
case "auto_pause_response": {
|
|
14383
|
-
const
|
|
14384
|
-
if (
|
|
14672
|
+
const resolve12 = this.pendingAutoPause.get(msg.requestId);
|
|
14673
|
+
if (resolve12) {
|
|
14385
14674
|
this.pendingAutoPause.delete(msg.requestId);
|
|
14386
|
-
|
|
14675
|
+
resolve12({ action: msg.action, message: msg.message });
|
|
14387
14676
|
}
|
|
14388
14677
|
return;
|
|
14389
14678
|
}
|
|
@@ -14398,10 +14687,10 @@ var SessionHandler = class {
|
|
|
14398
14687
|
this.hubOrchestrator?.abort();
|
|
14399
14688
|
return;
|
|
14400
14689
|
case "hub_steer": {
|
|
14401
|
-
const
|
|
14402
|
-
if (
|
|
14690
|
+
const resolve12 = this.pendingHubReview.get(msg.requestId);
|
|
14691
|
+
if (resolve12) {
|
|
14403
14692
|
this.pendingHubReview.delete(msg.requestId);
|
|
14404
|
-
|
|
14693
|
+
resolve12({ action: msg.action, message: msg.message });
|
|
14405
14694
|
}
|
|
14406
14695
|
return;
|
|
14407
14696
|
}
|
|
@@ -14411,12 +14700,12 @@ var SessionHandler = class {
|
|
|
14411
14700
|
}
|
|
14412
14701
|
}
|
|
14413
14702
|
onDisconnect() {
|
|
14414
|
-
runLifecycleHooks(this.config.get("hooks") ?? void 0, "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
|
|
14703
|
+
runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
|
|
14415
14704
|
this.toolExecutor.cancelAll();
|
|
14416
14705
|
if (this.abortController) this.abortController.abort();
|
|
14417
|
-
for (const
|
|
14706
|
+
for (const resolve12 of this.pendingAskUser.values()) resolve12(null);
|
|
14418
14707
|
this.pendingAskUser.clear();
|
|
14419
|
-
for (const
|
|
14708
|
+
for (const resolve12 of this.pendingAutoPause.values()) resolve12({ action: "stop" });
|
|
14420
14709
|
this.pendingAutoPause.clear();
|
|
14421
14710
|
this.saveIfNeeded();
|
|
14422
14711
|
}
|
|
@@ -14550,9 +14839,9 @@ var SessionHandler = class {
|
|
|
14550
14839
|
this.hubOrchestrator = orchestrator;
|
|
14551
14840
|
orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
|
|
14552
14841
|
if (config.humanSteer) {
|
|
14553
|
-
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((
|
|
14842
|
+
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve12) => {
|
|
14554
14843
|
const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
14555
|
-
this.pendingHubReview.set(requestId,
|
|
14844
|
+
this.pendingHubReview.set(requestId, resolve12);
|
|
14556
14845
|
this.send({ type: "hub_review", requestId, round, maxRounds });
|
|
14557
14846
|
});
|
|
14558
14847
|
}
|
|
@@ -14590,7 +14879,7 @@ var SessionHandler = class {
|
|
|
14590
14879
|
this.ensureSession();
|
|
14591
14880
|
const session = this.sessions.current;
|
|
14592
14881
|
const promptDecisions = runLifecycleHooks(
|
|
14593
|
-
this.config.get("hooks") ?? void 0,
|
|
14882
|
+
mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
|
|
14594
14883
|
"UserPromptSubmit",
|
|
14595
14884
|
{ sessionId: session.id, prompt: content },
|
|
14596
14885
|
{ configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
@@ -14885,7 +15174,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
14885
15174
|
this.send({ type: "response_done", content, usage });
|
|
14886
15175
|
if (pendingTeeSave && isCleanDocumentBody(content)) {
|
|
14887
15176
|
try {
|
|
14888
|
-
|
|
15177
|
+
mkdirSync12(dirname7(pendingTeeSave), { recursive: true });
|
|
14889
15178
|
const bodyToSave = stripOuterCodeFence(content);
|
|
14890
15179
|
atomicWriteFileSync(pendingTeeSave, bodyToSave);
|
|
14891
15180
|
undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
|
|
@@ -14910,8 +15199,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
14910
15199
|
onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
|
|
14911
15200
|
requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
|
|
14912
15201
|
const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
14913
|
-
const pauseResp = await new Promise((
|
|
14914
|
-
this.pendingAutoPause.set(requestId,
|
|
15202
|
+
const pauseResp = await new Promise((resolve12) => {
|
|
15203
|
+
this.pendingAutoPause.set(requestId, resolve12);
|
|
14915
15204
|
this.send({
|
|
14916
15205
|
type: "auto_pause_request",
|
|
14917
15206
|
requestId,
|
|
@@ -15061,7 +15350,7 @@ ${summaryContent}`,
|
|
|
15061
15350
|
let isError = false;
|
|
15062
15351
|
let summary;
|
|
15063
15352
|
try {
|
|
15064
|
-
|
|
15353
|
+
mkdirSync12(dirname7(saveToFile), { recursive: true });
|
|
15065
15354
|
fileStream = createWriteStream(saveToFile);
|
|
15066
15355
|
const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
|
|
15067
15356
|
const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
|
|
@@ -15090,8 +15379,8 @@ ${summaryContent}`,
|
|
|
15090
15379
|
}
|
|
15091
15380
|
if (chunk.done) break;
|
|
15092
15381
|
}
|
|
15093
|
-
await new Promise((
|
|
15094
|
-
fileStream.end((err) => err ? reject(err) :
|
|
15382
|
+
await new Promise((resolve12, reject) => {
|
|
15383
|
+
fileStream.end((err) => err ? reject(err) : resolve12());
|
|
15095
15384
|
});
|
|
15096
15385
|
const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
|
|
15097
15386
|
if (verdict.kind === "reject") {
|
|
@@ -15117,7 +15406,7 @@ ${summaryContent}`,
|
|
|
15117
15406
|
} catch (err) {
|
|
15118
15407
|
if (fileStream) {
|
|
15119
15408
|
try {
|
|
15120
|
-
await new Promise((
|
|
15409
|
+
await new Promise((resolve12) => fileStream.end(() => resolve12()));
|
|
15121
15410
|
} catch {
|
|
15122
15411
|
}
|
|
15123
15412
|
}
|
|
@@ -15432,6 +15721,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
|
|
|
15432
15721
|
" /add-dir [path|remove] \u2014 Add/remove directory from AI context",
|
|
15433
15722
|
" /mcp [reconnect] \u2014 Show/manage MCP servers",
|
|
15434
15723
|
" /plugins \u2014 Show loaded plugins",
|
|
15724
|
+
" /plugin <cmd> \u2014 Manage package plugins",
|
|
15435
15725
|
" /commands \u2014 List custom commands",
|
|
15436
15726
|
" /doctor \u2014 Health check (API keys, config, MCP)",
|
|
15437
15727
|
" /bug \u2014 Generate bug report template",
|
|
@@ -15530,7 +15820,7 @@ ${lines.join("\n")}` });
|
|
|
15530
15820
|
this.sendToolsList();
|
|
15531
15821
|
this.sendStatus();
|
|
15532
15822
|
} else if (sub === "reload") {
|
|
15533
|
-
this.skillManager.loadSkills();
|
|
15823
|
+
this.skillManager.loadSkills(getActivePluginAssets(this.config.getConfigDir()).skillDirs);
|
|
15534
15824
|
this.send({ type: "info", message: `\u{1F504} Reloaded ${this.skillManager.listSkills().length} skill(s).` });
|
|
15535
15825
|
this.sendToolsList();
|
|
15536
15826
|
} else {
|
|
@@ -15749,9 +16039,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15749
16039
|
let modifiedFiles = 0;
|
|
15750
16040
|
const diffLines2 = [];
|
|
15751
16041
|
for (const [filePath, { earliest }] of fileMap) {
|
|
15752
|
-
const currentContent =
|
|
16042
|
+
const currentContent = existsSync26(filePath) ? (() => {
|
|
15753
16043
|
try {
|
|
15754
|
-
return
|
|
16044
|
+
return readFileSync20(filePath, "utf-8");
|
|
15755
16045
|
} catch {
|
|
15756
16046
|
return null;
|
|
15757
16047
|
}
|
|
@@ -15852,7 +16142,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15852
16142
|
break;
|
|
15853
16143
|
}
|
|
15854
16144
|
const sub = args[0]?.toLowerCase();
|
|
15855
|
-
const
|
|
16145
|
+
const resolve12 = (ref) => {
|
|
15856
16146
|
const r = session.resolveBranchRef(ref);
|
|
15857
16147
|
if (r.ok) return r.id;
|
|
15858
16148
|
if (r.reason === "ambiguous") {
|
|
@@ -15906,7 +16196,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15906
16196
|
this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
|
|
15907
16197
|
break;
|
|
15908
16198
|
}
|
|
15909
|
-
const id =
|
|
16199
|
+
const id = resolve12(ref);
|
|
15910
16200
|
if (!id) break;
|
|
15911
16201
|
const ok = session.switchBranch(id);
|
|
15912
16202
|
if (ok) {
|
|
@@ -15926,7 +16216,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15926
16216
|
this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
|
|
15927
16217
|
break;
|
|
15928
16218
|
}
|
|
15929
|
-
const id =
|
|
16219
|
+
const id = resolve12(ref);
|
|
15930
16220
|
if (!id) break;
|
|
15931
16221
|
const ok = session.deleteBranch(id);
|
|
15932
16222
|
if (ok) {
|
|
@@ -15945,7 +16235,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15945
16235
|
this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
|
|
15946
16236
|
break;
|
|
15947
16237
|
}
|
|
15948
|
-
const id =
|
|
16238
|
+
const id = resolve12(ref);
|
|
15949
16239
|
if (!id) break;
|
|
15950
16240
|
const ok = session.renameBranch(id, title);
|
|
15951
16241
|
if (ok) {
|
|
@@ -15963,7 +16253,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15963
16253
|
this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
|
|
15964
16254
|
break;
|
|
15965
16255
|
}
|
|
15966
|
-
const id =
|
|
16256
|
+
const id = resolve12(ref);
|
|
15967
16257
|
if (!id) break;
|
|
15968
16258
|
const d = session.diffBranches(id);
|
|
15969
16259
|
if (!d) {
|
|
@@ -16005,7 +16295,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
16005
16295
|
this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
|
|
16006
16296
|
break;
|
|
16007
16297
|
}
|
|
16008
|
-
const id =
|
|
16298
|
+
const id = resolve12(ref);
|
|
16009
16299
|
if (!id) break;
|
|
16010
16300
|
const idx = parseInt(idxStr, 10);
|
|
16011
16301
|
if (Number.isNaN(idx)) {
|
|
@@ -16267,7 +16557,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
16267
16557
|
case "test": {
|
|
16268
16558
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
16269
16559
|
try {
|
|
16270
|
-
const { executeTests } = await import("./run-tests-
|
|
16560
|
+
const { executeTests } = await import("./run-tests-NXVVKAK2.js");
|
|
16271
16561
|
const argStr = args.join(" ").trim();
|
|
16272
16562
|
let testArgs = {};
|
|
16273
16563
|
if (argStr) {
|
|
@@ -16284,9 +16574,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
16284
16574
|
// ── /init ───────────────────────────────────────────────────────
|
|
16285
16575
|
case "init": {
|
|
16286
16576
|
const cwd = process.cwd();
|
|
16287
|
-
const targetPath =
|
|
16577
|
+
const targetPath = join21(cwd, "AICLI.md");
|
|
16288
16578
|
const force = args.includes("--force");
|
|
16289
|
-
if (
|
|
16579
|
+
if (existsSync26(targetPath) && !force) {
|
|
16290
16580
|
this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
16291
16581
|
Use /init --force to overwrite.` });
|
|
16292
16582
|
break;
|
|
@@ -16319,19 +16609,19 @@ Use /context reload to load it.` });
|
|
|
16319
16609
|
lines.push("**Config Files:**");
|
|
16320
16610
|
lines.push(` Dir: ${configDir}`);
|
|
16321
16611
|
const checkFile = (label, filePath) => {
|
|
16322
|
-
const exists =
|
|
16612
|
+
const exists = existsSync26(filePath);
|
|
16323
16613
|
let extra = "";
|
|
16324
16614
|
if (exists) {
|
|
16325
16615
|
try {
|
|
16326
|
-
extra = ` (${
|
|
16616
|
+
extra = ` (${statSync9(filePath).size} bytes)`;
|
|
16327
16617
|
} catch {
|
|
16328
16618
|
}
|
|
16329
16619
|
}
|
|
16330
16620
|
lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
|
|
16331
16621
|
};
|
|
16332
|
-
checkFile("config.json",
|
|
16333
|
-
checkFile("memory.md",
|
|
16334
|
-
checkFile("dev-state.md",
|
|
16622
|
+
checkFile("config.json", join21(configDir, "config.json"));
|
|
16623
|
+
checkFile("memory.md", join21(configDir, MEMORY_FILE_NAME));
|
|
16624
|
+
checkFile("dev-state.md", join21(configDir, "dev-state.md"));
|
|
16335
16625
|
lines.push("");
|
|
16336
16626
|
if (this.mcpManager) {
|
|
16337
16627
|
lines.push("**MCP Servers:**");
|
|
@@ -16434,7 +16724,7 @@ ${this.config.toFormattedJSON()}
|
|
|
16434
16724
|
const result = this.contextLoadResult;
|
|
16435
16725
|
const layers = ["\u{1F4DA} **Context Layers:**", ""];
|
|
16436
16726
|
if (result && result.layers.length > 0) {
|
|
16437
|
-
const labels = { global: "Global ", project: "Project", local: "Local " };
|
|
16727
|
+
const labels = { global: "Global ", project: "Project", plugin: "Plugin ", local: "Local " };
|
|
16438
16728
|
for (const layer of result.layers) {
|
|
16439
16729
|
layers.push(` \u2713 ${labels[layer.level] ?? layer.level}: ${layer.displayPath} (${layer.charCount} chars${layer.truncated ? ", truncated" : ""})`);
|
|
16440
16730
|
}
|
|
@@ -16518,12 +16808,12 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
16518
16808
|
this.send({ type: "info", message: "\u2713 All added directories removed from context." });
|
|
16519
16809
|
break;
|
|
16520
16810
|
}
|
|
16521
|
-
const dirPath =
|
|
16522
|
-
if (!
|
|
16811
|
+
const dirPath = resolve10(sub);
|
|
16812
|
+
if (!existsSync26(dirPath)) {
|
|
16523
16813
|
this.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
16524
16814
|
break;
|
|
16525
16815
|
}
|
|
16526
|
-
if (!
|
|
16816
|
+
if (!statSync9(dirPath).isDirectory()) {
|
|
16527
16817
|
this.send({ type: "error", message: `Not a directory: ${dirPath}` });
|
|
16528
16818
|
break;
|
|
16529
16819
|
}
|
|
@@ -16535,14 +16825,14 @@ It will be included in AI context for subsequent messages.` });
|
|
|
16535
16825
|
// ── /commands ───────────────────────────────────────────────────
|
|
16536
16826
|
case "commands": {
|
|
16537
16827
|
const configDir = this.config.getConfigDir();
|
|
16538
|
-
const commandsDir =
|
|
16539
|
-
if (!
|
|
16828
|
+
const commandsDir = join21(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
16829
|
+
if (!existsSync26(commandsDir)) {
|
|
16540
16830
|
this.send({ type: "info", message: `No custom commands directory.
|
|
16541
16831
|
Create: ${commandsDir}/ with .md files.` });
|
|
16542
16832
|
break;
|
|
16543
16833
|
}
|
|
16544
16834
|
try {
|
|
16545
|
-
const files =
|
|
16835
|
+
const files = readdirSync12(commandsDir).filter((f) => f.endsWith(".md"));
|
|
16546
16836
|
if (files.length === 0) {
|
|
16547
16837
|
this.send({ type: "info", message: `No custom commands found in ${commandsDir}
|
|
16548
16838
|
Add .md files to create commands.` });
|
|
@@ -16559,23 +16849,75 @@ Add .md files to create commands.` });
|
|
|
16559
16849
|
}
|
|
16560
16850
|
break;
|
|
16561
16851
|
}
|
|
16562
|
-
// ── /plugins
|
|
16852
|
+
// ── /plugins / /plugin ──────────────────────────────────────────
|
|
16563
16853
|
case "plugins": {
|
|
16564
16854
|
const configDir = this.config.getConfigDir();
|
|
16565
|
-
const pluginsDir =
|
|
16855
|
+
const pluginsDir = join21(configDir, PLUGINS_DIR_NAME);
|
|
16566
16856
|
const pluginTools = this.toolRegistry.listPluginTools();
|
|
16857
|
+
const pluginPackages = listInstalledPlugins(configDir);
|
|
16567
16858
|
const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
|
|
16568
|
-
if (pluginTools.length === 0) {
|
|
16859
|
+
if (pluginTools.length === 0 && pluginPackages.length === 0) {
|
|
16569
16860
|
lines.push("No plugins loaded.");
|
|
16570
|
-
lines.push("
|
|
16861
|
+
lines.push("Use /plugin install <path>, /plugin trust <name>, then /plugin enable <name>.");
|
|
16571
16862
|
} else {
|
|
16572
|
-
|
|
16573
|
-
lines.push(`
|
|
16863
|
+
if (pluginPackages.length > 0) {
|
|
16864
|
+
lines.push(`Package plugins (${pluginPackages.length}):`);
|
|
16865
|
+
for (const p of pluginPackages) lines.push(` \u2022 ${p.name}@${p.manifest.version} \u2014 ${p.enabled ? "enabled" : "disabled"} / ${p.trusted ? "trusted" : "untrusted"}`);
|
|
16866
|
+
lines.push("");
|
|
16867
|
+
}
|
|
16868
|
+
if (pluginTools.length > 0) {
|
|
16869
|
+
lines.push(`Legacy JS plugin tools (${pluginTools.length}):`);
|
|
16870
|
+
for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
|
|
16574
16871
|
}
|
|
16575
16872
|
}
|
|
16576
16873
|
this.send({ type: "info", message: lines.join("\n") });
|
|
16577
16874
|
break;
|
|
16578
16875
|
}
|
|
16876
|
+
case "plugin": {
|
|
16877
|
+
const configDir = this.config.getConfigDir();
|
|
16878
|
+
const sub = args[0] ?? "list";
|
|
16879
|
+
try {
|
|
16880
|
+
if (sub === "list") {
|
|
16881
|
+
const plugins = listInstalledPlugins(configDir);
|
|
16882
|
+
const lines = [`\u{1F50C} **Package Plugins**`, `Dir: ${pluginRoot(configDir)}`, ""];
|
|
16883
|
+
if (plugins.length === 0) lines.push("No package plugins installed.");
|
|
16884
|
+
for (const p of plugins) lines.push(` \u2022 ${p.name}@${p.manifest.version} \u2014 ${p.enabled ? "enabled" : "disabled"} / ${p.trusted ? "trusted" : "untrusted"}`);
|
|
16885
|
+
this.send({ type: "info", message: lines.join("\n") });
|
|
16886
|
+
break;
|
|
16887
|
+
}
|
|
16888
|
+
if (sub === "install") {
|
|
16889
|
+
const input = args[1];
|
|
16890
|
+
if (!input) throw new Error("Usage: /plugin install <path>");
|
|
16891
|
+
const plugin = installPlugin(configDir, input);
|
|
16892
|
+
this.send({ type: "info", message: `Installed plugin ${plugin.name}@${plugin.manifest.version}. Run /plugin trust ${plugin.name}, then /plugin enable ${plugin.name}.` });
|
|
16893
|
+
break;
|
|
16894
|
+
}
|
|
16895
|
+
if (sub === "trust") {
|
|
16896
|
+
const name2 = args[1];
|
|
16897
|
+
if (!name2) throw new Error("Usage: /plugin trust <name>");
|
|
16898
|
+
const state2 = trustPlugin(configDir, name2);
|
|
16899
|
+
this.send({ type: "info", message: `Trusted plugin ${state2.name}.` });
|
|
16900
|
+
break;
|
|
16901
|
+
}
|
|
16902
|
+
if (sub === "enable" || sub === "disable") {
|
|
16903
|
+
const name2 = args[1];
|
|
16904
|
+
if (!name2) throw new Error(`Usage: /plugin ${sub} <name>`);
|
|
16905
|
+
const state2 = setPluginEnabled(configDir, name2, sub === "enable");
|
|
16906
|
+
this.send({ type: "info", message: `${sub === "enable" ? "Enabled" : "Disabled"} plugin ${state2.name}. Restart Web UI for changes to take effect.` });
|
|
16907
|
+
break;
|
|
16908
|
+
}
|
|
16909
|
+
if (sub === "inspect") {
|
|
16910
|
+
const name2 = args[1];
|
|
16911
|
+
if (!name2) throw new Error("Usage: /plugin inspect <name>");
|
|
16912
|
+
this.send({ type: "info", message: describePlugin(getInstalledPlugin(configDir, name2)).join("\n") });
|
|
16913
|
+
break;
|
|
16914
|
+
}
|
|
16915
|
+
throw new Error("Usage: /plugin [list|install|enable|disable|inspect|trust]");
|
|
16916
|
+
} catch (err) {
|
|
16917
|
+
this.send({ type: "error", message: err instanceof Error ? err.message : String(err) });
|
|
16918
|
+
}
|
|
16919
|
+
break;
|
|
16920
|
+
}
|
|
16579
16921
|
// ── /bug ────────────────────────────────────────────────────────
|
|
16580
16922
|
case "bug": {
|
|
16581
16923
|
const session = this.sessions.current;
|
|
@@ -16618,7 +16960,7 @@ Add .md files to create commands.` });
|
|
|
16618
16960
|
return;
|
|
16619
16961
|
}
|
|
16620
16962
|
const preDecisions = runLifecycleHooks(
|
|
16621
|
-
this.config.get("hooks") ?? void 0,
|
|
16963
|
+
mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
|
|
16622
16964
|
"PreCompact",
|
|
16623
16965
|
{ sessionId: session.id, messageCount: session.messages.length, instruction },
|
|
16624
16966
|
{ configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
@@ -16655,7 +16997,7 @@ Add .md files to create commands.` });
|
|
|
16655
16997
|
const removedCount = session.compact(summaryMsg, ackMsg, keepLast);
|
|
16656
16998
|
const afterCount = session.messages.length;
|
|
16657
16999
|
runLifecycleHooks(
|
|
16658
|
-
this.config.get("hooks") ?? void 0,
|
|
17000
|
+
mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
|
|
16659
17001
|
"PostCompact",
|
|
16660
17002
|
{ sessionId: session.id, beforeCount, afterCount, removedCount, keepLast, instruction },
|
|
16661
17003
|
{ configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
|
|
@@ -16792,7 +17134,7 @@ ${entry.content}`;
|
|
|
16792
17134
|
const configDir = this.config.getConfigDir();
|
|
16793
17135
|
try {
|
|
16794
17136
|
atomicWriteFileSync(memoryStorePath(configDir), "");
|
|
16795
|
-
atomicWriteFileSync(
|
|
17137
|
+
atomicWriteFileSync(join21(configDir, MEMORY_FILE_NAME), "");
|
|
16796
17138
|
this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
|
|
16797
17139
|
this.sendMemoryEntries();
|
|
16798
17140
|
} catch (err) {
|
|
@@ -17074,8 +17416,8 @@ async function setupProxy(configProxy) {
|
|
|
17074
17416
|
}
|
|
17075
17417
|
|
|
17076
17418
|
// src/web/auth.ts
|
|
17077
|
-
import { existsSync as
|
|
17078
|
-
import { join as
|
|
17419
|
+
import { existsSync as existsSync27, readFileSync as readFileSync21, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, copyFileSync } from "fs";
|
|
17420
|
+
import { join as join22 } from "path";
|
|
17079
17421
|
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
17080
17422
|
var USERS_FILE = "users.json";
|
|
17081
17423
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
@@ -17090,7 +17432,7 @@ var AuthManager = class {
|
|
|
17090
17432
|
db;
|
|
17091
17433
|
constructor(baseDir) {
|
|
17092
17434
|
this.baseDir = baseDir;
|
|
17093
|
-
this.usersFile =
|
|
17435
|
+
this.usersFile = join22(baseDir, USERS_FILE);
|
|
17094
17436
|
this.db = this.loadOrCreate();
|
|
17095
17437
|
}
|
|
17096
17438
|
// ── Public API ─────────────────────────────────────────────────
|
|
@@ -17119,9 +17461,9 @@ var AuthManager = class {
|
|
|
17119
17461
|
}
|
|
17120
17462
|
const salt = randomBytes(16).toString("hex");
|
|
17121
17463
|
const passwordHash = this.hashPassword(password, salt);
|
|
17122
|
-
const dataDir =
|
|
17123
|
-
const fullDataDir =
|
|
17124
|
-
|
|
17464
|
+
const dataDir = join22(USERS_DIR, username);
|
|
17465
|
+
const fullDataDir = join22(this.baseDir, dataDir);
|
|
17466
|
+
mkdirSync13(join22(fullDataDir, "history"), { recursive: true });
|
|
17125
17467
|
const user = {
|
|
17126
17468
|
username,
|
|
17127
17469
|
passwordHash,
|
|
@@ -17236,7 +17578,7 @@ var AuthManager = class {
|
|
|
17236
17578
|
getUserDataDir(username) {
|
|
17237
17579
|
const user = this.db.users.find((u) => u.username === username);
|
|
17238
17580
|
if (!user) throw new Error(`User not found: ${username}`);
|
|
17239
|
-
return
|
|
17581
|
+
return join22(this.baseDir, user.dataDir);
|
|
17240
17582
|
}
|
|
17241
17583
|
/** List all usernames */
|
|
17242
17584
|
listUsers() {
|
|
@@ -17272,30 +17614,30 @@ var AuthManager = class {
|
|
|
17272
17614
|
const err = this.register(username, password);
|
|
17273
17615
|
if (err) return err;
|
|
17274
17616
|
const userDir = this.getUserDataDir(username);
|
|
17275
|
-
const globalConfig =
|
|
17276
|
-
if (
|
|
17617
|
+
const globalConfig = join22(this.baseDir, "config.json");
|
|
17618
|
+
if (existsSync27(globalConfig)) {
|
|
17277
17619
|
try {
|
|
17278
|
-
const content =
|
|
17279
|
-
writeFileSync3(
|
|
17620
|
+
const content = readFileSync21(globalConfig, "utf-8");
|
|
17621
|
+
writeFileSync3(join22(userDir, "config.json"), content, "utf-8");
|
|
17280
17622
|
} catch {
|
|
17281
17623
|
}
|
|
17282
17624
|
}
|
|
17283
|
-
const globalMemory =
|
|
17284
|
-
if (
|
|
17625
|
+
const globalMemory = join22(this.baseDir, "memory.md");
|
|
17626
|
+
if (existsSync27(globalMemory)) {
|
|
17285
17627
|
try {
|
|
17286
|
-
const content =
|
|
17287
|
-
writeFileSync3(
|
|
17628
|
+
const content = readFileSync21(globalMemory, "utf-8");
|
|
17629
|
+
writeFileSync3(join22(userDir, "memory.md"), content, "utf-8");
|
|
17288
17630
|
} catch {
|
|
17289
17631
|
}
|
|
17290
17632
|
}
|
|
17291
|
-
const globalHistory =
|
|
17292
|
-
if (
|
|
17633
|
+
const globalHistory = join22(this.baseDir, "history");
|
|
17634
|
+
if (existsSync27(globalHistory)) {
|
|
17293
17635
|
try {
|
|
17294
|
-
const files =
|
|
17295
|
-
const userHistory =
|
|
17636
|
+
const files = readdirSync13(globalHistory).filter((f) => f.endsWith(".json"));
|
|
17637
|
+
const userHistory = join22(userDir, "history");
|
|
17296
17638
|
for (const f of files) {
|
|
17297
17639
|
try {
|
|
17298
|
-
copyFileSync(
|
|
17640
|
+
copyFileSync(join22(globalHistory, f), join22(userHistory, f));
|
|
17299
17641
|
} catch {
|
|
17300
17642
|
}
|
|
17301
17643
|
}
|
|
@@ -17306,9 +17648,9 @@ var AuthManager = class {
|
|
|
17306
17648
|
}
|
|
17307
17649
|
// ── Private methods ────────────────────────────────────────────
|
|
17308
17650
|
loadOrCreate() {
|
|
17309
|
-
if (
|
|
17651
|
+
if (existsSync27(this.usersFile)) {
|
|
17310
17652
|
try {
|
|
17311
|
-
return JSON.parse(
|
|
17653
|
+
return JSON.parse(readFileSync21(this.usersFile, "utf-8"));
|
|
17312
17654
|
} catch {
|
|
17313
17655
|
}
|
|
17314
17656
|
}
|
|
@@ -17324,7 +17666,7 @@ var AuthManager = class {
|
|
|
17324
17666
|
this.saveDB(this.db);
|
|
17325
17667
|
}
|
|
17326
17668
|
saveDB(db) {
|
|
17327
|
-
|
|
17669
|
+
mkdirSync13(this.baseDir, { recursive: true });
|
|
17328
17670
|
atomicWriteFileSync(this.usersFile, JSON.stringify(db, null, 2));
|
|
17329
17671
|
}
|
|
17330
17672
|
/** Legacy hash — kept only for migrating old users (v0.2.x) */
|
|
@@ -17356,11 +17698,11 @@ function getModuleDir() {
|
|
|
17356
17698
|
if (typeof import.meta?.url === "string") {
|
|
17357
17699
|
const url = new URL(import.meta.url);
|
|
17358
17700
|
const filePath = url.pathname.replace(/^\/([A-Z]:)/i, "$1");
|
|
17359
|
-
return
|
|
17701
|
+
return dirname8(filePath);
|
|
17360
17702
|
}
|
|
17361
17703
|
} catch {
|
|
17362
17704
|
}
|
|
17363
|
-
return
|
|
17705
|
+
return resolve11(".");
|
|
17364
17706
|
}
|
|
17365
17707
|
async function startWebServer(options = {}) {
|
|
17366
17708
|
const port = options.port ?? 3e3;
|
|
@@ -17395,6 +17737,8 @@ async function startWebServer(options = {}) {
|
|
|
17395
17737
|
let mcpManager = null;
|
|
17396
17738
|
const mcpEnabled = config.get("mcpEnabled") !== false;
|
|
17397
17739
|
const globalMcpServers = mcpEnabled ? config.get("mcpServers") ?? {} : {};
|
|
17740
|
+
const pluginAssets = getActivePluginAssets(config.getConfigDir());
|
|
17741
|
+
const pluginMcpServers = mcpEnabled ? pluginAssets.mcpServers : {};
|
|
17398
17742
|
const projectMcpResolved = mcpEnabled ? resolveProjectMcpPath() : null;
|
|
17399
17743
|
let projectMcpServers = {};
|
|
17400
17744
|
if (projectMcpResolved) {
|
|
@@ -17407,7 +17751,7 @@ async function startWebServer(options = {}) {
|
|
|
17407
17751
|
console.log(` Skipped to prevent unintended RCE. Run "aicli" then "/mcp trust-project" to approve.`);
|
|
17408
17752
|
}
|
|
17409
17753
|
}
|
|
17410
|
-
const mergedMcpServers = { ...globalMcpServers, ...projectMcpServers };
|
|
17754
|
+
const mergedMcpServers = { ...globalMcpServers, ...pluginMcpServers, ...projectMcpServers };
|
|
17411
17755
|
if (!mcpEnabled) {
|
|
17412
17756
|
console.log(" \u{1F50C} MCP: disabled (config.mcpEnabled=false)");
|
|
17413
17757
|
}
|
|
@@ -17425,10 +17769,10 @@ async function startWebServer(options = {}) {
|
|
|
17425
17769
|
}
|
|
17426
17770
|
}
|
|
17427
17771
|
let skillManager = null;
|
|
17428
|
-
const skillsDir =
|
|
17429
|
-
if (
|
|
17772
|
+
const skillsDir = join23(config.getConfigDir(), SKILLS_DIR_NAME);
|
|
17773
|
+
if (existsSync28(skillsDir)) {
|
|
17430
17774
|
skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
|
|
17431
|
-
skillManager.loadSkills();
|
|
17775
|
+
skillManager.loadSkills(pluginAssets.skillDirs);
|
|
17432
17776
|
const count = skillManager.listSkills().length;
|
|
17433
17777
|
if (count > 0) {
|
|
17434
17778
|
console.log(` Skills: ${count} available`);
|
|
@@ -17497,18 +17841,18 @@ async function startWebServer(options = {}) {
|
|
|
17497
17841
|
next();
|
|
17498
17842
|
};
|
|
17499
17843
|
const moduleDir = getModuleDir();
|
|
17500
|
-
let clientDir =
|
|
17501
|
-
if (!
|
|
17502
|
-
clientDir =
|
|
17844
|
+
let clientDir = join23(moduleDir, "web", "client");
|
|
17845
|
+
if (!existsSync28(clientDir)) {
|
|
17846
|
+
clientDir = join23(moduleDir, "client");
|
|
17503
17847
|
}
|
|
17504
|
-
if (!
|
|
17505
|
-
clientDir =
|
|
17848
|
+
if (!existsSync28(clientDir)) {
|
|
17849
|
+
clientDir = join23(moduleDir, "..", "..", "src", "web", "client");
|
|
17506
17850
|
}
|
|
17507
|
-
if (!
|
|
17508
|
-
clientDir =
|
|
17851
|
+
if (!existsSync28(clientDir)) {
|
|
17852
|
+
clientDir = join23(process.cwd(), "src", "web", "client");
|
|
17509
17853
|
}
|
|
17510
17854
|
console.log(` Static files: ${clientDir}`);
|
|
17511
|
-
app.use("/vendor", express.static(
|
|
17855
|
+
app.use("/vendor", express.static(join23(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
|
|
17512
17856
|
app.use(express.static(clientDir));
|
|
17513
17857
|
app.get("/api/status", (_req, res) => {
|
|
17514
17858
|
res.json({
|
|
@@ -17575,10 +17919,10 @@ async function startWebServer(options = {}) {
|
|
|
17575
17919
|
app.get("/api/files", requireAuth, (req, res) => {
|
|
17576
17920
|
const cwd = process.cwd();
|
|
17577
17921
|
const prefix = req.query.prefix || "";
|
|
17578
|
-
const targetDir =
|
|
17922
|
+
const targetDir = join23(cwd, prefix);
|
|
17579
17923
|
try {
|
|
17580
|
-
const canonicalTarget = realpathSync(
|
|
17581
|
-
const canonicalCwd = realpathSync(
|
|
17924
|
+
const canonicalTarget = realpathSync(resolve11(targetDir));
|
|
17925
|
+
const canonicalCwd = realpathSync(resolve11(cwd));
|
|
17582
17926
|
if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
|
|
17583
17927
|
res.json({ files: [] });
|
|
17584
17928
|
return;
|
|
@@ -17589,10 +17933,10 @@ async function startWebServer(options = {}) {
|
|
|
17589
17933
|
}
|
|
17590
17934
|
try {
|
|
17591
17935
|
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", "__pycache__", ".next", ".nuxt", "coverage", ".cache"]);
|
|
17592
|
-
const entries =
|
|
17936
|
+
const entries = readdirSync14(targetDir, { withFileTypes: true });
|
|
17593
17937
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
17594
17938
|
name: e.name,
|
|
17595
|
-
path: relative4(cwd,
|
|
17939
|
+
path: relative4(cwd, join23(targetDir, e.name)).replace(/\\/g, "/"),
|
|
17596
17940
|
isDir: e.isDirectory()
|
|
17597
17941
|
}));
|
|
17598
17942
|
res.json({ files });
|
|
@@ -17628,8 +17972,8 @@ async function startWebServer(options = {}) {
|
|
|
17628
17972
|
try {
|
|
17629
17973
|
const authUser = req._authUser;
|
|
17630
17974
|
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
17631
|
-
const filePath =
|
|
17632
|
-
if (!
|
|
17975
|
+
const filePath = join23(histDir, `${id}.json`);
|
|
17976
|
+
if (!existsSync28(filePath)) {
|
|
17633
17977
|
res.status(404).json({ error: "Session not found" });
|
|
17634
17978
|
return;
|
|
17635
17979
|
}
|
|
@@ -17644,7 +17988,7 @@ async function startWebServer(options = {}) {
|
|
|
17644
17988
|
res.status(404).json({ error: "Session not found" });
|
|
17645
17989
|
return;
|
|
17646
17990
|
}
|
|
17647
|
-
const data = JSON.parse(
|
|
17991
|
+
const data = JSON.parse(readFileSync22(filePath, "utf-8"));
|
|
17648
17992
|
res.json({ session: data });
|
|
17649
17993
|
} catch (err) {
|
|
17650
17994
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -17657,10 +18001,10 @@ async function startWebServer(options = {}) {
|
|
|
17657
18001
|
return;
|
|
17658
18002
|
}
|
|
17659
18003
|
const cwd = process.cwd();
|
|
17660
|
-
const fullPath =
|
|
18004
|
+
const fullPath = resolve11(join23(cwd, filePath));
|
|
17661
18005
|
try {
|
|
17662
18006
|
const canonicalFull = realpathSync(fullPath);
|
|
17663
|
-
const canonicalCwd = realpathSync(
|
|
18007
|
+
const canonicalCwd = realpathSync(resolve11(cwd));
|
|
17664
18008
|
if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
|
|
17665
18009
|
res.json({ error: "Access denied" });
|
|
17666
18010
|
return;
|
|
@@ -17670,12 +18014,12 @@ async function startWebServer(options = {}) {
|
|
|
17670
18014
|
return;
|
|
17671
18015
|
}
|
|
17672
18016
|
try {
|
|
17673
|
-
const stat =
|
|
18017
|
+
const stat = statSync10(fullPath);
|
|
17674
18018
|
if (stat.size > 512 * 1024) {
|
|
17675
18019
|
res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
|
|
17676
18020
|
return;
|
|
17677
18021
|
}
|
|
17678
|
-
const content =
|
|
18022
|
+
const content = readFileSync22(fullPath, "utf-8");
|
|
17679
18023
|
res.json({ content, size: stat.size });
|
|
17680
18024
|
} catch {
|
|
17681
18025
|
res.json({ error: "Cannot read file" });
|
|
@@ -17879,7 +18223,7 @@ async function startWebServer(options = {}) {
|
|
|
17879
18223
|
});
|
|
17880
18224
|
const MAX_PORT_ATTEMPTS = 10;
|
|
17881
18225
|
let actualPort = port;
|
|
17882
|
-
const result = await new Promise((
|
|
18226
|
+
const result = await new Promise((resolve12, reject) => {
|
|
17883
18227
|
const tryListen = (attempt) => {
|
|
17884
18228
|
server.once("error", (err) => {
|
|
17885
18229
|
if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
|
|
@@ -17910,7 +18254,7 @@ async function startWebServer(options = {}) {
|
|
|
17910
18254
|
}
|
|
17911
18255
|
console.log(` Press Ctrl+C to stop
|
|
17912
18256
|
`);
|
|
17913
|
-
|
|
18257
|
+
resolve12({ port: actualPort, host, url });
|
|
17914
18258
|
});
|
|
17915
18259
|
};
|
|
17916
18260
|
tryListen(1);
|
|
@@ -17930,17 +18274,17 @@ function resolveProjectMcpPath() {
|
|
|
17930
18274
|
const cwd = process.cwd();
|
|
17931
18275
|
const gitRoot = getGitRoot(cwd);
|
|
17932
18276
|
const projectRoot = gitRoot ?? cwd;
|
|
17933
|
-
const configPath =
|
|
17934
|
-
return
|
|
18277
|
+
const configPath = join23(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
18278
|
+
return existsSync28(configPath) ? configPath : null;
|
|
17935
18279
|
}
|
|
17936
18280
|
function loadProjectMcpConfig() {
|
|
17937
18281
|
const cwd = process.cwd();
|
|
17938
18282
|
const gitRoot = getGitRoot(cwd);
|
|
17939
18283
|
const projectRoot = gitRoot ?? cwd;
|
|
17940
|
-
const configPath =
|
|
17941
|
-
if (!
|
|
18284
|
+
const configPath = join23(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
18285
|
+
if (!existsSync28(configPath)) return null;
|
|
17942
18286
|
try {
|
|
17943
|
-
const raw = JSON.parse(
|
|
18287
|
+
const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
|
|
17944
18288
|
return raw.mcpServers ?? raw;
|
|
17945
18289
|
} catch {
|
|
17946
18290
|
return null;
|