@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.
- package/LICENSE +21 -0
- package/README.md +155 -136
- 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 +27 -6
- package/dist/core/agent/agent-manager.js +147 -26
- 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/coze-adapter.d.ts +2 -0
- package/dist/core/agent/proxy/adapters/coze-adapter.js +406 -0
- package/dist/core/agent/proxy/adapters/local-adapter.d.ts +2 -0
- package/dist/core/agent/proxy/adapters/local-adapter.js +95 -0
- package/dist/core/agent/proxy/adapters/openclawx-adapter.d.ts +2 -0
- package/dist/core/agent/proxy/adapters/openclawx-adapter.js +115 -0
- package/dist/core/agent/proxy/adapters/opencode-adapter.d.ts +11 -0
- package/dist/core/agent/proxy/adapters/opencode-adapter.js +786 -0
- package/dist/core/agent/proxy/adapters/opencode-free-models.d.ts +20 -0
- package/dist/core/agent/proxy/adapters/opencode-free-models.js +14 -0
- package/dist/core/agent/proxy/adapters/opencode-local-runner.d.ts +5 -0
- package/dist/core/agent/proxy/adapters/opencode-local-runner.js +95 -0
- package/dist/core/agent/proxy/index.d.ts +3 -0
- package/dist/core/agent/proxy/index.js +18 -0
- package/dist/core/agent/proxy/registry.d.ts +7 -0
- package/dist/core/agent/proxy/registry.js +13 -0
- package/dist/core/agent/proxy/run-for-channel.d.ts +3 -0
- package/dist/core/agent/proxy/run-for-channel.js +31 -0
- package/dist/core/agent/proxy/types.d.ts +30 -0
- package/dist/core/agent/proxy/types.js +1 -0
- package/dist/core/agent/run.js +1 -1
- 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.d.ts +9 -0
- package/dist/core/config/agent-reload-pending.js +67 -0
- package/dist/core/config/desktop-config.d.ts +136 -5
- package/dist/core/config/desktop-config.js +470 -46
- 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/inbound-message-preprocess.d.ts +27 -0
- package/dist/core/inbound-message-preprocess.js +96 -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/client.d.ts +4 -0
- package/dist/core/mcp/client.js +2 -0
- package/dist/core/mcp/config.d.ts +14 -3
- package/dist/core/mcp/config.js +68 -3
- package/dist/core/mcp/index.d.ts +10 -6
- package/dist/core/mcp/index.js +7 -3
- package/dist/core/mcp/operator.d.ts +28 -2
- package/dist/core/mcp/operator.js +131 -30
- package/dist/core/mcp/transport/index.d.ts +4 -0
- package/dist/core/mcp/transport/index.js +6 -1
- package/dist/core/mcp/transport/stdio.d.ts +12 -0
- package/dist/core/mcp/transport/stdio.js +147 -29
- package/dist/core/mcp/types.d.ts +18 -0
- package/dist/core/memory/compaction-extension.d.ts +4 -3
- package/dist/core/memory/compaction-extension.js +6 -14
- package/dist/core/memory/embedding-types.d.ts +10 -0
- package/dist/core/memory/embedding-types.js +5 -0
- package/dist/core/memory/embedding.d.ts +2 -1
- package/dist/core/memory/embedding.js +38 -6
- package/dist/core/memory/index.js +3 -0
- package/dist/core/memory/local-embedding-llama.d.ts +13 -0
- package/dist/core/memory/local-embedding-llama.js +78 -0
- package/dist/core/memory/local-embedding.d.ts +11 -0
- package/dist/core/memory/local-embedding.js +69 -0
- package/dist/core/memory/persist-compaction-on-close.d.ts +14 -0
- package/dist/core/memory/persist-compaction-on-close.js +32 -0
- package/dist/core/session-outlet/index.d.ts +19 -0
- package/dist/core/session-outlet/index.js +33 -0
- package/dist/core/session-outlet/outlet.d.ts +15 -0
- package/dist/core/session-outlet/outlet.js +49 -0
- package/dist/core/session-outlet/types.d.ts +35 -0
- package/dist/core/session-outlet/types.js +5 -0
- package/dist/core/tools/bookmark-tool.d.ts +4 -0
- package/dist/core/tools/bookmark-tool.js +59 -3
- package/dist/core/tools/index.d.ts +3 -1
- package/dist/core/tools/index.js +3 -1
- package/dist/core/tools/memory-recall-tool.d.ts +6 -0
- package/dist/core/tools/memory-recall-tool.js +77 -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/channel/adapters/telegram.js +13 -2
- package/dist/gateway/channel/adapters/wechat.d.ts +24 -0
- package/dist/gateway/channel/adapters/wechat.js +205 -0
- package/dist/gateway/channel/channel-core.d.ts +1 -0
- package/dist/gateway/channel/channel-core.js +101 -59
- package/dist/gateway/channel/run-agent.d.ts +2 -4
- package/dist/gateway/channel/run-agent.js +13 -125
- package/dist/gateway/methods/agent-cancel.d.ts +3 -1
- package/dist/gateway/methods/agent-cancel.js +16 -2
- package/dist/gateway/methods/agent-chat.d.ts +4 -0
- package/dist/gateway/methods/agent-chat.js +377 -118
- package/dist/gateway/methods/run-scheduled-task.js +9 -7
- package/dist/gateway/proxy-run-abort.d.ts +6 -0
- package/dist/gateway/proxy-run-abort.js +39 -0
- package/dist/gateway/server.js +123 -19
- package/dist/server/agent-config/agent-config.controller.d.ts +10 -2
- package/dist/server/agent-config/agent-config.controller.js +19 -4
- package/dist/server/agent-config/agent-config.module.js +3 -1
- package/dist/server/agent-config/agent-config.service.d.ts +91 -6
- package/dist/server/agent-config/agent-config.service.js +115 -3
- package/dist/server/agents/agents.controller.d.ts +16 -0
- package/dist/server/agents/agents.controller.js +62 -1
- package/dist/server/agents/agents.gateway.js +1 -1
- package/dist/server/agents/agents.service.js +1 -1
- package/dist/server/bootstrap.d.ts +1 -0
- package/dist/server/bootstrap.js +28 -4
- package/dist/server/config/config.controller.d.ts +134 -2
- package/dist/server/config/config.controller.js +199 -3
- package/dist/server/config/config.module.js +5 -4
- package/dist/server/config/config.service.d.ts +32 -2
- package/dist/server/config/config.service.js +69 -9
- package/dist/server/config/local-models.service.d.ts +67 -0
- package/dist/server/config/local-models.service.js +242 -0
- package/dist/server/workspace/workspace.service.d.ts +7 -0
- package/dist/server/workspace/workspace.service.js +16 -0
- package/package.json +10 -2
- package/presets/preset-agents.json +128 -0
- package/presets/preset-config.json +29 -0
- package/presets/preset-providers.json +180 -0
- package/presets/recommended-local-models.json +36 -0
- package/presets/workspaces/code-assistant/skills/code-review/SKILL.md +19 -0
- package/presets/workspaces/code-assistant/skills/code-runner/SKILL.md +21 -0
- package/presets/workspaces/code-assistant/skills/git-helper/SKILL.md +29 -0
- package/presets/workspaces/creator-assistant/skills/.gitkeep +0 -0
- package/presets/workspaces/creator-assistant/skills/creator-tools/SKILL.md +15 -0
- package/presets/workspaces/doc-assistant/skills/doc-processor/SKILL.md +21 -0
- package/presets/workspaces/download-assistant/skills/downloader/SKILL.md +20 -0
- package/presets/workspaces/file-assistant/skills/file-converter/SKILL.md +21 -0
- package/presets/workspaces/file-assistant/skills/file-organizer/SKILL.md +17 -0
- package/presets/workspaces/file-assistant/skills/file-search/SKILL.md +22 -0
- package/presets/workspaces/morning-briefing/skills/news-fetcher/SKILL.md +16 -0
- package/presets/workspaces/morning-briefing/skills/web-summarizer/SKILL.md +20 -0
- package/presets/workspaces/news-assistant/skills/news-fetcher/SKILL.md +16 -0
- package/presets/workspaces/news-assistant/skills/web-summarizer/SKILL.md +20 -0
- 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-LCp1YPVA.css +0 -10
- 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 {
|
|
6
|
-
import {
|
|
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
|
|
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,
|
|
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:
|
|
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
|
-
|
|
146
|
+
const basePrompt = this.buildSystemPrompt(loadedSkills);
|
|
119
147
|
const withCustom = customAgentPrompt && customAgentPrompt.trim()
|
|
120
148
|
? customAgentPrompt.trim() + "\n\n" + basePrompt
|
|
121
149
|
: basePrompt;
|
|
122
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
289
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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,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
|
+
};
|