@next-open-ai/openbot 0.6.16 → 0.6.66
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 -4
- package/apps/desktop/renderer/dist/assets/index-CxDZnMBH.css +10 -0
- package/apps/desktop/renderer/dist/assets/index-k47Qiokg.js +93 -0
- package/apps/desktop/renderer/dist/index.html +2 -2
- package/dist/cli/cli.js +136 -0
- package/dist/cli/extension-cmd.d.ts +15 -0
- package/dist/cli/extension-cmd.js +107 -0
- package/dist/core/agent/agent-dir.d.ts +6 -0
- package/dist/core/agent/agent-dir.js +8 -0
- package/dist/core/agent/agent-manager.d.ts +13 -0
- package/dist/core/agent/agent-manager.js +88 -7
- package/dist/core/agent/proxy/adapters/claude-code-adapter.d.ts +2 -0
- package/dist/core/agent/proxy/adapters/claude-code-adapter.js +186 -0
- package/dist/core/agent/proxy/adapters/local-adapter.js +3 -1
- package/dist/core/agent/proxy/adapters/openclawx-adapter.js +3 -3
- package/dist/core/agent/proxy/adapters/opencode-adapter.js +65 -29
- package/dist/core/agent/proxy/adapters/opencode-local-runner.js +9 -0
- package/dist/core/agent/proxy/index.js +2 -0
- package/dist/core/agent/token-usage-log-extension.d.ts +14 -0
- package/dist/core/agent/token-usage-log-extension.js +61 -0
- package/dist/core/config/agent-reload-pending.js +3 -2
- package/dist/core/config/desktop-config.d.ts +29 -6
- package/dist/core/config/desktop-config.js +188 -27
- package/dist/core/config/provider-support-default.js +27 -0
- package/dist/core/extensions/index.d.ts +1 -0
- package/dist/core/extensions/index.js +1 -0
- package/dist/core/extensions/load.d.ts +11 -0
- package/dist/core/extensions/load.js +101 -0
- package/dist/core/local-llm-server/download-model.d.ts +16 -0
- package/dist/core/local-llm-server/download-model.js +37 -0
- package/dist/core/local-llm-server/index.d.ts +32 -0
- package/dist/core/local-llm-server/index.js +152 -0
- package/dist/core/local-llm-server/llm-context.d.ts +66 -0
- package/dist/core/local-llm-server/llm-context.js +270 -0
- package/dist/core/local-llm-server/model-resolve.d.ts +27 -0
- package/dist/core/local-llm-server/model-resolve.js +90 -0
- package/dist/core/local-llm-server/server.d.ts +1 -0
- package/dist/core/local-llm-server/server.js +234 -0
- package/dist/core/local-llm-server/start-from-config.d.ts +5 -0
- package/dist/core/local-llm-server/start-from-config.js +50 -0
- package/dist/core/mcp/adapter.d.ts +4 -2
- package/dist/core/mcp/adapter.js +10 -4
- package/dist/core/mcp/index.d.ts +2 -0
- package/dist/core/mcp/index.js +1 -0
- package/dist/core/mcp/operator.d.ts +11 -0
- package/dist/core/mcp/operator.js +41 -7
- package/dist/core/mcp/transport/stdio.d.ts +6 -0
- package/dist/core/mcp/transport/stdio.js +125 -28
- package/dist/core/memory/local-embedding-llama.js +8 -6
- package/dist/core/memory/local-embedding.d.ts +4 -3
- package/dist/core/memory/local-embedding.js +43 -3
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.js +1 -0
- package/dist/core/tools/truncate-result.d.ts +14 -0
- package/dist/core/tools/truncate-result.js +27 -0
- package/dist/core/tools/web-search/create-web-search-tool.d.ts +17 -0
- package/dist/core/tools/web-search/create-web-search-tool.js +87 -0
- package/dist/core/tools/web-search/index.d.ts +4 -0
- package/dist/core/tools/web-search/index.js +2 -0
- package/dist/core/tools/web-search/providers/brave.d.ts +2 -0
- package/dist/core/tools/web-search/providers/brave.js +87 -0
- package/dist/core/tools/web-search/providers/duck-duck-scrape.d.ts +2 -0
- package/dist/core/tools/web-search/providers/duck-duck-scrape.js +47 -0
- package/dist/core/tools/web-search/providers/index.d.ts +5 -0
- package/dist/core/tools/web-search/providers/index.js +13 -0
- package/dist/core/tools/web-search/types.d.ts +35 -0
- package/dist/core/tools/web-search/types.js +4 -0
- package/dist/gateway/methods/agent-chat.js +110 -42
- package/dist/gateway/methods/run-scheduled-task.js +2 -0
- package/dist/gateway/server.js +60 -13
- package/dist/server/agent-config/agent-config.controller.d.ts +9 -1
- package/dist/server/agent-config/agent-config.controller.js +11 -0
- package/dist/server/agent-config/agent-config.service.d.ts +29 -5
- package/dist/server/agent-config/agent-config.service.js +41 -1
- package/dist/server/agents/agents.gateway.js +1 -1
- package/dist/server/bootstrap.d.ts +1 -0
- package/dist/server/bootstrap.js +19 -2
- package/dist/server/config/config.controller.d.ts +107 -4
- package/dist/server/config/config.controller.js +185 -3
- package/dist/server/config/config.module.js +3 -2
- package/dist/server/config/config.service.d.ts +18 -1
- package/dist/server/config/config.service.js +68 -9
- package/dist/server/config/local-models.service.d.ts +67 -0
- package/dist/server/config/local-models.service.js +242 -0
- package/package.json +3 -1
- package/presets/preset-agents.json +125 -91
- package/presets/preset-config.json +24 -6
- package/presets/preset-providers.json +7 -0
- package/presets/recommended-local-models.json +36 -0
- package/presets/workspaces/download-assistant/skills/downloader/SKILL.md +2 -2
- package/presets/workspaces/office-automation/skills/rpa-helper/SKILL.md +9 -0
- package/presets/workspaces/self-media-bot/skills/self-media-tools/SKILL.md +9 -0
- package/skills/url-bookmark/SKILL.md +12 -12
- package/apps/desktop/renderer/dist/assets/index-BxqMW-uy.css +0 -10
- package/apps/desktop/renderer/dist/assets/index-DJs-wX3R.js +0 -89
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
<link
|
|
12
12
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Roboto+Mono:wght@400;500&display=swap"
|
|
13
13
|
rel="stylesheet">
|
|
14
|
-
<script type="module" crossorigin src="/assets/index-
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-k47Qiokg.js"></script>
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CxDZnMBH.css">
|
|
16
16
|
</head>
|
|
17
17
|
|
|
18
18
|
<body>
|
package/dist/cli/cli.js
CHANGED
|
@@ -6,7 +6,11 @@ import { Command } from "commander";
|
|
|
6
6
|
import { getOpenbotAgentDir } from "../core/agent/agent-dir.js";
|
|
7
7
|
import { run } from "../core/agent/run.js";
|
|
8
8
|
import { loadDesktopAgentConfig, getBoundAgentIdForCli, setProviderApiKey, setDefaultModel, getDesktopConfigList, syncDesktopConfigToModelsJson, ensureDesktopConfigInitialized, } from "../core/config/desktop-config.js";
|
|
9
|
+
import { downloadModel, DEFAULT_LLM_MODEL_URI, } from "../core/local-llm-server/download-model.js";
|
|
10
|
+
import { startLocalLlmServer, stopLocalLlmServer, } from "../core/local-llm-server/index.js";
|
|
11
|
+
import { LOCAL_LLM_CACHE_DIR, isModelFileInCache, toModelPathForStart, } from "../core/local-llm-server/model-resolve.js";
|
|
9
12
|
import { writeGatewayPid, removeGatewayPidFile, serviceInstall, serviceUninstall, serviceStop, } from "./service.js";
|
|
13
|
+
import { installExtension, listExtensions, uninstallExtension } from "./extension-cmd.js";
|
|
10
14
|
const require = createRequire(import.meta.url);
|
|
11
15
|
const PKG = require("../../package.json");
|
|
12
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -189,6 +193,138 @@ configCmd
|
|
|
189
193
|
await syncDesktopConfigToModelsJson();
|
|
190
194
|
console.log("[openbot] Synced desktop providers to agent models.json");
|
|
191
195
|
});
|
|
196
|
+
// Extension: 在 ~/.openbot/plugins 下通过 npm 包安装/列出/卸载扩展,Server 运行时从该目录加载
|
|
197
|
+
const extensionCmd = program
|
|
198
|
+
.command("extension")
|
|
199
|
+
.description("Install, list, or uninstall extensions (npm packages in ~/.openbot/plugins)");
|
|
200
|
+
extensionCmd
|
|
201
|
+
.command("install <pkg>")
|
|
202
|
+
.description("Install an extension package (e.g. openbot extension install my-extension)")
|
|
203
|
+
.action((pkg) => {
|
|
204
|
+
installExtension(pkg);
|
|
205
|
+
});
|
|
206
|
+
extensionCmd
|
|
207
|
+
.command("list")
|
|
208
|
+
.description("List installed extensions")
|
|
209
|
+
.action(() => {
|
|
210
|
+
const list = listExtensions();
|
|
211
|
+
if (list.length === 0) {
|
|
212
|
+
console.log("No extensions installed. Run: openbot extension install <package>");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
console.log("Installed extensions:\n");
|
|
216
|
+
console.table(list.map((r) => ({ Package: r.name, Spec: r.spec })));
|
|
217
|
+
});
|
|
218
|
+
extensionCmd
|
|
219
|
+
.command("uninstall <pkg>")
|
|
220
|
+
.description("Uninstall an extension package")
|
|
221
|
+
.action((pkg) => {
|
|
222
|
+
uninstallExtension(pkg);
|
|
223
|
+
});
|
|
224
|
+
// 本地模型:下载与启动服务
|
|
225
|
+
const localCmd = program
|
|
226
|
+
.command("local")
|
|
227
|
+
.description("下载本地 GGUF 模型与启动本地 LLM 服务");
|
|
228
|
+
localCmd
|
|
229
|
+
.command("download")
|
|
230
|
+
.description("下载推荐模型到 ~/.openbot/.cached_models/,不指定模型时下载 Qwen 3.5 4B")
|
|
231
|
+
.argument("[modelUri]", "模型 URI(如 hf:unsloth/Qwen3.5-4B-GGUF/Qwen3.5-4B-Q5_K_M.gguf),不传则下载 Qwen 3.5 4B")
|
|
232
|
+
.option("--mirror", "使用国内镜像 hf-mirror.com 下载")
|
|
233
|
+
.action(async (modelUri, opts) => {
|
|
234
|
+
const uri = (modelUri || "").trim() || DEFAULT_LLM_MODEL_URI;
|
|
235
|
+
console.log(`[openbot] 下载模型: ${uri}`);
|
|
236
|
+
if (opts.mirror)
|
|
237
|
+
console.log("[openbot] 使用国内镜像 hf-mirror.com");
|
|
238
|
+
try {
|
|
239
|
+
const path = await downloadModel(uri, {
|
|
240
|
+
useMirror: opts.mirror,
|
|
241
|
+
onProgress: (p) => {
|
|
242
|
+
const percent = p.totalSize ? Math.round((p.downloadedSize / p.totalSize) * 100) : (p.percent ?? 0);
|
|
243
|
+
const mb = (p.downloadedSize / 1024 / 1024).toFixed(1);
|
|
244
|
+
const totalMb = p.totalSize ? (p.totalSize / 1024 / 1024).toFixed(1) : "?";
|
|
245
|
+
process.stderr.write(`\r[openbot] 下载中 ${percent}% (${mb} / ${totalMb} MB)`);
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
console.log(`\n[openbot] 已保存: ${path}`);
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
252
|
+
console.error("\n[openbot] 下载失败:", msg);
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
localCmd
|
|
257
|
+
.command("start")
|
|
258
|
+
.description("启动本地 LLM 服务(至少指定 --llm 或 --embedding 之一)")
|
|
259
|
+
.option("--llm <uriOrFile>", "LLM 模型:hf: URI 或已下载文件名,不传则使用桌面缺省模型")
|
|
260
|
+
.option("--embedding <uriOrFile>", "Embedding 模型:hf: URI 或已下载文件名(可选)")
|
|
261
|
+
.option("--context-size <n>", "上下文长度(token 数),默认 32768 或环境变量 LOCAL_LLM_CONTEXT_MAX", (v) => parseInt(v, 10) || 32768)
|
|
262
|
+
.option("--port <port>", "服务端口", "11435")
|
|
263
|
+
.action(async (opts) => {
|
|
264
|
+
let llmPath;
|
|
265
|
+
let embPath;
|
|
266
|
+
if (opts.llm?.trim()) {
|
|
267
|
+
const llmArg = opts.llm.trim();
|
|
268
|
+
if (!llmArg.startsWith("hf:") && !isModelFileInCache(llmArg, LOCAL_LLM_CACHE_DIR)) {
|
|
269
|
+
console.error("[openbot] 模型未下载或路径不存在,请先执行: openbot local download [modelUri]");
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
llmPath = toModelPathForStart(llmArg, LOCAL_LLM_CACHE_DIR);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
const agentConfig = await loadDesktopAgentConfig("default");
|
|
276
|
+
const defaultModel = agentConfig?.model?.trim();
|
|
277
|
+
if (defaultModel) {
|
|
278
|
+
llmPath = toModelPathForStart(defaultModel, LOCAL_LLM_CACHE_DIR);
|
|
279
|
+
if (!isModelFileInCache(defaultModel, LOCAL_LLM_CACHE_DIR)) {
|
|
280
|
+
console.error("[openbot] 缺省模型未下载,请先执行: openbot local download");
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (opts.embedding?.trim()) {
|
|
286
|
+
const embArg = opts.embedding.trim();
|
|
287
|
+
if (!embArg.startsWith("hf:") && !isModelFileInCache(embArg, LOCAL_LLM_CACHE_DIR)) {
|
|
288
|
+
console.error("[openbot] Embedding 模型未下载或路径不存在,请先执行: openbot local download <embedding-uri>");
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
embPath = toModelPathForStart(embArg, LOCAL_LLM_CACHE_DIR);
|
|
292
|
+
}
|
|
293
|
+
if (!llmPath && !embPath) {
|
|
294
|
+
console.error("[openbot] 请至少指定 --llm 或 --embedding,或先配置桌面缺省模型");
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
const contextSize = opts.contextSize ??
|
|
298
|
+
(process.env.LOCAL_LLM_CONTEXT_MAX ? parseInt(process.env.LOCAL_LLM_CONTEXT_MAX, 10) : undefined) ??
|
|
299
|
+
32768;
|
|
300
|
+
const port = parseInt(opts.port || "11435", 10);
|
|
301
|
+
try {
|
|
302
|
+
const handle = await startLocalLlmServer({
|
|
303
|
+
port,
|
|
304
|
+
llmModelPath: llmPath,
|
|
305
|
+
embeddingModelPath: embPath,
|
|
306
|
+
contextSize,
|
|
307
|
+
});
|
|
308
|
+
console.log(`[openbot] 本地模型服务已启动: ${handle.baseUrl}`);
|
|
309
|
+
console.log("[openbot] 按 Ctrl+C 停止服务");
|
|
310
|
+
await new Promise((resolve) => {
|
|
311
|
+
process.on("SIGINT", () => {
|
|
312
|
+
stopLocalLlmServer();
|
|
313
|
+
resolve();
|
|
314
|
+
});
|
|
315
|
+
process.on("SIGTERM", () => {
|
|
316
|
+
stopLocalLlmServer();
|
|
317
|
+
resolve();
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
process.exit(0);
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
324
|
+
console.error("[openbot] 启动失败:", msg);
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
192
328
|
(async () => {
|
|
193
329
|
await ensureDesktopConfigInitialized();
|
|
194
330
|
await program.parseAsync(process.argv);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 安装扩展:将包加入 dependencies 并在 plugins 目录下执行 npm install
|
|
3
|
+
*/
|
|
4
|
+
export declare function installExtension(pkgSpec: string): void;
|
|
5
|
+
/**
|
|
6
|
+
* 列出已安装的扩展(package.json 的 dependencies)
|
|
7
|
+
*/
|
|
8
|
+
export declare function listExtensions(): {
|
|
9
|
+
name: string;
|
|
10
|
+
spec: string;
|
|
11
|
+
}[];
|
|
12
|
+
/**
|
|
13
|
+
* 卸载扩展:从 dependencies 移除并在 plugins 目录下执行 npm install
|
|
14
|
+
*/
|
|
15
|
+
export declare function uninstallExtension(pkgName: string): void;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* openbot extension install / list / uninstall
|
|
3
|
+
* 在 ~/.openbot/plugins 下维护 package.json 与 node_modules,Server 运行时从该目录加载扩展。
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { execSync } from "node:child_process";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { getOpenbotPluginsDir } from "../core/agent/agent-dir.js";
|
|
9
|
+
const PLUGINS_PACKAGE_NAME = "openbot-plugins-root";
|
|
10
|
+
function ensurePluginsDir() {
|
|
11
|
+
const dir = getOpenbotPluginsDir();
|
|
12
|
+
if (!existsSync(dir)) {
|
|
13
|
+
mkdirSync(dir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
const pkgPath = join(dir, "package.json");
|
|
16
|
+
if (!existsSync(pkgPath)) {
|
|
17
|
+
writeFileSync(pkgPath, JSON.stringify({
|
|
18
|
+
name: PLUGINS_PACKAGE_NAME,
|
|
19
|
+
version: "1.0.0",
|
|
20
|
+
private: true,
|
|
21
|
+
description: "OpenBot extension packages (managed by openbot extension install)",
|
|
22
|
+
dependencies: {},
|
|
23
|
+
}, null, 2), "utf-8");
|
|
24
|
+
}
|
|
25
|
+
return dir;
|
|
26
|
+
}
|
|
27
|
+
function readPluginsPackageJson(pluginsDir) {
|
|
28
|
+
const pkgPath = join(pluginsDir, "package.json");
|
|
29
|
+
if (!existsSync(pkgPath))
|
|
30
|
+
return {};
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function writePluginsPackageJson(pluginsDir, pkg) {
|
|
39
|
+
const pkgPath = join(pluginsDir, "package.json");
|
|
40
|
+
const full = {
|
|
41
|
+
name: PLUGINS_PACKAGE_NAME,
|
|
42
|
+
version: "1.0.0",
|
|
43
|
+
private: true,
|
|
44
|
+
description: "OpenBot extension packages (managed by openbot extension install)",
|
|
45
|
+
...pkg,
|
|
46
|
+
};
|
|
47
|
+
writeFileSync(pkgPath, JSON.stringify(full, null, 2), "utf-8");
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 解析用户输入的 pkgSpec(如 "foo" 或 "foo@1.0.0")为 { name, spec }。
|
|
51
|
+
*/
|
|
52
|
+
function parsePkgSpec(pkgSpec) {
|
|
53
|
+
const at = pkgSpec.indexOf("@");
|
|
54
|
+
if (at === -1)
|
|
55
|
+
return { name: pkgSpec.trim(), spec: "*" };
|
|
56
|
+
return { name: pkgSpec.slice(0, at).trim(), spec: pkgSpec.slice(at + 1).trim() || "*" };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 安装扩展:将包加入 dependencies 并在 plugins 目录下执行 npm install
|
|
60
|
+
*/
|
|
61
|
+
export function installExtension(pkgSpec) {
|
|
62
|
+
const pluginsDir = ensurePluginsDir();
|
|
63
|
+
const { name, spec } = parsePkgSpec(pkgSpec);
|
|
64
|
+
const pkg = readPluginsPackageJson(pluginsDir);
|
|
65
|
+
const deps = { ...pkg.dependencies };
|
|
66
|
+
deps[name] = spec; // 覆盖为本次指定的版本
|
|
67
|
+
writePluginsPackageJson(pluginsDir, { ...pkg, dependencies: deps });
|
|
68
|
+
execSync("npm install", {
|
|
69
|
+
cwd: pluginsDir,
|
|
70
|
+
stdio: "inherit",
|
|
71
|
+
});
|
|
72
|
+
console.log(`[openbot] Installed extension: ${name} (in ${pluginsDir})`);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 列出已安装的扩展(package.json 的 dependencies)
|
|
76
|
+
*/
|
|
77
|
+
export function listExtensions() {
|
|
78
|
+
const pluginsDir = getOpenbotPluginsDir();
|
|
79
|
+
if (!existsSync(pluginsDir))
|
|
80
|
+
return [];
|
|
81
|
+
const pkg = readPluginsPackageJson(pluginsDir);
|
|
82
|
+
const deps = pkg.dependencies ?? {};
|
|
83
|
+
return Object.entries(deps).map(([name, spec]) => ({ name, spec }));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 卸载扩展:从 dependencies 移除并在 plugins 目录下执行 npm install
|
|
87
|
+
*/
|
|
88
|
+
export function uninstallExtension(pkgName) {
|
|
89
|
+
const pluginsDir = getOpenbotPluginsDir();
|
|
90
|
+
if (!existsSync(pluginsDir)) {
|
|
91
|
+
console.warn("[openbot] No plugins directory found.");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const pkg = readPluginsPackageJson(pluginsDir);
|
|
95
|
+
const deps = { ...(pkg.dependencies ?? {}) };
|
|
96
|
+
if (!(pkgName in deps)) {
|
|
97
|
+
console.warn(`[openbot] Extension "${pkgName}" is not installed.`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
delete deps[pkgName];
|
|
101
|
+
writePluginsPackageJson(pluginsDir, { ...pkg, dependencies: deps });
|
|
102
|
+
execSync("npm install", {
|
|
103
|
+
cwd: pluginsDir,
|
|
104
|
+
stdio: "inherit",
|
|
105
|
+
});
|
|
106
|
+
console.log(`[openbot] Uninstalled extension: ${pkgName}`);
|
|
107
|
+
}
|
|
@@ -8,6 +8,12 @@ export declare function getOpenbotAgentDir(): string;
|
|
|
8
8
|
* 可通过环境变量 OPENBOT_WORKSPACE_DIR 覆盖
|
|
9
9
|
*/
|
|
10
10
|
export declare function getOpenbotWorkspaceDir(): string;
|
|
11
|
+
/**
|
|
12
|
+
* 获取 openbot 扩展(插件)目录(默认 ~/.openbot/plugins)
|
|
13
|
+
* 可通过环境变量 OPENBOT_PLUGINS_DIR 覆盖
|
|
14
|
+
* openbot extension install 会在此目录下维护 package.json 与 node_modules
|
|
15
|
+
*/
|
|
16
|
+
export declare function getOpenbotPluginsDir(): string;
|
|
11
17
|
/**
|
|
12
18
|
* 确保 agent 目录存在,并创建默认配置文件
|
|
13
19
|
*/
|
|
@@ -15,6 +15,14 @@ export function getOpenbotAgentDir() {
|
|
|
15
15
|
export function getOpenbotWorkspaceDir() {
|
|
16
16
|
return process.env.OPENBOT_WORKSPACE_DIR ?? join(homedir(), ".openbot", "workspace");
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* 获取 openbot 扩展(插件)目录(默认 ~/.openbot/plugins)
|
|
20
|
+
* 可通过环境变量 OPENBOT_PLUGINS_DIR 覆盖
|
|
21
|
+
* openbot extension install 会在此目录下维护 package.json 与 node_modules
|
|
22
|
+
*/
|
|
23
|
+
export function getOpenbotPluginsDir() {
|
|
24
|
+
return process.env.OPENBOT_PLUGINS_DIR ?? join(homedir(), ".openbot", "plugins");
|
|
25
|
+
}
|
|
18
26
|
/**
|
|
19
27
|
* 确保 agent 目录存在,并创建默认配置文件
|
|
20
28
|
*/
|
|
@@ -60,10 +60,23 @@ export declare class AgentManager {
|
|
|
60
60
|
maxSessions?: number;
|
|
61
61
|
targetAgentId?: string;
|
|
62
62
|
mcpServers?: McpServerConfig[] | McpServersStandardFormat;
|
|
63
|
+
/** MCP 单次返回最大 token;不配置则不限制 */
|
|
64
|
+
mcpMaxResultTokens?: number;
|
|
63
65
|
/** 自定义系统提示词(来自 agent 配置),会与技能等一起组成最终 systemPrompt */
|
|
64
66
|
systemPrompt?: string;
|
|
65
67
|
/** 是否使用长记忆(memory_recall/save_experience);默认 true */
|
|
66
68
|
useLongMemory?: boolean;
|
|
69
|
+
/** 在线搜索:启用时注册 web_search 工具 */
|
|
70
|
+
webSearch?: {
|
|
71
|
+
enabled: boolean;
|
|
72
|
+
provider: "brave" | "duck-duck-scrape";
|
|
73
|
+
apiKey?: string;
|
|
74
|
+
timeoutSeconds?: number;
|
|
75
|
+
cacheTtlMinutes?: number;
|
|
76
|
+
maxResults?: number;
|
|
77
|
+
/** 单次搜索返回最大 token;不配置则不限制;前端默认 64K */
|
|
78
|
+
maxResultTokens?: number;
|
|
79
|
+
};
|
|
67
80
|
}): Promise<AgentSession>;
|
|
68
81
|
/** 按复合 key 获取(key = sessionId + "::" + agentId) */
|
|
69
82
|
getSession(compositeKey: string): AgentSession | undefined;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { createAgentSession, AuthStorage, DefaultResourceLoader, ModelRegistry, SessionManager as CoreSessionManager, createReadTool, createWriteTool, createEditTool, createBashTool, createFindTool, createGrepTool, createLsTool } from "@mariozechner/pi-coding-agent";
|
|
1
|
+
import { createAgentSession, AuthStorage, DefaultResourceLoader, ModelRegistry, SessionManager as CoreSessionManager, SettingsManager, createReadTool, createWriteTool, createEditTool, createBashTool, createFindTool, createGrepTool, createLsTool } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { existsSync, mkdirSync } from "node:fs";
|
|
4
4
|
import { createCompactionMemoryExtensionFactory } from "../memory/compaction-extension.js";
|
|
5
|
+
import { loadExtensionFactories } from "../extensions/index.js";
|
|
5
6
|
import { addMemory } from "../memory/index.js";
|
|
6
7
|
import { persistStoredCompactionForSession, persistStoredCompactionForBusinessSession, } from "../memory/persist-compaction-on-close.js";
|
|
7
|
-
import { createBrowserTool, createSaveExperienceTool, createMemoryRecallTool, createInstallSkillTool, createSwitchAgentTool, createListAgentsTool, createCreateAgentTool, createGetBookmarkTagsTool, createSaveBookmarkTool, createAddBookmarkTagTool } from "../tools/index.js";
|
|
8
|
+
import { createBrowserTool, createSaveExperienceTool, createMemoryRecallTool, createInstallSkillTool, createSwitchAgentTool, createListAgentsTool, createCreateAgentTool, createGetBookmarkTagsTool, createSaveBookmarkTool, createAddBookmarkTagTool, createWebSearchTool } from "../tools/index.js";
|
|
8
9
|
/** Agent Session 缓存 key:sessionId + "::" + agentId,同一业务 session 下不同 agent 各自一个 Core Session */
|
|
9
10
|
const COMPOSITE_KEY_SEP = "::";
|
|
10
11
|
function toCompositeKey(sessionId, agentId) {
|
|
@@ -12,10 +13,16 @@ function toCompositeKey(sessionId, agentId) {
|
|
|
12
13
|
}
|
|
13
14
|
import { createMcpToolsForSession } from "../mcp/index.js";
|
|
14
15
|
import { registerBuiltInApiProviders } from "@mariozechner/pi-ai/dist/providers/register-builtins.js";
|
|
15
|
-
import { getOpenbotAgentDir, getOpenbotWorkspaceDir
|
|
16
|
+
import { getOpenbotAgentDir, getOpenbotWorkspaceDir } from "./agent-dir.js";
|
|
16
17
|
import { formatSkillsForPrompt } from "./skills.js";
|
|
18
|
+
import { createTokenUsageLogExtensionFactory, setTokenUsageInitialStats, } from "./token-usage-log-extension.js";
|
|
17
19
|
// Ensure all built-in providers are registered
|
|
18
20
|
registerBuiltInApiProviders();
|
|
21
|
+
/** 粗略按字符估算 token(中英混合约 1/3,纯英文约 1/4) */
|
|
22
|
+
function estTokensFromChars(chars) {
|
|
23
|
+
return Math.ceil(chars / 3);
|
|
24
|
+
}
|
|
25
|
+
const TOKEN_USAGE_LOG_PREFIX = "[token-usage]";
|
|
19
26
|
/** system prompt 中每个技能描述最大字符数,超出截断以省 token */
|
|
20
27
|
const MAX_SKILL_DESC_IN_PROMPT = 250;
|
|
21
28
|
/**
|
|
@@ -123,9 +130,17 @@ For downloads, provide either a direct URL or a selector to click.`;
|
|
|
123
130
|
agentDir: this.agentDir,
|
|
124
131
|
noSkills: true, // Disable SDK's built-in skills logic to take full control
|
|
125
132
|
additionalSkillPaths: this.resolveSkillPaths(workspaceDir),
|
|
126
|
-
extensionFactories:
|
|
127
|
-
|
|
128
|
-
|
|
133
|
+
extensionFactories: (() => {
|
|
134
|
+
const compositeKeyForLoader = sessionId && identity?.agentId
|
|
135
|
+
? sessionId + COMPOSITE_KEY_SEP + identity.agentId
|
|
136
|
+
: "";
|
|
137
|
+
const tokenLog = createTokenUsageLogExtensionFactory(compositeKeyForLoader);
|
|
138
|
+
const base = sessionId && onUpdateLatestCompaction
|
|
139
|
+
? [createCompactionMemoryExtensionFactory(sessionId, onUpdateLatestCompaction)]
|
|
140
|
+
: [];
|
|
141
|
+
const pluginFactories = loadExtensionFactories();
|
|
142
|
+
return [tokenLog, ...base, ...pluginFactories];
|
|
143
|
+
})(),
|
|
129
144
|
systemPromptOverride: (base) => {
|
|
130
145
|
const loadedSkills = loader.getSkills().skills;
|
|
131
146
|
const basePrompt = this.buildSystemPrompt(loadedSkills);
|
|
@@ -203,13 +218,22 @@ For downloads, provide either a direct URL or a selector to click.`;
|
|
|
203
218
|
const provider = options.provider ?? process.env.OPENBOT_PROVIDER ?? "deepseek";
|
|
204
219
|
const modelId = options.modelId ?? process.env.OPENBOT_MODEL ?? "deepseek-chat";
|
|
205
220
|
const apiKey = options.apiKey;
|
|
206
|
-
|
|
221
|
+
// local provider:指向本地 node-llama-cpp 子进程服务
|
|
222
|
+
if (provider === "local") {
|
|
223
|
+
const localBaseUrl = process.env.LOCAL_LLM_BASE_URL ?? "http://127.0.0.1:11435/v1";
|
|
224
|
+
process.env.OPENAI_API_KEY = process.env.OPENAI_API_KEY || "local";
|
|
225
|
+
process.env.OPENAI_BASE_URL = localBaseUrl;
|
|
226
|
+
}
|
|
207
227
|
const authPath = join(this.agentDir, "auth.json");
|
|
208
228
|
const modelsPath = join(this.agentDir, "models.json");
|
|
209
229
|
const authStorage = new AuthStorage(authPath);
|
|
210
230
|
if (apiKey) {
|
|
211
231
|
authStorage.setRuntimeApiKey(provider, apiKey);
|
|
212
232
|
}
|
|
233
|
+
// local 无需真实 API Key,显式设置占位凭证,避免 SDK 走默认凭证链(如 AWS)导致 "Could not load credentials from any providers"
|
|
234
|
+
if (provider === "local") {
|
|
235
|
+
authStorage.setRuntimeApiKey("local", process.env.OPENAI_API_KEY || "local");
|
|
236
|
+
}
|
|
213
237
|
if (await authStorage.hasAuth(provider)) {
|
|
214
238
|
const key = await authStorage.getApiKey(provider);
|
|
215
239
|
if (key) {
|
|
@@ -245,6 +269,8 @@ For downloads, provide either a direct URL or a selector to click.`;
|
|
|
245
269
|
return process.env.MOONSHOT_API_KEY || process.env.KIMI_API_KEY || process.env.OPENAI_API_KEY;
|
|
246
270
|
if (p === "openai" || p === "openai-custom")
|
|
247
271
|
return process.env.OPENAI_API_KEY;
|
|
272
|
+
if (p === "local")
|
|
273
|
+
return process.env.OPENAI_API_KEY || "local";
|
|
248
274
|
return process.env.OPENAI_API_KEY;
|
|
249
275
|
});
|
|
250
276
|
const loader = this.createResourceLoader(sessionWorkspaceDir, sessionId, options.systemPrompt, { agentId, workspace: workspaceName }, (summary) => this.sessionLatestCompactionSummary.set(compositeKey, summary));
|
|
@@ -262,7 +288,9 @@ For downloads, provide either a direct URL or a selector to click.`;
|
|
|
262
288
|
const mcpTools = await createMcpToolsForSession({
|
|
263
289
|
mcpServers: options.mcpServers,
|
|
264
290
|
sessionId,
|
|
291
|
+
mcpMaxResultTokens: options.mcpMaxResultTokens,
|
|
265
292
|
});
|
|
293
|
+
const webSearchTool = options.webSearch?.enabled === true ? createWebSearchTool(options.webSearch) : null;
|
|
266
294
|
const customTools = [
|
|
267
295
|
createBrowserTool(sessionWorkspaceDir),
|
|
268
296
|
createSaveExperienceTool(sessionId),
|
|
@@ -274,11 +302,57 @@ For downloads, provide either a direct URL or a selector to click.`;
|
|
|
274
302
|
createGetBookmarkTagsTool(),
|
|
275
303
|
createSaveBookmarkTool(),
|
|
276
304
|
createAddBookmarkTagTool(),
|
|
305
|
+
...(webSearchTool ? [webSearchTool] : []),
|
|
277
306
|
...mcpTools,
|
|
278
307
|
];
|
|
308
|
+
// 分类打印 token 占用估算(字符数 + 估算 token),便于分析超长请求来源
|
|
309
|
+
try {
|
|
310
|
+
const loadedSkills = loader.getSkills().skills;
|
|
311
|
+
const shortSkills = loadedSkills.map((s) => ({
|
|
312
|
+
...s,
|
|
313
|
+
description: s.description.length <= MAX_SKILL_DESC_IN_PROMPT
|
|
314
|
+
? s.description
|
|
315
|
+
: s.description.slice(0, MAX_SKILL_DESC_IN_PROMPT) + "…",
|
|
316
|
+
}));
|
|
317
|
+
const skillsBlock = formatSkillsForPrompt(shortSkills);
|
|
318
|
+
const basePrompt = this.buildSystemPrompt(loadedSkills);
|
|
319
|
+
const withCustom = options.systemPrompt?.trim()
|
|
320
|
+
? options.systemPrompt.trim() + "\n\n" + basePrompt
|
|
321
|
+
: basePrompt;
|
|
322
|
+
const sessionIdentity = { agentId, workspace: workspaceName };
|
|
323
|
+
const withIdentity = sessionIdentity?.agentId
|
|
324
|
+
? `[Session identity] You are the agent with ID: ${sessionIdentity.agentId}, workspace: ${sessionIdentity.workspace || sessionIdentity.agentId}. When asked which agent you are, answer according to this identity.\n\n` + withCustom
|
|
325
|
+
: withCustom;
|
|
326
|
+
const systemPromptChars = withIdentity.length;
|
|
327
|
+
const toolsDefs = [
|
|
328
|
+
...Object.values(coreTools),
|
|
329
|
+
...customTools,
|
|
330
|
+
].map((t) => ({
|
|
331
|
+
name: t?.name ?? "",
|
|
332
|
+
description: typeof t?.description === "string" ? t.description : "",
|
|
333
|
+
parameters: t?.parameters ?? {},
|
|
334
|
+
}));
|
|
335
|
+
const toolsJsonChars = JSON.stringify(toolsDefs).length;
|
|
336
|
+
const systemPromptEstTokens = estTokensFromChars(systemPromptChars);
|
|
337
|
+
const skillsBlockEstTokens = estTokensFromChars(skillsBlock.length);
|
|
338
|
+
const toolsDefsEstTokens = estTokensFromChars(toolsJsonChars);
|
|
339
|
+
console.log(`${TOKEN_USAGE_LOG_PREFIX} session create (${compositeKey}) | systemPrompt chars=${systemPromptChars} estTokens=${systemPromptEstTokens} | skillsBlock chars=${skillsBlock.length} estTokens=${skillsBlockEstTokens} | toolsDefs chars=${toolsJsonChars} estTokens=${toolsDefsEstTokens}`);
|
|
340
|
+
setTokenUsageInitialStats(compositeKey, {
|
|
341
|
+
systemPromptEstTokens,
|
|
342
|
+
skillsBlockEstTokens,
|
|
343
|
+
toolsDefsEstTokens,
|
|
344
|
+
});
|
|
345
|
+
console.log(`${TOKEN_USAGE_LOG_PREFIX} compaction (SDK): 触发条件 contextTokens > contextWindow - reserveTokens (默认 16384);保留最近 keepRecentTokens (默认 20000)。配置见 pi 文档 settings.json 或传入 settingsManager。`);
|
|
346
|
+
}
|
|
347
|
+
catch (e) {
|
|
348
|
+
console.warn(`${TOKEN_USAGE_LOG_PREFIX} session create log failed:`, e);
|
|
349
|
+
}
|
|
279
350
|
const { session } = await createAgentSession({
|
|
280
351
|
agentDir: this.agentDir,
|
|
281
352
|
sessionManager: CoreSessionManager.inMemory(),
|
|
353
|
+
settingsManager: SettingsManager.inMemory({
|
|
354
|
+
compaction: { enabled: true, reserveTokens: 16384, keepRecentTokens: 20000 },
|
|
355
|
+
}),
|
|
282
356
|
authStorage,
|
|
283
357
|
modelRegistry,
|
|
284
358
|
cwd: sessionWorkspaceDir,
|
|
@@ -291,6 +365,13 @@ For downloads, provide either a direct URL or a selector to click.`;
|
|
|
291
365
|
console.log(`Setting model to ${model.provider}/${model.id} (workspace: ${workspaceName})`);
|
|
292
366
|
await session.setModel(model);
|
|
293
367
|
}
|
|
368
|
+
else if (provider && modelId) {
|
|
369
|
+
// 配置了 provider/model 但在 models.json 中找不到,避免发请求后收到 400 model is required
|
|
370
|
+
const hint = provider === "ollama" || provider === "openai-custom"
|
|
371
|
+
? "若使用 Ollama,请确保模型名与终端中 `ollama list` 显示的名称完全一致(如 qwen3:4b),并在「模型配置」中保存。"
|
|
372
|
+
: "请检查「模型配置」中该 Provider 下是否已添加并保存该模型。";
|
|
373
|
+
throw new Error(`未找到模型 ${provider}/${modelId}。${hint}`);
|
|
374
|
+
}
|
|
294
375
|
this.sessions.set(compositeKey, session);
|
|
295
376
|
this.sessionLastActiveAt.set(compositeKey, now);
|
|
296
377
|
return session;
|