opc-agent 4.0.44 → 4.1.1

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 (250) 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/dist/channels/wechat.js +6 -6
  8. package/dist/cli.js +2 -2
  9. package/dist/core/runtime.js +18 -0
  10. package/dist/deploy/index.js +56 -56
  11. package/dist/providers/index.js +39 -13
  12. package/dist/studio/server.js +211 -20
  13. package/dist/studio-ui/index.html +279 -24
  14. package/dist/ui/components.js +105 -105
  15. package/examples/README.md +22 -22
  16. package/examples/basic-agent.ts +90 -90
  17. package/examples/brain-integration.ts +71 -71
  18. package/examples/multi-channel.ts +74 -74
  19. package/fix-sidebar.mjs +188 -188
  20. package/install.ps1 +154 -154
  21. package/install.sh +164 -164
  22. package/package.json +1 -1
  23. package/scripts/install.ps1 +31 -31
  24. package/scripts/install.sh +40 -40
  25. package/serve-studio.js +13 -13
  26. package/serve-test.js +25 -25
  27. package/src/channels/dingtalk.ts +46 -46
  28. package/src/channels/email.ts +351 -351
  29. package/src/channels/feishu.ts +349 -349
  30. package/src/channels/googlechat.ts +42 -42
  31. package/src/channels/imessage.ts +31 -31
  32. package/src/channels/irc.ts +82 -82
  33. package/src/channels/line.ts +32 -32
  34. package/src/channels/matrix.ts +33 -33
  35. package/src/channels/mattermost.ts +57 -57
  36. package/src/channels/msteams.ts +32 -32
  37. package/src/channels/nostr.ts +32 -32
  38. package/src/channels/qq.ts +33 -33
  39. package/src/channels/signal.ts +32 -32
  40. package/src/channels/sms.ts +33 -33
  41. package/src/channels/telegram.ts +616 -616
  42. package/src/channels/twitch.ts +65 -65
  43. package/src/channels/voice-call.ts +100 -100
  44. package/src/channels/websocket.ts +399 -399
  45. package/src/channels/wechat.ts +329 -329
  46. package/src/channels/whatsapp.ts +32 -32
  47. package/src/cli/chat.ts +99 -99
  48. package/src/cli/setup.ts +314 -314
  49. package/src/cli.ts +2 -2
  50. package/src/core/agent.ts +476 -476
  51. package/src/core/api-server.ts +277 -277
  52. package/src/core/audio.ts +98 -98
  53. package/src/core/collaboration.ts +275 -275
  54. package/src/core/context-discovery.ts +85 -85
  55. package/src/core/context-refs.ts +140 -140
  56. package/src/core/gateway.ts +106 -106
  57. package/src/core/heartbeat.ts +51 -51
  58. package/src/core/hooks.ts +105 -105
  59. package/src/core/ide-bridge.ts +133 -133
  60. package/src/core/node-network.ts +86 -86
  61. package/src/core/profiles.ts +122 -122
  62. package/src/core/runtime.ts +18 -0
  63. package/src/core/scheduler.ts +187 -187
  64. package/src/core/session-manager.ts +137 -137
  65. package/src/core/subagent.ts +98 -98
  66. package/src/core/vision.ts +180 -180
  67. package/src/core/workflow-graph.ts +365 -365
  68. package/src/daemon.ts +96 -96
  69. package/src/deploy/index.ts +255 -255
  70. package/src/doctor.ts +156 -156
  71. package/src/eval/index.ts +211 -211
  72. package/src/eval/suites/basic.json +16 -16
  73. package/src/eval/suites/memory.json +12 -12
  74. package/src/eval/suites/safety.json +14 -14
  75. package/src/hub/brain-seed.ts +54 -54
  76. package/src/hub/client.ts +60 -60
  77. package/src/mcp/servers/calculator-mcp.ts +65 -65
  78. package/src/mcp/servers/crypto-mcp.ts +73 -73
  79. package/src/mcp/servers/database-mcp.ts +72 -72
  80. package/src/mcp/servers/datetime-mcp.ts +69 -69
  81. package/src/mcp/servers/filesystem.ts +66 -66
  82. package/src/mcp/servers/github-mcp.ts +58 -58
  83. package/src/mcp/servers/index.ts +63 -63
  84. package/src/mcp/servers/json-mcp.ts +102 -102
  85. package/src/mcp/servers/memory-mcp.ts +56 -56
  86. package/src/mcp/servers/regex-mcp.ts +53 -53
  87. package/src/mcp/servers/web-mcp.ts +49 -49
  88. package/src/memory/context-compressor.ts +189 -189
  89. package/src/memory/seed-loader.ts +212 -212
  90. package/src/memory/user-profiler.ts +215 -215
  91. package/src/plugins/content-filter.ts +23 -23
  92. package/src/plugins/logger.ts +18 -18
  93. package/src/plugins/rate-limiter.ts +38 -38
  94. package/src/protocols/a2a/client.ts +132 -132
  95. package/src/protocols/a2a/index.ts +8 -8
  96. package/src/protocols/a2a/server.ts +333 -333
  97. package/src/protocols/a2a/types.ts +88 -88
  98. package/src/protocols/a2a/utils.ts +50 -50
  99. package/src/protocols/agui/client.ts +83 -83
  100. package/src/protocols/agui/index.ts +4 -4
  101. package/src/protocols/agui/server.ts +218 -218
  102. package/src/protocols/agui/types.ts +153 -153
  103. package/src/protocols/index.ts +2 -2
  104. package/src/protocols/mcp/agent-tools.ts +134 -134
  105. package/src/protocols/mcp/index.ts +8 -8
  106. package/src/protocols/mcp/server.ts +262 -262
  107. package/src/protocols/mcp/types.ts +69 -69
  108. package/src/providers/index.ts +632 -608
  109. package/src/publish/index.ts +376 -376
  110. package/src/scheduler/cron-engine.ts +191 -191
  111. package/src/scheduler/index.ts +2 -2
  112. package/src/schema/oad.ts +217 -217
  113. package/src/security/approval.ts +131 -131
  114. package/src/security/approvals.ts +143 -143
  115. package/src/security/elevated.ts +105 -105
  116. package/src/security/guardrails.ts +248 -248
  117. package/src/security/index.ts +9 -9
  118. package/src/security/keys.ts +87 -87
  119. package/src/security/secrets.ts +129 -129
  120. package/src/skills/builtin/index.ts +408 -408
  121. package/src/skills/marketplace.ts +113 -113
  122. package/src/skills/types.ts +42 -42
  123. package/src/studio/server.ts +209 -22
  124. package/src/studio/templates-data.ts +178 -178
  125. package/src/studio-ui/index.html +279 -24
  126. package/src/telemetry/index.ts +324 -324
  127. package/src/tools/builtin/browser.ts +299 -299
  128. package/src/tools/builtin/datetime.ts +41 -41
  129. package/src/tools/builtin/file.ts +107 -107
  130. package/src/tools/builtin/home-assistant.ts +116 -116
  131. package/src/tools/builtin/rl-tools.ts +243 -243
  132. package/src/tools/builtin/shell.ts +43 -43
  133. package/src/tools/builtin/vision.ts +64 -64
  134. package/src/tools/builtin/web-search.ts +126 -126
  135. package/src/tools/builtin/web.ts +35 -35
  136. package/src/tools/document-processor.ts +213 -213
  137. package/src/tools/image-generator.ts +150 -150
  138. package/src/tools/integrations/calendar.ts +73 -73
  139. package/src/tools/integrations/code-exec.ts +39 -39
  140. package/src/tools/integrations/csv-analyzer.ts +92 -92
  141. package/src/tools/integrations/database.ts +44 -44
  142. package/src/tools/integrations/email-send.ts +76 -76
  143. package/src/tools/integrations/git-tool.ts +42 -42
  144. package/src/tools/integrations/github-tool.ts +76 -76
  145. package/src/tools/integrations/image-gen.ts +56 -56
  146. package/src/tools/integrations/index.ts +92 -92
  147. package/src/tools/integrations/jira.ts +83 -83
  148. package/src/tools/integrations/notion.ts +71 -71
  149. package/src/tools/integrations/npm-tool.ts +48 -48
  150. package/src/tools/integrations/pdf-reader.ts +58 -58
  151. package/src/tools/integrations/slack.ts +65 -65
  152. package/src/tools/integrations/summarizer.ts +49 -49
  153. package/src/tools/integrations/translator.ts +48 -48
  154. package/src/tools/integrations/trello.ts +60 -60
  155. package/src/tools/integrations/vector-search.ts +42 -42
  156. package/src/tools/integrations/web-scraper.ts +47 -47
  157. package/src/tools/integrations/web-search.ts +58 -58
  158. package/src/tools/integrations/webhook.ts +38 -38
  159. package/src/tools/mcp-client.ts +131 -131
  160. package/src/tools/web-scraper.ts +179 -179
  161. package/src/tools/web-search.ts +180 -180
  162. package/src/ui/components.ts +127 -127
  163. package/srv-out.txt +1 -1
  164. package/templates/ecommerce-assistant/README.md +45 -45
  165. package/templates/ecommerce-assistant/oad.yaml +47 -47
  166. package/templates/tech-support/README.md +43 -43
  167. package/templates/tech-support/oad.yaml +45 -45
  168. package/test-agent/Dockerfile +9 -9
  169. package/test-agent/README.md +50 -50
  170. package/test-agent/agent.yaml +23 -23
  171. package/test-agent/docker-compose.yml +11 -11
  172. package/test-agent/oad.yaml +31 -31
  173. package/test-agent/package-lock.json +1492 -1492
  174. package/test-agent/package.json +17 -17
  175. package/test-agent/src/index.ts +24 -24
  176. package/test-agent/src/skills/echo.ts +15 -15
  177. package/test-agent/tsconfig.json +24 -24
  178. package/test-full.js +43 -43
  179. package/test-sidebar.js +22 -22
  180. package/test-studio3.js +75 -75
  181. package/test-studio4.js +41 -41
  182. package/tests/a2a-protocol.test.ts +285 -285
  183. package/tests/agui-protocol.test.ts +246 -246
  184. package/tests/api-server.test.ts +148 -148
  185. package/tests/approvals.test.ts +89 -89
  186. package/tests/audio.test.ts +40 -40
  187. package/tests/brain-seed-extended.test.ts +490 -490
  188. package/tests/brain-seed.test.ts +239 -239
  189. package/tests/browser.test.ts +179 -179
  190. package/tests/channels/discord.test.ts +79 -79
  191. package/tests/channels/email.test.ts +148 -148
  192. package/tests/channels/feishu.test.ts +123 -123
  193. package/tests/channels/telegram.test.ts +129 -129
  194. package/tests/channels/websocket.test.ts +53 -53
  195. package/tests/channels/wechat.test.ts +170 -170
  196. package/tests/channels-extra.test.ts +45 -45
  197. package/tests/chat-cli.test.ts +160 -160
  198. package/tests/cli.test.ts +46 -46
  199. package/tests/context-compressor.test.ts +172 -172
  200. package/tests/context-refs.test.ts +121 -121
  201. package/tests/cron-engine.test.ts +101 -101
  202. package/tests/daemon.test.ts +135 -135
  203. package/tests/deepbrain-wire.test.ts +234 -234
  204. package/tests/deploy-and-dag.test.ts +196 -196
  205. package/tests/doctor.test.ts +38 -38
  206. package/tests/document-processor.test.ts +69 -69
  207. package/tests/e2e-nocode.test.ts +442 -442
  208. package/tests/elevated.test.ts +69 -69
  209. package/tests/eval.test.ts +173 -173
  210. package/tests/gateway.test.ts +63 -63
  211. package/tests/guardrails.test.ts +177 -177
  212. package/tests/home-assistant.test.ts +40 -40
  213. package/tests/hooks.test.ts +79 -79
  214. package/tests/ide-bridge.test.ts +38 -38
  215. package/tests/image-generator.test.ts +84 -84
  216. package/tests/init-role.test.ts +124 -124
  217. package/tests/integrations.test.ts +249 -249
  218. package/tests/mcp-client.test.ts +92 -92
  219. package/tests/mcp-server.test.ts +178 -178
  220. package/tests/mcp-servers.test.ts +260 -260
  221. package/tests/node-network.test.ts +74 -74
  222. package/tests/plugin-a2a-enhanced.test.ts +230 -230
  223. package/tests/profiles.test.ts +61 -61
  224. package/tests/publish.test.ts +231 -231
  225. package/tests/rl-tools.test.ts +93 -93
  226. package/tests/sandbox-manager.test.ts +46 -46
  227. package/tests/scheduler.test.ts +200 -200
  228. package/tests/secrets.test.ts +107 -107
  229. package/tests/security-enhanced.test.ts +233 -233
  230. package/tests/settings-api.test.ts +148 -148
  231. package/tests/setup.test.ts +73 -73
  232. package/tests/subagent.test.ts +193 -193
  233. package/tests/telegram-discord.test.ts +60 -60
  234. package/tests/telemetry.test.ts +186 -186
  235. package/tests/user-profiler.test.ts +169 -169
  236. package/tests/v090-features.test.ts +254 -254
  237. package/tests/vision.test.ts +61 -61
  238. package/tests/voice-call.test.ts +47 -47
  239. package/tests/voice-enhanced.test.ts +169 -169
  240. package/tests/voice-interaction.test.ts +38 -38
  241. package/tests/web-search.test.ts +155 -155
  242. package/tests/workflow-graph.test.ts +279 -279
  243. package/tutorial/customer-service-agent/README.md +612 -612
  244. package/tutorial/customer-service-agent/SOUL.md +26 -26
  245. package/tutorial/customer-service-agent/agent.yaml +63 -63
  246. package/tutorial/customer-service-agent/package.json +19 -19
  247. package/tutorial/customer-service-agent/src/index.ts +69 -69
  248. package/tutorial/customer-service-agent/src/skills/faq.ts +27 -27
  249. package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -22
  250. package/tutorial/customer-service-agent/tsconfig.json +14 -14
@@ -155,12 +155,12 @@ class WeChatChannel extends index_1.BaseChannel {
155
155
  /** Format response as WeChat XML */
156
156
  static formatXMLResponse(toUser, fromUser, content) {
157
157
  const timestamp = Math.floor(Date.now() / 1000);
158
- return `<xml>
159
- <ToUserName><![CDATA[${toUser}]]></ToUserName>
160
- <FromUserName><![CDATA[${fromUser}]]></FromUserName>
161
- <CreateTime>${timestamp}</CreateTime>
162
- <MsgType><![CDATA[text]]></MsgType>
163
- <Content><![CDATA[${content}]]></Content>
158
+ return `<xml>
159
+ <ToUserName><![CDATA[${toUser}]]></ToUserName>
160
+ <FromUserName><![CDATA[${fromUser}]]></FromUserName>
161
+ <CreateTime>${timestamp}</CreateTime>
162
+ <MsgType><![CDATA[text]]></MsgType>
163
+ <Content><![CDATA[${content}]]></Content>
164
164
  </xml>`;
165
165
  }
166
166
  /** Handle incoming WeChat message */
package/dist/cli.js CHANGED
@@ -680,7 +680,7 @@ program
680
680
  let model;
681
681
  let agentName = 'Agent';
682
682
  let agentVersion = '1.0.0';
683
- let providerName = 'openai';
683
+ let providerName = 'auto';
684
684
  let skillNames = [];
685
685
  // Try loading SOUL.md and CONTEXT.md for enriched system prompt
686
686
  const soulPath = path.resolve('SOUL.md');
@@ -708,7 +708,7 @@ program
708
708
  }
709
709
  // Prepend SOUL.md and CONTEXT.md to system prompt
710
710
  systemPrompt = [soulContent, contextContent, systemPrompt].filter(Boolean).join('\n\n');
711
- const provider = (0, providers_1.createProvider)('openai', model);
711
+ const provider = (0, providers_1.createProvider)(providerName, model);
712
712
  const history = [];
713
713
  // Print startup banner
714
714
  const bannerLines = [
@@ -73,6 +73,24 @@ class AgentRuntime {
73
73
  agentBrain = null;
74
74
  evolveScheduler = null;
75
75
  async loadConfig(filePath) {
76
+ const fs = require('fs');
77
+ 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)');
93
+ }
76
94
  this.config = (0, config_1.loadOAD)(filePath);
77
95
  this.logger.info('Config loaded', { name: this.config.metadata.name });
78
96
  return this.config;
@@ -60,25 +60,25 @@ class AgentDeployer {
60
60
  }
61
61
  catch { }
62
62
  }
63
- return `FROM node:22-slim
64
- WORKDIR /app
65
-
66
- # Install dependencies
67
- COPY package*.json ./
68
- RUN npm ci --production
69
-
70
- # Copy source
71
- COPY . .
72
-
73
- # Build if needed
74
- RUN if [ -f "tsconfig.json" ]; then npx tsc || true; fi
75
-
76
- EXPOSE ${port}
77
-
78
- ENV NODE_ENV=production
79
- ENV PORT=${port}
80
-
81
- CMD ["${startCmd.split(' ')[0]}", ${startCmd.split(' ').slice(1).map(s => `"${s}"`).join(', ')}]
63
+ return `FROM node:22-slim
64
+ WORKDIR /app
65
+
66
+ # Install dependencies
67
+ COPY package*.json ./
68
+ RUN npm ci --production
69
+
70
+ # Copy source
71
+ COPY . .
72
+
73
+ # Build if needed
74
+ RUN if [ -f "tsconfig.json" ]; then npx tsc || true; fi
75
+
76
+ EXPOSE ${port}
77
+
78
+ ENV NODE_ENV=production
79
+ ENV PORT=${port}
80
+
81
+ CMD ["${startCmd.split(' ')[0]}", ${startCmd.split(' ').slice(1).map(s => `"${s}"`).join(', ')}]
82
82
  `;
83
83
  }
84
84
  /**
@@ -91,23 +91,23 @@ CMD ["${startCmd.split(' ')[0]}", ${startCmd.split(' ').slice(1).map(s => `"${s}
91
91
  const envLines = options?.env
92
92
  ? Object.entries(options.env).map(([k, v]) => ` - ${k}=${v}`).join('\n')
93
93
  : '';
94
- return `version: "3.8"
95
-
96
- services:
97
- ${agentName}:
98
- build: .
99
- ports:
100
- - "${port}:${port}"
101
- environment:
102
- - NODE_ENV=production
103
- - PORT=${port}
104
- ${envLines ? envLines + '\n' : ''} restart: unless-stopped
105
- ${replicas > 1 ? ` deploy:\n replicas: ${replicas}` : ''}
106
- volumes:
107
- - agent-data:/app/data
108
-
109
- volumes:
110
- agent-data:
94
+ return `version: "3.8"
95
+
96
+ services:
97
+ ${agentName}:
98
+ build: .
99
+ ports:
100
+ - "${port}:${port}"
101
+ environment:
102
+ - NODE_ENV=production
103
+ - PORT=${port}
104
+ ${envLines ? envLines + '\n' : ''} restart: unless-stopped
105
+ ${replicas > 1 ? ` deploy:\n replicas: ${replicas}` : ''}
106
+ volumes:
107
+ - agent-data:/app/data
108
+
109
+ volumes:
110
+ agent-data:
111
111
  `;
112
112
  }
113
113
  /**
@@ -155,21 +155,21 @@ volumes:
155
155
  // Generate fly.toml if not exists
156
156
  const flyTomlPath = path.join(agentDir, 'fly.toml');
157
157
  if (!fs.existsSync(flyTomlPath)) {
158
- fs.writeFileSync(flyTomlPath, `app = "${agentName}"
159
- primary_region = "sjc"
160
-
161
- [build]
162
-
163
- [http_service]
164
- internal_port = 3000
165
- force_https = true
166
- auto_stop_machines = true
167
- auto_start_machines = true
168
-
169
- [checks]
170
- [checks.alive]
171
- type = "tcp"
172
- port = 3000
158
+ fs.writeFileSync(flyTomlPath, `app = "${agentName}"
159
+ primary_region = "sjc"
160
+
161
+ [build]
162
+
163
+ [http_service]
164
+ internal_port = 3000
165
+ force_https = true
166
+ auto_stop_machines = true
167
+ auto_start_machines = true
168
+
169
+ [checks]
170
+ [checks.alive]
171
+ type = "tcp"
172
+ port = 3000
173
173
  `);
174
174
  }
175
175
  // Ensure Dockerfile exists
@@ -241,11 +241,11 @@ primary_region = "sjc"
241
241
  fs.writeFileSync(path.join(agentDir, 'docker-compose.yml'), compose);
242
242
  files.push('docker-compose.yml');
243
243
  // Generate .dockerignore
244
- const dockerignore = `node_modules
245
- .git
246
- .env
247
- *.log
248
- dist
244
+ const dockerignore = `node_modules
245
+ .git
246
+ .env
247
+ *.log
248
+ dist
249
249
  `;
250
250
  fs.writeFileSync(path.join(agentDir, '.dockerignore'), dockerignore);
251
251
  files.push('.dockerignore');
@@ -328,7 +328,21 @@ class ClaudeCLIProvider {
328
328
  name = 'claude-cli';
329
329
  model;
330
330
  constructor(model) {
331
- this.model = model || 'sonnet';
331
+ // Claude CLI uses short model names; don't pass API-style model names
332
+ // Let Claude CLI use its default model unless explicitly set to a known CLI model
333
+ const cliModels = ['sonnet', 'opus', 'haiku', 'claude-sonnet-4-20250514', 'claude-opus-4-20250514'];
334
+ if (model && !cliModels.includes(model)) {
335
+ // Map common patterns
336
+ if (model.includes('opus'))
337
+ this.model = 'opus';
338
+ else if (model.includes('haiku'))
339
+ this.model = 'haiku';
340
+ else
341
+ this.model = ''; // let CLI choose default
342
+ }
343
+ else {
344
+ this.model = model || '';
345
+ }
332
346
  }
333
347
  async chat(messages, systemPrompt, options) {
334
348
  const { writeFileSync, unlinkSync, mkdtempSync } = await Promise.resolve().then(() => __importStar(require('fs')));
@@ -343,7 +357,7 @@ class ClaudeCLIProvider {
343
357
  if (options?.tools && options.tools.length > 0) {
344
358
  prompt += buildToolPrompt(options.tools);
345
359
  }
346
- const args = ['-p', '--no-project-context'];
360
+ const args = ['-p'];
347
361
  // Write system prompt to temp file to avoid shell escaping issues
348
362
  let tmpFile;
349
363
  if (systemPrompt) {
@@ -407,7 +421,7 @@ class ClaudeCLIProvider {
407
421
  }
408
422
  }
409
423
  async *chatStream(messages, systemPrompt) {
410
- const args = ['-p', '--no-project-context', '--output-format', 'stream-json', '--include-partial-messages'];
424
+ const args = ['-p', '--verbose', '--output-format', 'stream-json'];
411
425
  if (this.model) {
412
426
  args.push('--model', this.model);
413
427
  }
@@ -442,14 +456,25 @@ class ClaudeCLIProvider {
442
456
  continue;
443
457
  try {
444
458
  const event = JSON.parse(trimmed);
445
- // Handle partial message chunks (content_block_delta style)
446
- if (event.type === 'content' && event.content) {
447
- const newContent = event.content;
448
- if (newContent.length > lastContent.length) {
449
- yield newContent.slice(lastContent.length);
450
- lastContent = newContent;
459
+ // Claude CLI stream-json format:
460
+ // {"type":"assistant","message":{"content":[{"type":"text","text":"..."}]}}
461
+ if (event.type === 'assistant' && event.message?.content) {
462
+ for (const block of event.message.content) {
463
+ if (block.type === 'text' && block.text) {
464
+ const newContent = block.text;
465
+ if (newContent.length > lastContent.length) {
466
+ yield newContent.slice(lastContent.length);
467
+ lastContent = newContent;
468
+ }
469
+ }
451
470
  }
452
471
  }
472
+ // Also handle result type for final content
473
+ if (event.type === 'result' && event.result) {
474
+ const remaining = event.result.slice(lastContent.length);
475
+ if (remaining)
476
+ yield remaining;
477
+ }
453
478
  // Handle assistant message with content array
454
479
  if (event.type === 'assistant' && event.message?.content) {
455
480
  for (const block of event.message.content) {
@@ -520,10 +545,11 @@ function detectClaudeCLI() {
520
545
  }
521
546
  function detectOllama() {
522
547
  try {
523
- (0, child_process_1.execSync)('ollama --version', { stdio: 'pipe', timeout: 3000 });
524
- // Also check if server is running
525
- const res = (0, child_process_1.execSync)('curl -s http://localhost:11434/api/tags', { stdio: 'pipe', timeout: 3000 });
526
- return res.toString().includes('models');
548
+ // Use node http instead of curl for Windows compatibility
549
+ const { execSync: es } = require('child_process');
550
+ // Quick check: try to connect to Ollama API via node
551
+ const result = es(`node -e "const h=require('http');const r=h.get('http://localhost:11434/api/tags',{timeout:2000},s=>{let d='';s.on('data',c=>d+=c);s.on('end',()=>{process.stdout.write(d.includes('models')?'1':'0')})});r.on('error',()=>process.stdout.write('0'));r.on('timeout',()=>{r.destroy();process.stdout.write('0')})"`, { stdio: 'pipe', timeout: 5000 });
552
+ return result.toString().trim() === '1';
527
553
  }
528
554
  catch {
529
555
  return false;
@@ -166,12 +166,96 @@ class StudioServer {
166
166
  const industry = url.searchParams.get('industry') || '';
167
167
  const search = url.searchParams.get('q') || '';
168
168
  data = this.getTemplates(industry, search);
169
+ // Merge with real workstation templates
170
+ try {
171
+ const ws = require('agent-workstation');
172
+ const categories = ws.getCategories();
173
+ const wsTemplates = [];
174
+ for (const cat of categories) {
175
+ for (const roleName of cat.roles) {
176
+ const role = ws.getRole(cat.name, roleName);
177
+ if (!role)
178
+ continue;
179
+ let oad = {};
180
+ try {
181
+ if (role.files?.['oad.yaml']) {
182
+ const yaml = require('js-yaml');
183
+ oad = yaml.load(role.files['oad.yaml']) || {};
184
+ }
185
+ }
186
+ catch { }
187
+ const tpl = {
188
+ id: `ws-${cat.name}-${roleName}`,
189
+ name: oad.name || roleName.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()),
190
+ nameZh: oad.nameZh || '',
191
+ icon: oad.icon || '🤖',
192
+ description: oad.description || '',
193
+ descriptionZh: oad.descriptionZh || '',
194
+ industry: cat.name,
195
+ industryZh: cat.name,
196
+ tags: [cat.name, 'workstation'],
197
+ suggestedModel: 'auto',
198
+ systemPrompt: oad.systemPrompt || role.files?.['brain-seed.md'] || '',
199
+ source: 'workstation',
200
+ ego: oad.ego || null,
201
+ mission: oad.mission || null,
202
+ skills: oad.skills || [],
203
+ };
204
+ if (!search || tpl.name.toLowerCase().includes(search.toLowerCase()) || tpl.nameZh.includes(search)) {
205
+ if (!industry || tpl.industry === industry) {
206
+ wsTemplates.push(tpl);
207
+ }
208
+ }
209
+ }
210
+ }
211
+ data.templates = [...data.templates, ...wsTemplates];
212
+ // Add workstation industries to list
213
+ const existingIds = new Set(data.industries.map((i) => i.id));
214
+ for (const cat of categories) {
215
+ if (!existingIds.has(cat.name)) {
216
+ data.industries.push({ id: cat.name, name: cat.name, nameZh: cat.name });
217
+ }
218
+ }
219
+ }
220
+ catch (wsErr) {
221
+ // workstation not available, use built-in templates only
222
+ }
169
223
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
170
224
  res.end(JSON.stringify(data));
171
225
  return;
172
226
  }
173
227
  if (route.match(/^templates\/[^/]+$/) && req.method === 'GET') {
174
228
  const tplId = route.split('/')[1];
229
+ // Check workstation first
230
+ if (tplId.startsWith('ws-')) {
231
+ const parts = tplId.replace('ws-', '').split('-');
232
+ const catName = parts[0];
233
+ const roleName = parts.slice(1).join('-');
234
+ try {
235
+ const ws = require('agent-workstation');
236
+ const role = ws.getRole(catName, roleName);
237
+ if (role) {
238
+ let oad = {};
239
+ try {
240
+ if (role.files?.['oad.yaml']) {
241
+ const yaml = require('js-yaml');
242
+ oad = yaml.load(role.files['oad.yaml']) || {};
243
+ }
244
+ }
245
+ catch { }
246
+ data = {
247
+ id: tplId, name: oad.name || roleName, source: 'workstation',
248
+ category: catName, role: roleName, files: role.files,
249
+ ego: oad.ego, mission: oad.mission, skills: oad.skills,
250
+ systemPrompt: oad.systemPrompt || role.files?.['brain-seed.md'] || '',
251
+ };
252
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
253
+ res.end(JSON.stringify(data));
254
+ return;
255
+ }
256
+ }
257
+ catch { }
258
+ }
175
259
  data = this.getTemplateById(tplId);
176
260
  res.writeHead(data.error ? 404 : 200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
177
261
  res.end(JSON.stringify(data));
@@ -473,6 +557,64 @@ class StudioServer {
473
557
  res.end(JSON.stringify(data));
474
558
  return;
475
559
  }
560
+ // === Global config API (reads/writes ~/.opc/config.json) ===
561
+ if (route === 'config' && req.method === 'GET') {
562
+ data = loadSettingsConfig();
563
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
564
+ res.end(JSON.stringify(data));
565
+ return;
566
+ }
567
+ if (route === 'config' && req.method === 'PUT') {
568
+ const body = JSON.parse(await this.readBody(req));
569
+ const cfg = loadSettingsConfig();
570
+ Object.assign(cfg, body);
571
+ saveSettingsConfig(cfg);
572
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
573
+ res.end(JSON.stringify({ success: true, config: cfg }));
574
+ return;
575
+ }
576
+ // === Models API (real agentkits integration) ===
577
+ if (route === 'models' && req.method === 'GET') {
578
+ try {
579
+ const ak = await Promise.resolve().then(() => __importStar(require('agentkits')));
580
+ const providers = ak.listLLMProviders();
581
+ data = { providers };
582
+ }
583
+ catch (e) {
584
+ data = { providers: [], error: 'agentkits not available: ' + e.message };
585
+ }
586
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
587
+ res.end(JSON.stringify(data));
588
+ return;
589
+ }
590
+ // === Memory stats API (real deepbrain integration) ===
591
+ if (route === 'memory/stats' && req.method === 'GET') {
592
+ try {
593
+ const { Brain } = require('deepbrain');
594
+ const oad = this.loadOAD();
595
+ const dbPath = oad?.spec?.memory?.longTerm?.database || './data/brain.db';
596
+ const brain = new Brain({ database: dbPath, embedding_provider: 'ollama' });
597
+ await brain.connect();
598
+ const stats = await brain.stats();
599
+ await brain.disconnect();
600
+ data = { connected: true, ...stats };
601
+ }
602
+ catch {
603
+ data = { connected: false, pages: 0, chunks: 0, error: 'DeepBrain not installed or not configured. Install with: npm i deepbrain' };
604
+ }
605
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
606
+ res.end(JSON.stringify(data));
607
+ return;
608
+ }
609
+ if (route.match(/^memory\/[^/]+$/) && req.method === 'GET') {
610
+ const agentId = route.split('/')[1];
611
+ if (agentId !== 'stats' && agentId !== 'list' && agentId !== 'search') {
612
+ data = this.getAgentMemory(agentId);
613
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
614
+ res.end(JSON.stringify(data));
615
+ return;
616
+ }
617
+ }
476
618
  switch (route) {
477
619
  case 'modules':
478
620
  data = await this.getModulesStatus();
@@ -643,6 +785,7 @@ class StudioServer {
643
785
  return agent;
644
786
  }
645
787
  listAgents() {
788
+ // 1. Load Studio-created agents from ~/.opc/agents/*.json
646
789
  const dir = this.getAgentsDir();
647
790
  const files = (0, fs_1.readdirSync)(dir).filter(f => f.endsWith('.json'));
648
791
  const agents = files.map(f => {
@@ -652,7 +795,35 @@ class StudioServer {
652
795
  catch {
653
796
  return null;
654
797
  }
655
- }).filter(Boolean).sort((a, b) => new Date(b.updated).getTime() - new Date(a.updated).getTime());
798
+ }).filter(Boolean);
799
+ // 2. Also detect current working directory agent (oad.yaml)
800
+ const seenIds = new Set(agents.map((a) => a.id));
801
+ const oadPath = (0, path_1.join)(this.config.agentDir, 'oad.yaml');
802
+ if ((0, fs_1.existsSync)(oadPath)) {
803
+ try {
804
+ const oadRaw = (0, fs_1.readFileSync)(oadPath, 'utf-8');
805
+ const yamlMod = require('js-yaml');
806
+ const oad = yamlMod.load(oadRaw);
807
+ const name = oad?.name || oad?.metadata?.name || 'My Agent';
808
+ const id = oad?.id || name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
809
+ if (!seenIds.has(id)) {
810
+ agents.push({
811
+ id,
812
+ name,
813
+ description: oad?.description || oad?.spec?.description || '',
814
+ icon: oad?.icon || '🤖',
815
+ emoji: oad?.icon || '🤖',
816
+ status: 'running',
817
+ source: 'oad.yaml',
818
+ model: oad?.spec?.model || oad?.spec?.provider?.model || 'auto',
819
+ created: new Date().toISOString(),
820
+ updated: new Date().toISOString(),
821
+ });
822
+ }
823
+ }
824
+ catch { /* ignore parse errors */ }
825
+ }
826
+ agents.sort((a, b) => new Date(b.updated).getTime() - new Date(a.updated).getTime());
656
827
  return { agents };
657
828
  }
658
829
  getAgentById(id) {
@@ -710,8 +881,21 @@ class StudioServer {
710
881
  return { entries, timeline: entries.map((e) => ({ date: e.timestamp, summary: e.summary || e.content?.slice(0, 100) })) };
711
882
  }
712
883
  async handleAgentChat(req, res, agentId) {
713
- const body = JSON.parse(await this.readBody(req));
714
- const { messages = [] } = body;
884
+ let body;
885
+ try {
886
+ body = JSON.parse(await this.readBody(req));
887
+ }
888
+ catch {
889
+ res.writeHead(400, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
890
+ res.end(JSON.stringify({ error: 'Invalid JSON body' }));
891
+ return;
892
+ }
893
+ // Accept both { messages: [...] } and { message: "...", history: [...] }
894
+ let messages = body.messages || [];
895
+ if (body.message) {
896
+ // Frontend sends { message, history }
897
+ messages = [...(body.history || []), { role: 'user', content: body.message }];
898
+ }
715
899
  const agent = this.getAgentById(agentId);
716
900
  if (agent.error) {
717
901
  res.writeHead(404, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
@@ -722,8 +906,8 @@ class StudioServer {
722
906
  agent.messageCount = (agent.messageCount || 0) + 1;
723
907
  agent.lastActive = new Date().toISOString();
724
908
  agent.updated = new Date().toISOString();
725
- const filePath = (0, path_1.join)(this.getAgentsDir(), `${agentId}.json`);
726
- (0, fs_1.writeFileSync)(filePath, JSON.stringify(agent, null, 2));
909
+ const agentFilePath = (0, path_1.join)(this.getAgentsDir(), `${agentId}.json`);
910
+ (0, fs_1.writeFileSync)(agentFilePath, JSON.stringify(agent, null, 2));
727
911
  // SSE streaming response
728
912
  res.writeHead(200, {
729
913
  'Content-Type': 'text/event-stream',
@@ -731,30 +915,31 @@ class StudioServer {
731
915
  'Connection': 'keep-alive',
732
916
  'Access-Control-Allow-Origin': '*',
733
917
  });
734
- const allMsgs = [{ role: 'system', content: agent.systemPrompt }, ...messages];
735
- const lastMsg = allMsgs[allMsgs.length - 1]?.content || '';
736
918
  // Use createProvider directly to call LLM
737
919
  try {
738
920
  const { createProvider } = require('../providers');
739
- // Read OAD config for provider info
921
+ // Determine provider: agent config > OAD yaml > env > auto
740
922
  let providerName = agent.provider || process.env.OPC_LLM_PROVIDER;
741
923
  if (!providerName) {
742
- // Try reading from oad.yaml
743
924
  try {
744
- const oadPath = (0, path_1.join)(this.config.agentDir, 'oad.yaml');
745
- if ((0, fs_1.existsSync)(oadPath)) {
746
- const yaml = require('js-yaml');
747
- const oad = yaml.load((0, fs_1.readFileSync)(oadPath, 'utf-8'));
748
- providerName = oad?.spec?.provider?.default;
925
+ for (const fname of ['oad.yaml', 'agent.yaml']) {
926
+ const oadPath = (0, path_1.join)(this.config.agentDir, fname);
927
+ if ((0, fs_1.existsSync)(oadPath)) {
928
+ const yaml = require('js-yaml');
929
+ const oad = yaml.load((0, fs_1.readFileSync)(oadPath, 'utf-8'));
930
+ providerName = oad?.spec?.provider?.default;
931
+ if (providerName)
932
+ break;
933
+ }
749
934
  }
750
935
  }
751
936
  catch { }
752
937
  }
753
- providerName = providerName || 'openai';
938
+ providerName = providerName || 'auto';
754
939
  const provider = createProvider(providerName, agent.model);
755
940
  let fullText = '';
756
941
  try {
757
- for await (const chunk of provider.chatStream(allMsgs, agent.systemPrompt)) {
942
+ for await (const chunk of provider.chatStream(messages, agent.systemPrompt)) {
758
943
  const sseData = JSON.stringify({
759
944
  choices: [{ delta: { content: chunk }, index: 0 }],
760
945
  });
@@ -764,8 +949,9 @@ class StudioServer {
764
949
  }
765
950
  catch (streamErr) {
766
951
  if (!fullText) {
767
- // No content streamed yet, send error
768
- const errData = JSON.stringify({ error: streamErr.message });
952
+ const errData = JSON.stringify({
953
+ choices: [{ delta: { content: `⚠️ LLM Error: ${streamErr.message}` }, index: 0 }],
954
+ });
769
955
  res.write(`data: ${errData}\n\n`);
770
956
  }
771
957
  }
@@ -773,8 +959,13 @@ class StudioServer {
773
959
  res.end();
774
960
  }
775
961
  catch (err) {
776
- // Fallback: try simulated response
777
- this.sendSimulatedResponse(res, lastMsg, agent);
962
+ // Provider creation failed — send error as SSE so frontend can display it
963
+ const errData = JSON.stringify({
964
+ choices: [{ delta: { content: `⚠️ Provider error: ${err.message}\n\nTip: Install Claude CLI (npm i -g @anthropic-ai/claude-code) or set OPENAI_API_KEY.` }, index: 0 }],
965
+ });
966
+ res.write(`data: ${errData}\n\n`);
967
+ res.write('data: [DONE]\n\n');
968
+ res.end();
778
969
  }
779
970
  }
780
971
  sendSimulatedResponse(res, lastMsg, agent) {