@next-open-ai/openclawx 0.8.36 → 0.8.48

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.
Files changed (78) hide show
  1. package/README.md +60 -42
  2. package/apps/desktop/renderer/dist/assets/index-BHY1xIZQ.css +10 -0
  3. package/apps/desktop/renderer/dist/assets/index-DQxlVuBe.js +93 -0
  4. package/apps/desktop/renderer/dist/index.html +2 -2
  5. package/dist/cli/cli.js +29 -0
  6. package/dist/cli/extension-cmd.d.ts +15 -0
  7. package/dist/cli/extension-cmd.js +107 -0
  8. package/dist/core/agent/agent-dir.d.ts +6 -0
  9. package/dist/core/agent/agent-dir.js +8 -0
  10. package/dist/core/agent/agent-manager.d.ts +13 -0
  11. package/dist/core/agent/agent-manager.js +77 -7
  12. package/dist/core/agent/proxy/adapters/claude-code-adapter.d.ts +2 -0
  13. package/dist/core/agent/proxy/adapters/claude-code-adapter.js +186 -0
  14. package/dist/core/agent/proxy/adapters/local-adapter.js +3 -1
  15. package/dist/core/agent/proxy/adapters/opencode-adapter.js +65 -29
  16. package/dist/core/agent/proxy/adapters/opencode-local-runner.js +9 -0
  17. package/dist/core/agent/proxy/index.js +2 -0
  18. package/dist/core/agent/token-usage-log-extension.d.ts +14 -0
  19. package/dist/core/agent/token-usage-log-extension.js +61 -0
  20. package/dist/core/config/desktop-config.d.ts +24 -2
  21. package/dist/core/config/desktop-config.js +87 -10
  22. package/dist/core/config/provider-support-default.js +26 -0
  23. package/dist/core/extensions/index.d.ts +1 -0
  24. package/dist/core/extensions/index.js +1 -0
  25. package/dist/core/extensions/load.d.ts +11 -0
  26. package/dist/core/extensions/load.js +101 -0
  27. package/dist/core/local-llm-server/index.d.ts +32 -0
  28. package/dist/core/local-llm-server/index.js +126 -0
  29. package/dist/core/local-llm-server/llm-context.d.ts +60 -0
  30. package/dist/core/local-llm-server/llm-context.js +221 -0
  31. package/dist/core/local-llm-server/model-resolve.d.ts +20 -0
  32. package/dist/core/local-llm-server/model-resolve.js +58 -0
  33. package/dist/core/local-llm-server/server.d.ts +1 -0
  34. package/dist/core/local-llm-server/server.js +235 -0
  35. package/dist/core/mcp/adapter.d.ts +4 -2
  36. package/dist/core/mcp/adapter.js +10 -4
  37. package/dist/core/mcp/index.d.ts +2 -0
  38. package/dist/core/mcp/index.js +1 -0
  39. package/dist/core/mcp/operator.d.ts +2 -0
  40. package/dist/core/mcp/operator.js +1 -1
  41. package/dist/core/memory/local-embedding.d.ts +4 -3
  42. package/dist/core/memory/local-embedding.js +43 -3
  43. package/dist/core/tools/index.d.ts +1 -0
  44. package/dist/core/tools/index.js +1 -0
  45. package/dist/core/tools/truncate-result.d.ts +14 -0
  46. package/dist/core/tools/truncate-result.js +27 -0
  47. package/dist/core/tools/web-search/create-web-search-tool.d.ts +17 -0
  48. package/dist/core/tools/web-search/create-web-search-tool.js +87 -0
  49. package/dist/core/tools/web-search/index.d.ts +4 -0
  50. package/dist/core/tools/web-search/index.js +2 -0
  51. package/dist/core/tools/web-search/providers/brave.d.ts +2 -0
  52. package/dist/core/tools/web-search/providers/brave.js +87 -0
  53. package/dist/core/tools/web-search/providers/duck-duck-scrape.d.ts +2 -0
  54. package/dist/core/tools/web-search/providers/duck-duck-scrape.js +47 -0
  55. package/dist/core/tools/web-search/providers/index.d.ts +5 -0
  56. package/dist/core/tools/web-search/providers/index.js +13 -0
  57. package/dist/core/tools/web-search/types.d.ts +35 -0
  58. package/dist/core/tools/web-search/types.js +4 -0
  59. package/dist/gateway/methods/agent-chat.js +74 -42
  60. package/dist/gateway/methods/run-scheduled-task.js +2 -0
  61. package/dist/gateway/server.js +54 -1
  62. package/dist/server/agent-config/agent-config.controller.d.ts +1 -1
  63. package/dist/server/agent-config/agent-config.service.d.ts +17 -3
  64. package/dist/server/agent-config/agent-config.service.js +23 -0
  65. package/dist/server/config/config.controller.d.ts +84 -4
  66. package/dist/server/config/config.controller.js +135 -3
  67. package/dist/server/config/config.module.js +3 -2
  68. package/dist/server/config/config.service.d.ts +14 -0
  69. package/dist/server/config/local-models.service.d.ts +52 -0
  70. package/dist/server/config/local-models.service.js +211 -0
  71. package/package.json +3 -1
  72. package/presets/preset-agents.json +121 -91
  73. package/presets/recommended-local-models.json +42 -0
  74. package/presets/workspaces/finance-expert/skills/akshare-helper/SKILL.md +9 -0
  75. package/presets/workspaces/office-automation/skills/rpa-helper/SKILL.md +9 -0
  76. package/presets/workspaces/self-media-bot/skills/self-media-tools/SKILL.md +9 -0
  77. package/apps/desktop/renderer/dist/assets/index-BGHtXhm3.js +0 -89
  78. package/apps/desktop/renderer/dist/assets/index-CB2-m4ae.css +0 -10
@@ -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-BGHtXhm3.js"></script>
15
- <link rel="stylesheet" crossorigin href="/assets/index-CB2-m4ae.css">
14
+ <script type="module" crossorigin src="/assets/index-DQxlVuBe.js"></script>
15
+ <link rel="stylesheet" crossorigin href="/assets/index-BHY1xIZQ.css">
16
16
  </head>
17
17
 
18
18
  <body>
package/dist/cli/cli.js CHANGED
@@ -7,6 +7,7 @@ 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
9
  import { writeGatewayPid, removeGatewayPidFile, serviceInstall, serviceUninstall, serviceStop, } from "./service.js";
10
+ import { installExtension, listExtensions, uninstallExtension } from "./extension-cmd.js";
10
11
  const require = createRequire(import.meta.url);
11
12
  const PKG = require("../../package.json");
12
13
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -189,6 +190,34 @@ configCmd
189
190
  await syncDesktopConfigToModelsJson();
190
191
  console.log("[openbot] Synced desktop providers to agent models.json");
191
192
  });
193
+ // Extension: 在 ~/.openbot/plugins 下通过 npm 包安装/列出/卸载扩展,Server 运行时从该目录加载
194
+ const extensionCmd = program
195
+ .command("extension")
196
+ .description("Install, list, or uninstall extensions (npm packages in ~/.openbot/plugins)");
197
+ extensionCmd
198
+ .command("install <pkg>")
199
+ .description("Install an extension package (e.g. openbot extension install my-extension)")
200
+ .action((pkg) => {
201
+ installExtension(pkg);
202
+ });
203
+ extensionCmd
204
+ .command("list")
205
+ .description("List installed extensions")
206
+ .action(() => {
207
+ const list = listExtensions();
208
+ if (list.length === 0) {
209
+ console.log("No extensions installed. Run: openbot extension install <package>");
210
+ return;
211
+ }
212
+ console.log("Installed extensions:\n");
213
+ console.table(list.map((r) => ({ Package: r.name, Spec: r.spec })));
214
+ });
215
+ extensionCmd
216
+ .command("uninstall <pkg>")
217
+ .description("Uninstall an extension package")
218
+ .action((pkg) => {
219
+ uninstallExtension(pkg);
220
+ });
192
221
  (async () => {
193
222
  await ensureDesktopConfigInitialized();
194
223
  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: "OpenClawX 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: "OpenClawX 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, ensureDefaultAgentDir } from "./agent-dir.js";
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: sessionId && onUpdateLatestCompaction
127
- ? [createCompactionMemoryExtensionFactory(sessionId, onUpdateLatestCompaction)]
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,7 +218,12 @@ 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
- ensureDefaultAgentDir(this.agentDir);
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);
@@ -245,6 +265,8 @@ For downloads, provide either a direct URL or a selector to click.`;
245
265
  return process.env.MOONSHOT_API_KEY || process.env.KIMI_API_KEY || process.env.OPENAI_API_KEY;
246
266
  if (p === "openai" || p === "openai-custom")
247
267
  return process.env.OPENAI_API_KEY;
268
+ if (p === "local")
269
+ return process.env.OPENAI_API_KEY || "local";
248
270
  return process.env.OPENAI_API_KEY;
249
271
  });
250
272
  const loader = this.createResourceLoader(sessionWorkspaceDir, sessionId, options.systemPrompt, { agentId, workspace: workspaceName }, (summary) => this.sessionLatestCompactionSummary.set(compositeKey, summary));
@@ -262,7 +284,9 @@ For downloads, provide either a direct URL or a selector to click.`;
262
284
  const mcpTools = await createMcpToolsForSession({
263
285
  mcpServers: options.mcpServers,
264
286
  sessionId,
287
+ mcpMaxResultTokens: options.mcpMaxResultTokens,
265
288
  });
289
+ const webSearchTool = options.webSearch?.enabled === true ? createWebSearchTool(options.webSearch) : null;
266
290
  const customTools = [
267
291
  createBrowserTool(sessionWorkspaceDir),
268
292
  createSaveExperienceTool(sessionId),
@@ -274,11 +298,57 @@ For downloads, provide either a direct URL or a selector to click.`;
274
298
  createGetBookmarkTagsTool(),
275
299
  createSaveBookmarkTool(),
276
300
  createAddBookmarkTagTool(),
301
+ ...(webSearchTool ? [webSearchTool] : []),
277
302
  ...mcpTools,
278
303
  ];
304
+ // 分类打印 token 占用估算(字符数 + 估算 token),便于分析超长请求来源
305
+ try {
306
+ const loadedSkills = loader.getSkills().skills;
307
+ const shortSkills = loadedSkills.map((s) => ({
308
+ ...s,
309
+ description: s.description.length <= MAX_SKILL_DESC_IN_PROMPT
310
+ ? s.description
311
+ : s.description.slice(0, MAX_SKILL_DESC_IN_PROMPT) + "…",
312
+ }));
313
+ const skillsBlock = formatSkillsForPrompt(shortSkills);
314
+ const basePrompt = this.buildSystemPrompt(loadedSkills);
315
+ const withCustom = options.systemPrompt?.trim()
316
+ ? options.systemPrompt.trim() + "\n\n" + basePrompt
317
+ : basePrompt;
318
+ const sessionIdentity = { agentId, workspace: workspaceName };
319
+ const withIdentity = sessionIdentity?.agentId
320
+ ? `[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
321
+ : withCustom;
322
+ const systemPromptChars = withIdentity.length;
323
+ const toolsDefs = [
324
+ ...Object.values(coreTools),
325
+ ...customTools,
326
+ ].map((t) => ({
327
+ name: t?.name ?? "",
328
+ description: typeof t?.description === "string" ? t.description : "",
329
+ parameters: t?.parameters ?? {},
330
+ }));
331
+ const toolsJsonChars = JSON.stringify(toolsDefs).length;
332
+ const systemPromptEstTokens = estTokensFromChars(systemPromptChars);
333
+ const skillsBlockEstTokens = estTokensFromChars(skillsBlock.length);
334
+ const toolsDefsEstTokens = estTokensFromChars(toolsJsonChars);
335
+ 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}`);
336
+ setTokenUsageInitialStats(compositeKey, {
337
+ systemPromptEstTokens,
338
+ skillsBlockEstTokens,
339
+ toolsDefsEstTokens,
340
+ });
341
+ console.log(`${TOKEN_USAGE_LOG_PREFIX} compaction (SDK): 触发条件 contextTokens > contextWindow - reserveTokens (默认 16384);保留最近 keepRecentTokens (默认 20000)。配置见 pi 文档 settings.json 或传入 settingsManager。`);
342
+ }
343
+ catch (e) {
344
+ console.warn(`${TOKEN_USAGE_LOG_PREFIX} session create log failed:`, e);
345
+ }
279
346
  const { session } = await createAgentSession({
280
347
  agentDir: this.agentDir,
281
348
  sessionManager: CoreSessionManager.inMemory(),
349
+ settingsManager: SettingsManager.inMemory({
350
+ compaction: { enabled: true, reserveTokens: 16384, keepRecentTokens: 20000 },
351
+ }),
282
352
  authStorage,
283
353
  modelRegistry,
284
354
  cwd: sessionWorkspaceDir,
@@ -0,0 +1,2 @@
1
+ import type { IAgentProxyAdapter } from "../types.js";
2
+ export declare const claudeCodeAdapter: IAgentProxyAdapter;
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Claude Code CLI AgentProxy 适配器:通过 @instantlyeasy/claude-code-sdk-ts 调用本机 Claude Code CLI,
3
+ * 将用户消息作为 prompt 提交,流式/一次性返回 CLI 输出(类似 Codea 的代理方式)。
4
+ * 底层 CLI 按完整 message 返回,无 token 级流式;此处对收到的整段正文做分片推送,使前端能逐片渲染,避免长时间空白后一次性刷屏。
5
+ * 需本机已安装并可用 `claude` 命令。
6
+ */
7
+ import { join, resolve } from "path";
8
+ import { query } from "@instantlyeasy/claude-code-sdk-ts";
9
+ import { getOpenbotWorkspaceDir } from "../../agent-dir.js";
10
+ const DEFAULT_TIMEOUT_MS = 300_000;
11
+ function getCwd(config) {
12
+ const custom = config.claudeCode?.workingDirectory;
13
+ if (typeof custom === "string" && custom.trim()) {
14
+ return resolve(custom.trim());
15
+ }
16
+ const w = config.workspace;
17
+ if (typeof w !== "string" || !w.trim())
18
+ return undefined;
19
+ const name = w.trim();
20
+ return join(getOpenbotWorkspaceDir(), name);
21
+ }
22
+ /** 工具名到简短中文描述的映射,用于过程提示 */
23
+ const TOOL_LABELS = {
24
+ Read: "读取文件",
25
+ Write: "写入文件",
26
+ Edit: "编辑",
27
+ Bash: "执行命令",
28
+ Grep: "搜索",
29
+ Glob: "文件匹配",
30
+ LS: "列出目录",
31
+ MultiEdit: "多文件编辑",
32
+ NotebookRead: "读取 Notebook",
33
+ NotebookEdit: "编辑 Notebook",
34
+ WebFetch: "网页抓取",
35
+ TodoRead: "读取待办",
36
+ TodoWrite: "写入待办",
37
+ WebSearch: "网页搜索",
38
+ Task: "任务",
39
+ MCPTool: "MCP 工具",
40
+ };
41
+ /** 从 tool_use input 中取一行简短摘要(用于前端过程展示) */
42
+ function toolUseSummary(name, input) {
43
+ if (name === "Read" && typeof input.path === "string")
44
+ return input.path;
45
+ if (name === "Write" && typeof input.path === "string")
46
+ return input.path;
47
+ if (name === "Bash" && typeof input.command === "string") {
48
+ const cmd = input.command.trim().split(/\n/)[0] ?? "";
49
+ return cmd.length > 60 ? cmd.slice(0, 57) + "…" : cmd;
50
+ }
51
+ if (name === "Grep" && typeof input.pattern === "string")
52
+ return `"${input.pattern.slice(0, 40)}"`;
53
+ if (name === "Edit" && typeof input.path === "string")
54
+ return input.path;
55
+ return "";
56
+ }
57
+ /**
58
+ * 处理单条 Message:提取可展示的正文,并通过 onChunk 推送过程信息(工具调用、系统消息等)。
59
+ */
60
+ function processMessage(msg, callbacks) {
61
+ if (msg.type === "result" && typeof msg.content === "string") {
62
+ return msg.content;
63
+ }
64
+ if (msg.type === "system") {
65
+ const subtype = msg.subtype;
66
+ const data = msg.data;
67
+ const label = subtype === "init" ? "初始化" : subtype ? String(subtype) : "系统";
68
+ const extra = data != null && typeof data === "object" && !Array.isArray(data)
69
+ ? ""
70
+ : typeof data === "string" ? ` ${data.slice(0, 80)}` : "";
71
+ callbacks.onChunk(`\n[Claude Code] ${label}${extra}\n`);
72
+ return "";
73
+ }
74
+ if (msg.type === "assistant" && Array.isArray(msg.content)) {
75
+ const blocks = msg.content;
76
+ let text = "";
77
+ for (const b of blocks) {
78
+ if (b.type === "text" && typeof b.text === "string") {
79
+ text += b.text;
80
+ }
81
+ else if (b.type === "tool_use" && b.name) {
82
+ const label = TOOL_LABELS[b.name] ?? b.name;
83
+ const summary = b.input && typeof b.input === "object" ? toolUseSummary(b.name, b.input) : "";
84
+ const detail = summary ? `: ${summary}` : "";
85
+ callbacks.onChunk(`\n\n---\n🔧 **使用工具**: ${label}${detail}\n---\n\n`);
86
+ }
87
+ else if (b.type === "tool_result") {
88
+ callbacks.onChunk("\n✓ 工具返回\n");
89
+ }
90
+ }
91
+ return text;
92
+ }
93
+ return "";
94
+ }
95
+ export const claudeCodeAdapter = {
96
+ type: "claude_code",
97
+ async runStream(options, config, callbacks) {
98
+ const cwd = getCwd(config);
99
+ const controller = new AbortController();
100
+ const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
101
+ const userSignal = options.signal;
102
+ if (userSignal) {
103
+ if (userSignal.aborted)
104
+ controller.abort();
105
+ else
106
+ userSignal.addEventListener("abort", () => controller.abort(), { once: true });
107
+ }
108
+ try {
109
+ callbacks.onChunk("⏳ Claude Code CLI 正在处理…\n\n");
110
+ const generator = query(options.message, {
111
+ cwd: cwd ?? process.cwd(),
112
+ signal: controller.signal,
113
+ timeout: Math.floor(DEFAULT_TIMEOUT_MS / 1000),
114
+ });
115
+ for await (const message of generator) {
116
+ if (controller.signal.aborted)
117
+ break;
118
+ const text = processMessage(message, callbacks);
119
+ if (text)
120
+ callbacks.onChunk(text);
121
+ }
122
+ callbacks.onDone();
123
+ }
124
+ catch (err) {
125
+ const msg = err instanceof Error ? err.message : String(err);
126
+ const name = err instanceof Error ? err.name : "";
127
+ const isUserOrTimeoutAbort = controller.signal.aborted ||
128
+ name === "AbortError" ||
129
+ (typeof msg === "string" && (msg === "Aborted" || msg === "The operation was aborted"));
130
+ if (isUserOrTimeoutAbort) {
131
+ callbacks.onChunk("\n\n[已中止]");
132
+ }
133
+ else {
134
+ console.error("[Claude Code adapter] runStream error:", err);
135
+ callbacks.onChunk(`\n\n[错误] ${msg}`);
136
+ }
137
+ callbacks.onDone();
138
+ }
139
+ finally {
140
+ clearTimeout(timeoutId);
141
+ }
142
+ },
143
+ async runCollect(options, config) {
144
+ const cwd = getCwd(config);
145
+ const controller = new AbortController();
146
+ const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
147
+ const userSignal = options.signal;
148
+ if (userSignal) {
149
+ if (userSignal.aborted)
150
+ controller.abort();
151
+ else
152
+ userSignal.addEventListener("abort", () => controller.abort(), { once: true });
153
+ }
154
+ const parts = [];
155
+ const collectChunk = (delta) => parts.push(delta);
156
+ try {
157
+ const generator = query(options.message, {
158
+ cwd: cwd ?? process.cwd(),
159
+ signal: controller.signal,
160
+ timeout: Math.floor(DEFAULT_TIMEOUT_MS / 1000),
161
+ });
162
+ for await (const message of generator) {
163
+ if (controller.signal.aborted)
164
+ break;
165
+ const text = processMessage(message, { onChunk: collectChunk });
166
+ if (text)
167
+ parts.push(text);
168
+ }
169
+ return parts.join("").trim() || "(无文本回复)";
170
+ }
171
+ catch (err) {
172
+ const msg = err instanceof Error ? err.message : String(err);
173
+ const name = err instanceof Error ? err.name : "";
174
+ const isAbort = controller.signal.aborted ||
175
+ name === "AbortError" ||
176
+ (typeof msg === "string" && (msg === "Aborted" || msg === "The operation was aborted"));
177
+ if (isAbort)
178
+ return parts.join("").trim() + "\n\n[已中止]";
179
+ console.error("[Claude Code adapter] runCollect error:", err);
180
+ throw err;
181
+ }
182
+ finally {
183
+ clearTimeout(timeoutId);
184
+ }
185
+ },
186
+ };
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { agentManager } from "../../agent-manager.js";
5
5
  import { getDesktopConfig } from "../../../config/desktop-config.js";
6
- const CHANNEL_AGENT_TIMEOUT_MS = 120_000;
6
+ const CHANNEL_AGENT_TIMEOUT_MS = 300_000; // 5 分钟,本地/大模型回复可能较慢
7
7
  export const localAdapter = {
8
8
  type: "local",
9
9
  async runStream(options, config, callbacks) {
@@ -21,6 +21,7 @@ export const localAdapter = {
21
21
  mcpServers: config.mcpServers,
22
22
  systemPrompt: config.systemPrompt,
23
23
  useLongMemory: config.useLongMemory,
24
+ webSearch: config.webSearch,
24
25
  });
25
26
  let resolveDone;
26
27
  const donePromise = new Promise((r) => {
@@ -64,6 +65,7 @@ export const localAdapter = {
64
65
  mcpServers: config.mcpServers,
65
66
  systemPrompt: config.systemPrompt,
66
67
  useLongMemory: config.useLongMemory,
68
+ webSearch: config.webSearch,
67
69
  });
68
70
  const chunks = [];
69
71
  let resolveDone;