@next-open-ai/openbot 0.6.8 → 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.
Files changed (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +155 -136
  3. package/apps/desktop/renderer/dist/assets/index-CxDZnMBH.css +10 -0
  4. package/apps/desktop/renderer/dist/assets/index-k47Qiokg.js +93 -0
  5. package/apps/desktop/renderer/dist/index.html +2 -2
  6. package/dist/cli/cli.js +136 -0
  7. package/dist/cli/extension-cmd.d.ts +15 -0
  8. package/dist/cli/extension-cmd.js +107 -0
  9. package/dist/core/agent/agent-dir.d.ts +6 -0
  10. package/dist/core/agent/agent-dir.js +8 -0
  11. package/dist/core/agent/agent-manager.d.ts +27 -6
  12. package/dist/core/agent/agent-manager.js +147 -26
  13. package/dist/core/agent/proxy/adapters/claude-code-adapter.d.ts +2 -0
  14. package/dist/core/agent/proxy/adapters/claude-code-adapter.js +186 -0
  15. package/dist/core/agent/proxy/adapters/coze-adapter.d.ts +2 -0
  16. package/dist/core/agent/proxy/adapters/coze-adapter.js +406 -0
  17. package/dist/core/agent/proxy/adapters/local-adapter.d.ts +2 -0
  18. package/dist/core/agent/proxy/adapters/local-adapter.js +95 -0
  19. package/dist/core/agent/proxy/adapters/openclawx-adapter.d.ts +2 -0
  20. package/dist/core/agent/proxy/adapters/openclawx-adapter.js +115 -0
  21. package/dist/core/agent/proxy/adapters/opencode-adapter.d.ts +11 -0
  22. package/dist/core/agent/proxy/adapters/opencode-adapter.js +786 -0
  23. package/dist/core/agent/proxy/adapters/opencode-free-models.d.ts +20 -0
  24. package/dist/core/agent/proxy/adapters/opencode-free-models.js +14 -0
  25. package/dist/core/agent/proxy/adapters/opencode-local-runner.d.ts +5 -0
  26. package/dist/core/agent/proxy/adapters/opencode-local-runner.js +95 -0
  27. package/dist/core/agent/proxy/index.d.ts +3 -0
  28. package/dist/core/agent/proxy/index.js +18 -0
  29. package/dist/core/agent/proxy/registry.d.ts +7 -0
  30. package/dist/core/agent/proxy/registry.js +13 -0
  31. package/dist/core/agent/proxy/run-for-channel.d.ts +3 -0
  32. package/dist/core/agent/proxy/run-for-channel.js +31 -0
  33. package/dist/core/agent/proxy/types.d.ts +30 -0
  34. package/dist/core/agent/proxy/types.js +1 -0
  35. package/dist/core/agent/run.js +1 -1
  36. package/dist/core/agent/token-usage-log-extension.d.ts +14 -0
  37. package/dist/core/agent/token-usage-log-extension.js +61 -0
  38. package/dist/core/config/agent-reload-pending.d.ts +9 -0
  39. package/dist/core/config/agent-reload-pending.js +67 -0
  40. package/dist/core/config/desktop-config.d.ts +136 -5
  41. package/dist/core/config/desktop-config.js +470 -46
  42. package/dist/core/config/provider-support-default.js +27 -0
  43. package/dist/core/extensions/index.d.ts +1 -0
  44. package/dist/core/extensions/index.js +1 -0
  45. package/dist/core/extensions/load.d.ts +11 -0
  46. package/dist/core/extensions/load.js +101 -0
  47. package/dist/core/inbound-message-preprocess.d.ts +27 -0
  48. package/dist/core/inbound-message-preprocess.js +96 -0
  49. package/dist/core/local-llm-server/download-model.d.ts +16 -0
  50. package/dist/core/local-llm-server/download-model.js +37 -0
  51. package/dist/core/local-llm-server/index.d.ts +32 -0
  52. package/dist/core/local-llm-server/index.js +152 -0
  53. package/dist/core/local-llm-server/llm-context.d.ts +66 -0
  54. package/dist/core/local-llm-server/llm-context.js +270 -0
  55. package/dist/core/local-llm-server/model-resolve.d.ts +27 -0
  56. package/dist/core/local-llm-server/model-resolve.js +90 -0
  57. package/dist/core/local-llm-server/server.d.ts +1 -0
  58. package/dist/core/local-llm-server/server.js +234 -0
  59. package/dist/core/local-llm-server/start-from-config.d.ts +5 -0
  60. package/dist/core/local-llm-server/start-from-config.js +50 -0
  61. package/dist/core/mcp/adapter.d.ts +4 -2
  62. package/dist/core/mcp/adapter.js +10 -4
  63. package/dist/core/mcp/client.d.ts +4 -0
  64. package/dist/core/mcp/client.js +2 -0
  65. package/dist/core/mcp/config.d.ts +14 -3
  66. package/dist/core/mcp/config.js +68 -3
  67. package/dist/core/mcp/index.d.ts +10 -6
  68. package/dist/core/mcp/index.js +7 -3
  69. package/dist/core/mcp/operator.d.ts +28 -2
  70. package/dist/core/mcp/operator.js +131 -30
  71. package/dist/core/mcp/transport/index.d.ts +4 -0
  72. package/dist/core/mcp/transport/index.js +6 -1
  73. package/dist/core/mcp/transport/stdio.d.ts +12 -0
  74. package/dist/core/mcp/transport/stdio.js +147 -29
  75. package/dist/core/mcp/types.d.ts +18 -0
  76. package/dist/core/memory/compaction-extension.d.ts +4 -3
  77. package/dist/core/memory/compaction-extension.js +6 -14
  78. package/dist/core/memory/embedding-types.d.ts +10 -0
  79. package/dist/core/memory/embedding-types.js +5 -0
  80. package/dist/core/memory/embedding.d.ts +2 -1
  81. package/dist/core/memory/embedding.js +38 -6
  82. package/dist/core/memory/index.js +3 -0
  83. package/dist/core/memory/local-embedding-llama.d.ts +13 -0
  84. package/dist/core/memory/local-embedding-llama.js +78 -0
  85. package/dist/core/memory/local-embedding.d.ts +11 -0
  86. package/dist/core/memory/local-embedding.js +69 -0
  87. package/dist/core/memory/persist-compaction-on-close.d.ts +14 -0
  88. package/dist/core/memory/persist-compaction-on-close.js +32 -0
  89. package/dist/core/session-outlet/index.d.ts +19 -0
  90. package/dist/core/session-outlet/index.js +33 -0
  91. package/dist/core/session-outlet/outlet.d.ts +15 -0
  92. package/dist/core/session-outlet/outlet.js +49 -0
  93. package/dist/core/session-outlet/types.d.ts +35 -0
  94. package/dist/core/session-outlet/types.js +5 -0
  95. package/dist/core/tools/bookmark-tool.d.ts +4 -0
  96. package/dist/core/tools/bookmark-tool.js +59 -3
  97. package/dist/core/tools/index.d.ts +3 -1
  98. package/dist/core/tools/index.js +3 -1
  99. package/dist/core/tools/memory-recall-tool.d.ts +6 -0
  100. package/dist/core/tools/memory-recall-tool.js +77 -0
  101. package/dist/core/tools/truncate-result.d.ts +14 -0
  102. package/dist/core/tools/truncate-result.js +27 -0
  103. package/dist/core/tools/web-search/create-web-search-tool.d.ts +17 -0
  104. package/dist/core/tools/web-search/create-web-search-tool.js +87 -0
  105. package/dist/core/tools/web-search/index.d.ts +4 -0
  106. package/dist/core/tools/web-search/index.js +2 -0
  107. package/dist/core/tools/web-search/providers/brave.d.ts +2 -0
  108. package/dist/core/tools/web-search/providers/brave.js +87 -0
  109. package/dist/core/tools/web-search/providers/duck-duck-scrape.d.ts +2 -0
  110. package/dist/core/tools/web-search/providers/duck-duck-scrape.js +47 -0
  111. package/dist/core/tools/web-search/providers/index.d.ts +5 -0
  112. package/dist/core/tools/web-search/providers/index.js +13 -0
  113. package/dist/core/tools/web-search/types.d.ts +35 -0
  114. package/dist/core/tools/web-search/types.js +4 -0
  115. package/dist/gateway/channel/adapters/telegram.js +13 -2
  116. package/dist/gateway/channel/adapters/wechat.d.ts +24 -0
  117. package/dist/gateway/channel/adapters/wechat.js +205 -0
  118. package/dist/gateway/channel/channel-core.d.ts +1 -0
  119. package/dist/gateway/channel/channel-core.js +101 -59
  120. package/dist/gateway/channel/run-agent.d.ts +2 -4
  121. package/dist/gateway/channel/run-agent.js +13 -125
  122. package/dist/gateway/methods/agent-cancel.d.ts +3 -1
  123. package/dist/gateway/methods/agent-cancel.js +16 -2
  124. package/dist/gateway/methods/agent-chat.d.ts +4 -0
  125. package/dist/gateway/methods/agent-chat.js +377 -118
  126. package/dist/gateway/methods/run-scheduled-task.js +9 -7
  127. package/dist/gateway/proxy-run-abort.d.ts +6 -0
  128. package/dist/gateway/proxy-run-abort.js +39 -0
  129. package/dist/gateway/server.js +123 -19
  130. package/dist/server/agent-config/agent-config.controller.d.ts +10 -2
  131. package/dist/server/agent-config/agent-config.controller.js +19 -4
  132. package/dist/server/agent-config/agent-config.module.js +3 -1
  133. package/dist/server/agent-config/agent-config.service.d.ts +91 -6
  134. package/dist/server/agent-config/agent-config.service.js +115 -3
  135. package/dist/server/agents/agents.controller.d.ts +16 -0
  136. package/dist/server/agents/agents.controller.js +62 -1
  137. package/dist/server/agents/agents.gateway.js +1 -1
  138. package/dist/server/agents/agents.service.js +1 -1
  139. package/dist/server/bootstrap.d.ts +1 -0
  140. package/dist/server/bootstrap.js +28 -4
  141. package/dist/server/config/config.controller.d.ts +134 -2
  142. package/dist/server/config/config.controller.js +199 -3
  143. package/dist/server/config/config.module.js +5 -4
  144. package/dist/server/config/config.service.d.ts +32 -2
  145. package/dist/server/config/config.service.js +69 -9
  146. package/dist/server/config/local-models.service.d.ts +67 -0
  147. package/dist/server/config/local-models.service.js +242 -0
  148. package/dist/server/workspace/workspace.service.d.ts +7 -0
  149. package/dist/server/workspace/workspace.service.js +16 -0
  150. package/package.json +10 -2
  151. package/presets/preset-agents.json +128 -0
  152. package/presets/preset-config.json +29 -0
  153. package/presets/preset-providers.json +180 -0
  154. package/presets/recommended-local-models.json +36 -0
  155. package/presets/workspaces/code-assistant/skills/code-review/SKILL.md +19 -0
  156. package/presets/workspaces/code-assistant/skills/code-runner/SKILL.md +21 -0
  157. package/presets/workspaces/code-assistant/skills/git-helper/SKILL.md +29 -0
  158. package/presets/workspaces/creator-assistant/skills/.gitkeep +0 -0
  159. package/presets/workspaces/creator-assistant/skills/creator-tools/SKILL.md +15 -0
  160. package/presets/workspaces/doc-assistant/skills/doc-processor/SKILL.md +21 -0
  161. package/presets/workspaces/download-assistant/skills/downloader/SKILL.md +20 -0
  162. package/presets/workspaces/file-assistant/skills/file-converter/SKILL.md +21 -0
  163. package/presets/workspaces/file-assistant/skills/file-organizer/SKILL.md +17 -0
  164. package/presets/workspaces/file-assistant/skills/file-search/SKILL.md +22 -0
  165. package/presets/workspaces/morning-briefing/skills/news-fetcher/SKILL.md +16 -0
  166. package/presets/workspaces/morning-briefing/skills/web-summarizer/SKILL.md +20 -0
  167. package/presets/workspaces/news-assistant/skills/news-fetcher/SKILL.md +16 -0
  168. package/presets/workspaces/news-assistant/skills/web-summarizer/SKILL.md +20 -0
  169. package/presets/workspaces/office-automation/skills/rpa-helper/SKILL.md +9 -0
  170. package/presets/workspaces/self-media-bot/skills/self-media-tools/SKILL.md +9 -0
  171. package/skills/url-bookmark/SKILL.md +12 -12
  172. package/apps/desktop/renderer/dist/assets/index-LCp1YPVA.css +0 -10
  173. package/apps/desktop/renderer/dist/assets/index-l5fpDsHs.js +0 -89
@@ -1,9 +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 { getCompactionContextForSystemPrompt } from "../memory/index.js";
6
- import { createBrowserTool, createSaveExperienceTool, createInstallSkillTool, createSwitchAgentTool, createListAgentsTool, createCreateAgentTool, createGetBookmarkTagsTool, createSaveBookmarkTool } from "../tools/index.js";
5
+ import { loadExtensionFactories } from "../extensions/index.js";
6
+ import { addMemory } from "../memory/index.js";
7
+ import { persistStoredCompactionForSession, persistStoredCompactionForBusinessSession, } from "../memory/persist-compaction-on-close.js";
8
+ import { createBrowserTool, createSaveExperienceTool, createMemoryRecallTool, createInstallSkillTool, createSwitchAgentTool, createListAgentsTool, createCreateAgentTool, createGetBookmarkTagsTool, createSaveBookmarkTool, createAddBookmarkTagTool, createWebSearchTool } from "../tools/index.js";
7
9
  /** Agent Session 缓存 key:sessionId + "::" + agentId,同一业务 session 下不同 agent 各自一个 Core Session */
8
10
  const COMPOSITE_KEY_SEP = "::";
9
11
  function toCompositeKey(sessionId, agentId) {
@@ -11,10 +13,16 @@ function toCompositeKey(sessionId, agentId) {
11
13
  }
12
14
  import { createMcpToolsForSession } from "../mcp/index.js";
13
15
  import { registerBuiltInApiProviders } from "@mariozechner/pi-ai/dist/providers/register-builtins.js";
14
- import { getOpenbotAgentDir, getOpenbotWorkspaceDir, ensureDefaultAgentDir } from "./agent-dir.js";
16
+ import { getOpenbotAgentDir, getOpenbotWorkspaceDir } from "./agent-dir.js";
15
17
  import { formatSkillsForPrompt } from "./skills.js";
18
+ import { createTokenUsageLogExtensionFactory, setTokenUsageInitialStats, } from "./token-usage-log-extension.js";
16
19
  // Ensure all built-in providers are registered
17
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]";
18
26
  /** system prompt 中每个技能描述最大字符数,超出截断以省 token */
19
27
  const MAX_SKILL_DESC_IN_PROMPT = 250;
20
28
  /**
@@ -24,6 +32,8 @@ export class AgentManager {
24
32
  sessions = new Map();
25
33
  /** 每个 session 最后被使用的时间戳,用于 LRU 淘汰 */
26
34
  sessionLastActiveAt = new Map();
35
+ /** 每个 SessionAgent 当前最新的 compaction summary,关闭会话时写入向量库(infotype: compaction) */
36
+ sessionLatestCompactionSummary = new Map();
27
37
  agentDir;
28
38
  workspaceDir;
29
39
  skillPaths = [];
@@ -89,10 +99,18 @@ You have access to a \`browser\` tool for web automation:
89
99
 
90
100
  Use refs from snapshots (e.g., @e1) for reliable element selection.
91
101
  For downloads, provide either a direct URL or a selector to click.`;
102
+ const experienceMemoryDesc = `
103
+ ## Long-term memory
104
+
105
+ - **save_experience**: Store summaries in long-term memory. **Call at most once per conversation**, and **only after all tasks are fully completed** (as the last step before ending your reply). **Do not call it for very simple dialogue tasks** (e.g. single-turn Q&A, greetings, trivial questions). When you do call it, briefly summarize the main outcomes, conclusions, or reusable points in one \`save_experience\` call.
106
+ - **memory_recall**: Retrieve relevant memories by semantic search. **When the user asks about past work, decisions, dates, people, preferences, todos, complex tasks, scheduled tasks, or anything that may need past experience**, call \`memory_recall\` first with a suitable query, then answer using the recalled content. Do not inject any history or memory into your replies unless you have just retrieved it via \`memory_recall\` for that question.`;
107
+ const terminologyNote = "【术语】本系统中「智能体」「助手」「专家」均指同一概念(Agent),可互换使用。用户说切换助手/专家或提到某个助手/专家时,即指切换或使用对应智能体。";
92
108
  const parts = [
109
+ terminologyNote,
93
110
  "You are a helpful assistant. When users ask about skills, explain what skills are available.",
94
111
  browserToolDesc,
95
112
  skillsBlock,
113
+ experienceMemoryDesc,
96
114
  ].filter(Boolean);
97
115
  return parts.join("\n\n");
98
116
  }
@@ -106,27 +124,33 @@ For downloads, provide either a direct URL or a selector to click.`;
106
124
  const systemPrompt = this.buildSystemPrompt(loadedSkills);
107
125
  return { systemPrompt, skills: loadedSkills };
108
126
  }
109
- createResourceLoader(workspaceDir, sessionId, compactionBlock, customAgentPrompt, identity) {
127
+ createResourceLoader(workspaceDir, sessionId, customAgentPrompt, identity, onUpdateLatestCompaction) {
110
128
  const loader = new DefaultResourceLoader({
111
129
  cwd: workspaceDir,
112
130
  agentDir: this.agentDir,
113
131
  noSkills: true, // Disable SDK's built-in skills logic to take full control
114
132
  additionalSkillPaths: this.resolveSkillPaths(workspaceDir),
115
- extensionFactories: sessionId ? [createCompactionMemoryExtensionFactory(sessionId)] : [],
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
+ })(),
116
144
  systemPromptOverride: (base) => {
117
145
  const loadedSkills = loader.getSkills().skills;
118
- let basePrompt = this.buildSystemPrompt(loadedSkills);
146
+ const basePrompt = this.buildSystemPrompt(loadedSkills);
119
147
  const withCustom = customAgentPrompt && customAgentPrompt.trim()
120
148
  ? customAgentPrompt.trim() + "\n\n" + basePrompt
121
149
  : basePrompt;
122
- const withIdentity = identity && identity.agentId
150
+ return identity && identity.agentId
123
151
  ? `[Session identity] You are the agent with ID: ${identity.agentId}, workspace: ${identity.workspace || identity.agentId}. When asked which agent you are, answer according to this identity.\n\n` +
124
152
  withCustom
125
153
  : withCustom;
126
- if (compactionBlock?.trim()) {
127
- return withIdentity + "\n\n" + compactionBlock.trim();
128
- }
129
- return withIdentity;
130
154
  },
131
155
  });
132
156
  return loader;
@@ -182,7 +206,7 @@ For downloads, provide either a direct URL or a selector to click.`;
182
206
  }
183
207
  }
184
208
  if (oldestId != null) {
185
- this.deleteSession(oldestId);
209
+ await this.deleteSession(oldestId);
186
210
  }
187
211
  }
188
212
  const workspaceRoot = getOpenbotWorkspaceDir();
@@ -194,13 +218,22 @@ For downloads, provide either a direct URL or a selector to click.`;
194
218
  const provider = options.provider ?? process.env.OPENBOT_PROVIDER ?? "deepseek";
195
219
  const modelId = options.modelId ?? process.env.OPENBOT_MODEL ?? "deepseek-chat";
196
220
  const apiKey = options.apiKey;
197
- 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
+ }
198
227
  const authPath = join(this.agentDir, "auth.json");
199
228
  const modelsPath = join(this.agentDir, "models.json");
200
229
  const authStorage = new AuthStorage(authPath);
201
230
  if (apiKey) {
202
231
  authStorage.setRuntimeApiKey(provider, apiKey);
203
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
+ }
204
237
  if (await authStorage.hasAuth(provider)) {
205
238
  const key = await authStorage.getApiKey(provider);
206
239
  if (key) {
@@ -236,10 +269,11 @@ For downloads, provide either a direct URL or a selector to click.`;
236
269
  return process.env.MOONSHOT_API_KEY || process.env.KIMI_API_KEY || process.env.OPENAI_API_KEY;
237
270
  if (p === "openai" || p === "openai-custom")
238
271
  return process.env.OPENAI_API_KEY;
272
+ if (p === "local")
273
+ return process.env.OPENAI_API_KEY || "local";
239
274
  return process.env.OPENAI_API_KEY;
240
275
  });
241
- const compactionBlock = await getCompactionContextForSystemPrompt(sessionId);
242
- const loader = this.createResourceLoader(sessionWorkspaceDir, sessionId, compactionBlock, options.systemPrompt, { agentId, workspace: workspaceName });
276
+ const loader = this.createResourceLoader(sessionWorkspaceDir, sessionId, options.systemPrompt, { agentId, workspace: workspaceName }, (summary) => this.sessionLatestCompactionSummary.set(compositeKey, summary));
243
277
  await loader.reload();
244
278
  const coreTools = {
245
279
  read: createReadTool(sessionWorkspaceDir),
@@ -250,21 +284,75 @@ For downloads, provide either a direct URL or a selector to click.`;
250
284
  grep: createGrepTool(sessionWorkspaceDir),
251
285
  ls: createLsTool(sessionWorkspaceDir),
252
286
  };
253
- const mcpTools = await createMcpToolsForSession({ mcpServers: options.mcpServers });
287
+ const useLongMemory = options.useLongMemory !== false;
288
+ const mcpTools = await createMcpToolsForSession({
289
+ mcpServers: options.mcpServers,
290
+ sessionId,
291
+ mcpMaxResultTokens: options.mcpMaxResultTokens,
292
+ });
293
+ const webSearchTool = options.webSearch?.enabled === true ? createWebSearchTool(options.webSearch) : null;
254
294
  const customTools = [
255
295
  createBrowserTool(sessionWorkspaceDir),
256
296
  createSaveExperienceTool(sessionId),
297
+ createMemoryRecallTool(useLongMemory),
257
298
  createInstallSkillTool(options.targetAgentId ?? agentId),
258
299
  createSwitchAgentTool(sessionId),
259
300
  createListAgentsTool(),
260
301
  createCreateAgentTool(),
261
302
  createGetBookmarkTagsTool(),
262
303
  createSaveBookmarkTool(),
304
+ createAddBookmarkTagTool(),
305
+ ...(webSearchTool ? [webSearchTool] : []),
263
306
  ...mcpTools,
264
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
+ }
265
350
  const { session } = await createAgentSession({
266
351
  agentDir: this.agentDir,
267
352
  sessionManager: CoreSessionManager.inMemory(),
353
+ settingsManager: SettingsManager.inMemory({
354
+ compaction: { enabled: true, reserveTokens: 16384, keepRecentTokens: 20000 },
355
+ }),
268
356
  authStorage,
269
357
  modelRegistry,
270
358
  cwd: sessionWorkspaceDir,
@@ -277,6 +365,13 @@ For downloads, provide either a direct URL or a selector to click.`;
277
365
  console.log(`Setting model to ${model.provider}/${model.id} (workspace: ${workspaceName})`);
278
366
  await session.setModel(model);
279
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
+ }
280
375
  this.sessions.set(compositeKey, session);
281
376
  this.sessionLastActiveAt.set(compositeKey, now);
282
377
  return session;
@@ -285,24 +380,50 @@ For downloads, provide either a direct URL or a selector to click.`;
285
380
  getSession(compositeKey) {
286
381
  return this.sessions.get(compositeKey);
287
382
  }
288
- /** 删除一个 Agent Session(传入复合 key) */
289
- deleteSession(compositeKey) {
383
+ /** 按业务 sessionId 查找一个 Session(取最近活跃的),用于 agent.cancel */
384
+ getSessionBySessionId(sessionId) {
385
+ const prefix = sessionId + COMPOSITE_KEY_SEP;
386
+ let bestKey;
387
+ let bestAt = 0;
388
+ for (const key of this.sessions.keys()) {
389
+ if (!key.startsWith(prefix))
390
+ continue;
391
+ const at = this.sessionLastActiveAt.get(key) ?? 0;
392
+ if (at >= bestAt) {
393
+ bestAt = at;
394
+ bestKey = key;
395
+ }
396
+ }
397
+ return bestKey != null ? this.sessions.get(bestKey) : undefined;
398
+ }
399
+ /** 删除一个 Agent Session(传入复合 key);关闭前将本 session 最新 compaction summary 写入向量库 */
400
+ async deleteSession(compositeKey) {
401
+ await persistStoredCompactionForSession(this.sessionLatestCompactionSummary, compositeKey, addMemory);
290
402
  this.sessionLastActiveAt.delete(compositeKey);
291
403
  return this.sessions.delete(compositeKey);
292
404
  }
293
- /** 按业务 sessionId 删除该会话下所有 agent 的 Core Session(如删除会话时) */
294
- deleteSessionsByBusinessId(sessionId) {
405
+ /** 按业务 sessionId 删除该会话下所有 agent 的 Core Session(如删除会话时);关闭前将各 session 最新 compaction 写入向量库 */
406
+ async deleteSessionsByBusinessId(sessionId) {
295
407
  const prefix = sessionId + COMPOSITE_KEY_SEP;
296
- for (const key of Array.from(this.sessions.keys())) {
297
- if (key.startsWith(prefix)) {
298
- this.sessionLastActiveAt.delete(key);
299
- this.sessions.delete(key);
300
- }
408
+ const keysToProcess = Array.from(this.sessions.keys()).filter((k) => k.startsWith(prefix));
409
+ await persistStoredCompactionForBusinessSession(this.sessionLatestCompactionSummary, keysToProcess, sessionId, addMemory);
410
+ for (const key of keysToProcess) {
411
+ this.sessionLastActiveAt.delete(key);
412
+ this.sessions.delete(key);
413
+ }
414
+ }
415
+ /** 按 agentId 删除该智能体下所有 Session(配置更新后使旧会话失效,下次请求会用新配置建新会话) */
416
+ async deleteSessionsByAgentId(agentId) {
417
+ const suffix = COMPOSITE_KEY_SEP + agentId;
418
+ const keysToProcess = Array.from(this.sessions.keys()).filter((k) => k.endsWith(suffix));
419
+ for (const key of keysToProcess) {
420
+ await this.deleteSession(key);
301
421
  }
302
422
  }
303
423
  clearAll() {
304
424
  this.sessions.clear();
305
425
  this.sessionLastActiveAt.clear();
426
+ this.sessionLatestCompactionSummary.clear();
306
427
  }
307
428
  }
308
429
  // Singleton for easy access (e.g., from Gateway)
@@ -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
+ };
@@ -0,0 +1,2 @@
1
+ import type { IAgentProxyAdapter } from "../types.js";
2
+ export declare const cozeAdapter: IAgentProxyAdapter;