@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.
- package/README.md +60 -42
- package/apps/desktop/renderer/dist/assets/index-BHY1xIZQ.css +10 -0
- package/apps/desktop/renderer/dist/assets/index-DQxlVuBe.js +93 -0
- package/apps/desktop/renderer/dist/index.html +2 -2
- package/dist/cli/cli.js +29 -0
- package/dist/cli/extension-cmd.d.ts +15 -0
- package/dist/cli/extension-cmd.js +107 -0
- package/dist/core/agent/agent-dir.d.ts +6 -0
- package/dist/core/agent/agent-dir.js +8 -0
- package/dist/core/agent/agent-manager.d.ts +13 -0
- package/dist/core/agent/agent-manager.js +77 -7
- package/dist/core/agent/proxy/adapters/claude-code-adapter.d.ts +2 -0
- package/dist/core/agent/proxy/adapters/claude-code-adapter.js +186 -0
- package/dist/core/agent/proxy/adapters/local-adapter.js +3 -1
- package/dist/core/agent/proxy/adapters/opencode-adapter.js +65 -29
- package/dist/core/agent/proxy/adapters/opencode-local-runner.js +9 -0
- package/dist/core/agent/proxy/index.js +2 -0
- package/dist/core/agent/token-usage-log-extension.d.ts +14 -0
- package/dist/core/agent/token-usage-log-extension.js +61 -0
- package/dist/core/config/desktop-config.d.ts +24 -2
- package/dist/core/config/desktop-config.js +87 -10
- package/dist/core/config/provider-support-default.js +26 -0
- package/dist/core/extensions/index.d.ts +1 -0
- package/dist/core/extensions/index.js +1 -0
- package/dist/core/extensions/load.d.ts +11 -0
- package/dist/core/extensions/load.js +101 -0
- package/dist/core/local-llm-server/index.d.ts +32 -0
- package/dist/core/local-llm-server/index.js +126 -0
- package/dist/core/local-llm-server/llm-context.d.ts +60 -0
- package/dist/core/local-llm-server/llm-context.js +221 -0
- package/dist/core/local-llm-server/model-resolve.d.ts +20 -0
- package/dist/core/local-llm-server/model-resolve.js +58 -0
- package/dist/core/local-llm-server/server.d.ts +1 -0
- package/dist/core/local-llm-server/server.js +235 -0
- package/dist/core/mcp/adapter.d.ts +4 -2
- package/dist/core/mcp/adapter.js +10 -4
- package/dist/core/mcp/index.d.ts +2 -0
- package/dist/core/mcp/index.js +1 -0
- package/dist/core/mcp/operator.d.ts +2 -0
- package/dist/core/mcp/operator.js +1 -1
- package/dist/core/memory/local-embedding.d.ts +4 -3
- package/dist/core/memory/local-embedding.js +43 -3
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.js +1 -0
- package/dist/core/tools/truncate-result.d.ts +14 -0
- package/dist/core/tools/truncate-result.js +27 -0
- package/dist/core/tools/web-search/create-web-search-tool.d.ts +17 -0
- package/dist/core/tools/web-search/create-web-search-tool.js +87 -0
- package/dist/core/tools/web-search/index.d.ts +4 -0
- package/dist/core/tools/web-search/index.js +2 -0
- package/dist/core/tools/web-search/providers/brave.d.ts +2 -0
- package/dist/core/tools/web-search/providers/brave.js +87 -0
- package/dist/core/tools/web-search/providers/duck-duck-scrape.d.ts +2 -0
- package/dist/core/tools/web-search/providers/duck-duck-scrape.js +47 -0
- package/dist/core/tools/web-search/providers/index.d.ts +5 -0
- package/dist/core/tools/web-search/providers/index.js +13 -0
- package/dist/core/tools/web-search/types.d.ts +35 -0
- package/dist/core/tools/web-search/types.js +4 -0
- package/dist/gateway/methods/agent-chat.js +74 -42
- package/dist/gateway/methods/run-scheduled-task.js +2 -0
- package/dist/gateway/server.js +54 -1
- package/dist/server/agent-config/agent-config.controller.d.ts +1 -1
- package/dist/server/agent-config/agent-config.service.d.ts +17 -3
- package/dist/server/agent-config/agent-config.service.js +23 -0
- package/dist/server/config/config.controller.d.ts +84 -4
- package/dist/server/config/config.controller.js +135 -3
- package/dist/server/config/config.module.js +3 -2
- package/dist/server/config/config.service.d.ts +14 -0
- package/dist/server/config/local-models.service.d.ts +52 -0
- package/dist/server/config/local-models.service.js +211 -0
- package/package.json +3 -1
- package/presets/preset-agents.json +121 -91
- package/presets/recommended-local-models.json +42 -0
- package/presets/workspaces/finance-expert/skills/akshare-helper/SKILL.md +9 -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/apps/desktop/renderer/dist/assets/index-BGHtXhm3.js +0 -89
- package/apps/desktop/renderer/dist/assets/index-CB2-m4ae.css +0 -10
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
* - opencode/big-pickle — Big Pickle
|
|
21
21
|
* 本适配器请求不传 model 时由 OpenCode 服务端使用上述配置的默认模型。详见 https://opencode.ai/docs/zen
|
|
22
22
|
*/
|
|
23
|
+
import { join, resolve } from "path";
|
|
23
24
|
import { createOpencodeClient } from "@opencode-ai/sdk";
|
|
25
|
+
import { getOpenbotWorkspaceDir } from "../../agent-dir.js";
|
|
24
26
|
import { ensureLocalOpencodeRunning } from "./opencode-local-runner.js";
|
|
25
27
|
/** 单次请求与事件流超时。长任务(如 init 分析大项目、多轮工具调用)可能较久,故设 15 分钟 */
|
|
26
28
|
const REQUEST_TIMEOUT_MS = 15 * 60 * 1000; // 15 min
|
|
@@ -85,6 +87,17 @@ function getOpenCodeConfig(config) {
|
|
|
85
87
|
model: { providerID, modelID: modelID || "default" },
|
|
86
88
|
};
|
|
87
89
|
}
|
|
90
|
+
/** 与 Claude Code 一致:未显式配置时使用智能体工作区路径 ~/.openbot/workspace/<workspace>/ */
|
|
91
|
+
function getOpencodeWorkingDirectory(config) {
|
|
92
|
+
const custom = config.opencode?.workingDirectory;
|
|
93
|
+
if (typeof custom === "string" && custom.trim()) {
|
|
94
|
+
return resolve(custom.trim());
|
|
95
|
+
}
|
|
96
|
+
const w = config.workspace;
|
|
97
|
+
if (typeof w !== "string" || !w.trim())
|
|
98
|
+
return undefined;
|
|
99
|
+
return join(getOpenbotWorkspaceDir(), w.trim());
|
|
100
|
+
}
|
|
88
101
|
function buildAuthHeaders(oc) {
|
|
89
102
|
const user = oc.username || DEFAULT_SERVER_USERNAME;
|
|
90
103
|
const pass = oc.password ?? "";
|
|
@@ -208,7 +221,7 @@ function parseSlashCommand(message) {
|
|
|
208
221
|
const args = space >= 0 ? rest.slice(space + 1).trim() : "";
|
|
209
222
|
return command ? { command, args } : null;
|
|
210
223
|
}
|
|
211
|
-
/** 从 session.prompt / session.command 返回的 parts
|
|
224
|
+
/** 从 session.prompt / session.command 返回的 parts 提取文本(含所有 type,用于兼容旧逻辑) */
|
|
212
225
|
function partsToText(parts) {
|
|
213
226
|
if (!Array.isArray(parts))
|
|
214
227
|
return "";
|
|
@@ -217,6 +230,17 @@ function partsToText(parts) {
|
|
|
217
230
|
.filter(Boolean)
|
|
218
231
|
.join("");
|
|
219
232
|
}
|
|
233
|
+
/** 仅取 type=text 的 part 拼接为助手正文,与流式时只推 text 保持一致,不包含 reasoning/step-start/step-finish */
|
|
234
|
+
function partsToReplyText(parts) {
|
|
235
|
+
if (!Array.isArray(parts))
|
|
236
|
+
return "";
|
|
237
|
+
return parts
|
|
238
|
+
.filter((p) => p?.type === "text")
|
|
239
|
+
.map((p) => (typeof p.text === "string" ? p.text : typeof p.content === "string" ? p.content : ""))
|
|
240
|
+
.filter(Boolean)
|
|
241
|
+
.join("")
|
|
242
|
+
.trim();
|
|
243
|
+
}
|
|
220
244
|
/** 日志用:可序列化对象,避免循环引用、过长字符串和不可序列化字段 */
|
|
221
245
|
function safeForLog(obj, maxStrLen = 2000) {
|
|
222
246
|
if (obj === null || obj === undefined)
|
|
@@ -348,7 +372,7 @@ async function pollForAssistantMessage(session, sessionId) {
|
|
|
348
372
|
const parts = item?.info?.parts ?? item?.parts;
|
|
349
373
|
if (!Array.isArray(parts))
|
|
350
374
|
continue;
|
|
351
|
-
const text =
|
|
375
|
+
const text = partsToReplyText(parts);
|
|
352
376
|
if (text)
|
|
353
377
|
return text;
|
|
354
378
|
// 已有 assistant 但 parts 为空,可能仍在生成,继续轮询
|
|
@@ -367,7 +391,7 @@ export const opencodeAdapter = {
|
|
|
367
391
|
throw new Error("OpenCode adapter: missing opencode.port or (remote 模式下缺少 address) in agent config");
|
|
368
392
|
}
|
|
369
393
|
if (config.opencode?.mode === "local" && config.opencode.port != null) {
|
|
370
|
-
await ensureLocalOpencodeRunning(Number(config.opencode.port), config.opencode.model?.trim() || undefined, config
|
|
394
|
+
await ensureLocalOpencodeRunning(Number(config.opencode.port), config.opencode.model?.trim() || undefined, getOpencodeWorkingDirectory(config));
|
|
371
395
|
}
|
|
372
396
|
const hasPassword = Boolean(oc.password?.trim());
|
|
373
397
|
const userSignal = options.signal;
|
|
@@ -538,15 +562,36 @@ export const opencodeAdapter = {
|
|
|
538
562
|
let hadAnyChunk = false;
|
|
539
563
|
let lastTextLength = 0;
|
|
540
564
|
let eventCount = 0;
|
|
541
|
-
//
|
|
542
|
-
const
|
|
565
|
+
// reasoning 只缓存在见到 text 或 session.idle 时再整体输出一次,避免「先部分后完整」重复显示
|
|
566
|
+
const reasoningBuffer = new Map();
|
|
567
|
+
// 正文累积后去掉 [step-start]/[step-finish] 再推送,避免服务端把这类标签混在 text 里
|
|
568
|
+
let textAccumulated = "";
|
|
569
|
+
let lastEmittedCleanLen = 0;
|
|
570
|
+
const stepMarkerRe = /\n?\[step-(?:start|finish)\]\n?/g;
|
|
571
|
+
const emitStrippedText = () => {
|
|
572
|
+
const cleaned = textAccumulated.replace(stepMarkerRe, "");
|
|
573
|
+
const toEmit = cleaned.slice(lastEmittedCleanLen);
|
|
574
|
+
if (toEmit) {
|
|
575
|
+
hadAnyChunk = true;
|
|
576
|
+
callbacks.onChunk(toEmit);
|
|
577
|
+
lastEmittedCleanLen = cleaned.length;
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
const flushReasoningForMessage = (messageID) => {
|
|
581
|
+
const raw = reasoningBuffer.get(messageID);
|
|
582
|
+
if (!raw?.trim())
|
|
583
|
+
return;
|
|
584
|
+
reasoningBuffer.delete(messageID);
|
|
585
|
+
const formatted = `\n\n---\nreasoning: ${raw.slice(0, 2000)}${raw.length > 2000 ? "…" : ""}\n---\n\n`;
|
|
586
|
+
hadAnyChunk = true;
|
|
587
|
+
callbacks.onChunk(formatted);
|
|
588
|
+
};
|
|
543
589
|
try {
|
|
544
590
|
for await (const event of eventStream) {
|
|
545
591
|
if (userSignal?.aborted)
|
|
546
592
|
break;
|
|
547
593
|
eventCount++;
|
|
548
594
|
const ev = event;
|
|
549
|
-
// 日志:长任务排查时可见 OpenCode 下发了哪些事件类型与 part 类型,便于确认是否有中间状态未回显
|
|
550
595
|
if (ev.type !== "message.part.updated" || (ev.properties?.part?.type !== "text")) {
|
|
551
596
|
const partType = ev.properties?.part?.type ?? "(no part)";
|
|
552
597
|
console.log(`[OpenCode] event #${eventCount} type=${ev.type} part.type=${partType}`);
|
|
@@ -558,41 +603,32 @@ export const opencodeAdapter = {
|
|
|
558
603
|
if (ev.type === "message.part.updated" && ev.properties?.part?.sessionID === opencodeSessionId) {
|
|
559
604
|
const part = ev.properties.part;
|
|
560
605
|
if (userMessageID != null && part.messageID === userMessageID)
|
|
561
|
-
continue;
|
|
606
|
+
continue;
|
|
562
607
|
const partType = part.type ?? "";
|
|
563
608
|
const delta = ev.properties.delta;
|
|
564
609
|
const fullText = typeof part?.text === "string" ? part.text : "";
|
|
565
610
|
if (partType === "text") {
|
|
611
|
+
const msgId = part.messageID ?? "";
|
|
612
|
+
flushReasoningForMessage(msgId);
|
|
566
613
|
if (typeof delta === "string" && delta) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
lastTextLength += delta.length;
|
|
614
|
+
textAccumulated += delta;
|
|
615
|
+
emitStrippedText();
|
|
570
616
|
}
|
|
571
617
|
else if (fullText.length > lastTextLength) {
|
|
572
|
-
|
|
573
|
-
callbacks.onChunk(fullText.slice(lastTextLength));
|
|
618
|
+
textAccumulated = fullText;
|
|
574
619
|
lastTextLength = fullText.length;
|
|
620
|
+
emitStrippedText();
|
|
575
621
|
}
|
|
576
622
|
}
|
|
577
|
-
else {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
if (fullText && fullText.trim()) {
|
|
581
|
-
const formatted = `\n\n---\n${partType}: ${fullText.slice(0, 500)}${fullText.length > 500 ? "…" : ""}\n---\n\n`;
|
|
582
|
-
if (lastEmittedNonText.get(key) === formatted)
|
|
583
|
-
continue;
|
|
584
|
-
lastEmittedNonText.set(key, formatted);
|
|
585
|
-
hadAnyChunk = true;
|
|
586
|
-
callbacks.onChunk(formatted);
|
|
587
|
-
}
|
|
588
|
-
else if (partType) {
|
|
589
|
-
// [tool]/[step-start]/[step-finish] 等无正文:每次事件都发(无法区分“同一事件重复”与“多次不同 tool/step”,故不做去重)
|
|
590
|
-
hadAnyChunk = true;
|
|
591
|
-
callbacks.onChunk(`\n[${partType}]\n`);
|
|
592
|
-
}
|
|
623
|
+
else if (partType === "reasoning" && fullText.trim()) {
|
|
624
|
+
const msgId = part.messageID ?? "";
|
|
625
|
+
reasoningBuffer.set(msgId, fullText);
|
|
593
626
|
}
|
|
627
|
+
// step-start、step-finish、tool_call 等不推给前端
|
|
594
628
|
}
|
|
595
629
|
if (ev.type === "session.idle" && ev.properties?.sessionID === opencodeSessionId) {
|
|
630
|
+
for (const msgId of reasoningBuffer.keys())
|
|
631
|
+
flushReasoningForMessage(msgId);
|
|
596
632
|
console.log(`[OpenCode] session.idle after ${eventCount} events, hadAnyChunk=${hadAnyChunk}`);
|
|
597
633
|
break;
|
|
598
634
|
}
|
|
@@ -625,7 +661,7 @@ export const opencodeAdapter = {
|
|
|
625
661
|
throw new Error("OpenCode adapter: missing opencode.port or (remote 模式下缺少 address) in agent config");
|
|
626
662
|
}
|
|
627
663
|
if (config.opencode?.mode === "local" && config.opencode.port != null) {
|
|
628
|
-
await ensureLocalOpencodeRunning(Number(config.opencode.port), config.opencode.model?.trim() || undefined, config
|
|
664
|
+
await ensureLocalOpencodeRunning(Number(config.opencode.port), config.opencode.model?.trim() || undefined, getOpencodeWorkingDirectory(config));
|
|
629
665
|
}
|
|
630
666
|
const hasPassword = oc.password != null && String(oc.password).trim() !== "";
|
|
631
667
|
const client = createOpencodeClient({
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* 并通过 OPENCODE_CONFIG_CONTENT 注入默认模型与端口;可选设置工作目录(cwd)。
|
|
4
4
|
*/
|
|
5
5
|
import { spawn } from "child_process";
|
|
6
|
+
import { mkdirSync } from "node:fs";
|
|
6
7
|
import { createInterface } from "readline";
|
|
7
8
|
import { resolve } from "path";
|
|
8
9
|
const HEALTH_PATH = "/global/health";
|
|
@@ -41,6 +42,14 @@ export async function ensureLocalOpencodeRunning(port, model, workingDirectory)
|
|
|
41
42
|
...process.env,
|
|
42
43
|
OPENCODE_CONFIG_CONTENT: JSON.stringify(config),
|
|
43
44
|
};
|
|
45
|
+
if (cwd) {
|
|
46
|
+
try {
|
|
47
|
+
mkdirSync(cwd, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
console.warn("[OpenCode local runner] mkdir cwd failed:", e.message);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
44
53
|
const child = spawn("opencode", ["serve", "--port", String(port), "--hostname", "127.0.0.1"], {
|
|
45
54
|
env,
|
|
46
55
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -8,9 +8,11 @@ import { localAdapter } from "./adapters/local-adapter.js";
|
|
|
8
8
|
import { cozeAdapter } from "./adapters/coze-adapter.js";
|
|
9
9
|
import { openclawxAdapter } from "./adapters/openclawx-adapter.js";
|
|
10
10
|
import { opencodeAdapter } from "./adapters/opencode-adapter.js";
|
|
11
|
+
import { claudeCodeAdapter } from "./adapters/claude-code-adapter.js";
|
|
11
12
|
registerAgentProxyAdapter(localAdapter);
|
|
12
13
|
registerAgentProxyAdapter(cozeAdapter);
|
|
13
14
|
registerAgentProxyAdapter(openclawxAdapter);
|
|
14
15
|
registerAgentProxyAdapter(opencodeAdapter);
|
|
16
|
+
registerAgentProxyAdapter(claudeCodeAdapter);
|
|
15
17
|
export { runForChannelStream, runForChannelCollect } from "./run-for-channel.js";
|
|
16
18
|
export { registerAgentProxyAdapter, getAgentProxyAdapter, listAgentProxyAdapterTypes } from "./registry.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内置 extension:在 turn_start / turn_end 和 compaction 相关事件时打印 context 用量与 compaction 信息,
|
|
3
|
+
* 便于分析 token 占用。仅打 log,不改变行为。
|
|
4
|
+
* 若 agent-manager 在 session 创建后调用了 setTokenUsageInitialStats,则每轮会打印 systemPrompt/skills/tools/conversation 的估算占比。
|
|
5
|
+
*/
|
|
6
|
+
import type { ExtensionFactory } from "@mariozechner/pi-coding-agent";
|
|
7
|
+
export interface TokenUsageInitialStats {
|
|
8
|
+
systemPromptEstTokens: number;
|
|
9
|
+
skillsBlockEstTokens: number;
|
|
10
|
+
toolsDefsEstTokens: number;
|
|
11
|
+
}
|
|
12
|
+
/** 由 agent-manager 在 session 创建并算完 systemPrompt/skills/tools 后调用,供本 extension 在 turn 时打印占比 */
|
|
13
|
+
export declare function setTokenUsageInitialStats(compositeKey: string, stats: TokenUsageInitialStats): void;
|
|
14
|
+
export declare function createTokenUsageLogExtensionFactory(compositeKey: string): ExtensionFactory;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const LOG_PREFIX = "[token-usage]";
|
|
2
|
+
const initialStatsByKey = new Map();
|
|
3
|
+
/** 由 agent-manager 在 session 创建并算完 systemPrompt/skills/tools 后调用,供本 extension 在 turn 时打印占比 */
|
|
4
|
+
export function setTokenUsageInitialStats(compositeKey, stats) {
|
|
5
|
+
initialStatsByKey.set(compositeKey, stats);
|
|
6
|
+
}
|
|
7
|
+
function logContextBreakdown(phase, totalTokens, compositeKey) {
|
|
8
|
+
const stats = initialStatsByKey.get(compositeKey);
|
|
9
|
+
if (!stats || totalTokens <= 0)
|
|
10
|
+
return;
|
|
11
|
+
const { systemPromptEstTokens, skillsBlockEstTokens, toolsDefsEstTokens } = stats;
|
|
12
|
+
const conversationEst = Math.max(0, totalTokens - systemPromptEstTokens - toolsDefsEstTokens);
|
|
13
|
+
console.log(`${LOG_PREFIX} ${phase} breakdown | total=${totalTokens} systemPrompt≈${systemPromptEstTokens} (含skills≈${skillsBlockEstTokens}) tools≈${toolsDefsEstTokens} conversation≈${conversationEst}`);
|
|
14
|
+
}
|
|
15
|
+
export function createTokenUsageLogExtensionFactory(compositeKey) {
|
|
16
|
+
return (pi) => {
|
|
17
|
+
const on = pi.on.bind(pi);
|
|
18
|
+
on("turn_start", (_event, ctx) => {
|
|
19
|
+
const c = ctx;
|
|
20
|
+
try {
|
|
21
|
+
const usage = c?.getContextUsage?.();
|
|
22
|
+
if (usage && typeof usage.tokens === "number") {
|
|
23
|
+
console.log(`${LOG_PREFIX} turn_start contextUsage.tokens=${usage.tokens} (估算,用于判断是否触发 compaction)`);
|
|
24
|
+
logContextBreakdown("turn_start", usage.tokens, compositeKey);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// ignore
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
on("turn_end", (_event, ctx) => {
|
|
32
|
+
const c = ctx;
|
|
33
|
+
try {
|
|
34
|
+
const usage = c?.getContextUsage?.();
|
|
35
|
+
if (usage && typeof usage.tokens === "number") {
|
|
36
|
+
console.log(`${LOG_PREFIX} turn_end contextUsage.tokens=${usage.tokens} (本轮结束后)`);
|
|
37
|
+
logContextBreakdown("turn_end", usage.tokens, compositeKey);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// ignore
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
on("session_compact", (event) => {
|
|
45
|
+
const e = event;
|
|
46
|
+
const entry = e?.compactionEntry;
|
|
47
|
+
const tokensBefore = entry?.tokensBefore;
|
|
48
|
+
const summaryLen = typeof entry?.summary === "string" ? entry.summary.length : 0;
|
|
49
|
+
console.log(`${LOG_PREFIX} session_compact 已触发 tokensBefore=${tokensBefore ?? "?"} summaryChars=${summaryLen}`);
|
|
50
|
+
});
|
|
51
|
+
on("auto_compaction_start", (event) => {
|
|
52
|
+
const e = event;
|
|
53
|
+
console.log(`${LOG_PREFIX} auto_compaction_start 已触发 reason=${String(e?.reason ?? "?")}`);
|
|
54
|
+
});
|
|
55
|
+
on("auto_compaction_end", (event) => {
|
|
56
|
+
const e = event;
|
|
57
|
+
const { result, aborted, willRetry, errorMessage } = e ?? {};
|
|
58
|
+
console.log(`${LOG_PREFIX} auto_compaction_end 已结束 aborted=${aborted} willRetry=${willRetry} result=${result != null ? "ok" : "null"}${errorMessage ? ` error=${errorMessage}` : ""}`);
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -56,8 +56,8 @@ export interface ChannelsConfig {
|
|
|
56
56
|
export type DesktopMcpServerConfig = import("../mcp/index.js").McpServerConfig;
|
|
57
57
|
/** MCP 标准 JSON 格式(key 为服务器名称),存储与 UI 可读写 */
|
|
58
58
|
export type DesktopMcpServersStandardFormat = import("../mcp/index.js").McpServersStandardFormat;
|
|
59
|
-
/** Agent 执行器类型:local=本机 pi-coding-agent,coze/openclawx/opencode
|
|
60
|
-
export type AgentRunnerType = "local" | "coze" | "openclawx" | "opencode";
|
|
59
|
+
/** Agent 执行器类型:local=本机 pi-coding-agent,coze/openclawx/opencode=远程代理,claude_code=本机 Claude Code CLI */
|
|
60
|
+
export type AgentRunnerType = "local" | "coze" | "openclawx" | "opencode" | "claude_code";
|
|
61
61
|
/** Coze 站点:国内站 api.coze.cn / 国际站 api.coze.com,凭证不通用 */
|
|
62
62
|
export type CozeRegion = "cn" | "com";
|
|
63
63
|
/** 某站点的 Bot 凭证(国内/国际各自独立) */
|
|
@@ -90,6 +90,11 @@ export interface AgentOpenClawXConfig {
|
|
|
90
90
|
}
|
|
91
91
|
/** OpenCode 启动模式:local=由本应用按需启动本机服务;remote=连接已运行的远端服务 */
|
|
92
92
|
export type OpenCodeServerMode = "local" | "remote";
|
|
93
|
+
/** Claude Code CLI 代理配置(当 runnerType 为 claude_code 时使用) */
|
|
94
|
+
export interface AgentClaudeCodeConfig {
|
|
95
|
+
/** 工作目录:Claude Code CLI 执行时的 cwd;留空则使用该智能体工作区路径 */
|
|
96
|
+
workingDirectory?: string;
|
|
97
|
+
}
|
|
93
98
|
/** OpenCode 代理配置:仅对接 [OpenCode 官方 Server API](https://opencode.ai/docs/server)(Session/Message + HTTP Basic) */
|
|
94
99
|
export interface AgentOpenCodeConfig {
|
|
95
100
|
/** 启动模式:local=本应用控制启动并可选设置默认模型;remote=连接已有服务 */
|
|
@@ -143,6 +148,8 @@ export interface DesktopAgentConfig {
|
|
|
143
148
|
workspace?: string;
|
|
144
149
|
/** MCP 服务器配置(数组或标准对象格式),创建 Session 时传入并归一化 */
|
|
145
150
|
mcpServers?: DesktopMcpServerConfig[] | DesktopMcpServersStandardFormat;
|
|
151
|
+
/** MCP 单次返回最大 token;超过则从尾部裁剪;不配置则不限制 */
|
|
152
|
+
mcpMaxResultTokens?: number;
|
|
146
153
|
/** 自定义系统提示词,会与技能等一起组成最终 systemPrompt */
|
|
147
154
|
systemPrompt?: string;
|
|
148
155
|
/** 执行器类型,缺省 local */
|
|
@@ -153,8 +160,23 @@ export interface DesktopAgentConfig {
|
|
|
153
160
|
openclawx?: AgentOpenClawXConfig;
|
|
154
161
|
/** OpenCode 代理配置 */
|
|
155
162
|
opencode?: AgentOpenCodeConfig;
|
|
163
|
+
/** Claude Code CLI 代理配置 */
|
|
164
|
+
claudeCode?: AgentClaudeCodeConfig;
|
|
156
165
|
/** 是否使用经验(长记忆);默认 true */
|
|
157
166
|
useLongMemory?: boolean;
|
|
167
|
+
/** 在线搜索:解析后的运行时配置,仅当 runnerType 为 local 时用于注册 web_search 工具 */
|
|
168
|
+
webSearch?: {
|
|
169
|
+
enabled: boolean;
|
|
170
|
+
provider: "brave" | "duck-duck-scrape";
|
|
171
|
+
apiKey?: string;
|
|
172
|
+
timeoutSeconds: number;
|
|
173
|
+
cacheTtlMinutes: number;
|
|
174
|
+
maxResults: number;
|
|
175
|
+
/** 单次搜索返回最大 token;超过则从尾部裁剪;不配置则不限制 */
|
|
176
|
+
maxResultTokens?: number;
|
|
177
|
+
};
|
|
178
|
+
/** 本地模型上下文长度(token 数),仅 runnerType 为 local 时用于启动本地 LLM;默认 32768 */
|
|
179
|
+
contextSize?: number;
|
|
158
180
|
}
|
|
159
181
|
/**
|
|
160
182
|
* 从 config.json 读取缺省智能体 id(defaultAgentId)。
|
|
@@ -209,8 +209,8 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
const resolvedAgentId = agentId === "default" ? "default" : agentId;
|
|
212
|
-
let provider = config.defaultProvider ?? "
|
|
213
|
-
let model = config.defaultModel ?? "
|
|
212
|
+
let provider = config.defaultProvider ?? "ollama";
|
|
213
|
+
let model = config.defaultModel ?? "qwen3:4b";
|
|
214
214
|
if (config.defaultModelItemCode && Array.isArray(config.configuredModels)) {
|
|
215
215
|
const configured = config.configuredModels.find((m) => m.modelItemCode === config.defaultModelItemCode);
|
|
216
216
|
if (configured) {
|
|
@@ -220,8 +220,10 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
220
220
|
}
|
|
221
221
|
let workspaceName = resolvedAgentId;
|
|
222
222
|
let mcpServers;
|
|
223
|
+
let mcpMaxResultTokens;
|
|
223
224
|
let systemPrompt;
|
|
224
225
|
let useLongMemory = true;
|
|
226
|
+
let contextSize;
|
|
225
227
|
if (existsSync(agentsPath)) {
|
|
226
228
|
try {
|
|
227
229
|
const raw = await readFile(agentsPath, "utf-8");
|
|
@@ -233,6 +235,12 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
233
235
|
workspaceName = agent.workspace;
|
|
234
236
|
else if (agent.id)
|
|
235
237
|
workspaceName = agent.id;
|
|
238
|
+
if (agent.mcpMaxResultTokens != null && typeof agent.mcpMaxResultTokens === "number" && agent.mcpMaxResultTokens > 0) {
|
|
239
|
+
mcpMaxResultTokens = agent.mcpMaxResultTokens;
|
|
240
|
+
}
|
|
241
|
+
if (agent.contextSize != null && typeof agent.contextSize === "number" && agent.contextSize > 0) {
|
|
242
|
+
contextSize = agent.contextSize;
|
|
243
|
+
}
|
|
236
244
|
if (agent.mcpServers != null) {
|
|
237
245
|
if (Array.isArray(agent.mcpServers) || (typeof agent.mcpServers === "object" && !Array.isArray(agent.mcpServers))) {
|
|
238
246
|
mcpServers = agent.mcpServers;
|
|
@@ -276,6 +284,19 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
276
284
|
let coze;
|
|
277
285
|
let openclawx;
|
|
278
286
|
let opencode;
|
|
287
|
+
let claudeCode;
|
|
288
|
+
const tw = config.tools?.webSearch;
|
|
289
|
+
const timeoutSeconds = typeof tw?.timeoutSeconds === "number" && tw.timeoutSeconds > 0 ? tw.timeoutSeconds : 15;
|
|
290
|
+
const cacheTtlMinutes = typeof tw?.cacheTtlMinutes === "number" && tw.cacheTtlMinutes >= 0 ? tw.cacheTtlMinutes : 5;
|
|
291
|
+
const maxResultsRaw = typeof tw?.maxResults === "number" ? tw.maxResults : 5;
|
|
292
|
+
const maxResults = Math.min(10, Math.max(1, maxResultsRaw));
|
|
293
|
+
let webSearch = {
|
|
294
|
+
enabled: false,
|
|
295
|
+
provider: "duck-duck-scrape",
|
|
296
|
+
timeoutSeconds,
|
|
297
|
+
cacheTtlMinutes,
|
|
298
|
+
maxResults,
|
|
299
|
+
};
|
|
279
300
|
if (existsSync(agentsPath)) {
|
|
280
301
|
try {
|
|
281
302
|
const rawAgents = await readFile(agentsPath, "utf-8");
|
|
@@ -285,9 +306,16 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
285
306
|
if (agentRow) {
|
|
286
307
|
if (agentRow.runnerType === "coze" ||
|
|
287
308
|
agentRow.runnerType === "openclawx" ||
|
|
288
|
-
agentRow.runnerType === "opencode"
|
|
309
|
+
agentRow.runnerType === "opencode" ||
|
|
310
|
+
agentRow.runnerType === "claude_code") {
|
|
289
311
|
runnerType = agentRow.runnerType;
|
|
290
312
|
}
|
|
313
|
+
if (agentRow.runnerType === "claude_code") {
|
|
314
|
+
const wd = agentRow.claudeCode?.workingDirectory;
|
|
315
|
+
claudeCode = {
|
|
316
|
+
workingDirectory: typeof wd === "string" && wd.trim() ? wd.trim() : undefined,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
291
319
|
if (agentRow.coze) {
|
|
292
320
|
const row = agentRow.coze;
|
|
293
321
|
const region = row.region === "cn" || row.region === "com" ? row.region : "com";
|
|
@@ -353,6 +381,35 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
353
381
|
}
|
|
354
382
|
}
|
|
355
383
|
}
|
|
384
|
+
if (agentRow.webSearch?.enabled === true) {
|
|
385
|
+
let preferredProvider = agentRow.webSearch?.provider === "brave" || agentRow.webSearch?.provider === "duck-duck-scrape"
|
|
386
|
+
? agentRow.webSearch.provider
|
|
387
|
+
: tw?.defaultProvider === "brave" || tw?.defaultProvider === "duck-duck-scrape"
|
|
388
|
+
? tw.defaultProvider
|
|
389
|
+
: "duck-duck-scrape";
|
|
390
|
+
let braveKey;
|
|
391
|
+
if (preferredProvider === "brave") {
|
|
392
|
+
braveKey =
|
|
393
|
+
(typeof tw?.providers?.brave?.apiKey === "string" && tw.providers.brave.apiKey.trim()
|
|
394
|
+
? tw.providers.brave.apiKey.trim()
|
|
395
|
+
: undefined) ??
|
|
396
|
+
(process.env.BRAVE_API_KEY && process.env.BRAVE_API_KEY.trim() ? process.env.BRAVE_API_KEY.trim() : undefined);
|
|
397
|
+
if (!braveKey)
|
|
398
|
+
preferredProvider = "duck-duck-scrape";
|
|
399
|
+
}
|
|
400
|
+
const maxResultTokens = agentRow.webSearch?.maxResultTokens != null && typeof agentRow.webSearch?.maxResultTokens === "number" && agentRow.webSearch.maxResultTokens > 0
|
|
401
|
+
? agentRow.webSearch.maxResultTokens
|
|
402
|
+
: undefined;
|
|
403
|
+
webSearch = {
|
|
404
|
+
enabled: true,
|
|
405
|
+
provider: preferredProvider,
|
|
406
|
+
apiKey: preferredProvider === "brave" ? braveKey : undefined,
|
|
407
|
+
timeoutSeconds,
|
|
408
|
+
cacheTtlMinutes,
|
|
409
|
+
maxResults,
|
|
410
|
+
maxResultTokens,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
356
413
|
}
|
|
357
414
|
}
|
|
358
415
|
catch {
|
|
@@ -365,12 +422,16 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
365
422
|
apiKey: apiKey ?? undefined,
|
|
366
423
|
workspace: workspaceName,
|
|
367
424
|
mcpServers,
|
|
425
|
+
mcpMaxResultTokens,
|
|
368
426
|
systemPrompt,
|
|
369
427
|
runnerType,
|
|
370
428
|
coze,
|
|
371
429
|
openclawx,
|
|
372
430
|
opencode,
|
|
431
|
+
claudeCode,
|
|
373
432
|
useLongMemory,
|
|
433
|
+
webSearch,
|
|
434
|
+
contextSize,
|
|
374
435
|
};
|
|
375
436
|
}
|
|
376
437
|
function ensureDesktopDir() {
|
|
@@ -572,12 +633,22 @@ export async function ensureProviderSupportFile() {
|
|
|
572
633
|
async function ensureConfigJsonInitialized() {
|
|
573
634
|
const presetPath = join(getPresetsDir(), "preset-config.json");
|
|
574
635
|
let presetConfig = {
|
|
575
|
-
defaultProvider: "
|
|
576
|
-
defaultModel: "
|
|
636
|
+
defaultProvider: "ollama",
|
|
637
|
+
defaultModel: "qwen3:4b",
|
|
577
638
|
defaultAgentId: DEFAULT_AGENT_ID,
|
|
578
639
|
maxAgentSessions: DEFAULT_MAX_AGENT_SESSIONS,
|
|
579
|
-
providers: {
|
|
580
|
-
|
|
640
|
+
providers: {
|
|
641
|
+
ollama: { baseUrl: "http://localhost:11434/v1" },
|
|
642
|
+
},
|
|
643
|
+
configuredModels: [
|
|
644
|
+
{
|
|
645
|
+
provider: "ollama",
|
|
646
|
+
modelId: "qwen3:4b",
|
|
647
|
+
type: "llm",
|
|
648
|
+
alias: "Qwen3 4B (本地)",
|
|
649
|
+
modelItemCode: "ollama:qwen3:4b",
|
|
650
|
+
},
|
|
651
|
+
],
|
|
581
652
|
};
|
|
582
653
|
if (existsSync(presetPath)) {
|
|
583
654
|
try {
|
|
@@ -714,6 +785,10 @@ const SYNC_DEFAULTS = {
|
|
|
714
785
|
"openai-custom": { baseUrl: "", apiKey: "OPENAI_API_KEY", api: "openai-completions" },
|
|
715
786
|
nvidia: { baseUrl: "https://integrate.api.nvidia.com/v1", apiKey: "NVIDIA_API_KEY", api: "openai-completions" },
|
|
716
787
|
kimi: { baseUrl: "https://api.moonshot.cn/v1", apiKey: "MOONSHOT_API_KEY", api: "openai-completions" },
|
|
788
|
+
/** 本地 Ollama,无需真实 API Key */
|
|
789
|
+
ollama: { baseUrl: "http://localhost:11434/v1", apiKey: "OPENAI_API_KEY", api: "openai-completions" },
|
|
790
|
+
/** 内置本地推理(node-llama-cpp),无需 API Key,baseUrl 指向本地子进程服务 */
|
|
791
|
+
local: { baseUrl: "http://127.0.0.1:11435/v1", apiKey: "OPENAI_API_KEY", api: "openai-completions" },
|
|
717
792
|
};
|
|
718
793
|
const DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
719
794
|
const DEFAULT_CONTEXT_WINDOW = 64000;
|
|
@@ -750,10 +825,12 @@ export async function syncDesktopConfigToModelsJson() {
|
|
|
750
825
|
const support = await getProviderSupport();
|
|
751
826
|
const piProviders = {};
|
|
752
827
|
for (const [providerId, userConfig] of Object.entries(configured)) {
|
|
753
|
-
|
|
828
|
+
// ollama / local 不需要 API Key,其他 provider 必须有 apiKey
|
|
829
|
+
const isNoKeyProvider = providerId === "ollama" || providerId === "local";
|
|
830
|
+
if (!isNoKeyProvider && !userConfig?.apiKey?.trim())
|
|
754
831
|
continue;
|
|
755
832
|
const defaults = SYNC_DEFAULTS[providerId] ?? { baseUrl: "", apiKey: "OPENAI_API_KEY", api: "openai-completions" };
|
|
756
|
-
const baseUrl = userConfig
|
|
833
|
+
const baseUrl = userConfig?.baseUrl?.trim() || (support[providerId]?.baseUrl ?? "").trim() || defaults.baseUrl;
|
|
757
834
|
if (!baseUrl)
|
|
758
835
|
continue;
|
|
759
836
|
const def = support[providerId];
|
|
@@ -783,7 +860,7 @@ export async function syncDesktopConfigToModelsJson() {
|
|
|
783
860
|
continue;
|
|
784
861
|
}
|
|
785
862
|
piProviders[providerId] = {
|
|
786
|
-
name: (userConfig
|
|
863
|
+
name: (userConfig?.alias?.trim() || def?.name) || providerId,
|
|
787
864
|
apiKey: defaults.apiKey,
|
|
788
865
|
api: defaults.api,
|
|
789
866
|
baseUrl: baseUrl.replace(/\/$/, ""),
|
|
@@ -54,4 +54,30 @@ export const DEFAULT_PROVIDER_SUPPORT = {
|
|
|
54
54
|
{ id: "moonshot-v1-128k", name: "Moonshot 128K", types: ["llm"] },
|
|
55
55
|
],
|
|
56
56
|
},
|
|
57
|
+
/** 本地 Ollama 服务,兼容 OpenAI API;baseUrl 指向本机 Ollama 默认端口 */
|
|
58
|
+
ollama: {
|
|
59
|
+
name: "Ollama (本地)",
|
|
60
|
+
baseUrl: "http://localhost:11434/v1",
|
|
61
|
+
models: [
|
|
62
|
+
{ id: "qwen3:4b", name: "Qwen3 4B", types: ["llm"] },
|
|
63
|
+
{ id: "qwen3:8b", name: "Qwen3 8B", types: ["llm"] },
|
|
64
|
+
{ id: "qwen3:14b", name: "Qwen3 14B", types: ["llm"] },
|
|
65
|
+
{ id: "llama3.2:3b", name: "Llama 3.2 3B", types: ["llm"] },
|
|
66
|
+
{ id: "llama3.2:1b", name: "Llama 3.2 1B", types: ["llm"] },
|
|
67
|
+
{ id: "nomic-embed-text", name: "Nomic Embed Text", types: ["embedding"] },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* 内置本地推理(node-llama-cpp),无需安装 Ollama。
|
|
72
|
+
* baseUrl 指向本地 LLM 子进程服务;模型列表为推荐的 GGUF 模型,可在本地模型管理页面增删。
|
|
73
|
+
* 无需 API Key。
|
|
74
|
+
*/
|
|
75
|
+
local: {
|
|
76
|
+
name: "本地推理 (node-llama-cpp)",
|
|
77
|
+
baseUrl: "http://127.0.0.1:11435/v1",
|
|
78
|
+
models: [
|
|
79
|
+
{ id: "local-llm", name: "本地 LLM(当前加载)", types: ["llm"] },
|
|
80
|
+
{ id: "local-embedding", name: "本地 Embedding(当前加载)", types: ["embedding"] },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
57
83
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { loadExtensionFactories, clearExtensionFactoriesCache } from "./load.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { loadExtensionFactories, clearExtensionFactoriesCache } from "./load.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ExtensionFactory } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
/**
|
|
3
|
+
* 扫描 ~/.openbot/plugins,加载所有已安装的扩展包,返回 ExtensionFactory 数组。
|
|
4
|
+
* 进程内缓存结果;若需重载可调用 clearExtensionFactoriesCache()。
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadExtensionFactories(): ExtensionFactory[];
|
|
7
|
+
/**
|
|
8
|
+
* 清除扩展 factory 缓存,下次 loadExtensionFactories() 时会重新扫描并加载。
|
|
9
|
+
* 用于安装/卸载扩展后希望不重启即生效的场景(若调用方在适当时机调用)。
|
|
10
|
+
*/
|
|
11
|
+
export declare function clearExtensionFactoriesCache(): void;
|