opc-agent 4.1.0 → 4.1.2

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 (258) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +20 -20
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +14 -14
  3. package/.github/PULL_REQUEST_TEMPLATE.md +13 -13
  4. package/CHANGELOG.md +48 -48
  5. package/CONTRIBUTING.md +36 -36
  6. package/README.zh-CN.md +497 -497
  7. package/USABILITY-ISSUES.md +73 -0
  8. package/dist/channels/web.js +8 -2
  9. package/dist/channels/wechat.js +6 -6
  10. package/dist/cli.js +200 -85
  11. package/dist/core/runtime.js +37 -15
  12. package/dist/deploy/index.js +56 -56
  13. package/dist/doctor.d.ts +1 -0
  14. package/dist/doctor.js +105 -10
  15. package/dist/memory/deepbrain.d.ts +1 -1
  16. package/dist/memory/deepbrain.js +95 -4
  17. package/dist/scheduler/cron-engine.js +3 -36
  18. package/dist/studio/server.js +30 -1
  19. package/dist/studio-ui/index.html +230 -10
  20. package/dist/ui/components.js +105 -105
  21. package/examples/README.md +22 -22
  22. package/examples/basic-agent.ts +90 -90
  23. package/examples/brain-integration.ts +71 -71
  24. package/examples/multi-channel.ts +74 -74
  25. package/fix-sidebar.mjs +188 -188
  26. package/install.ps1 +154 -154
  27. package/install.sh +164 -164
  28. package/package.json +1 -1
  29. package/scripts/install.ps1 +31 -31
  30. package/scripts/install.sh +40 -40
  31. package/serve-studio.js +13 -13
  32. package/serve-test.js +25 -25
  33. package/src/channels/dingtalk.ts +46 -46
  34. package/src/channels/email.ts +351 -351
  35. package/src/channels/feishu.ts +349 -349
  36. package/src/channels/googlechat.ts +42 -42
  37. package/src/channels/imessage.ts +31 -31
  38. package/src/channels/irc.ts +82 -82
  39. package/src/channels/line.ts +32 -32
  40. package/src/channels/matrix.ts +33 -33
  41. package/src/channels/mattermost.ts +57 -57
  42. package/src/channels/msteams.ts +32 -32
  43. package/src/channels/nostr.ts +32 -32
  44. package/src/channels/qq.ts +33 -33
  45. package/src/channels/signal.ts +32 -32
  46. package/src/channels/sms.ts +33 -33
  47. package/src/channels/telegram.ts +616 -616
  48. package/src/channels/twitch.ts +65 -65
  49. package/src/channels/voice-call.ts +100 -100
  50. package/src/channels/web.ts +8 -2
  51. package/src/channels/websocket.ts +399 -399
  52. package/src/channels/wechat.ts +329 -329
  53. package/src/channels/whatsapp.ts +32 -32
  54. package/src/cli/chat.ts +99 -99
  55. package/src/cli/setup.ts +314 -314
  56. package/src/cli.ts +195 -92
  57. package/src/core/agent.ts +476 -476
  58. package/src/core/api-server.ts +277 -277
  59. package/src/core/audio.ts +98 -98
  60. package/src/core/collaboration.ts +275 -275
  61. package/src/core/context-discovery.ts +85 -85
  62. package/src/core/context-refs.ts +140 -140
  63. package/src/core/gateway.ts +106 -106
  64. package/src/core/heartbeat.ts +51 -51
  65. package/src/core/hooks.ts +105 -105
  66. package/src/core/ide-bridge.ts +133 -133
  67. package/src/core/node-network.ts +86 -86
  68. package/src/core/profiles.ts +122 -122
  69. package/src/core/runtime.ts +25 -0
  70. package/src/core/scheduler.ts +187 -187
  71. package/src/core/session-manager.ts +137 -137
  72. package/src/core/subagent.ts +98 -98
  73. package/src/core/vision.ts +180 -180
  74. package/src/core/workflow-graph.ts +365 -365
  75. package/src/daemon.ts +96 -96
  76. package/src/deploy/index.ts +255 -255
  77. package/src/doctor.ts +98 -11
  78. package/src/eval/index.ts +211 -211
  79. package/src/eval/suites/basic.json +16 -16
  80. package/src/eval/suites/memory.json +12 -12
  81. package/src/eval/suites/safety.json +14 -14
  82. package/src/hub/brain-seed.ts +54 -54
  83. package/src/hub/client.ts +60 -60
  84. package/src/mcp/servers/calculator-mcp.ts +65 -65
  85. package/src/mcp/servers/crypto-mcp.ts +73 -73
  86. package/src/mcp/servers/database-mcp.ts +72 -72
  87. package/src/mcp/servers/datetime-mcp.ts +69 -69
  88. package/src/mcp/servers/filesystem.ts +66 -66
  89. package/src/mcp/servers/github-mcp.ts +58 -58
  90. package/src/mcp/servers/index.ts +63 -63
  91. package/src/mcp/servers/json-mcp.ts +102 -102
  92. package/src/mcp/servers/memory-mcp.ts +56 -56
  93. package/src/mcp/servers/regex-mcp.ts +53 -53
  94. package/src/mcp/servers/web-mcp.ts +49 -49
  95. package/src/memory/context-compressor.ts +189 -189
  96. package/src/memory/deepbrain.ts +99 -5
  97. package/src/memory/seed-loader.ts +212 -212
  98. package/src/memory/user-profiler.ts +215 -215
  99. package/src/plugins/content-filter.ts +23 -23
  100. package/src/plugins/logger.ts +18 -18
  101. package/src/plugins/rate-limiter.ts +38 -38
  102. package/src/protocols/a2a/client.ts +132 -132
  103. package/src/protocols/a2a/index.ts +8 -8
  104. package/src/protocols/a2a/server.ts +333 -333
  105. package/src/protocols/a2a/types.ts +88 -88
  106. package/src/protocols/a2a/utils.ts +50 -50
  107. package/src/protocols/agui/client.ts +83 -83
  108. package/src/protocols/agui/index.ts +4 -4
  109. package/src/protocols/agui/server.ts +218 -218
  110. package/src/protocols/agui/types.ts +153 -153
  111. package/src/protocols/index.ts +2 -2
  112. package/src/protocols/mcp/agent-tools.ts +134 -134
  113. package/src/protocols/mcp/index.ts +8 -8
  114. package/src/protocols/mcp/server.ts +262 -262
  115. package/src/protocols/mcp/types.ts +69 -69
  116. package/src/providers/index.ts +632 -632
  117. package/src/publish/index.ts +376 -376
  118. package/src/scheduler/cron-engine.ts +191 -191
  119. package/src/scheduler/index.ts +2 -2
  120. package/src/schema/oad.ts +217 -217
  121. package/src/security/approval.ts +131 -131
  122. package/src/security/approvals.ts +143 -143
  123. package/src/security/elevated.ts +105 -105
  124. package/src/security/guardrails.ts +248 -248
  125. package/src/security/index.ts +9 -9
  126. package/src/security/keys.ts +87 -87
  127. package/src/security/secrets.ts +129 -129
  128. package/src/skills/builtin/index.ts +408 -408
  129. package/src/skills/marketplace.ts +113 -113
  130. package/src/skills/types.ts +42 -42
  131. package/src/studio/server.ts +31 -1
  132. package/src/studio/templates-data.ts +178 -178
  133. package/src/studio-ui/index.html +230 -10
  134. package/src/telemetry/index.ts +324 -324
  135. package/src/tools/builtin/browser.ts +299 -299
  136. package/src/tools/builtin/datetime.ts +41 -41
  137. package/src/tools/builtin/file.ts +107 -107
  138. package/src/tools/builtin/home-assistant.ts +116 -116
  139. package/src/tools/builtin/rl-tools.ts +243 -243
  140. package/src/tools/builtin/shell.ts +43 -43
  141. package/src/tools/builtin/vision.ts +64 -64
  142. package/src/tools/builtin/web-search.ts +126 -126
  143. package/src/tools/builtin/web.ts +35 -35
  144. package/src/tools/document-processor.ts +213 -213
  145. package/src/tools/image-generator.ts +150 -150
  146. package/src/tools/integrations/calendar.ts +73 -73
  147. package/src/tools/integrations/code-exec.ts +39 -39
  148. package/src/tools/integrations/csv-analyzer.ts +92 -92
  149. package/src/tools/integrations/database.ts +44 -44
  150. package/src/tools/integrations/email-send.ts +76 -76
  151. package/src/tools/integrations/git-tool.ts +42 -42
  152. package/src/tools/integrations/github-tool.ts +76 -76
  153. package/src/tools/integrations/image-gen.ts +56 -56
  154. package/src/tools/integrations/index.ts +92 -92
  155. package/src/tools/integrations/jira.ts +83 -83
  156. package/src/tools/integrations/notion.ts +71 -71
  157. package/src/tools/integrations/npm-tool.ts +48 -48
  158. package/src/tools/integrations/pdf-reader.ts +58 -58
  159. package/src/tools/integrations/slack.ts +65 -65
  160. package/src/tools/integrations/summarizer.ts +49 -49
  161. package/src/tools/integrations/translator.ts +48 -48
  162. package/src/tools/integrations/trello.ts +60 -60
  163. package/src/tools/integrations/vector-search.ts +42 -42
  164. package/src/tools/integrations/web-scraper.ts +47 -47
  165. package/src/tools/integrations/web-search.ts +58 -58
  166. package/src/tools/integrations/webhook.ts +38 -38
  167. package/src/tools/mcp-client.ts +131 -131
  168. package/src/tools/web-scraper.ts +179 -179
  169. package/src/tools/web-search.ts +180 -180
  170. package/src/ui/components.ts +127 -127
  171. package/srv-out.txt +1 -1
  172. package/templates/ecommerce-assistant/README.md +45 -45
  173. package/templates/ecommerce-assistant/oad.yaml +47 -47
  174. package/templates/tech-support/README.md +43 -43
  175. package/templates/tech-support/oad.yaml +45 -45
  176. package/test-agent/Dockerfile +9 -9
  177. package/test-agent/README.md +50 -50
  178. package/test-agent/agent.yaml +23 -23
  179. package/test-agent/docker-compose.yml +11 -11
  180. package/test-agent/oad.yaml +31 -31
  181. package/test-agent/package-lock.json +1492 -1492
  182. package/test-agent/package.json +17 -17
  183. package/test-agent/src/index.ts +24 -24
  184. package/test-agent/src/skills/echo.ts +15 -15
  185. package/test-agent/tsconfig.json +24 -24
  186. package/test-full.js +43 -43
  187. package/test-sidebar.js +22 -22
  188. package/test-studio3.js +75 -75
  189. package/test-studio4.js +41 -41
  190. package/tests/a2a-protocol.test.ts +285 -285
  191. package/tests/agui-protocol.test.ts +246 -246
  192. package/tests/api-server.test.ts +148 -148
  193. package/tests/approvals.test.ts +89 -89
  194. package/tests/audio.test.ts +40 -40
  195. package/tests/brain-seed-extended.test.ts +490 -490
  196. package/tests/brain-seed.test.ts +239 -239
  197. package/tests/browser.test.ts +179 -179
  198. package/tests/channels/discord.test.ts +79 -79
  199. package/tests/channels/email.test.ts +148 -148
  200. package/tests/channels/feishu.test.ts +123 -123
  201. package/tests/channels/telegram.test.ts +129 -129
  202. package/tests/channels/websocket.test.ts +53 -53
  203. package/tests/channels/wechat.test.ts +170 -170
  204. package/tests/channels-extra.test.ts +45 -45
  205. package/tests/chat-cli.test.ts +160 -160
  206. package/tests/cli.test.ts +46 -46
  207. package/tests/context-compressor.test.ts +172 -172
  208. package/tests/context-refs.test.ts +121 -121
  209. package/tests/cron-engine.test.ts +101 -101
  210. package/tests/daemon.test.ts +135 -135
  211. package/tests/deepbrain-wire.test.ts +234 -234
  212. package/tests/deploy-and-dag.test.ts +196 -196
  213. package/tests/doctor.test.ts +38 -38
  214. package/tests/document-processor.test.ts +69 -69
  215. package/tests/e2e-nocode.test.ts +442 -442
  216. package/tests/elevated.test.ts +69 -69
  217. package/tests/eval.test.ts +173 -173
  218. package/tests/gateway.test.ts +63 -63
  219. package/tests/guardrails.test.ts +177 -177
  220. package/tests/home-assistant.test.ts +40 -40
  221. package/tests/hooks.test.ts +79 -79
  222. package/tests/ide-bridge.test.ts +38 -38
  223. package/tests/image-generator.test.ts +84 -84
  224. package/tests/init-role.test.ts +124 -124
  225. package/tests/integrations.test.ts +249 -249
  226. package/tests/mcp-client.test.ts +92 -92
  227. package/tests/mcp-server.test.ts +178 -178
  228. package/tests/mcp-servers.test.ts +260 -260
  229. package/tests/node-network.test.ts +74 -74
  230. package/tests/plugin-a2a-enhanced.test.ts +230 -230
  231. package/tests/profiles.test.ts +61 -61
  232. package/tests/publish.test.ts +231 -231
  233. package/tests/rl-tools.test.ts +93 -93
  234. package/tests/sandbox-manager.test.ts +46 -46
  235. package/tests/scheduler.test.ts +200 -200
  236. package/tests/secrets.test.ts +107 -107
  237. package/tests/security-enhanced.test.ts +233 -233
  238. package/tests/settings-api.test.ts +148 -148
  239. package/tests/setup.test.ts +73 -73
  240. package/tests/subagent.test.ts +193 -193
  241. package/tests/telegram-discord.test.ts +60 -60
  242. package/tests/telemetry.test.ts +186 -186
  243. package/tests/user-profiler.test.ts +169 -169
  244. package/tests/v090-features.test.ts +254 -254
  245. package/tests/vision.test.ts +61 -61
  246. package/tests/voice-call.test.ts +47 -47
  247. package/tests/voice-enhanced.test.ts +169 -169
  248. package/tests/voice-interaction.test.ts +38 -38
  249. package/tests/web-search.test.ts +155 -155
  250. package/tests/workflow-graph.test.ts +279 -279
  251. package/tutorial/customer-service-agent/README.md +612 -612
  252. package/tutorial/customer-service-agent/SOUL.md +26 -26
  253. package/tutorial/customer-service-agent/agent.yaml +63 -63
  254. package/tutorial/customer-service-agent/package.json +19 -19
  255. package/tutorial/customer-service-agent/src/index.ts +69 -69
  256. package/tutorial/customer-service-agent/src/skills/faq.ts +27 -27
  257. package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -22
  258. package/tutorial/customer-service-agent/tsconfig.json +14 -14
package/dist/cli.js CHANGED
@@ -124,7 +124,7 @@ async function select(question, options) {
124
124
  program
125
125
  .name('opc')
126
126
  .description('OPC Agent - Open Agent Framework for business workstations')
127
- .version('2.0.0');
127
+ .version(require('../package.json').version);
128
128
  // ── Init command ─────────────────────────────────────────────
129
129
  program
130
130
  .command('init')
@@ -186,7 +186,7 @@ program
186
186
  }
187
187
  const roleDisplayName = roleMeta.name || matched.role;
188
188
  const roleDescription = roleMeta.name_zh ? `${roleMeta.name} (${roleMeta.name_zh})` : (roleMeta.name || matched.role);
189
- console.log(` ${icon.info} Matched role: ${color.cyan(matched.category + '/' + matched.role)} ${roleDisplayName}`);
189
+ console.log(` ${icon.info} Matched role: ${color.cyan(matched.category + '/' + matched.role)} - ${roleDisplayName}`);
190
190
  // Create directories
191
191
  fs.mkdirSync(dir, { recursive: true });
192
192
  fs.mkdirSync(path.join(dir, 'src', 'skills'), { recursive: true });
@@ -205,9 +205,9 @@ program
205
205
  // Company-specific knowledge belongs to Desk (closed-source), not here.
206
206
  const workstationSeedFromRole = workstationMatch?.[0]?.trim() || '';
207
207
  fs.writeFileSync(path.join(dir, 'brain-seeds', 'workstation.md'), workstationSeedFromRole || `# Workstation Knowledge\n\n## Tools & Environment\n\nCommon tools and setup for this workstation role.\n\n## Workflows\n\nStandard operating procedures and workflows.\n\n## Best Practices\n\nIndustry best practices for this role.\n`);
208
- // agent.yaml with role system prompt and brain seeds
208
+ // oad.yaml with role system prompt and brain seeds(不再生成 agent.yaml)
209
209
  const firstLine = systemPromptContent.split('\n').find((l) => l.trim() && !l.startsWith('#'))?.trim() || 'You are a helpful AI assistant.';
210
- fs.writeFileSync(path.join(dir, 'agent.yaml'), `apiVersion: opc/v1
210
+ fs.writeFileSync(path.join(dir, 'oad.yaml'), `apiVersion: opc/v1
211
211
  kind: Agent
212
212
  metadata:
213
213
  name: ${name}
@@ -251,14 +251,14 @@ spec:
251
251
  if (roleData.files['oad.yaml']) {
252
252
  fs.writeFileSync(path.join(dir, 'oad.yaml'), roleData.files['oad.yaml']);
253
253
  }
254
- // src/index.ts entry point (same as generic)
254
+ // src/index.ts - entry point (same as generic)
255
255
  fs.writeFileSync(path.join(dir, 'src', 'index.ts'), `import { AgentRuntime } from 'opc-agent';
256
256
  import { EchoSkill } from './skills/echo';
257
257
  import { readFileSync, existsSync } from 'fs';
258
258
 
259
259
  async function main() {
260
260
  const runtime = new AgentRuntime();
261
- const config = await runtime.loadConfig('./agent.yaml');
261
+ const config = await runtime.loadConfig('./oad.yaml');
262
262
 
263
263
  const soul = existsSync('./SOUL.md') ? readFileSync('./SOUL.md', 'utf-8') : '';
264
264
  const context = existsSync('./CONTEXT.md') ? readFileSync('./CONTEXT.md', 'utf-8') : '';
@@ -305,15 +305,15 @@ export class EchoSkill extends BaseSkill {
305
305
  fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name, version: '1.0.0', private: true, scripts: { start: 'opc run', dev: 'opc dev', chat: 'opc chat', build: 'tsc' }, dependencies: { 'opc-agent': '^1.3.0' }, devDependencies: { typescript: '^5.5.0', tsx: '^4.0.0' } }, null, 2));
306
306
  // .gitignore, .env.example, .env
307
307
  fs.writeFileSync(path.join(dir, '.gitignore'), 'node_modules\ndist\n.env\n.opc-knowledge.json\ndata/\n');
308
- fs.writeFileSync(path.join(dir, '.env.example'), `# LLM API Configuration\nOPC_LLM_API_KEY=your-api-key-here\nOPC_LLM_BASE_URL=https://api.openai.com/v1\nOPC_LLM_MODEL=gpt-4o-mini\n`);
309
- fs.writeFileSync(path.join(dir, '.env'), `OPC_LLM_API_KEY=your-api-key-here\nOPC_LLM_BASE_URL=https://api.openai.com/v1\nOPC_LLM_MODEL=gpt-4o-mini\n`);
308
+ fs.writeFileSync(path.join(dir, '.env.example'), `# LLM API Configuration\n# Ollama (免费本地,默认,无需 API key):\nOPC_LLM_BASE_URL=http://localhost:11434/v1\nOPC_LLM_MODEL=qwen2.5\n\n# 如需使用商业模型,取消以下注释:\n# OPC_LLM_API_KEY=your-api-key-here\n# OPC_LLM_BASE_URL=https://api.deepseek.com/v1\n# OPC_LLM_MODEL=deepseek-chat\n`);
309
+ fs.writeFileSync(path.join(dir, '.env'), `# Ollama (免费本地) - 无需 API key\nOPC_LLM_BASE_URL=http://localhost:11434/v1\nOPC_LLM_MODEL=qwen2.5\n`);
310
310
  // README.md
311
311
  fs.writeFileSync(path.join(dir, 'README.md'), `# ${name}\n\nCreated with [OPC Agent](https://github.com/Deepleaper/opc-agent) using the \`${matched.category}/${matched.role}\` workstation role.\n\n## Quick Start\n\n\`\`\`bash\nnpm install\nollama pull qwen2.5\nnpx tsx src/index.ts\n\`\`\`\n\nOpen [http://localhost:3000](http://localhost:3000)\n`);
312
312
  // Dockerfile + docker-compose
313
- fs.writeFileSync(path.join(dir, 'Dockerfile'), `FROM node:22-alpine\nWORKDIR /app\nCOPY package.json package-lock.json* ./\nRUN npm ci --production 2>/dev/null || npm install --production\nCOPY oad.yaml agent.yaml .env* ./\nCOPY src/ ./src/\nCOPY prompts/ ./prompts/ 2>/dev/null || true\nEXPOSE 3000\nCMD ["npx", "opc", "run"]\n`);
314
- fs.writeFileSync(path.join(dir, 'docker-compose.yml'), `version: '3.8'\nservices:\n agent:\n build: .\n ports:\n - "3000:3000"\n env_file:\n - .env\n volumes:\n - ./agent.yaml:/app/agent.yaml:ro\n restart: unless-stopped\n`);
313
+ fs.writeFileSync(path.join(dir, 'Dockerfile'), `FROM node:22-alpine\nWORKDIR /app\nCOPY package.json package-lock.json* ./\nRUN npm ci --production 2>/dev/null || npm install --production\nCOPY oad.yaml .env* ./\nCOPY src/ ./src/\nCOPY prompts/ ./prompts/ 2>/dev/null || true\nEXPOSE 3000\nCMD ["npx", "opc", "run"]\n`);
314
+ fs.writeFileSync(path.join(dir, 'docker-compose.yml'), `version: '3.8'\nservices:\n agent:\n build: .\n ports:\n - "3000:3000"\n env_file:\n - .env\n volumes:\n - ./oad.yaml:/app/oad.yaml:ro\n restart: unless-stopped\n`);
315
315
  console.log(`\n${icon.success} Created agent project: ${color.bold(name + '/')} from role ${color.cyan(matched.category + '/' + matched.role)}`);
316
- console.log(` ${icon.file} agent.yaml - Agent definition with role system prompt`);
316
+ console.log(` ${icon.file} oad.yaml - Agent definition with role system prompt`);
317
317
  console.log(` ${icon.file} SOUL.md - Role personality (${systemPromptContent.split('\n').length} lines)`);
318
318
  console.log(` ${icon.file} CONTEXT.md - Role context & documentation`);
319
319
  console.log(` ${icon.file} brain-seeds/ - 3-tier brain seed knowledge`);
@@ -344,7 +344,7 @@ export class EchoSkill extends BaseSkill {
344
344
  }
345
345
  }
346
346
  catch {
347
- // Hub unreachable fall back to bundled templates
347
+ // Hub unreachable - fall back to bundled templates
348
348
  }
349
349
  let template;
350
350
  let selectedHubTemplate;
@@ -364,6 +364,116 @@ export class EchoSkill extends BaseSkill {
364
364
  else {
365
365
  template = await select('Select a template:', Object.entries(TEMPLATES).map(([value, { label }]) => ({ value, label })));
366
366
  }
367
+ // ── LLM Provider 选择(Ollama-first)──
368
+ let llmProvider = 'ollama';
369
+ let llmModel = 'qwen2.5';
370
+ let llmBaseUrl = 'http://localhost:11434/v1';
371
+ let llmApiKey = '';
372
+ let ollamaRunning = false;
373
+ let modelNames = [];
374
+ // 无论 --yes 还是交互式,都先检测 Ollama
375
+ try {
376
+ const controller = new AbortController();
377
+ const ollamaTimeout = setTimeout(() => controller.abort(), 3000);
378
+ const ollamaRes = await fetch('http://localhost:11434/api/tags', { signal: controller.signal });
379
+ clearTimeout(ollamaTimeout);
380
+ const ollamaData = await ollamaRes.json();
381
+ modelNames = (ollamaData.models || []).map((m) => m.name || m.model);
382
+ ollamaRunning = true;
383
+ if (opts.yes && modelNames.length > 0) {
384
+ // --yes 模式:自动选第一个已有模型
385
+ llmModel = modelNames[0];
386
+ }
387
+ }
388
+ catch {
389
+ ollamaRunning = false;
390
+ }
391
+ if (!opts.yes) {
392
+ if (ollamaRunning) {
393
+ console.log(`\n ${icon.info} ${color.dim('正在检测 Ollama...')}`);
394
+ console.log(` ${icon.success} Ollama 已运行,发现 ${modelNames.length} 个模型`);
395
+ // 选择 provider
396
+ llmProvider = await select('选择 LLM 引擎:', [
397
+ { value: 'ollama', label: '🟢 Ollama (免费本地,推荐) - 已检测到运行中' },
398
+ { value: 'deepseek', label: '🔵 DeepSeek - 高性价比国产模型' },
399
+ { value: 'openai', label: '⚪ OpenAI (GPT-4o)' },
400
+ { value: 'anthropic', label: '🟣 Anthropic (Claude)' },
401
+ { value: 'custom', label: '⚙️ 自定义 (手动输入 Base URL)' },
402
+ ]);
403
+ if (llmProvider === 'ollama') {
404
+ // 选择本地模型
405
+ const defaultModel = modelNames.includes('qwen2.5') ? 'qwen2.5' : (modelNames.includes('llama3') ? 'llama3' : (modelNames[0] || 'qwen2.5'));
406
+ if (modelNames.length > 0) {
407
+ llmModel = await select('选择 Ollama 模型:', modelNames.map((m) => ({ value: m, label: m + (m === defaultModel ? ' (推荐)' : '') })));
408
+ }
409
+ else {
410
+ console.log(` ${color.yellow('⚠️')} 没有发现已下载的模型,将使用默认 qwen2.5`);
411
+ console.log(` 运行 ${color.cyan('ollama pull qwen2.5')} 下载模型`);
412
+ llmModel = 'qwen2.5';
413
+ }
414
+ }
415
+ }
416
+ else {
417
+ // Ollama not running
418
+ console.log(`\n ${icon.info} ${color.dim('正在检测 Ollama...')}`);
419
+ console.log(` ${color.yellow('⚠️')} Ollama 未运行或未安装`);
420
+ llmProvider = await select('选择 LLM 引擎:', [
421
+ { value: 'ollama', label: '🟢 Ollama (免费本地,推荐) - 需先安装: https://ollama.ai' },
422
+ { value: 'deepseek', label: '🔵 DeepSeek - 高性价比国产模型' },
423
+ { value: 'openai', label: '⚪ OpenAI (GPT-4o)' },
424
+ { value: 'anthropic', label: '🟣 Anthropic (Claude)' },
425
+ { value: 'custom', label: '⚙️ 自定义 (手动输入 Base URL)' },
426
+ ]);
427
+ if (llmProvider === 'ollama') {
428
+ console.log(`\n ${icon.info} Ollama 安装指南:`);
429
+ console.log(` 1. 访问 ${color.cyan('https://ollama.ai')} 下载并安装`);
430
+ console.log(` 2. 运行 ${color.cyan('ollama pull qwen2.5')} 下载推荐模型`);
431
+ console.log(` 3. 然后 ${color.cyan('opc run')} 即可开始对话\n`);
432
+ llmModel = 'qwen2.5';
433
+ }
434
+ }
435
+ // 商业模型需要 API key
436
+ if (llmProvider === 'deepseek') {
437
+ llmBaseUrl = 'https://api.deepseek.com/v1';
438
+ llmModel = 'deepseek-chat';
439
+ llmApiKey = await promptUser('输入 DeepSeek API Key (可稍后在 .env 中配置,直接回车跳过)');
440
+ if (!llmApiKey) {
441
+ console.log(` ${icon.info} 稍后在 ${color.cyan('.env')} 文件中设置 ${color.bold('OPC_LLM_API_KEY')}`);
442
+ }
443
+ }
444
+ else if (llmProvider === 'openai') {
445
+ llmBaseUrl = 'https://api.openai.com/v1';
446
+ llmModel = 'gpt-4o-mini';
447
+ llmApiKey = await promptUser('输入 OpenAI API Key (可稍后在 .env 中配置,直接回车跳过)');
448
+ if (!llmApiKey) {
449
+ console.log(` ${icon.info} 稍后在 ${color.cyan('.env')} 文件中设置 ${color.bold('OPC_LLM_API_KEY')}`);
450
+ }
451
+ }
452
+ else if (llmProvider === 'anthropic') {
453
+ llmBaseUrl = 'https://api.anthropic.com/v1';
454
+ llmModel = 'claude-sonnet-4-20250514';
455
+ llmApiKey = await promptUser('输入 Anthropic API Key (可稍后在 .env 中配置,直接回车跳过)');
456
+ if (!llmApiKey) {
457
+ console.log(` ${icon.info} 稍后在 ${color.cyan('.env')} 文件中设置 ${color.bold('OPC_LLM_API_KEY')}`);
458
+ }
459
+ }
460
+ else if (llmProvider === 'custom') {
461
+ llmBaseUrl = await promptUser('输入 Base URL', 'http://localhost:11434/v1');
462
+ llmModel = await promptUser('输入模型名称', 'qwen2.5');
463
+ llmApiKey = await promptUser('输入 API Key (可选,直接回车跳过)');
464
+ // 尝试推断 provider
465
+ if (llmBaseUrl.includes('deepseek.com'))
466
+ llmProvider = 'deepseek';
467
+ else if (llmBaseUrl.includes('openai.com'))
468
+ llmProvider = 'openai';
469
+ else if (llmBaseUrl.includes('anthropic.com'))
470
+ llmProvider = 'anthropic';
471
+ else if (llmBaseUrl.includes('localhost:11434'))
472
+ llmProvider = 'ollama';
473
+ else
474
+ llmProvider = 'openai'; // OpenAI-compatible fallback
475
+ }
476
+ }
367
477
  const dir = path.resolve(name);
368
478
  if (fs.existsSync(dir)) {
369
479
  console.error(`\n${icon.error} Directory ${color.bold(name)} already exists.`);
@@ -374,37 +484,16 @@ export class EchoSkill extends BaseSkill {
374
484
  const factory = TEMPLATES[template]?.factory ?? customer_service_1.createCustomerServiceConfig;
375
485
  const config = factory();
376
486
  config.metadata.name = name;
487
+ // 用用户选择的 provider 和 model 覆盖模板默认值
488
+ config.spec.model = llmModel;
489
+ config.spec.provider = { default: llmProvider };
377
490
  // Ensure web channel exists
378
491
  if (!config.spec.channels.some((c) => c.type === 'web')) {
379
492
  config.spec.channels.push({ type: 'web', port: 3000 });
380
493
  }
494
+ // 只生成 oad.yaml,不生成 agent.yaml
381
495
  fs.writeFileSync(path.join(dir, 'oad.yaml'), yaml.dump(config, { lineWidth: 120 }));
382
- // agent.yaml standalone OAD config for runtime usage
383
- fs.writeFileSync(path.join(dir, 'agent.yaml'), `apiVersion: opc/v1
384
- kind: Agent
385
- metadata:
386
- name: ${name}
387
- version: 1.0.0
388
- description: My AI Agent
389
- spec:
390
- model: qwen2.5
391
- provider:
392
- default: ollama
393
- systemPrompt: |
394
- You are a helpful AI assistant named ${name}.
395
- Be concise, helpful, and friendly.
396
- channels:
397
- - type: web
398
- port: 3000
399
- memory:
400
- shortTerm: true
401
- longTerm:
402
- provider: deepbrain
403
- skills:
404
- - name: echo
405
- description: Echo test skill
406
- `);
407
- // src/index.ts — entry point
496
+ // src/index.ts - entry point
408
497
  fs.writeFileSync(path.join(dir, 'src', 'index.ts'), `import { AgentRuntime } from 'opc-agent';
409
498
  import { EchoSkill } from './skills/echo';
410
499
  import { readFileSync, existsSync } from 'fs';
@@ -413,7 +502,7 @@ async function main() {
413
502
  const runtime = new AgentRuntime();
414
503
 
415
504
  // Load OAD config
416
- const config = await runtime.loadConfig('./agent.yaml');
505
+ const config = await runtime.loadConfig('./oad.yaml');
417
506
 
418
507
  // Load personality and context files
419
508
  const soul = existsSync('./SOUL.md') ? readFileSync('./SOUL.md', 'utf-8') : '';
@@ -439,7 +528,7 @@ async function main() {
439
528
 
440
529
  main().catch(console.error);
441
530
  `);
442
- // src/skills/echo.ts example skill
531
+ // src/skills/echo.ts - example skill
443
532
  fs.writeFileSync(path.join(dir, 'src', 'skills', 'echo.ts'), `import { BaseSkill } from 'opc-agent';
444
533
  import type { AgentContext, Message, SkillResult } from 'opc-agent';
445
534
 
@@ -477,23 +566,39 @@ export class EchoSkill extends BaseSkill {
477
566
  }, null, 2));
478
567
  // .env.example
479
568
  fs.writeFileSync(path.join(dir, '.env.example'), `# LLM API Configuration
480
- OPC_LLM_API_KEY=your-api-key-here
481
- OPC_LLM_BASE_URL=https://api.openai.com/v1
482
- OPC_LLM_MODEL=gpt-4o-mini
569
+ # Ollama (免费本地,默认):
570
+ # OPC_LLM_BASE_URL=http://localhost:11434/v1
571
+ # OPC_LLM_MODEL=qwen2.5
572
+ # (Ollama 无需 API key)
483
573
 
484
- # For DeepSeek:
574
+ # DeepSeek:
575
+ # OPC_LLM_API_KEY=your-deepseek-key
485
576
  # OPC_LLM_BASE_URL=https://api.deepseek.com/v1
486
577
  # OPC_LLM_MODEL=deepseek-chat
487
578
 
488
- # For local Ollama (default in agent.yaml):
489
- # OPC_LLM_BASE_URL=http://localhost:11434/v1
490
- # OPC_LLM_MODEL=qwen2.5
491
- `);
492
- // .env (copy of example)
493
- fs.writeFileSync(path.join(dir, '.env'), `OPC_LLM_API_KEY=your-api-key-here
494
- OPC_LLM_BASE_URL=https://api.openai.com/v1
495
- OPC_LLM_MODEL=gpt-4o-mini
579
+ # OpenAI:
580
+ # OPC_LLM_API_KEY=your-openai-key
581
+ # OPC_LLM_BASE_URL=https://api.openai.com/v1
582
+ # OPC_LLM_MODEL=gpt-4o-mini
583
+
584
+ # Anthropic:
585
+ # OPC_LLM_API_KEY=your-anthropic-key
586
+ # OPC_LLM_BASE_URL=https://api.anthropic.com/v1
587
+ # OPC_LLM_MODEL=claude-sonnet-4-20250514
496
588
  `);
589
+ // .env - 根据用户选择生成正确的配置
590
+ const envLines = [];
591
+ if (llmProvider === 'ollama') {
592
+ envLines.push('# Ollama (免费本地) - 无需 API key');
593
+ envLines.push(`OPC_LLM_BASE_URL=${llmBaseUrl}`);
594
+ envLines.push(`OPC_LLM_MODEL=${llmModel}`);
595
+ }
596
+ else {
597
+ envLines.push(`OPC_LLM_API_KEY=${llmApiKey || 'your-api-key-here'}`);
598
+ envLines.push(`OPC_LLM_BASE_URL=${llmBaseUrl}`);
599
+ envLines.push(`OPC_LLM_MODEL=${llmModel}`);
600
+ }
601
+ fs.writeFileSync(path.join(dir, '.env'), envLines.join('\n') + '\n');
497
602
  // package.json
498
603
  fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({
499
604
  name,
@@ -520,7 +625,7 @@ OPC_LLM_MODEL=gpt-4o-mini
520
625
  WORKDIR /app
521
626
  COPY package.json package-lock.json* ./
522
627
  RUN npm ci --production 2>/dev/null || npm install --production
523
- COPY oad.yaml agent.yaml .env* ./
628
+ COPY oad.yaml .env* ./
524
629
  COPY src/ ./src/
525
630
  COPY prompts/ ./prompts/ 2>/dev/null || true
526
631
  EXPOSE 3000
@@ -536,7 +641,7 @@ services:
536
641
  env_file:
537
642
  - .env
538
643
  volumes:
539
- - ./agent.yaml:/app/agent.yaml:ro
644
+ - ./oad.yaml:/app/oad.yaml:ro
540
645
  restart: unless-stopped
541
646
  `);
542
647
  // README.md
@@ -577,8 +682,7 @@ npx opc chat # CLI chat
577
682
 
578
683
  \`\`\`
579
684
  ${name}/
580
- ├── agent.yaml # OAD agent config (used by src/index.ts)
581
- ├── oad.yaml # OAD config (used by opc CLI)
685
+ ├── oad.yaml # Agent 配置 (唯一配置文件)
582
686
  ├── src/
583
687
  │ ├── index.ts # Entry point
584
688
  │ └── skills/
@@ -589,9 +693,9 @@ ${name}/
589
693
 
590
694
  ## Configuration
591
695
 
592
- Edit \`agent.yaml\` to customize your agent's personality, skills, and behavior.
696
+ Edit \`oad.yaml\` to customize your agent's personality, skills, and behavior.
593
697
  `);
594
- // SOUL.md agent personality
698
+ // SOUL.md - agent personality
595
699
  const createdDate = new Date().toISOString().split('T')[0];
596
700
  fs.writeFileSync(path.join(dir, 'SOUL.md'), `# ${name} Personality
597
701
 
@@ -607,7 +711,7 @@ Edit \`agent.yaml\` to customize your agent's personality, skills, and behavior.
607
711
 
608
712
  ## Communication Style
609
713
  - Use clear, simple language
610
- - Be direct answer the question first, then explain
714
+ - Be direct - answer the question first, then explain
611
715
  - Use markdown formatting when helpful
612
716
 
613
717
  ## Rules
@@ -615,7 +719,7 @@ Edit \`agent.yaml\` to customize your agent's personality, skills, and behavior.
615
719
  - Ask for clarification when the request is ambiguous
616
720
  - Never make up information
617
721
  `);
618
- // CONTEXT.md project context
722
+ // CONTEXT.md - project context
619
723
  fs.writeFileSync(path.join(dir, 'CONTEXT.md'), `# Project Context
620
724
 
621
725
  ## About This Agent
@@ -631,7 +735,8 @@ on startup to understand the project context.
631
735
  - Add company policies here
632
736
  `);
633
737
  console.log(`\n${icon.success} Created agent project: ${color.bold(name + '/')}`);
634
- console.log(` ${icon.file} agent.yaml - Agent definition (OAD)`);
738
+ console.log(` ${icon.file} oad.yaml - Agent 配置 (${llmProvider}/${llmModel})`);
739
+ console.log(` ${icon.file} .env - 环境变量${llmProvider === 'ollama' ? '' : ' (API Key)'}`);
635
740
  console.log(` ${icon.file} src/index.ts - Entry point`);
636
741
  console.log(` ${icon.file} src/skills/echo.ts - Example skill`);
637
742
  console.log(` ${icon.file} SOUL.md - Agent personality`);
@@ -656,14 +761,24 @@ on startup to understand the project context.
656
761
  }
657
762
  }
658
763
  catch {
659
- // Brain-seed download failed non-fatal, project still usable
764
+ // Brain-seed download failed - non-fatal, project still usable
660
765
  }
661
766
  }
662
767
  console.log(`\n${color.bold('Next steps:')}`);
663
768
  console.log(` 1. cd ${name}`);
664
769
  console.log(` 2. npm install`);
665
- console.log(` 3. npx tsx src/index.ts ${color.dim('# or: npx opc run')}`);
666
- console.log(` 4. Open http://localhost:3000\n`);
770
+ if (llmProvider === 'ollama' && !ollamaRunning) {
771
+ console.log(` 3. ollama pull ${llmModel} ${color.dim('# 下载模型')}`);
772
+ console.log(` 4. npx opc run ${color.dim('# 启动 Agent')}`);
773
+ }
774
+ else if (llmProvider !== 'ollama' && !llmApiKey) {
775
+ console.log(` 3. 编辑 .env 设置 OPC_LLM_API_KEY`);
776
+ console.log(` 4. npx opc run`);
777
+ }
778
+ else {
779
+ console.log(` 3. npx opc run ${color.dim('# 启动 Agent')}`);
780
+ }
781
+ console.log(` Open http://localhost:3000\n`);
667
782
  console.log(`${color.dim('💡 Tip: Use --role to start from a workstation template:')}`);
668
783
  console.log(`${color.dim(' opc init my-agent --role customer-service')}`);
669
784
  console.log(`${color.dim(' opc init --list-roles (see all roles)')}\n`);
@@ -713,7 +828,7 @@ program
713
828
  // Print startup banner
714
829
  const bannerLines = [
715
830
  '╔══════════════════════════════════════╗',
716
- '║ 🤖 OPC Agent Interactive Chat ║',
831
+ '║ 🤖 OPC Agent - Interactive Chat ║',
717
832
  `║ Agent: ${(agentName + ' v' + agentVersion).padEnd(27)}║`,
718
833
  `║ Model: ${((providerName + '/' + (model ?? 'default')).slice(0, 27)).padEnd(27)}║`,
719
834
  `║ Skills: ${(String(skillNames.length) + ' loaded').padEnd(26)}║`,
@@ -740,12 +855,12 @@ program
740
855
  }
741
856
  if (lower === '/help') {
742
857
  console.log(`\n ${color.bold('Available commands:')}`);
743
- console.log(` ${color.cyan('/help')} Show this help`);
744
- console.log(` ${color.cyan('/quit')} Exit chat (/exit also works)`);
745
- console.log(` ${color.cyan('/clear')} Clear conversation history`);
746
- console.log(` ${color.cyan('/skills')} List registered skills`);
747
- console.log(` ${color.cyan('/memory')} Show memory stats`);
748
- console.log(` ${color.cyan('/info')} Show agent info\n`);
858
+ console.log(` ${color.cyan('/help')} - Show this help`);
859
+ console.log(` ${color.cyan('/quit')} - Exit chat (/exit also works)`);
860
+ console.log(` ${color.cyan('/clear')} - Clear conversation history`);
861
+ console.log(` ${color.cyan('/skills')} - List registered skills`);
862
+ console.log(` ${color.cyan('/memory')} - Show memory stats`);
863
+ console.log(` ${color.cyan('/info')} - Show agent info\n`);
749
864
  return true;
750
865
  }
751
866
  if (lower === '/clear') {
@@ -1460,7 +1575,7 @@ protocolCmd.command('list')
1460
1575
  const protocols = config?.spec?.protocols || {};
1461
1576
  const items = [
1462
1577
  { name: 'a2a', description: 'Agent-to-Agent protocol', enabled: !!protocols.a2a?.enabled, detail: protocols.a2a?.port ? `port ${protocols.a2a.port}` : '' },
1463
- { name: 'agui', description: 'AG-UI Agent-User Interaction (SSE)', enabled: !!protocols.agui?.enabled, detail: protocols.agui?.path || '/agui' },
1578
+ { name: 'agui', description: 'AG-UI - Agent-User Interaction (SSE)', enabled: !!protocols.agui?.enabled, detail: protocols.agui?.path || '/agui' },
1464
1579
  ];
1465
1580
  console.log(`\n${icon.gear} ${color.bold('Protocols')}\n`);
1466
1581
  for (const p of items) {
@@ -1593,7 +1708,7 @@ brainCmd
1593
1708
  .description('Show brain stats (pages, tiers, last evolve)')
1594
1709
  .option('--url <url>', 'DeepBrain server URL', 'http://localhost:3333')
1595
1710
  .action(async (opts) => {
1596
- console.log(`\n${icon.gear} ${color.bold('DeepBrain Status')} ${color.dim(opts.url)}\n`);
1711
+ console.log(`\n${icon.gear} ${color.bold('DeepBrain Status')} - ${color.dim(opts.url)}\n`);
1597
1712
  try {
1598
1713
  const res = await fetch(`${opts.url}/api/stats`);
1599
1714
  if (!res.ok)
@@ -1626,7 +1741,7 @@ brainCmd
1626
1741
  brainCmd
1627
1742
  .command('seed')
1628
1743
  .description('Import brain seed files into memory')
1629
- .option('-f, --file <file>', 'OAD file', 'agent.yaml')
1744
+ .option('-f, --file <file>', 'OAD file', 'oad.yaml')
1630
1745
  .option('--status', 'Check if seeds have been imported')
1631
1746
  .option('--reset', 'Re-import seeds (clear marker and re-seed)')
1632
1747
  .action(async (opts) => {
@@ -1696,7 +1811,7 @@ brainCmd
1696
1811
  console.log(` ${color.cyan(c.slug)} → ${c.fromTier} → ${c.toTier} (confidence: ${(c.confidence * 100).toFixed(0)}%)`);
1697
1812
  }
1698
1813
  if (opts.dryRun) {
1699
- console.log(`\n ${icon.info} Dry run no changes made.\n`);
1814
+ console.log(`\n ${icon.info} Dry run - no changes made.\n`);
1700
1815
  }
1701
1816
  else {
1702
1817
  console.log(`\n ${icon.success} Promoted ${result.promoted} knowledge entries.\n`);
@@ -2145,7 +2260,7 @@ program
2145
2260
  return;
2146
2261
  }
2147
2262
  // Create a minimal mock agent for eval (real usage would load from OAD)
2148
- const oadPath = path.resolve('agent.yaml');
2263
+ const oadPath = path.resolve(fs.existsSync('oad.yaml') ? 'oad.yaml' : 'agent.yaml');
2149
2264
  let agent;
2150
2265
  if (fs.existsSync(oadPath)) {
2151
2266
  const runtime = new runtime_1.AgentRuntime();
@@ -2154,7 +2269,7 @@ program
2154
2269
  agent = runtime.agent;
2155
2270
  }
2156
2271
  if (!agent) {
2157
- console.log(`${icon.warn} No agent.yaml found running with dry-run mock agent.`);
2272
+ console.log(`${icon.warn} No oad.yaml or agent.yaml found - running with dry-run mock agent.`);
2158
2273
  agent = { chat: async (input) => `[mock response to: ${input}]` };
2159
2274
  }
2160
2275
  const evaluator = new eval_1.AgentEvaluator(agent);
@@ -2214,7 +2329,7 @@ guardrailsCmd
2214
2329
  console.log();
2215
2330
  const result = await manager.checkInput(message);
2216
2331
  if (result.passed) {
2217
- console.log(color.green('✓ PASSED no violations'));
2332
+ console.log(color.green('✓ PASSED - no violations'));
2218
2333
  }
2219
2334
  else {
2220
2335
  if (result.blocked)
@@ -2243,7 +2358,7 @@ program
2243
2358
  .action(async (opts) => {
2244
2359
  console.log(color.bold('🎤 Voice Conversation Mode'));
2245
2360
  console.log(` STT: ${opts.stt} | TTS: ${opts.tts} | Voice: ${opts.voice ?? 'default'} | Language: ${opts.language}`);
2246
- console.log(color.dim(' (Voice conversation requires audio input integration use as library)'));
2361
+ console.log(color.dim(' (Voice conversation requires audio input integration - use as library)'));
2247
2362
  console.log();
2248
2363
  console.log('To use voice in your agent:');
2249
2364
  console.log(color.cyan(`
@@ -2307,7 +2422,7 @@ keysCmd
2307
2422
  });
2308
2423
  // ── Approve command ───────────────────────────────────────────
2309
2424
  const approveCmd = program.command('approve').description('Manage command approvals');
2310
- // Singleton for CLI in real usage this would be loaded from daemon state
2425
+ // Singleton for CLI - in real usage this would be loaded from daemon state
2311
2426
  const approvalManager = new approval_1.ApprovalManager();
2312
2427
  approveCmd
2313
2428
  .command('list')
@@ -2439,7 +2554,7 @@ a2aCmd
2439
2554
  const { oadToAgentCard } = require('./protocols/a2a');
2440
2555
  const oad = loadOADFile();
2441
2556
  if (!oad) {
2442
- console.log(`${icon.error} No agent.yaml found`);
2557
+ console.log(`${icon.error} No oad.yaml or agent.yaml found`);
2443
2558
  return;
2444
2559
  }
2445
2560
  const card = oadToAgentCard(oad, 'http://localhost:3001');
@@ -2479,7 +2594,7 @@ a2aCmd
2479
2594
  function loadOADFile() {
2480
2595
  const fs = require('fs');
2481
2596
  const yaml = require('js-yaml');
2482
- for (const name of ['agent.yaml', 'agent.yml']) {
2597
+ for (const name of ['oad.yaml', 'agent.yaml', 'agent.yml']) {
2483
2598
  if (fs.existsSync(name)) {
2484
2599
  return yaml.load(fs.readFileSync(name, 'utf-8'));
2485
2600
  }
@@ -2487,7 +2602,7 @@ function loadOADFile() {
2487
2602
  return null;
2488
2603
  }
2489
2604
  // ── MCP Server Commands ────────────────────────────────────
2490
- const mcpCmd = program.command('mcp').description('MCP server commands expose agent as MCP tools');
2605
+ const mcpCmd = program.command('mcp').description('MCP server commands - expose agent as MCP tools');
2491
2606
  mcpCmd
2492
2607
  .command('serve')
2493
2608
  .option('--http <port>', 'Start HTTP+SSE mode on given port')
@@ -2516,7 +2631,7 @@ mcpCmd
2516
2631
  console.log(`${icon.info} Tools: ${server.getToolCount()}`);
2517
2632
  }
2518
2633
  else {
2519
- console.error(`${icon.success} MCP server (stdio) started ${server.getToolCount()} tools`);
2634
+ console.error(`${icon.success} MCP server (stdio) started - ${server.getToolCount()} tools`);
2520
2635
  await server.serveStdio();
2521
2636
  }
2522
2637
  });
@@ -2565,7 +2680,7 @@ mcpCmd
2565
2680
  console.log(`${icon.info} Tools: ${server.getToolCount()}`);
2566
2681
  }
2567
2682
  else {
2568
- console.error(`${icon.success} MCP server ${color.cyan(name)} (stdio) ${server.getToolCount()} tools`);
2683
+ console.error(`${icon.success} MCP server ${color.cyan(name)} (stdio) - ${server.getToolCount()} tools`);
2569
2684
  await server.serveStdio();
2570
2685
  }
2571
2686
  });
@@ -74,25 +74,39 @@ class AgentRuntime {
74
74
  evolveScheduler = null;
75
75
  async loadConfig(filePath) {
76
76
  const fs = require('fs');
77
+ const path = require('path');
78
+ // 如果指定文件不存在,尝试 fallback
77
79
  if (!fs.existsSync(filePath)) {
78
- // Auto-create a minimal oad.yaml with auto-detect provider
79
- const yaml = require('js-yaml');
80
- const defaultOAD = {
81
- apiVersion: 'opc/v1',
82
- kind: 'Agent',
83
- metadata: { name: 'my-agent', version: '1.0.0', description: 'OPC Agent' },
84
- spec: {
85
- model: 'auto',
86
- provider: { default: 'auto' },
87
- systemPrompt: 'You are a helpful AI assistant.',
88
- channels: [{ type: 'web', config: { port: 3000 } }],
89
- },
90
- };
91
- fs.writeFileSync(filePath, yaml.dump(defaultOAD, { lineWidth: 120 }));
92
- this.logger.info('Created default oad.yaml (no config file found)');
80
+ // 如果发现旧的 agent.yaml,提示迁移
81
+ if (filePath === 'oad.yaml' && fs.existsSync('agent.yaml')) {
82
+ this.logger.warn('⚠️ 发现 agent.yaml 但未找到 oad.yaml。建议运行 `opc migrate` 统一为 oad.yaml。');
83
+ this.logger.info('暂时使用 agent.yaml 加载配置...');
84
+ filePath = 'agent.yaml';
85
+ }
86
+ else {
87
+ // Auto-create a minimal oad.yaml with auto-detect provider
88
+ const yaml = require('js-yaml');
89
+ const defaultOAD = {
90
+ apiVersion: 'opc/v1',
91
+ kind: 'Agent',
92
+ metadata: { name: 'my-agent', version: '1.0.0', description: 'OPC Agent' },
93
+ spec: {
94
+ model: 'auto',
95
+ provider: { default: 'auto' },
96
+ systemPrompt: 'You are a helpful AI assistant.',
97
+ channels: [{ type: 'web', config: { port: 3000 } }],
98
+ },
99
+ };
100
+ fs.writeFileSync(filePath, yaml.dump(defaultOAD, { lineWidth: 120 }));
101
+ this.logger.info('Created default oad.yaml (no config file found)');
102
+ }
93
103
  }
94
104
  this.config = (0, config_1.loadOAD)(filePath);
95
105
  this.logger.info('Config loaded', { name: this.config.metadata.name });
106
+ // 如果同时存在 agent.yaml 和 oad.yaml,提示用户清理
107
+ if (fs.existsSync('agent.yaml') && fs.existsSync('oad.yaml')) {
108
+ this.logger.warn('⚠️ 同时存在 agent.yaml 和 oad.yaml。建议删除 agent.yaml,统一使用 oad.yaml。');
109
+ }
96
110
  return this.config;
97
111
  }
98
112
  setHistoryLimit(limit) {
@@ -128,6 +142,14 @@ class AgentRuntime {
128
142
  const cfg = config ?? this.config;
129
143
  if (!cfg)
130
144
  throw new Error('No config loaded. Call loadConfig() first.');
145
+ // 检查 API key 是否为占位符,启动时警告
146
+ const apiKey = process.env.OPC_LLM_API_KEY;
147
+ const cfgProvider = cfg.spec.provider?.default;
148
+ if (cfgProvider !== 'ollama' && cfgProvider !== 'auto') {
149
+ if (!apiKey || apiKey === 'your-api-key-here') {
150
+ this.logger.warn('⚠️ API Key 未配置或仍是占位符。请编辑 .env 文件设置 OPC_LLM_API_KEY。');
151
+ }
152
+ }
131
153
  let memory;
132
154
  const memCfg = cfg.spec.memory;
133
155
  if (memCfg && typeof memCfg.longTerm === 'object' && memCfg.longTerm.provider === 'deepbrain') {