@next-open-ai/openbot 0.6.8 → 0.6.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +155 -136
  3. package/apps/desktop/renderer/dist/assets/index-CxDZnMBH.css +10 -0
  4. package/apps/desktop/renderer/dist/assets/index-k47Qiokg.js +93 -0
  5. package/apps/desktop/renderer/dist/index.html +2 -2
  6. package/dist/cli/cli.js +136 -0
  7. package/dist/cli/extension-cmd.d.ts +15 -0
  8. package/dist/cli/extension-cmd.js +107 -0
  9. package/dist/core/agent/agent-dir.d.ts +6 -0
  10. package/dist/core/agent/agent-dir.js +8 -0
  11. package/dist/core/agent/agent-manager.d.ts +27 -6
  12. package/dist/core/agent/agent-manager.js +147 -26
  13. package/dist/core/agent/proxy/adapters/claude-code-adapter.d.ts +2 -0
  14. package/dist/core/agent/proxy/adapters/claude-code-adapter.js +186 -0
  15. package/dist/core/agent/proxy/adapters/coze-adapter.d.ts +2 -0
  16. package/dist/core/agent/proxy/adapters/coze-adapter.js +406 -0
  17. package/dist/core/agent/proxy/adapters/local-adapter.d.ts +2 -0
  18. package/dist/core/agent/proxy/adapters/local-adapter.js +95 -0
  19. package/dist/core/agent/proxy/adapters/openclawx-adapter.d.ts +2 -0
  20. package/dist/core/agent/proxy/adapters/openclawx-adapter.js +115 -0
  21. package/dist/core/agent/proxy/adapters/opencode-adapter.d.ts +11 -0
  22. package/dist/core/agent/proxy/adapters/opencode-adapter.js +786 -0
  23. package/dist/core/agent/proxy/adapters/opencode-free-models.d.ts +20 -0
  24. package/dist/core/agent/proxy/adapters/opencode-free-models.js +14 -0
  25. package/dist/core/agent/proxy/adapters/opencode-local-runner.d.ts +5 -0
  26. package/dist/core/agent/proxy/adapters/opencode-local-runner.js +95 -0
  27. package/dist/core/agent/proxy/index.d.ts +3 -0
  28. package/dist/core/agent/proxy/index.js +18 -0
  29. package/dist/core/agent/proxy/registry.d.ts +7 -0
  30. package/dist/core/agent/proxy/registry.js +13 -0
  31. package/dist/core/agent/proxy/run-for-channel.d.ts +3 -0
  32. package/dist/core/agent/proxy/run-for-channel.js +31 -0
  33. package/dist/core/agent/proxy/types.d.ts +30 -0
  34. package/dist/core/agent/proxy/types.js +1 -0
  35. package/dist/core/agent/run.js +1 -1
  36. package/dist/core/agent/token-usage-log-extension.d.ts +14 -0
  37. package/dist/core/agent/token-usage-log-extension.js +61 -0
  38. package/dist/core/config/agent-reload-pending.d.ts +9 -0
  39. package/dist/core/config/agent-reload-pending.js +67 -0
  40. package/dist/core/config/desktop-config.d.ts +136 -5
  41. package/dist/core/config/desktop-config.js +470 -46
  42. package/dist/core/config/provider-support-default.js +27 -0
  43. package/dist/core/extensions/index.d.ts +1 -0
  44. package/dist/core/extensions/index.js +1 -0
  45. package/dist/core/extensions/load.d.ts +11 -0
  46. package/dist/core/extensions/load.js +101 -0
  47. package/dist/core/inbound-message-preprocess.d.ts +27 -0
  48. package/dist/core/inbound-message-preprocess.js +96 -0
  49. package/dist/core/local-llm-server/download-model.d.ts +16 -0
  50. package/dist/core/local-llm-server/download-model.js +37 -0
  51. package/dist/core/local-llm-server/index.d.ts +32 -0
  52. package/dist/core/local-llm-server/index.js +152 -0
  53. package/dist/core/local-llm-server/llm-context.d.ts +66 -0
  54. package/dist/core/local-llm-server/llm-context.js +270 -0
  55. package/dist/core/local-llm-server/model-resolve.d.ts +27 -0
  56. package/dist/core/local-llm-server/model-resolve.js +90 -0
  57. package/dist/core/local-llm-server/server.d.ts +1 -0
  58. package/dist/core/local-llm-server/server.js +234 -0
  59. package/dist/core/local-llm-server/start-from-config.d.ts +5 -0
  60. package/dist/core/local-llm-server/start-from-config.js +50 -0
  61. package/dist/core/mcp/adapter.d.ts +4 -2
  62. package/dist/core/mcp/adapter.js +10 -4
  63. package/dist/core/mcp/client.d.ts +4 -0
  64. package/dist/core/mcp/client.js +2 -0
  65. package/dist/core/mcp/config.d.ts +14 -3
  66. package/dist/core/mcp/config.js +68 -3
  67. package/dist/core/mcp/index.d.ts +10 -6
  68. package/dist/core/mcp/index.js +7 -3
  69. package/dist/core/mcp/operator.d.ts +28 -2
  70. package/dist/core/mcp/operator.js +131 -30
  71. package/dist/core/mcp/transport/index.d.ts +4 -0
  72. package/dist/core/mcp/transport/index.js +6 -1
  73. package/dist/core/mcp/transport/stdio.d.ts +12 -0
  74. package/dist/core/mcp/transport/stdio.js +147 -29
  75. package/dist/core/mcp/types.d.ts +18 -0
  76. package/dist/core/memory/compaction-extension.d.ts +4 -3
  77. package/dist/core/memory/compaction-extension.js +6 -14
  78. package/dist/core/memory/embedding-types.d.ts +10 -0
  79. package/dist/core/memory/embedding-types.js +5 -0
  80. package/dist/core/memory/embedding.d.ts +2 -1
  81. package/dist/core/memory/embedding.js +38 -6
  82. package/dist/core/memory/index.js +3 -0
  83. package/dist/core/memory/local-embedding-llama.d.ts +13 -0
  84. package/dist/core/memory/local-embedding-llama.js +78 -0
  85. package/dist/core/memory/local-embedding.d.ts +11 -0
  86. package/dist/core/memory/local-embedding.js +69 -0
  87. package/dist/core/memory/persist-compaction-on-close.d.ts +14 -0
  88. package/dist/core/memory/persist-compaction-on-close.js +32 -0
  89. package/dist/core/session-outlet/index.d.ts +19 -0
  90. package/dist/core/session-outlet/index.js +33 -0
  91. package/dist/core/session-outlet/outlet.d.ts +15 -0
  92. package/dist/core/session-outlet/outlet.js +49 -0
  93. package/dist/core/session-outlet/types.d.ts +35 -0
  94. package/dist/core/session-outlet/types.js +5 -0
  95. package/dist/core/tools/bookmark-tool.d.ts +4 -0
  96. package/dist/core/tools/bookmark-tool.js +59 -3
  97. package/dist/core/tools/index.d.ts +3 -1
  98. package/dist/core/tools/index.js +3 -1
  99. package/dist/core/tools/memory-recall-tool.d.ts +6 -0
  100. package/dist/core/tools/memory-recall-tool.js +77 -0
  101. package/dist/core/tools/truncate-result.d.ts +14 -0
  102. package/dist/core/tools/truncate-result.js +27 -0
  103. package/dist/core/tools/web-search/create-web-search-tool.d.ts +17 -0
  104. package/dist/core/tools/web-search/create-web-search-tool.js +87 -0
  105. package/dist/core/tools/web-search/index.d.ts +4 -0
  106. package/dist/core/tools/web-search/index.js +2 -0
  107. package/dist/core/tools/web-search/providers/brave.d.ts +2 -0
  108. package/dist/core/tools/web-search/providers/brave.js +87 -0
  109. package/dist/core/tools/web-search/providers/duck-duck-scrape.d.ts +2 -0
  110. package/dist/core/tools/web-search/providers/duck-duck-scrape.js +47 -0
  111. package/dist/core/tools/web-search/providers/index.d.ts +5 -0
  112. package/dist/core/tools/web-search/providers/index.js +13 -0
  113. package/dist/core/tools/web-search/types.d.ts +35 -0
  114. package/dist/core/tools/web-search/types.js +4 -0
  115. package/dist/gateway/channel/adapters/telegram.js +13 -2
  116. package/dist/gateway/channel/adapters/wechat.d.ts +24 -0
  117. package/dist/gateway/channel/adapters/wechat.js +205 -0
  118. package/dist/gateway/channel/channel-core.d.ts +1 -0
  119. package/dist/gateway/channel/channel-core.js +101 -59
  120. package/dist/gateway/channel/run-agent.d.ts +2 -4
  121. package/dist/gateway/channel/run-agent.js +13 -125
  122. package/dist/gateway/methods/agent-cancel.d.ts +3 -1
  123. package/dist/gateway/methods/agent-cancel.js +16 -2
  124. package/dist/gateway/methods/agent-chat.d.ts +4 -0
  125. package/dist/gateway/methods/agent-chat.js +377 -118
  126. package/dist/gateway/methods/run-scheduled-task.js +9 -7
  127. package/dist/gateway/proxy-run-abort.d.ts +6 -0
  128. package/dist/gateway/proxy-run-abort.js +39 -0
  129. package/dist/gateway/server.js +123 -19
  130. package/dist/server/agent-config/agent-config.controller.d.ts +10 -2
  131. package/dist/server/agent-config/agent-config.controller.js +19 -4
  132. package/dist/server/agent-config/agent-config.module.js +3 -1
  133. package/dist/server/agent-config/agent-config.service.d.ts +91 -6
  134. package/dist/server/agent-config/agent-config.service.js +115 -3
  135. package/dist/server/agents/agents.controller.d.ts +16 -0
  136. package/dist/server/agents/agents.controller.js +62 -1
  137. package/dist/server/agents/agents.gateway.js +1 -1
  138. package/dist/server/agents/agents.service.js +1 -1
  139. package/dist/server/bootstrap.d.ts +1 -0
  140. package/dist/server/bootstrap.js +28 -4
  141. package/dist/server/config/config.controller.d.ts +134 -2
  142. package/dist/server/config/config.controller.js +199 -3
  143. package/dist/server/config/config.module.js +5 -4
  144. package/dist/server/config/config.service.d.ts +32 -2
  145. package/dist/server/config/config.service.js +69 -9
  146. package/dist/server/config/local-models.service.d.ts +67 -0
  147. package/dist/server/config/local-models.service.js +242 -0
  148. package/dist/server/workspace/workspace.service.d.ts +7 -0
  149. package/dist/server/workspace/workspace.service.js +16 -0
  150. package/package.json +10 -2
  151. package/presets/preset-agents.json +128 -0
  152. package/presets/preset-config.json +29 -0
  153. package/presets/preset-providers.json +180 -0
  154. package/presets/recommended-local-models.json +36 -0
  155. package/presets/workspaces/code-assistant/skills/code-review/SKILL.md +19 -0
  156. package/presets/workspaces/code-assistant/skills/code-runner/SKILL.md +21 -0
  157. package/presets/workspaces/code-assistant/skills/git-helper/SKILL.md +29 -0
  158. package/presets/workspaces/creator-assistant/skills/.gitkeep +0 -0
  159. package/presets/workspaces/creator-assistant/skills/creator-tools/SKILL.md +15 -0
  160. package/presets/workspaces/doc-assistant/skills/doc-processor/SKILL.md +21 -0
  161. package/presets/workspaces/download-assistant/skills/downloader/SKILL.md +20 -0
  162. package/presets/workspaces/file-assistant/skills/file-converter/SKILL.md +21 -0
  163. package/presets/workspaces/file-assistant/skills/file-organizer/SKILL.md +17 -0
  164. package/presets/workspaces/file-assistant/skills/file-search/SKILL.md +22 -0
  165. package/presets/workspaces/morning-briefing/skills/news-fetcher/SKILL.md +16 -0
  166. package/presets/workspaces/morning-briefing/skills/web-summarizer/SKILL.md +20 -0
  167. package/presets/workspaces/news-assistant/skills/news-fetcher/SKILL.md +16 -0
  168. package/presets/workspaces/news-assistant/skills/web-summarizer/SKILL.md +20 -0
  169. package/presets/workspaces/office-automation/skills/rpa-helper/SKILL.md +9 -0
  170. package/presets/workspaces/self-media-bot/skills/self-media-tools/SKILL.md +9 -0
  171. package/skills/url-bookmark/SKILL.md +12 -12
  172. package/apps/desktop/renderer/dist/assets/index-LCp1YPVA.css +0 -10
  173. package/apps/desktop/renderer/dist/assets/index-l5fpDsHs.js +0 -89
@@ -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 配置;未配置或无效时返回 null,长记忆将空转 */
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 provider = data.rag?.embeddingProvider?.trim();
69
- const modelId = data.rag?.embeddingModel?.trim();
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": "",
@@ -149,8 +209,8 @@ export async function loadDesktopAgentConfig(agentId) {
149
209
  }
150
210
  }
151
211
  const resolvedAgentId = agentId === "default" ? "default" : agentId;
152
- let provider = config.defaultProvider ?? "deepseek";
153
- let model = config.defaultModel ?? "deepseek-chat";
212
+ let provider = config.defaultProvider ?? "ollama";
213
+ let model = config.defaultModel ?? "qwen3:4b";
154
214
  if (config.defaultModelItemCode && Array.isArray(config.configuredModels)) {
155
215
  const configured = config.configuredModels.find((m) => m.modelItemCode === config.defaultModelItemCode);
156
216
  if (configured) {
@@ -158,9 +218,14 @@ export async function loadDesktopAgentConfig(agentId) {
158
218
  model = configured.modelId;
159
219
  }
160
220
  }
221
+ /** 是否从当前智能体自己的配置得到了模型(有 modelItemCode 或 provider/model);若否,则使用的是全局默认 */
222
+ let agentHadOwnModel = false;
161
223
  let workspaceName = resolvedAgentId;
162
224
  let mcpServers;
225
+ let mcpMaxResultTokens;
163
226
  let systemPrompt;
227
+ let useLongMemory = true;
228
+ let contextSize;
164
229
  if (existsSync(agentsPath)) {
165
230
  try {
166
231
  const raw = await readFile(agentsPath, "utf-8");
@@ -172,30 +237,49 @@ export async function loadDesktopAgentConfig(agentId) {
172
237
  workspaceName = agent.workspace;
173
238
  else if (agent.id)
174
239
  workspaceName = agent.id;
175
- if (agent.mcpServers && Array.isArray(agent.mcpServers)) {
176
- mcpServers = agent.mcpServers;
240
+ if (agent.mcpMaxResultTokens != null && typeof agent.mcpMaxResultTokens === "number" && agent.mcpMaxResultTokens > 0) {
241
+ mcpMaxResultTokens = agent.mcpMaxResultTokens;
242
+ }
243
+ if (agent.contextSize != null && typeof agent.contextSize === "number" && agent.contextSize > 0) {
244
+ contextSize = agent.contextSize;
245
+ }
246
+ if (agent.mcpServers != null) {
247
+ if (Array.isArray(agent.mcpServers) || (typeof agent.mcpServers === "object" && !Array.isArray(agent.mcpServers))) {
248
+ mcpServers = agent.mcpServers;
249
+ }
177
250
  }
178
251
  if (agent.systemPrompt && typeof agent.systemPrompt === "string") {
179
252
  systemPrompt = agent.systemPrompt.trim();
180
253
  }
254
+ if (agent.useLongMemory !== undefined)
255
+ useLongMemory = !!agent.useLongMemory;
181
256
  if (agent.modelItemCode && Array.isArray(config.configuredModels)) {
182
257
  const configured = config.configuredModels.find((m) => m.modelItemCode === agent.modelItemCode);
183
258
  if (configured) {
184
259
  provider = configured.provider;
185
260
  model = configured.modelId;
261
+ agentHadOwnModel = true;
186
262
  }
187
263
  else {
188
- if (agent.provider)
264
+ if (agent.provider) {
189
265
  provider = agent.provider;
190
- if (agent.model)
266
+ agentHadOwnModel = true;
267
+ }
268
+ if (agent.model) {
191
269
  model = agent.model;
270
+ agentHadOwnModel = true;
271
+ }
192
272
  }
193
273
  }
194
274
  else {
195
- if (agent.provider)
275
+ if (agent.provider) {
196
276
  provider = agent.provider;
197
- if (agent.model)
277
+ agentHadOwnModel = true;
278
+ }
279
+ if (agent.model) {
198
280
  model = agent.model;
281
+ agentHadOwnModel = true;
282
+ }
199
283
  }
200
284
  }
201
285
  }
@@ -203,11 +287,168 @@ export async function loadDesktopAgentConfig(agentId) {
203
287
  // ignore
204
288
  }
205
289
  }
290
+ // 本地 LLM 可用且当前智能体未配置自己的模型时,使用本地推理作为缺省,使所有智能体“拥有”该配置
291
+ if (!agentHadOwnModel && process.env.LOCAL_LLM_BASE_URL?.trim()) {
292
+ provider = "local";
293
+ model = "local-llm";
294
+ }
206
295
  const provConfig = config.providers?.[provider];
207
296
  const apiKey = provConfig?.apiKey && typeof provConfig.apiKey === "string" && provConfig.apiKey.trim()
208
297
  ? provConfig.apiKey.trim()
209
298
  : undefined;
210
- return { provider, model, apiKey: apiKey ?? undefined, workspace: workspaceName, mcpServers, systemPrompt };
299
+ let runnerType = "local";
300
+ let coze;
301
+ let openclawx;
302
+ let opencode;
303
+ let claudeCode;
304
+ const tw = config.tools?.webSearch;
305
+ const timeoutSeconds = typeof tw?.timeoutSeconds === "number" && tw.timeoutSeconds > 0 ? tw.timeoutSeconds : 15;
306
+ const cacheTtlMinutes = typeof tw?.cacheTtlMinutes === "number" && tw.cacheTtlMinutes >= 0 ? tw.cacheTtlMinutes : 5;
307
+ const maxResultsRaw = typeof tw?.maxResults === "number" ? tw.maxResults : 5;
308
+ const maxResults = Math.min(10, Math.max(1, maxResultsRaw));
309
+ let webSearch = {
310
+ enabled: false,
311
+ provider: "duck-duck-scrape",
312
+ timeoutSeconds,
313
+ cacheTtlMinutes,
314
+ maxResults,
315
+ };
316
+ if (existsSync(agentsPath)) {
317
+ try {
318
+ const rawAgents = await readFile(agentsPath, "utf-8");
319
+ const dataAgents = JSON.parse(rawAgents);
320
+ const agentsList = Array.isArray(dataAgents.agents) ? dataAgents.agents : [];
321
+ const agentRow = agentsList.find((a) => a.id === resolvedAgentId);
322
+ if (agentRow) {
323
+ if (agentRow.runnerType === "coze" ||
324
+ agentRow.runnerType === "openclawx" ||
325
+ agentRow.runnerType === "opencode" ||
326
+ agentRow.runnerType === "claude_code") {
327
+ runnerType = agentRow.runnerType;
328
+ }
329
+ if (agentRow.runnerType === "claude_code") {
330
+ const wd = agentRow.claudeCode?.workingDirectory;
331
+ claudeCode = {
332
+ workingDirectory: typeof wd === "string" && wd.trim() ? wd.trim() : undefined,
333
+ };
334
+ }
335
+ if (agentRow.coze) {
336
+ const row = agentRow.coze;
337
+ const region = row.region === "cn" || row.region === "com" ? row.region : "com";
338
+ const credsCn = row.cn;
339
+ const credsCom = row.com;
340
+ const legacyBotId = row.botId != null ? String(row.botId).trim() : "";
341
+ const legacyKey = row.apiKey != null ? String(row.apiKey).trim() : "";
342
+ const fromRegion = region === "cn"
343
+ ? credsCn && String(credsCn.botId || "").trim() && String(credsCn.apiKey || "").trim()
344
+ ? { botId: String(credsCn.botId).trim(), apiKey: String(credsCn.apiKey).trim() }
345
+ : null
346
+ : credsCom && String(credsCom.botId || "").trim() && String(credsCom.apiKey || "").trim()
347
+ ? { botId: String(credsCom.botId).trim(), apiKey: String(credsCom.apiKey).trim() }
348
+ : null;
349
+ const fromLegacy = legacyBotId && legacyKey ? { botId: legacyBotId, apiKey: legacyKey } : null;
350
+ const creds = fromRegion ?? fromLegacy;
351
+ if (creds) {
352
+ coze = {
353
+ botId: creds.botId,
354
+ apiKey: creds.apiKey,
355
+ region,
356
+ endpoint: row.endpoint?.trim(),
357
+ };
358
+ }
359
+ else if (runnerType === "coze") {
360
+ console.warn(`[loadDesktopAgentConfig] agentId=${resolvedAgentId} runnerType=coze but no credentials for region=${region} (configure 国内/国际 in proxy settings)`);
361
+ }
362
+ }
363
+ if (agentRow.openclawx?.baseUrl) {
364
+ openclawx = {
365
+ baseUrl: String(agentRow.openclawx.baseUrl).replace(/\/$/, ""),
366
+ apiKey: agentRow.openclawx.apiKey?.trim(),
367
+ };
368
+ }
369
+ if (agentRow.opencode?.port != null) {
370
+ const raw = agentRow.opencode;
371
+ const port = Number(raw.port);
372
+ if (!Number.isNaN(port) && port > 0) {
373
+ const mode = raw.mode === "local" || raw.mode === "remote"
374
+ ? raw.mode
375
+ : raw.address != null && String(raw.address).trim()
376
+ ? "remote"
377
+ : "local";
378
+ const address = mode === "remote" && raw.address != null
379
+ ? String(raw.address).trim()
380
+ : "127.0.0.1";
381
+ if (mode === "local" || address) {
382
+ const apiStyle = raw.apiStyle === "openai" ? "openai" : "server";
383
+ opencode = {
384
+ mode,
385
+ address: mode === "remote" ? address : "127.0.0.1",
386
+ port,
387
+ password: raw.password != null ? String(raw.password).trim() : undefined,
388
+ username: raw.username != null ? String(raw.username).trim() || undefined : undefined,
389
+ apiStyle,
390
+ path: raw.path != null ? String(raw.path).trim() || undefined : undefined,
391
+ streamPath: raw.streamPath != null
392
+ ? String(raw.streamPath).trim() || undefined
393
+ : undefined,
394
+ model: raw.model != null ? String(raw.model).trim() || undefined : undefined,
395
+ workingDirectory: raw.workingDirectory != null ? String(raw.workingDirectory).trim() || undefined : undefined,
396
+ };
397
+ }
398
+ }
399
+ }
400
+ if (agentRow.webSearch?.enabled === true) {
401
+ let preferredProvider = agentRow.webSearch?.provider === "brave" || agentRow.webSearch?.provider === "duck-duck-scrape"
402
+ ? agentRow.webSearch.provider
403
+ : tw?.defaultProvider === "brave" || tw?.defaultProvider === "duck-duck-scrape"
404
+ ? tw.defaultProvider
405
+ : "duck-duck-scrape";
406
+ let braveKey;
407
+ if (preferredProvider === "brave") {
408
+ braveKey =
409
+ (typeof tw?.providers?.brave?.apiKey === "string" && tw.providers.brave.apiKey.trim()
410
+ ? tw.providers.brave.apiKey.trim()
411
+ : undefined) ??
412
+ (process.env.BRAVE_API_KEY && process.env.BRAVE_API_KEY.trim() ? process.env.BRAVE_API_KEY.trim() : undefined);
413
+ if (!braveKey)
414
+ preferredProvider = "duck-duck-scrape";
415
+ }
416
+ const maxResultTokens = agentRow.webSearch?.maxResultTokens != null && typeof agentRow.webSearch?.maxResultTokens === "number" && agentRow.webSearch.maxResultTokens > 0
417
+ ? agentRow.webSearch.maxResultTokens
418
+ : undefined;
419
+ webSearch = {
420
+ enabled: true,
421
+ provider: preferredProvider,
422
+ apiKey: preferredProvider === "brave" ? braveKey : undefined,
423
+ timeoutSeconds,
424
+ cacheTtlMinutes,
425
+ maxResults,
426
+ maxResultTokens,
427
+ };
428
+ }
429
+ }
430
+ }
431
+ catch {
432
+ // ignore
433
+ }
434
+ }
435
+ return {
436
+ provider,
437
+ model,
438
+ apiKey: apiKey ?? undefined,
439
+ workspace: workspaceName,
440
+ mcpServers,
441
+ mcpMaxResultTokens,
442
+ systemPrompt,
443
+ runnerType,
444
+ coze,
445
+ openclawx,
446
+ opencode,
447
+ claudeCode,
448
+ useLongMemory,
449
+ webSearch,
450
+ contextSize,
451
+ };
211
452
  }
212
453
  function ensureDesktopDir() {
213
454
  const desktopDir = getDesktopDir();
@@ -357,50 +598,209 @@ export async function getDesktopConfigList() {
357
598
  * 确保桌面目录下存在 provider-support.json(不存在则写入默认内容)。
358
599
  * 供配置供应商时作备选下拉列表项。
359
600
  */
601
+ /**
602
+ * 确保桌面目录下存在 provider-support.json,并与预装的最新供应商列表合并。
603
+ */
360
604
  export async function ensureProviderSupportFile() {
605
+ const presetPath = join(getPresetsDir(), "preset-providers.json");
606
+ let presetData = { providers: DEFAULT_PROVIDER_SUPPORT };
607
+ if (existsSync(presetPath)) {
608
+ try {
609
+ presetData = JSON.parse(await readFile(presetPath, "utf-8"));
610
+ }
611
+ catch { }
612
+ }
613
+ const presetProviders = presetData.providers || DEFAULT_PROVIDER_SUPPORT;
361
614
  const path = getProviderSupportPath();
362
- if (existsSync(path))
363
- return;
364
615
  ensureDesktopDir();
365
- await writeFile(path, JSON.stringify(DEFAULT_PROVIDER_SUPPORT, null, 2), "utf-8");
616
+ if (!existsSync(path)) {
617
+ await writeFile(path, JSON.stringify(presetProviders, null, 2), "utf-8");
618
+ return;
619
+ }
620
+ try {
621
+ const userContent = await readFile(path, "utf-8");
622
+ const userProviders = JSON.parse(userContent);
623
+ let changed = false;
624
+ for (const [providerId, presetEntry] of Object.entries(presetProviders)) {
625
+ if (!userProviders[providerId]) {
626
+ userProviders[providerId] = presetEntry;
627
+ changed = true;
628
+ }
629
+ else {
630
+ const userModels = userProviders[providerId].models || [];
631
+ for (const presetModel of presetEntry.models) {
632
+ if (!userModels.some((m) => m.id === presetModel.id)) {
633
+ userModels.push(presetModel);
634
+ changed = true;
635
+ }
636
+ }
637
+ userProviders[providerId].models = userModels;
638
+ }
639
+ }
640
+ if (changed) {
641
+ await writeFile(path, JSON.stringify(userProviders, null, 2), "utf-8");
642
+ }
643
+ }
644
+ catch {
645
+ await writeFile(path, JSON.stringify(presetProviders, null, 2), "utf-8");
646
+ }
366
647
  }
367
- /** config.json 不存在则写入默认结构,供 CLI/Gateway 启动时初始化 */
648
+ /** 预装本地推理缺省:推荐列表第一个 LLM(Qwen 3.5 4B)对应的本地文件名,与 modelUriToFilename 一致 */
649
+ const DEFAULT_LOCAL_LLM_MODEL_ID = "hf_unsloth_Qwen3.5-4B-GGUF_Qwen3.5-4B-Q5_K_M.gguf";
650
+ const DEFAULT_LOCAL_MODEL_ITEM_CODE = "local-qwen35-4b";
651
+ /** 代码内建默认:local provider + 本地 Qwen 3.5 4B,首次与合并时优先保证存在 */
652
+ const BUILTIN_DEFAULT_CONFIG = {
653
+ defaultProvider: "local",
654
+ defaultModel: DEFAULT_LOCAL_LLM_MODEL_ID,
655
+ defaultModelItemCode: DEFAULT_LOCAL_MODEL_ITEM_CODE,
656
+ defaultAgentId: DEFAULT_AGENT_ID,
657
+ maxAgentSessions: DEFAULT_MAX_AGENT_SESSIONS,
658
+ providers: {
659
+ local: { baseUrl: "http://127.0.0.1:11435/v1" },
660
+ },
661
+ configuredModels: [
662
+ {
663
+ provider: "local",
664
+ modelId: DEFAULT_LOCAL_LLM_MODEL_ID,
665
+ type: "llm",
666
+ alias: "Qwen 3.5 4B Q5_K_M",
667
+ modelItemCode: DEFAULT_LOCAL_MODEL_ITEM_CODE,
668
+ },
669
+ {
670
+ provider: "local",
671
+ modelId: "hf_ggml-org_embeddinggemma-300M-GGUF_embeddinggemma-300M-Q8_0.gguf",
672
+ type: "embedding",
673
+ alias: "EmbeddingGemma 300M Q8 (768维)",
674
+ modelItemCode: "local-embeddinggemma-300m",
675
+ },
676
+ ],
677
+ };
678
+ /** 若 config.json 不存在则用 preset-config.json 初始化,若存在则浅合并补充新基础键值。预装 local provider + 本地 Qwen 3.5 4B 模型并设为缺省;preset 与代码默认合并,保证 local 一定存在。 */
368
679
  async function ensureConfigJsonInitialized() {
680
+ const presetPath = join(getPresetsDir(), "preset-config.json");
681
+ let presetConfig = { ...BUILTIN_DEFAULT_CONFIG };
682
+ if (existsSync(presetPath)) {
683
+ try {
684
+ const data = JSON.parse(await readFile(presetPath, "utf-8"));
685
+ if (data.config && typeof data.config === "object") {
686
+ presetConfig = { ...BUILTIN_DEFAULT_CONFIG, ...data.config };
687
+ presetConfig.providers = { ...BUILTIN_DEFAULT_CONFIG.providers, ...(presetConfig.providers || {}) };
688
+ const hasLocalModel = (presetConfig.configuredModels || []).some((m) => m?.provider === "local" && (m?.modelId === DEFAULT_LOCAL_LLM_MODEL_ID || m?.modelItemCode === DEFAULT_LOCAL_MODEL_ITEM_CODE));
689
+ if (!hasLocalModel) {
690
+ presetConfig.configuredModels = [
691
+ ...(BUILTIN_DEFAULT_CONFIG.configuredModels || []),
692
+ ...(presetConfig.configuredModels || []),
693
+ ];
694
+ }
695
+ }
696
+ }
697
+ catch { }
698
+ }
369
699
  const configPath = getConfigPath();
370
- if (existsSync(configPath))
371
- return;
372
700
  ensureDesktopDir();
373
- const defaultConfig = {
374
- defaultProvider: "deepseek",
375
- defaultModel: "deepseek-chat",
376
- defaultAgentId: DEFAULT_AGENT_ID,
377
- maxAgentSessions: DEFAULT_MAX_AGENT_SESSIONS,
378
- providers: {},
379
- configuredModels: [],
380
- };
381
- await writeFile(configPath, JSON.stringify(defaultConfig, null, 2), "utf-8");
701
+ if (!existsSync(configPath)) {
702
+ await writeFile(configPath, JSON.stringify(presetConfig, null, 2), "utf-8");
703
+ return;
704
+ }
705
+ try {
706
+ const userConfig = JSON.parse(await readFile(configPath, "utf-8"));
707
+ let changed = false;
708
+ for (const [key, value] of Object.entries(presetConfig)) {
709
+ if (userConfig[key] === undefined) {
710
+ userConfig[key] = value;
711
+ changed = true;
712
+ }
713
+ }
714
+ if (changed) {
715
+ await writeFile(configPath, JSON.stringify(userConfig, null, 2), "utf-8");
716
+ }
717
+ }
718
+ catch { }
382
719
  }
383
- /** agents.json 不存在则写入默认智能体,供 CLI/Gateway 启动时初始化 */
720
+ /** 合并 preset-agents.json 中的预置智能体,并随之释放对应的工作区技能 */
384
721
  async function ensureAgentsJsonInitialized() {
722
+ const presetPath = join(getPresetsDir(), "preset-agents.json");
723
+ let presetAgents = [];
724
+ if (existsSync(presetPath)) {
725
+ try {
726
+ const data = JSON.parse(await readFile(presetPath, "utf-8"));
727
+ if (Array.isArray(data.agents))
728
+ presetAgents = data.agents;
729
+ }
730
+ catch { }
731
+ }
385
732
  const agentsPath = join(getDesktopDir(), "agents.json");
386
- if (existsSync(agentsPath))
387
- return;
388
733
  ensureDesktopDir();
389
- const defaultAgents = {
390
- agents: [
391
- { id: DEFAULT_AGENT_ID, name: "主智能体", workspace: DEFAULT_AGENT_ID },
392
- ],
393
- };
394
- await writeFile(agentsPath, JSON.stringify(defaultAgents, null, 2), "utf-8");
734
+ let currentData = { agents: [] };
735
+ if (existsSync(agentsPath)) {
736
+ try {
737
+ currentData = JSON.parse(await readFile(agentsPath, "utf-8"));
738
+ if (!Array.isArray(currentData.agents))
739
+ currentData.agents = [];
740
+ }
741
+ catch { }
742
+ }
743
+ let changed = false;
744
+ for (const pa of presetAgents) {
745
+ if (!currentData.agents.some((a) => a.id === pa.id)) {
746
+ currentData.agents.push(pa);
747
+ changed = true;
748
+ try {
749
+ const srcSkillsDir = join(getPresetsDir(), "workspaces", pa.id, "skills");
750
+ const homeDir = process.env.HOME || process.env.USERPROFILE || homedir();
751
+ const destSkillsDir = join(homeDir, ".openbot", "workspace", pa.id, "skills");
752
+ if (existsSync(srcSkillsDir)) {
753
+ if (!existsSync(destSkillsDir)) {
754
+ await mkdir(destSkillsDir, { recursive: true });
755
+ }
756
+ await cp(srcSkillsDir, destSkillsDir, { recursive: true, force: false, errorOnExist: false });
757
+ }
758
+ }
759
+ catch (err) {
760
+ console.error(`Failed to copy preset skills for agent ${pa.id}:`, err);
761
+ }
762
+ }
763
+ }
764
+ // 所有未单独配置模型的智能体使用 config 的缺省模型(预装为 local + Qwen 3.5 4B)
765
+ const configPath = join(getDesktopDir(), "config.json");
766
+ if (existsSync(configPath)) {
767
+ try {
768
+ const configRaw = await readFile(configPath, "utf-8");
769
+ const configData = JSON.parse(configRaw);
770
+ const defProvider = configData.defaultProvider?.trim();
771
+ const defModel = configData.defaultModel?.trim();
772
+ const defCode = configData.defaultModelItemCode?.trim();
773
+ if (defProvider && defModel) {
774
+ for (const agent of currentData.agents) {
775
+ const hasOwn = (agent.provider && String(agent.provider).trim()) || (agent.model && String(agent.model).trim()) || (agent.modelItemCode && String(agent.modelItemCode).trim());
776
+ if (!hasOwn) {
777
+ agent.provider = defProvider;
778
+ agent.model = defModel;
779
+ if (defCode)
780
+ agent.modelItemCode = defCode;
781
+ changed = true;
782
+ }
783
+ }
784
+ }
785
+ }
786
+ catch { /* ignore */ }
787
+ }
788
+ if (changed || !existsSync(agentsPath)) {
789
+ await writeFile(agentsPath, JSON.stringify(currentData, null, 2), "utf-8");
790
+ }
395
791
  }
396
792
  /**
397
- * CLI / Gateway 运行时调用,确保 config.json、provider-support.json、agents.json 均完成初始化。
793
+ * CLI / Gateway 运行时调用,确保 config.json、provider-support.json、agents.json 均完成初始化,
794
+ * 并同步到 agent 目录 models.json,供 pi ModelRegistry 解析 local 等模型与凭证。
398
795
  */
399
796
  export async function ensureDesktopConfigInitialized() {
400
797
  ensureDesktopDir();
401
798
  await ensureProviderSupportFile();
402
799
  await ensureConfigJsonInitialized();
403
800
  await ensureAgentsJsonInitialized();
801
+ await syncDesktopConfigToModelsJson().catch((err) => {
802
+ console.warn("[ensureDesktopConfigInitialized] syncDesktopConfigToModelsJson failed:", err);
803
+ });
404
804
  }
405
805
  /**
406
806
  * 取某 provider 在 provider-support 中的第一个 llm 模型 id;若无则返回第一个模型 id。
@@ -451,6 +851,10 @@ const SYNC_DEFAULTS = {
451
851
  "openai-custom": { baseUrl: "", apiKey: "OPENAI_API_KEY", api: "openai-completions" },
452
852
  nvidia: { baseUrl: "https://integrate.api.nvidia.com/v1", apiKey: "NVIDIA_API_KEY", api: "openai-completions" },
453
853
  kimi: { baseUrl: "https://api.moonshot.cn/v1", apiKey: "MOONSHOT_API_KEY", api: "openai-completions" },
854
+ /** 本地 Ollama,无需真实 API Key */
855
+ ollama: { baseUrl: "http://localhost:11434/v1", apiKey: "OPENAI_API_KEY", api: "openai-completions" },
856
+ /** 内置本地推理(node-llama-cpp),无需 API Key,baseUrl 指向本地子进程服务 */
857
+ local: { baseUrl: "http://127.0.0.1:11435/v1", apiKey: "OPENAI_API_KEY", api: "openai-completions" },
454
858
  };
455
859
  const DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
456
860
  const DEFAULT_CONTEXT_WINDOW = 64000;
@@ -475,25 +879,41 @@ function configuredModelToPi(item, displayName) {
475
879
  }
476
880
  /**
477
881
  * 根据桌面 config(已配置的 providers + configuredModels)与 provider-support,生成并写入 agent 目录的 models.json。
478
- * 仅包含在 configproviders 中已配置的 provider;每个 provider models 来自 configuredModels,结构含 reasoning、cost 等。
882
+ * 包含:config.providers 中已配置的 provider;以及 configuredModels / defaultProvider 中引用但未在 providers 中的 provider(用 support 默认 baseUrl 补全,避免 Ollama 等仅选模型未填 baseUrl 时连不上)。
479
883
  */
480
884
  export async function syncDesktopConfigToModelsJson() {
481
885
  const config = await readDesktopConfigJson();
482
886
  const configured = config.providers ?? {};
483
887
  const configuredModels = Array.isArray(config.configuredModels) ? config.configuredModels : [];
484
- if (Object.keys(configured).length === 0) {
888
+ const support = await getProviderSupport();
889
+ const providerIdsFromModels = new Set();
890
+ for (const m of configuredModels)
891
+ if (m?.provider)
892
+ providerIdsFromModels.add(m.provider);
893
+ if (config.defaultProvider)
894
+ providerIdsFromModels.add(config.defaultProvider);
895
+ const allProviderIds = new Set([...Object.keys(configured), ...providerIdsFromModels]);
896
+ if (allProviderIds.size === 0) {
485
897
  return;
486
898
  }
487
- const support = await getProviderSupport();
488
899
  const piProviders = {};
489
- for (const [providerId, userConfig] of Object.entries(configured)) {
490
- if (!userConfig?.apiKey?.trim())
900
+ for (const providerId of allProviderIds) {
901
+ const userConfig = configured[providerId];
902
+ // ollama / local 不需要 API Key,其他 provider 必须有 apiKey
903
+ const isNoKeyProvider = providerId === "ollama" || providerId === "local";
904
+ if (!isNoKeyProvider && !userConfig?.apiKey?.trim())
491
905
  continue;
492
906
  const defaults = SYNC_DEFAULTS[providerId] ?? { baseUrl: "", apiKey: "OPENAI_API_KEY", api: "openai-completions" };
493
- const baseUrl = userConfig.baseUrl?.trim() || (support[providerId]?.baseUrl ?? "").trim() || defaults.baseUrl;
907
+ let baseUrl = userConfig?.baseUrl?.trim() || (support[providerId]?.baseUrl ?? "").trim() || defaults.baseUrl;
908
+ if (providerId === "ollama" && process.env.OLLAMA_BASE_URL?.trim()) {
909
+ const u = process.env.OLLAMA_BASE_URL.trim().replace(/\/$/, "");
910
+ baseUrl = u.endsWith("/v1") ? u : u + "/v1";
911
+ }
494
912
  if (!baseUrl)
495
913
  continue;
496
914
  const def = support[providerId];
915
+ if (!def)
916
+ continue;
497
917
  const items = configuredModels.filter((m) => m.provider === providerId);
498
918
  let models;
499
919
  if (items.length > 0) {
@@ -501,7 +921,11 @@ export async function syncDesktopConfigToModelsJson() {
501
921
  const displayName = (item.alias && item.alias.trim()) ||
502
922
  (def?.models?.find((m) => m.id === item.modelId)?.name) ||
503
923
  item.modelId;
504
- return configuredModelToPi(item, displayName);
924
+ const pi = configuredModelToPi(item, displayName);
925
+ // 本地 node-llama-cpp 关闭思考:不向 SDK 发送 reasoning,避免启用 thinking 相关参数
926
+ if (providerId === "local")
927
+ return { ...pi, reasoning: false };
928
+ return pi;
505
929
  });
506
930
  }
507
931
  else if (def?.models?.length) {
@@ -520,7 +944,7 @@ export async function syncDesktopConfigToModelsJson() {
520
944
  continue;
521
945
  }
522
946
  piProviders[providerId] = {
523
- name: (userConfig.alias?.trim() || def?.name) || providerId,
947
+ name: (userConfig?.alias?.trim() || def?.name) || providerId,
524
948
  apiKey: defaults.apiKey,
525
949
  api: defaults.api,
526
950
  baseUrl: baseUrl.replace(/\/$/, ""),