@next-open-ai/openclawx 0.8.16 → 0.8.22
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 +89 -126
- package/apps/desktop/renderer/dist/assets/index-B0_RWD2F.css +10 -0
- package/apps/desktop/renderer/dist/assets/index-vZN87oBP.js +89 -0
- package/apps/desktop/renderer/dist/index.html +2 -2
- package/dist/core/agent/agent-manager.d.ts +10 -4
- package/dist/core/agent/agent-manager.js +49 -22
- package/dist/core/agent/proxy/adapters/coze-adapter.js +7 -0
- package/dist/core/agent/proxy/adapters/local-adapter.js +4 -11
- package/dist/core/agent/proxy/adapters/openclawx-adapter.js +7 -0
- package/dist/core/agent/proxy/adapters/opencode-adapter.d.ts +11 -0
- package/dist/core/agent/proxy/adapters/opencode-adapter.js +716 -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 +86 -0
- package/dist/core/agent/proxy/index.js +3 -1
- package/dist/core/agent/proxy/run-for-channel.js +1 -1
- package/dist/core/agent/proxy/types.d.ts +2 -0
- package/dist/core/agent/run.js +1 -1
- package/dist/core/config/desktop-config.d.ts +71 -3
- package/dist/core/config/desktop-config.js +222 -24
- 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 +76 -0
- package/dist/core/memory/local-embedding.d.ts +10 -0
- package/dist/core/memory/local-embedding.js +29 -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/tools/bookmark-tool.d.ts +4 -0
- package/dist/core/tools/bookmark-tool.js +59 -3
- package/dist/core/tools/index.d.ts +2 -1
- package/dist/core/tools/index.js +2 -1
- package/dist/core/tools/memory-recall-tool.d.ts +6 -0
- package/dist/core/tools/memory-recall-tool.js +77 -0
- package/dist/gateway/channel/adapters/wechat.d.ts +24 -0
- package/dist/gateway/channel/adapters/wechat.js +205 -0
- package/dist/gateway/methods/agent-cancel.d.ts +3 -1
- package/dist/gateway/methods/agent-cancel.js +13 -2
- package/dist/gateway/methods/agent-chat.js +109 -23
- package/dist/gateway/methods/run-scheduled-task.js +3 -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 +62 -7
- package/dist/server/agent-config/agent-config.controller.d.ts +2 -2
- package/dist/server/agent-config/agent-config.controller.js +8 -4
- package/dist/server/agent-config/agent-config.module.js +3 -1
- package/dist/server/agent-config/agent-config.service.d.ts +41 -6
- package/dist/server/agent-config/agent-config.service.js +30 -3
- package/dist/server/agents/agents.service.js +1 -1
- package/dist/server/bootstrap.js +9 -2
- package/dist/server/config/config.controller.d.ts +31 -2
- package/dist/server/config/config.controller.js +14 -0
- package/dist/server/config/config.module.js +2 -2
- package/dist/server/config/config.service.d.ts +14 -1
- package/dist/server/config/config.service.js +1 -0
- package/dist/server/workspace/workspace.service.d.ts +7 -0
- package/dist/server/workspace/workspace.service.js +16 -0
- package/package.json +6 -1
- package/skills/url-bookmark/SKILL.md +12 -12
- package/apps/desktop/renderer/dist/assets/index-DmIfN-Vc.js +0 -89
- package/apps/desktop/renderer/dist/assets/index-DvB8yW8I.css +0 -10
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode 官方免费/推荐模型列表(OpenCode Zen 等),供配置界面下拉选择与本地/远程模式默认模型使用。
|
|
3
|
+
*
|
|
4
|
+
* 维护说明:
|
|
5
|
+
* - 唯一维护点:本文件。前端通过 GET /config/opencode-free-models 拉取,配置界面下拉与默认模型均来源于此。
|
|
6
|
+
* - 新增/下架模型:在 OPENCODE_FREE_MODELS 中增删或修改项;id 格式为 opencode/<model-id>,与 OpenCode Zen 文档一致。
|
|
7
|
+
* - 详见 https://opencode.ai/docs/zen
|
|
8
|
+
*/
|
|
9
|
+
export interface OpenCodeFreeModelOption {
|
|
10
|
+
/** 配置用 ID,如 opencode/minimax-m2.5-free */
|
|
11
|
+
id: string;
|
|
12
|
+
/** 界面展示名称 */
|
|
13
|
+
label: string;
|
|
14
|
+
/** 是否免费(Zen 免费额度内) */
|
|
15
|
+
free?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** OpenCode Zen 等提供的免费/推荐模型,按展示顺序 */
|
|
18
|
+
export declare const OPENCODE_FREE_MODELS: OpenCodeFreeModelOption[];
|
|
19
|
+
/** 仅免费项,用于界面“免费模型”分组 */
|
|
20
|
+
export declare const OPENCODE_FREE_ONLY: OpenCodeFreeModelOption[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** OpenCode Zen 等提供的免费/推荐模型,按展示顺序 */
|
|
2
|
+
export const OPENCODE_FREE_MODELS = [
|
|
3
|
+
{ id: "opencode/minimax-m2.5-free", label: "MiniMax M2.5 Free", free: true },
|
|
4
|
+
{ id: "opencode/glm-5-free", label: "GLM 5 Free", free: true },
|
|
5
|
+
{ id: "opencode/kimi-k2.5-free", label: "Kimi K2.5 Free", free: true },
|
|
6
|
+
{ id: "opencode/big-pickle", label: "Big Pickle", free: true },
|
|
7
|
+
{ id: "opencode/gpt-5-nano", label: "GPT 5 Nano", free: true },
|
|
8
|
+
{ id: "opencode/minimax-m2.5", label: "MiniMax M2.5" },
|
|
9
|
+
{ id: "opencode/glm-5", label: "GLM 5" },
|
|
10
|
+
{ id: "opencode/gemini-3-flash", label: "Gemini 3 Flash" },
|
|
11
|
+
{ id: "opencode/claude-haiku-4-5", label: "Claude Haiku 4.5" },
|
|
12
|
+
];
|
|
13
|
+
/** 仅免费项,用于界面“免费模型”分组 */
|
|
14
|
+
export const OPENCODE_FREE_ONLY = OPENCODE_FREE_MODELS.filter((m) => m.free);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本地 OpenCode 进程:在 mode=local 且配置了默认模型时,由本应用按需启动 opencode serve,
|
|
3
|
+
* 并通过 OPENCODE_CONFIG_CONTENT 注入默认模型与端口;可选设置工作目录(cwd)。
|
|
4
|
+
*/
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
import { createInterface } from "readline";
|
|
7
|
+
import { resolve } from "path";
|
|
8
|
+
const HEALTH_PATH = "/global/health";
|
|
9
|
+
const POLL_INTERVAL_MS = 500;
|
|
10
|
+
const STARTUP_TIMEOUT_MS = 30_000;
|
|
11
|
+
let running = null;
|
|
12
|
+
function isPortHealthy(port) {
|
|
13
|
+
const url = `http://127.0.0.1:${port}${HEALTH_PATH}`;
|
|
14
|
+
return fetch(url, { signal: AbortSignal.timeout(3000) })
|
|
15
|
+
.then((r) => r.ok)
|
|
16
|
+
.catch(() => false);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 确保本机 OpenCode 服务在指定端口运行,并使用给定默认模型与工作目录。
|
|
20
|
+
* 若已存在同端口、同工作目录进程则直接返回;否则启动 opencode serve 并轮询健康检查。
|
|
21
|
+
*/
|
|
22
|
+
export async function ensureLocalOpencodeRunning(port, model, workingDirectory) {
|
|
23
|
+
const cwd = workingDirectory?.trim() ? resolve(workingDirectory.trim()) : undefined;
|
|
24
|
+
if (running) {
|
|
25
|
+
if (running.port === port &&
|
|
26
|
+
(running.model === model || (!model && !running.model)) &&
|
|
27
|
+
(running.cwd === cwd || (!cwd && !running.cwd))) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (running.port === port)
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const alreadyUp = await isPortHealthy(port);
|
|
34
|
+
if (alreadyUp) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const config = { server: { port } };
|
|
38
|
+
if (model && model.trim())
|
|
39
|
+
config.model = model.trim();
|
|
40
|
+
const env = {
|
|
41
|
+
...process.env,
|
|
42
|
+
OPENCODE_CONFIG_CONTENT: JSON.stringify(config),
|
|
43
|
+
};
|
|
44
|
+
const child = spawn("opencode", ["serve", "--port", String(port), "--hostname", "127.0.0.1"], {
|
|
45
|
+
env,
|
|
46
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
47
|
+
windowsHide: true,
|
|
48
|
+
...(cwd && { cwd }),
|
|
49
|
+
});
|
|
50
|
+
const stdout = createInterface({ input: child.stdout });
|
|
51
|
+
const stderr = createInterface({ input: child.stderr });
|
|
52
|
+
stdout.on("line", (line) => {
|
|
53
|
+
if (process.env.DEBUG_OPENCODE)
|
|
54
|
+
console.log("[opencode]", line);
|
|
55
|
+
});
|
|
56
|
+
stderr.on("line", (line) => {
|
|
57
|
+
if (process.env.DEBUG_OPENCODE)
|
|
58
|
+
console.warn("[opencode stderr]", line);
|
|
59
|
+
});
|
|
60
|
+
child.on("error", (err) => {
|
|
61
|
+
if (running?.process === child)
|
|
62
|
+
running = null;
|
|
63
|
+
console.warn("[OpenCode local runner] spawn error:", err.message);
|
|
64
|
+
});
|
|
65
|
+
child.on("exit", (code, signal) => {
|
|
66
|
+
if (running?.process === child)
|
|
67
|
+
running = null;
|
|
68
|
+
if (code != null && code !== 0 && process.env.DEBUG_OPENCODE) {
|
|
69
|
+
console.warn("[OpenCode local runner] exit", { code, signal });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
running = { port, model, cwd, process: child };
|
|
73
|
+
const deadline = Date.now() + STARTUP_TIMEOUT_MS;
|
|
74
|
+
while (Date.now() < deadline) {
|
|
75
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
76
|
+
if (await isPortHealthy(port))
|
|
77
|
+
return;
|
|
78
|
+
if (child.exitCode != null) {
|
|
79
|
+
running = null;
|
|
80
|
+
throw new Error(`OpenCode 本地进程已退出(code=${child.exitCode})。请确认已安装 opencode CLI 且可执行 opencode serve。`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
running = null;
|
|
84
|
+
child.kill("SIGTERM");
|
|
85
|
+
throw new Error(`OpenCode 本地服务在 ${STARTUP_TIMEOUT_MS / 1000} 秒内未就绪(端口 ${port})。请检查 opencode 是否已安装并可用。`);
|
|
86
|
+
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AgentProxy 模块:统一执行入口与适配器注册。
|
|
3
3
|
* 使用前需确保已通过 registerAgentProxyAdapter 注册各类型适配器;
|
|
4
|
-
* 本模块在加载时注册内置的 local、coze、openclawx 适配器。
|
|
4
|
+
* 本模块在加载时注册内置的 local、coze、openclawx、opencode 适配器。
|
|
5
5
|
*/
|
|
6
6
|
import { registerAgentProxyAdapter } from "./registry.js";
|
|
7
7
|
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
|
+
import { opencodeAdapter } from "./adapters/opencode-adapter.js";
|
|
10
11
|
registerAgentProxyAdapter(localAdapter);
|
|
11
12
|
registerAgentProxyAdapter(cozeAdapter);
|
|
12
13
|
registerAgentProxyAdapter(openclawxAdapter);
|
|
14
|
+
registerAgentProxyAdapter(opencodeAdapter);
|
|
13
15
|
export { runForChannelStream, runForChannelCollect } from "./run-for-channel.js";
|
|
14
16
|
export { registerAgentProxyAdapter, getAgentProxyAdapter, listAgentProxyAdapterTypes } from "./registry.js";
|
|
@@ -14,7 +14,7 @@ export async function runForChannelStream(options, callbacks) {
|
|
|
14
14
|
if (!adapter) {
|
|
15
15
|
throw new Error(`No AgentProxy adapter registered for type: "${runnerType}"`);
|
|
16
16
|
}
|
|
17
|
-
await adapter.runStream({ ...options, agentId }, config, callbacks);
|
|
17
|
+
await adapter.runStream({ ...options, agentId, signal: options.signal }, config, callbacks);
|
|
18
18
|
}
|
|
19
19
|
export async function runForChannelCollect(options) {
|
|
20
20
|
const agentId = options.agentId ?? "default";
|
|
@@ -6,6 +6,8 @@ export interface RunAgentForChannelOptions {
|
|
|
6
6
|
sessionId: string;
|
|
7
7
|
message: string;
|
|
8
8
|
agentId: string;
|
|
9
|
+
/** When aborted (e.g. user clicked stop), adapters should stop the run. */
|
|
10
|
+
signal?: AbortSignal;
|
|
9
11
|
}
|
|
10
12
|
export interface RunAgentStreamCallbacks {
|
|
11
13
|
onChunk(delta: string): void;
|
package/dist/core/agent/run.js
CHANGED
|
@@ -59,7 +59,7 @@ export async function run(options) {
|
|
|
59
59
|
});
|
|
60
60
|
// Send prompt and wait for completion
|
|
61
61
|
await session.prompt(userPrompt);
|
|
62
|
-
manager.deleteSession(sessionId + "::" + "default");
|
|
62
|
+
await manager.deleteSession(sessionId + "::" + "default");
|
|
63
63
|
result.assistantContent = assistantContent.trim();
|
|
64
64
|
return result;
|
|
65
65
|
}
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import { type ProviderSupport, type ProviderSupportEntry, type ProviderSupportModel, type ModelSupportType } from "./provider-support-default.js";
|
|
2
2
|
export type { ProviderSupport, ProviderSupportEntry, ProviderSupportModel, ModelSupportType };
|
|
3
|
+
/** 知识库 RAG 配置(config.json.rag) */
|
|
4
|
+
export interface RagConfig {
|
|
5
|
+
/** 向量模型来源:local=本地 GGUF,online=在线 OpenAPI */
|
|
6
|
+
embeddingSource?: "local" | "online";
|
|
7
|
+
/** 在线时:从已配置模型中选的 embedding 项,对应 modelItemCode */
|
|
8
|
+
embeddingModelItemCode?: string;
|
|
9
|
+
/** 本地时:GGUF 模型路径;优先用 node-llama-cpp */
|
|
10
|
+
localModelPath?: string;
|
|
11
|
+
/** 兼容旧配置:在线时的 provider+model */
|
|
12
|
+
embeddingProvider?: string;
|
|
13
|
+
embeddingModel?: string;
|
|
14
|
+
/** 向量库类型:local=本地 Vectra,qdrant=远程 Qdrant */
|
|
15
|
+
vectorStore?: "local" | "qdrant";
|
|
16
|
+
/** 远程 Qdrant 配置 */
|
|
17
|
+
qdrant?: {
|
|
18
|
+
url: string;
|
|
19
|
+
apiKey?: string;
|
|
20
|
+
collection?: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
3
23
|
/** RAG 长记忆:使用远端 embedding 模型;未配置时基于 RAG 的长记忆空转 */
|
|
4
24
|
export interface RagEmbeddingConfig {
|
|
5
25
|
provider: string;
|
|
@@ -26,11 +46,16 @@ export interface ChannelsConfig {
|
|
|
26
46
|
botToken?: string;
|
|
27
47
|
defaultAgentId?: string;
|
|
28
48
|
};
|
|
49
|
+
wechat?: {
|
|
50
|
+
enabled?: boolean;
|
|
51
|
+
puppet?: string;
|
|
52
|
+
defaultAgentId?: string;
|
|
53
|
+
};
|
|
29
54
|
}
|
|
30
55
|
/** MCP 服务器配置(与 core/mcp 类型一致,避免 core/config 依赖 core/mcp 实现) */
|
|
31
56
|
export type DesktopMcpServerConfig = import("../mcp/index.js").McpServerConfig;
|
|
32
|
-
/** Agent 执行器类型:local=本机 pi-coding-agent,coze/openclawx=远程代理 */
|
|
33
|
-
export type AgentRunnerType = "local" | "coze" | "openclawx";
|
|
57
|
+
/** Agent 执行器类型:local=本机 pi-coding-agent,coze/openclawx/opencode=远程代理 */
|
|
58
|
+
export type AgentRunnerType = "local" | "coze" | "openclawx" | "opencode";
|
|
34
59
|
/** Coze 站点:国内站 api.coze.cn / 国际站 api.coze.com,凭证不通用 */
|
|
35
60
|
export type CozeRegion = "cn" | "com";
|
|
36
61
|
/** 某站点的 Bot 凭证(国内/国际各自独立) */
|
|
@@ -61,6 +86,34 @@ export interface AgentOpenClawXConfig {
|
|
|
61
86
|
baseUrl: string;
|
|
62
87
|
apiKey?: string;
|
|
63
88
|
}
|
|
89
|
+
/** OpenCode 启动模式:local=由本应用按需启动本机服务;remote=连接已运行的远端服务 */
|
|
90
|
+
export type OpenCodeServerMode = "local" | "remote";
|
|
91
|
+
/** OpenCode 代理配置:仅对接 [OpenCode 官方 Server API](https://opencode.ai/docs/server)(Session/Message + HTTP Basic) */
|
|
92
|
+
export interface AgentOpenCodeConfig {
|
|
93
|
+
/** 启动模式:local=本应用控制启动并可选设置默认模型;remote=连接已有服务 */
|
|
94
|
+
mode?: OpenCodeServerMode;
|
|
95
|
+
/** 地址(仅 remote 必填;local 时固定为 127.0.0.1) */
|
|
96
|
+
address?: string;
|
|
97
|
+
/** 端口(opencode serve 默认 4096) */
|
|
98
|
+
port: number;
|
|
99
|
+
/** HTTP Basic 认证密码(见 OPENCODE_SERVER_PASSWORD) */
|
|
100
|
+
password?: string;
|
|
101
|
+
/** HTTP Basic 认证用户名(默认 opencode) */
|
|
102
|
+
username?: string;
|
|
103
|
+
/** @deprecated 仅保留向后兼容,产品仅支持官方 Server API */
|
|
104
|
+
apiStyle?: "server" | "openai";
|
|
105
|
+
/** @deprecated 仅保留向后兼容 */
|
|
106
|
+
path?: string;
|
|
107
|
+
/** @deprecated 仅保留向后兼容 */
|
|
108
|
+
streamPath?: string;
|
|
109
|
+
/**
|
|
110
|
+
* 默认模型:local 时由本应用启动服务时写入 OpenCode 配置;remote 时作为请求 model 发送。
|
|
111
|
+
* 不填则 local 使用本机已有 OpenCode 配置、remote 使用服务端默认。
|
|
112
|
+
*/
|
|
113
|
+
model?: string;
|
|
114
|
+
/** 工作目录(仅 local 模式生效):启动 opencode serve 时使用的 cwd,留空则使用进程当前目录 */
|
|
115
|
+
workingDirectory?: string;
|
|
116
|
+
}
|
|
64
117
|
/**
|
|
65
118
|
* 同步读取桌面全局配置中的 maxAgentSessions 等。
|
|
66
119
|
* Gateway 进程内使用,用于会话上限等。
|
|
@@ -70,8 +123,16 @@ export declare function getDesktopConfig(): {
|
|
|
70
123
|
};
|
|
71
124
|
/** 同步读取通道配置(Gateway 启动时用) */
|
|
72
125
|
export declare function getChannelsConfigSync(): ChannelsConfig;
|
|
73
|
-
/** 同步读取 RAG embedding
|
|
126
|
+
/** 同步读取 RAG embedding 配置;embeddingSource 为 local 或未配置在线模型时返回 null,长记忆将空转 */
|
|
74
127
|
export declare function getRagEmbeddingConfigSync(): RagEmbeddingConfig | null;
|
|
128
|
+
/** 同步读取 RAG 本地 GGUF 模型路径;embeddingSource 非 local 或未填时返回 null,调用方使用默认模型(如 embeddinggemma)。 */
|
|
129
|
+
export declare function getRagLocalModelPathSync(): string | null;
|
|
130
|
+
/** 同步读取 RAG 向量库配置;vectorStore 为 qdrant 且 url 有效时返回,否则为 null(使用本地向量库)。 */
|
|
131
|
+
export declare function getRagQdrantConfigSync(): {
|
|
132
|
+
url: string;
|
|
133
|
+
apiKey?: string;
|
|
134
|
+
collection?: string;
|
|
135
|
+
} | null;
|
|
75
136
|
export interface DesktopAgentConfig {
|
|
76
137
|
provider: string;
|
|
77
138
|
model: string;
|
|
@@ -88,6 +149,10 @@ export interface DesktopAgentConfig {
|
|
|
88
149
|
coze?: CozeResolvedConfig;
|
|
89
150
|
/** OpenClawX 代理配置 */
|
|
90
151
|
openclawx?: AgentOpenClawXConfig;
|
|
152
|
+
/** OpenCode 代理配置 */
|
|
153
|
+
opencode?: AgentOpenCodeConfig;
|
|
154
|
+
/** 是否使用经验(长记忆);默认 true */
|
|
155
|
+
useLongMemory?: boolean;
|
|
91
156
|
}
|
|
92
157
|
/**
|
|
93
158
|
* 从 config.json 读取缺省智能体 id(defaultAgentId)。
|
|
@@ -131,6 +196,9 @@ export declare function getDesktopConfigList(): Promise<DesktopConfigList>;
|
|
|
131
196
|
* 确保桌面目录下存在 provider-support.json(不存在则写入默认内容)。
|
|
132
197
|
* 供配置供应商时作备选下拉列表项。
|
|
133
198
|
*/
|
|
199
|
+
/**
|
|
200
|
+
* 确保桌面目录下存在 provider-support.json,并与预装的最新供应商列表合并。
|
|
201
|
+
*/
|
|
134
202
|
export declare function ensureProviderSupportFile(): Promise<void>;
|
|
135
203
|
/**
|
|
136
204
|
* CLI / Gateway 运行时调用,确保 config.json、provider-support.json、agents.json 均完成初始化。
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 供 CLI、WebSocket Gateway 等读取与写入,与 Nest Desktop Backend 使用的 config.json / agents.json 一致。
|
|
4
4
|
* provider-support.json 提供流行 provider 及模型目录,供配置时下拉备选;配置完成后可同步到 agent 目录 models.json 供 pi 使用。
|
|
5
5
|
*/
|
|
6
|
-
import { readFile, writeFile } from "fs/promises";
|
|
6
|
+
import { readFile, writeFile, cp, mkdir } from "fs/promises";
|
|
7
7
|
import { readFileSync, existsSync, mkdirSync } from "fs";
|
|
8
8
|
import { join } from "path";
|
|
9
9
|
import { homedir } from "os";
|
|
@@ -13,6 +13,10 @@ function getDesktopDir() {
|
|
|
13
13
|
const home = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
14
14
|
return join(home, ".openbot", "desktop");
|
|
15
15
|
}
|
|
16
|
+
/** 获取预装资源目录(打包后在 Resources/presets,dev 时在仓库根 presets) */
|
|
17
|
+
function getPresetsDir() {
|
|
18
|
+
return process.env.OPENBOT_PRESETS_DIR || join(process.cwd(), "presets");
|
|
19
|
+
}
|
|
16
20
|
const DEFAULT_AGENT_ID = "default";
|
|
17
21
|
const DEFAULT_MAX_AGENT_SESSIONS = 5;
|
|
18
22
|
/** 同步读取桌面全局配置(Gateway 等需要同步读的场景) */
|
|
@@ -57,7 +61,7 @@ export function getChannelsConfigSync() {
|
|
|
57
61
|
return {};
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
|
-
/** 同步读取 RAG embedding
|
|
64
|
+
/** 同步读取 RAG embedding 配置;embeddingSource 为 local 或未配置在线模型时返回 null,长记忆将空转 */
|
|
61
65
|
export function getRagEmbeddingConfigSync() {
|
|
62
66
|
try {
|
|
63
67
|
const configPath = getConfigPath();
|
|
@@ -65,8 +69,27 @@ export function getRagEmbeddingConfigSync() {
|
|
|
65
69
|
return null;
|
|
66
70
|
const content = readFileSync(configPath, "utf-8");
|
|
67
71
|
const data = JSON.parse(content);
|
|
68
|
-
const
|
|
69
|
-
|
|
72
|
+
const rag = data.rag;
|
|
73
|
+
if (rag?.embeddingSource === "local")
|
|
74
|
+
return null;
|
|
75
|
+
let provider;
|
|
76
|
+
let modelId;
|
|
77
|
+
if (rag?.embeddingModelItemCode && Array.isArray(data.configuredModels)) {
|
|
78
|
+
const code = rag.embeddingModelItemCode;
|
|
79
|
+
const item = data.configuredModels.find((m) => {
|
|
80
|
+
if (m.type !== "embedding")
|
|
81
|
+
return false;
|
|
82
|
+
return m.modelItemCode === code || (m.provider && m.modelId && `${m.provider}:${m.modelId}` === code);
|
|
83
|
+
});
|
|
84
|
+
if (item && item.provider) {
|
|
85
|
+
provider = item.provider;
|
|
86
|
+
modelId = item.modelId;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!provider || !modelId) {
|
|
90
|
+
provider = rag?.embeddingProvider?.trim();
|
|
91
|
+
modelId = rag?.embeddingModel?.trim();
|
|
92
|
+
}
|
|
70
93
|
if (!provider || !modelId)
|
|
71
94
|
return null;
|
|
72
95
|
const prov = data.providers?.[provider];
|
|
@@ -86,6 +109,43 @@ export function getRagEmbeddingConfigSync() {
|
|
|
86
109
|
return null;
|
|
87
110
|
}
|
|
88
111
|
}
|
|
112
|
+
/** 同步读取 RAG 本地 GGUF 模型路径;embeddingSource 非 local 或未填时返回 null,调用方使用默认模型(如 embeddinggemma)。 */
|
|
113
|
+
export function getRagLocalModelPathSync() {
|
|
114
|
+
try {
|
|
115
|
+
const configPath = getConfigPath();
|
|
116
|
+
if (!existsSync(configPath))
|
|
117
|
+
return null;
|
|
118
|
+
const content = readFileSync(configPath, "utf-8");
|
|
119
|
+
const data = JSON.parse(content);
|
|
120
|
+
if (data.rag?.embeddingSource !== "local")
|
|
121
|
+
return null;
|
|
122
|
+
const modelPath = data.rag?.localModelPath?.trim();
|
|
123
|
+
return modelPath || null;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** 同步读取 RAG 向量库配置;vectorStore 为 qdrant 且 url 有效时返回,否则为 null(使用本地向量库)。 */
|
|
130
|
+
export function getRagQdrantConfigSync() {
|
|
131
|
+
try {
|
|
132
|
+
const configPath = getConfigPath();
|
|
133
|
+
if (!existsSync(configPath))
|
|
134
|
+
return null;
|
|
135
|
+
const content = readFileSync(configPath, "utf-8");
|
|
136
|
+
const data = JSON.parse(content);
|
|
137
|
+
if (data.rag?.vectorStore !== "qdrant" || !data.rag?.qdrant?.url?.trim())
|
|
138
|
+
return null;
|
|
139
|
+
return {
|
|
140
|
+
url: data.rag.qdrant.url.trim().replace(/\/$/, ""),
|
|
141
|
+
apiKey: data.rag.qdrant.apiKey?.trim(),
|
|
142
|
+
collection: data.rag.qdrant.collection?.trim(),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
89
149
|
const EMBEDDING_DEFAULT_BASE_URL = {
|
|
90
150
|
openai: "https://api.openai.com/v1",
|
|
91
151
|
"openai-custom": "",
|
|
@@ -161,6 +221,7 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
161
221
|
let workspaceName = resolvedAgentId;
|
|
162
222
|
let mcpServers;
|
|
163
223
|
let systemPrompt;
|
|
224
|
+
let useLongMemory = true;
|
|
164
225
|
if (existsSync(agentsPath)) {
|
|
165
226
|
try {
|
|
166
227
|
const raw = await readFile(agentsPath, "utf-8");
|
|
@@ -178,6 +239,8 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
178
239
|
if (agent.systemPrompt && typeof agent.systemPrompt === "string") {
|
|
179
240
|
systemPrompt = agent.systemPrompt.trim();
|
|
180
241
|
}
|
|
242
|
+
if (agent.useLongMemory !== undefined)
|
|
243
|
+
useLongMemory = !!agent.useLongMemory;
|
|
181
244
|
if (agent.modelItemCode && Array.isArray(config.configuredModels)) {
|
|
182
245
|
const configured = config.configuredModels.find((m) => m.modelItemCode === agent.modelItemCode);
|
|
183
246
|
if (configured) {
|
|
@@ -210,6 +273,7 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
210
273
|
let runnerType = "local";
|
|
211
274
|
let coze;
|
|
212
275
|
let openclawx;
|
|
276
|
+
let opencode;
|
|
213
277
|
if (existsSync(agentsPath)) {
|
|
214
278
|
try {
|
|
215
279
|
const rawAgents = await readFile(agentsPath, "utf-8");
|
|
@@ -217,7 +281,9 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
217
281
|
const agentsList = Array.isArray(dataAgents.agents) ? dataAgents.agents : [];
|
|
218
282
|
const agentRow = agentsList.find((a) => a.id === resolvedAgentId);
|
|
219
283
|
if (agentRow) {
|
|
220
|
-
if (agentRow.runnerType === "coze" ||
|
|
284
|
+
if (agentRow.runnerType === "coze" ||
|
|
285
|
+
agentRow.runnerType === "openclawx" ||
|
|
286
|
+
agentRow.runnerType === "opencode") {
|
|
221
287
|
runnerType = agentRow.runnerType;
|
|
222
288
|
}
|
|
223
289
|
if (agentRow.coze) {
|
|
@@ -254,6 +320,37 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
254
320
|
apiKey: agentRow.openclawx.apiKey?.trim(),
|
|
255
321
|
};
|
|
256
322
|
}
|
|
323
|
+
if (agentRow.opencode?.port != null) {
|
|
324
|
+
const raw = agentRow.opencode;
|
|
325
|
+
const port = Number(raw.port);
|
|
326
|
+
if (!Number.isNaN(port) && port > 0) {
|
|
327
|
+
const mode = raw.mode === "local" || raw.mode === "remote"
|
|
328
|
+
? raw.mode
|
|
329
|
+
: raw.address != null && String(raw.address).trim()
|
|
330
|
+
? "remote"
|
|
331
|
+
: "local";
|
|
332
|
+
const address = mode === "remote" && raw.address != null
|
|
333
|
+
? String(raw.address).trim()
|
|
334
|
+
: "127.0.0.1";
|
|
335
|
+
if (mode === "local" || address) {
|
|
336
|
+
const apiStyle = raw.apiStyle === "openai" ? "openai" : "server";
|
|
337
|
+
opencode = {
|
|
338
|
+
mode,
|
|
339
|
+
address: mode === "remote" ? address : "127.0.0.1",
|
|
340
|
+
port,
|
|
341
|
+
password: raw.password != null ? String(raw.password).trim() : undefined,
|
|
342
|
+
username: raw.username != null ? String(raw.username).trim() || undefined : undefined,
|
|
343
|
+
apiStyle,
|
|
344
|
+
path: raw.path != null ? String(raw.path).trim() || undefined : undefined,
|
|
345
|
+
streamPath: raw.streamPath != null
|
|
346
|
+
? String(raw.streamPath).trim() || undefined
|
|
347
|
+
: undefined,
|
|
348
|
+
model: raw.model != null ? String(raw.model).trim() || undefined : undefined,
|
|
349
|
+
workingDirectory: raw.workingDirectory != null ? String(raw.workingDirectory).trim() || undefined : undefined,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
257
354
|
}
|
|
258
355
|
}
|
|
259
356
|
catch {
|
|
@@ -270,6 +367,8 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
270
367
|
runnerType,
|
|
271
368
|
coze,
|
|
272
369
|
openclawx,
|
|
370
|
+
opencode,
|
|
371
|
+
useLongMemory,
|
|
273
372
|
};
|
|
274
373
|
}
|
|
275
374
|
function ensureDesktopDir() {
|
|
@@ -420,20 +519,57 @@ export async function getDesktopConfigList() {
|
|
|
420
519
|
* 确保桌面目录下存在 provider-support.json(不存在则写入默认内容)。
|
|
421
520
|
* 供配置供应商时作备选下拉列表项。
|
|
422
521
|
*/
|
|
522
|
+
/**
|
|
523
|
+
* 确保桌面目录下存在 provider-support.json,并与预装的最新供应商列表合并。
|
|
524
|
+
*/
|
|
423
525
|
export async function ensureProviderSupportFile() {
|
|
526
|
+
const presetPath = join(getPresetsDir(), "preset-providers.json");
|
|
527
|
+
let presetData = { providers: DEFAULT_PROVIDER_SUPPORT };
|
|
528
|
+
if (existsSync(presetPath)) {
|
|
529
|
+
try {
|
|
530
|
+
presetData = JSON.parse(await readFile(presetPath, "utf-8"));
|
|
531
|
+
}
|
|
532
|
+
catch { }
|
|
533
|
+
}
|
|
534
|
+
const presetProviders = presetData.providers || DEFAULT_PROVIDER_SUPPORT;
|
|
424
535
|
const path = getProviderSupportPath();
|
|
425
|
-
if (existsSync(path))
|
|
426
|
-
return;
|
|
427
536
|
ensureDesktopDir();
|
|
428
|
-
|
|
537
|
+
if (!existsSync(path)) {
|
|
538
|
+
await writeFile(path, JSON.stringify(presetProviders, null, 2), "utf-8");
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
try {
|
|
542
|
+
const userContent = await readFile(path, "utf-8");
|
|
543
|
+
const userProviders = JSON.parse(userContent);
|
|
544
|
+
let changed = false;
|
|
545
|
+
for (const [providerId, presetEntry] of Object.entries(presetProviders)) {
|
|
546
|
+
if (!userProviders[providerId]) {
|
|
547
|
+
userProviders[providerId] = presetEntry;
|
|
548
|
+
changed = true;
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
const userModels = userProviders[providerId].models || [];
|
|
552
|
+
for (const presetModel of presetEntry.models) {
|
|
553
|
+
if (!userModels.some((m) => m.id === presetModel.id)) {
|
|
554
|
+
userModels.push(presetModel);
|
|
555
|
+
changed = true;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
userProviders[providerId].models = userModels;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (changed) {
|
|
562
|
+
await writeFile(path, JSON.stringify(userProviders, null, 2), "utf-8");
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
await writeFile(path, JSON.stringify(presetProviders, null, 2), "utf-8");
|
|
567
|
+
}
|
|
429
568
|
}
|
|
430
|
-
/** 若 config.json
|
|
569
|
+
/** 若 config.json 不存在则用 preset-config.json 初始化,若存在则浅合并补充新基础键值 */
|
|
431
570
|
async function ensureConfigJsonInitialized() {
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
return;
|
|
435
|
-
ensureDesktopDir();
|
|
436
|
-
const defaultConfig = {
|
|
571
|
+
const presetPath = join(getPresetsDir(), "preset-config.json");
|
|
572
|
+
let presetConfig = {
|
|
437
573
|
defaultProvider: "deepseek",
|
|
438
574
|
defaultModel: "deepseek-chat",
|
|
439
575
|
defaultAgentId: DEFAULT_AGENT_ID,
|
|
@@ -441,20 +577,82 @@ async function ensureConfigJsonInitialized() {
|
|
|
441
577
|
providers: {},
|
|
442
578
|
configuredModels: [],
|
|
443
579
|
};
|
|
444
|
-
|
|
580
|
+
if (existsSync(presetPath)) {
|
|
581
|
+
try {
|
|
582
|
+
const data = JSON.parse(await readFile(presetPath, "utf-8"));
|
|
583
|
+
if (data.config)
|
|
584
|
+
presetConfig = data.config;
|
|
585
|
+
}
|
|
586
|
+
catch { }
|
|
587
|
+
}
|
|
588
|
+
const configPath = getConfigPath();
|
|
589
|
+
ensureDesktopDir();
|
|
590
|
+
if (!existsSync(configPath)) {
|
|
591
|
+
await writeFile(configPath, JSON.stringify(presetConfig, null, 2), "utf-8");
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
try {
|
|
595
|
+
const userConfig = JSON.parse(await readFile(configPath, "utf-8"));
|
|
596
|
+
let changed = false;
|
|
597
|
+
for (const [key, value] of Object.entries(presetConfig)) {
|
|
598
|
+
if (userConfig[key] === undefined) {
|
|
599
|
+
userConfig[key] = value;
|
|
600
|
+
changed = true;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (changed) {
|
|
604
|
+
await writeFile(configPath, JSON.stringify(userConfig, null, 2), "utf-8");
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
catch { }
|
|
445
608
|
}
|
|
446
|
-
/**
|
|
609
|
+
/** 合并 preset-agents.json 中的预置智能体,并随之释放对应的工作区技能 */
|
|
447
610
|
async function ensureAgentsJsonInitialized() {
|
|
611
|
+
const presetPath = join(getPresetsDir(), "preset-agents.json");
|
|
612
|
+
let presetAgents = [];
|
|
613
|
+
if (existsSync(presetPath)) {
|
|
614
|
+
try {
|
|
615
|
+
const data = JSON.parse(await readFile(presetPath, "utf-8"));
|
|
616
|
+
if (Array.isArray(data.agents))
|
|
617
|
+
presetAgents = data.agents;
|
|
618
|
+
}
|
|
619
|
+
catch { }
|
|
620
|
+
}
|
|
448
621
|
const agentsPath = join(getDesktopDir(), "agents.json");
|
|
449
|
-
if (existsSync(agentsPath))
|
|
450
|
-
return;
|
|
451
622
|
ensureDesktopDir();
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
623
|
+
let currentData = { agents: [] };
|
|
624
|
+
if (existsSync(agentsPath)) {
|
|
625
|
+
try {
|
|
626
|
+
currentData = JSON.parse(await readFile(agentsPath, "utf-8"));
|
|
627
|
+
if (!Array.isArray(currentData.agents))
|
|
628
|
+
currentData.agents = [];
|
|
629
|
+
}
|
|
630
|
+
catch { }
|
|
631
|
+
}
|
|
632
|
+
let changed = false;
|
|
633
|
+
for (const pa of presetAgents) {
|
|
634
|
+
if (!currentData.agents.some((a) => a.id === pa.id)) {
|
|
635
|
+
currentData.agents.push(pa);
|
|
636
|
+
changed = true;
|
|
637
|
+
try {
|
|
638
|
+
const srcSkillsDir = join(getPresetsDir(), "workspaces", pa.id, "skills");
|
|
639
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
640
|
+
const destSkillsDir = join(homeDir, ".openbot", "workspace", pa.id, "skills");
|
|
641
|
+
if (existsSync(srcSkillsDir)) {
|
|
642
|
+
if (!existsSync(destSkillsDir)) {
|
|
643
|
+
await mkdir(destSkillsDir, { recursive: true });
|
|
644
|
+
}
|
|
645
|
+
await cp(srcSkillsDir, destSkillsDir, { recursive: true, force: false, errorOnExist: false });
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
catch (err) {
|
|
649
|
+
console.error(`Failed to copy preset skills for agent ${pa.id}:`, err);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (changed || !existsSync(agentsPath)) {
|
|
654
|
+
await writeFile(agentsPath, JSON.stringify(currentData, null, 2), "utf-8");
|
|
655
|
+
}
|
|
458
656
|
}
|
|
459
657
|
/**
|
|
460
658
|
* CLI / Gateway 运行时调用,确保 config.json、provider-support.json、agents.json 均完成初始化。
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionFactory } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
/**
|
|
3
|
-
* 创建用于在 session_compact
|
|
4
|
-
*
|
|
3
|
+
* 创建用于在 session_compact 事件时更新「当前 session 最新 compaction」的 extension factory。
|
|
4
|
+
* 发生 compaction 时只回调 onUpdateLatestCompaction(summary),由调用方保存;
|
|
5
|
+
* 向量库写入推迟到 SessionAgent 关闭时由 AgentManager 统一执行。
|
|
5
6
|
*/
|
|
6
|
-
export declare function createCompactionMemoryExtensionFactory(
|
|
7
|
+
export declare function createCompactionMemoryExtensionFactory(_sessionId: string, onUpdateLatestCompaction: (summary: string) => void): ExtensionFactory;
|