opc-agent 4.1.2 → 4.1.4

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 (745) hide show
  1. package/dist/analytics/index.d.ts.map +1 -0
  2. package/dist/analytics/index.js.map +1 -0
  3. package/dist/channels/dingtalk.d.ts.map +1 -0
  4. package/dist/channels/dingtalk.js.map +1 -0
  5. package/dist/channels/discord.d.ts.map +1 -0
  6. package/dist/channels/discord.js.map +1 -0
  7. package/dist/channels/email.d.ts.map +1 -0
  8. package/dist/channels/email.js.map +1 -0
  9. package/dist/channels/feishu.d.ts.map +1 -0
  10. package/dist/channels/feishu.js.map +1 -0
  11. package/dist/channels/googlechat.d.ts.map +1 -0
  12. package/dist/channels/googlechat.js.map +1 -0
  13. package/dist/channels/imessage.d.ts.map +1 -0
  14. package/dist/channels/imessage.js.map +1 -0
  15. package/dist/channels/index.d.ts.map +1 -0
  16. package/dist/channels/index.js.map +1 -0
  17. package/dist/channels/irc.d.ts.map +1 -0
  18. package/dist/channels/irc.js.map +1 -0
  19. package/dist/channels/line.d.ts.map +1 -0
  20. package/dist/channels/line.js.map +1 -0
  21. package/dist/channels/matrix.d.ts.map +1 -0
  22. package/dist/channels/matrix.js.map +1 -0
  23. package/dist/channels/mattermost.d.ts.map +1 -0
  24. package/dist/channels/mattermost.js.map +1 -0
  25. package/dist/channels/msteams.d.ts.map +1 -0
  26. package/dist/channels/msteams.js.map +1 -0
  27. package/dist/channels/nostr.d.ts.map +1 -0
  28. package/dist/channels/nostr.js.map +1 -0
  29. package/dist/channels/qq.d.ts.map +1 -0
  30. package/dist/channels/qq.js.map +1 -0
  31. package/dist/channels/signal.d.ts.map +1 -0
  32. package/dist/channels/signal.js.map +1 -0
  33. package/dist/channels/slack.d.ts.map +1 -0
  34. package/dist/channels/slack.js.map +1 -0
  35. package/dist/channels/sms.d.ts.map +1 -0
  36. package/dist/channels/sms.js.map +1 -0
  37. package/dist/channels/telegram.d.ts.map +1 -0
  38. package/dist/channels/telegram.js.map +1 -0
  39. package/dist/channels/twitch.d.ts.map +1 -0
  40. package/dist/channels/twitch.js.map +1 -0
  41. package/dist/channels/voice-call.d.ts.map +1 -0
  42. package/dist/channels/voice-call.js.map +1 -0
  43. package/dist/channels/voice.d.ts.map +1 -0
  44. package/dist/channels/voice.js.map +1 -0
  45. package/dist/channels/web.d.ts.map +1 -0
  46. package/dist/channels/web.js.map +1 -0
  47. package/dist/channels/webhook.d.ts.map +1 -0
  48. package/dist/channels/webhook.js.map +1 -0
  49. package/dist/channels/websocket.d.ts.map +1 -0
  50. package/dist/channels/websocket.js.map +1 -0
  51. package/dist/channels/wechat.d.ts.map +1 -0
  52. package/dist/channels/wechat.js.map +1 -0
  53. package/dist/channels/whatsapp.d.ts.map +1 -0
  54. package/dist/channels/whatsapp.js.map +1 -0
  55. package/dist/cli/chat.d.ts.map +1 -0
  56. package/dist/cli/chat.js.map +1 -0
  57. package/dist/cli/setup.d.ts.map +1 -0
  58. package/dist/cli/setup.js.map +1 -0
  59. package/dist/cli.d.ts.map +1 -0
  60. package/dist/cli.js +108 -11
  61. package/dist/cli.js.map +1 -0
  62. package/dist/core/a2a.d.ts.map +1 -0
  63. package/dist/core/a2a.js.map +1 -0
  64. package/dist/core/agent.d.ts.map +1 -0
  65. package/dist/core/agent.js.map +1 -0
  66. package/dist/core/analytics-engine.d.ts.map +1 -0
  67. package/dist/core/analytics-engine.js.map +1 -0
  68. package/dist/core/api-server.d.ts.map +1 -0
  69. package/dist/core/api-server.js.map +1 -0
  70. package/dist/core/audio.d.ts.map +1 -0
  71. package/dist/core/audio.js.map +1 -0
  72. package/dist/core/auth.d.ts.map +1 -0
  73. package/dist/core/auth.js.map +1 -0
  74. package/dist/core/cache.d.ts.map +1 -0
  75. package/dist/core/cache.js.map +1 -0
  76. package/dist/core/collaboration.d.ts.map +1 -0
  77. package/dist/core/collaboration.js.map +1 -0
  78. package/dist/core/compose.d.ts.map +1 -0
  79. package/dist/core/compose.js.map +1 -0
  80. package/dist/core/config.d.ts.map +1 -0
  81. package/dist/core/config.js.map +1 -0
  82. package/dist/core/context-discovery.d.ts.map +1 -0
  83. package/dist/core/context-discovery.js.map +1 -0
  84. package/dist/core/context-refs.d.ts.map +1 -0
  85. package/dist/core/context-refs.js.map +1 -0
  86. package/dist/core/errors.d.ts.map +1 -0
  87. package/dist/core/errors.js.map +1 -0
  88. package/dist/core/gateway.d.ts.map +1 -0
  89. package/dist/core/gateway.js.map +1 -0
  90. package/dist/core/heartbeat.d.ts.map +1 -0
  91. package/dist/core/heartbeat.js.map +1 -0
  92. package/dist/core/hitl.d.ts.map +1 -0
  93. package/dist/core/hitl.js.map +1 -0
  94. package/dist/core/hooks.d.ts.map +1 -0
  95. package/dist/core/hooks.js.map +1 -0
  96. package/dist/core/ide-bridge.d.ts.map +1 -0
  97. package/dist/core/ide-bridge.js.map +1 -0
  98. package/dist/core/knowledge.d.ts.map +1 -0
  99. package/dist/core/knowledge.js.map +1 -0
  100. package/dist/core/logger.d.ts.map +1 -0
  101. package/dist/core/logger.js.map +1 -0
  102. package/dist/core/model-recommender.d.ts +40 -0
  103. package/dist/core/model-recommender.d.ts.map +1 -0
  104. package/dist/core/model-recommender.js +186 -0
  105. package/dist/core/model-recommender.js.map +1 -0
  106. package/dist/core/node-network.d.ts.map +1 -0
  107. package/dist/core/node-network.js.map +1 -0
  108. package/dist/core/orchestrator.d.ts.map +1 -0
  109. package/dist/core/orchestrator.js.map +1 -0
  110. package/dist/core/performance.d.ts.map +1 -0
  111. package/dist/core/performance.js.map +1 -0
  112. package/dist/core/profiles.d.ts.map +1 -0
  113. package/dist/core/profiles.js.map +1 -0
  114. package/dist/core/rate-limiter.d.ts.map +1 -0
  115. package/dist/core/rate-limiter.js.map +1 -0
  116. package/dist/core/room.d.ts.map +1 -0
  117. package/dist/core/room.js.map +1 -0
  118. package/dist/core/runtime.d.ts.map +1 -0
  119. package/dist/core/runtime.js.map +1 -0
  120. package/dist/core/sandbox.d.ts.map +1 -0
  121. package/dist/core/sandbox.js.map +1 -0
  122. package/dist/core/scheduler.d.ts.map +1 -0
  123. package/dist/core/scheduler.js.map +1 -0
  124. package/dist/core/security.d.ts.map +1 -0
  125. package/dist/core/security.js.map +1 -0
  126. package/dist/core/session-manager.d.ts.map +1 -0
  127. package/dist/core/session-manager.js.map +1 -0
  128. package/dist/core/streaming.d.ts.map +1 -0
  129. package/dist/core/streaming.js.map +1 -0
  130. package/dist/core/subagent.d.ts.map +1 -0
  131. package/dist/core/subagent.js.map +1 -0
  132. package/dist/core/types.d.ts.map +1 -0
  133. package/dist/core/types.js.map +1 -0
  134. package/dist/core/versioning.d.ts.map +1 -0
  135. package/dist/core/versioning.js.map +1 -0
  136. package/dist/core/vision.d.ts.map +1 -0
  137. package/dist/core/vision.js.map +1 -0
  138. package/dist/core/watch.d.ts.map +1 -0
  139. package/dist/core/watch.js.map +1 -0
  140. package/dist/core/workflow-graph.d.ts.map +1 -0
  141. package/dist/core/workflow-graph.js.map +1 -0
  142. package/dist/core/workflow.d.ts.map +1 -0
  143. package/dist/core/workflow.js.map +1 -0
  144. package/dist/daemon.d.ts.map +1 -0
  145. package/dist/daemon.js.map +1 -0
  146. package/dist/deploy/hermes.d.ts.map +1 -0
  147. package/dist/deploy/hermes.js.map +1 -0
  148. package/dist/deploy/index.d.ts.map +1 -0
  149. package/dist/deploy/index.js.map +1 -0
  150. package/dist/deploy/openclaw.d.ts.map +1 -0
  151. package/dist/deploy/openclaw.js.map +1 -0
  152. package/dist/doctor.d.ts.map +1 -0
  153. package/dist/doctor.js.map +1 -0
  154. package/dist/eval/index.d.ts.map +1 -0
  155. package/dist/eval/index.js.map +1 -0
  156. package/dist/hub/brain-seed.d.ts.map +1 -0
  157. package/dist/hub/brain-seed.js.map +1 -0
  158. package/dist/hub/client.d.ts.map +1 -0
  159. package/dist/hub/client.js.map +1 -0
  160. package/dist/i18n/index.d.ts.map +1 -0
  161. package/dist/i18n/index.js.map +1 -0
  162. package/dist/index.d.ts +2 -0
  163. package/dist/index.d.ts.map +1 -0
  164. package/dist/index.js +11 -5
  165. package/dist/index.js.map +1 -0
  166. package/dist/mcp/servers/calculator-mcp.d.ts.map +1 -0
  167. package/dist/mcp/servers/calculator-mcp.js.map +1 -0
  168. package/dist/mcp/servers/crypto-mcp.d.ts.map +1 -0
  169. package/dist/mcp/servers/crypto-mcp.js.map +1 -0
  170. package/dist/mcp/servers/database-mcp.d.ts.map +1 -0
  171. package/dist/mcp/servers/database-mcp.js.map +1 -0
  172. package/dist/mcp/servers/datetime-mcp.d.ts.map +1 -0
  173. package/dist/mcp/servers/datetime-mcp.js.map +1 -0
  174. package/dist/mcp/servers/filesystem.d.ts.map +1 -0
  175. package/dist/mcp/servers/filesystem.js.map +1 -0
  176. package/dist/mcp/servers/github-mcp.d.ts.map +1 -0
  177. package/dist/mcp/servers/github-mcp.js.map +1 -0
  178. package/dist/mcp/servers/index.d.ts.map +1 -0
  179. package/dist/mcp/servers/index.js.map +1 -0
  180. package/dist/mcp/servers/json-mcp.d.ts.map +1 -0
  181. package/dist/mcp/servers/json-mcp.js.map +1 -0
  182. package/dist/mcp/servers/memory-mcp.d.ts.map +1 -0
  183. package/dist/mcp/servers/memory-mcp.js.map +1 -0
  184. package/dist/mcp/servers/regex-mcp.d.ts.map +1 -0
  185. package/dist/mcp/servers/regex-mcp.js.map +1 -0
  186. package/dist/mcp/servers/web-mcp.d.ts.map +1 -0
  187. package/dist/mcp/servers/web-mcp.js.map +1 -0
  188. package/dist/memory/context-compressor.d.ts.map +1 -0
  189. package/dist/memory/context-compressor.js.map +1 -0
  190. package/dist/memory/deepbrain.d.ts.map +1 -0
  191. package/dist/memory/deepbrain.js.map +1 -0
  192. package/dist/memory/index.d.ts.map +1 -0
  193. package/dist/memory/index.js.map +1 -0
  194. package/dist/memory/seed-loader.d.ts.map +1 -0
  195. package/dist/memory/seed-loader.js.map +1 -0
  196. package/dist/memory/user-profiler.d.ts.map +1 -0
  197. package/dist/memory/user-profiler.js.map +1 -0
  198. package/dist/plugins/content-filter.d.ts.map +1 -0
  199. package/dist/plugins/content-filter.js.map +1 -0
  200. package/dist/plugins/index.d.ts.map +1 -0
  201. package/dist/plugins/index.js.map +1 -0
  202. package/dist/plugins/logger.d.ts.map +1 -0
  203. package/dist/plugins/logger.js.map +1 -0
  204. package/dist/plugins/rate-limiter.d.ts.map +1 -0
  205. package/dist/plugins/rate-limiter.js.map +1 -0
  206. package/dist/protocols/a2a/client.d.ts.map +1 -0
  207. package/dist/protocols/a2a/client.js.map +1 -0
  208. package/dist/protocols/a2a/index.d.ts.map +1 -0
  209. package/dist/protocols/a2a/index.js.map +1 -0
  210. package/dist/protocols/a2a/server.d.ts.map +1 -0
  211. package/dist/protocols/a2a/server.js.map +1 -0
  212. package/dist/protocols/a2a/types.d.ts.map +1 -0
  213. package/dist/protocols/a2a/types.js.map +1 -0
  214. package/dist/protocols/a2a/utils.d.ts.map +1 -0
  215. package/dist/protocols/a2a/utils.js.map +1 -0
  216. package/dist/protocols/agui/client.d.ts.map +1 -0
  217. package/dist/protocols/agui/client.js.map +1 -0
  218. package/dist/protocols/agui/index.d.ts.map +1 -0
  219. package/dist/protocols/agui/index.js.map +1 -0
  220. package/dist/protocols/agui/server.d.ts.map +1 -0
  221. package/dist/protocols/agui/server.js.map +1 -0
  222. package/dist/protocols/agui/types.d.ts.map +1 -0
  223. package/dist/protocols/agui/types.js.map +1 -0
  224. package/dist/protocols/index.d.ts.map +1 -0
  225. package/dist/protocols/index.js.map +1 -0
  226. package/dist/protocols/mcp/agent-tools.d.ts.map +1 -0
  227. package/dist/protocols/mcp/agent-tools.js.map +1 -0
  228. package/dist/protocols/mcp/index.d.ts.map +1 -0
  229. package/dist/protocols/mcp/index.js.map +1 -0
  230. package/dist/protocols/mcp/server.d.ts.map +1 -0
  231. package/dist/protocols/mcp/server.js.map +1 -0
  232. package/dist/protocols/mcp/types.d.ts.map +1 -0
  233. package/dist/protocols/mcp/types.js.map +1 -0
  234. package/dist/providers/index.d.ts.map +1 -0
  235. package/dist/providers/index.js.map +1 -0
  236. package/dist/publish/index.d.ts.map +1 -0
  237. package/dist/publish/index.js.map +1 -0
  238. package/dist/scheduler/cron-engine.d.ts.map +1 -0
  239. package/dist/scheduler/cron-engine.js.map +1 -0
  240. package/dist/scheduler/index.d.ts.map +1 -0
  241. package/dist/scheduler/index.js.map +1 -0
  242. package/dist/schema/oad.d.ts.map +1 -0
  243. package/dist/schema/oad.js.map +1 -0
  244. package/dist/security/approval.d.ts.map +1 -0
  245. package/dist/security/approval.js.map +1 -0
  246. package/dist/security/approvals.d.ts.map +1 -0
  247. package/dist/security/approvals.js.map +1 -0
  248. package/dist/security/elevated.d.ts.map +1 -0
  249. package/dist/security/elevated.js.map +1 -0
  250. package/dist/security/guardrails.d.ts.map +1 -0
  251. package/dist/security/guardrails.js.map +1 -0
  252. package/dist/security/index.d.ts.map +1 -0
  253. package/dist/security/index.js.map +1 -0
  254. package/dist/security/keys.d.ts.map +1 -0
  255. package/dist/security/keys.js.map +1 -0
  256. package/dist/security/secrets.d.ts.map +1 -0
  257. package/dist/security/secrets.js.map +1 -0
  258. package/dist/skills/auto-learn.d.ts.map +1 -0
  259. package/dist/skills/auto-learn.js.map +1 -0
  260. package/dist/skills/base.d.ts.map +1 -0
  261. package/dist/skills/base.js.map +1 -0
  262. package/dist/skills/builtin/index.d.ts.map +1 -0
  263. package/dist/skills/builtin/index.js.map +1 -0
  264. package/dist/skills/document.d.ts.map +1 -0
  265. package/dist/skills/document.js.map +1 -0
  266. package/dist/skills/http.d.ts.map +1 -0
  267. package/dist/skills/http.js.map +1 -0
  268. package/dist/skills/index.d.ts.map +1 -0
  269. package/dist/skills/index.js.map +1 -0
  270. package/dist/skills/marketplace.d.ts.map +1 -0
  271. package/dist/skills/marketplace.js.map +1 -0
  272. package/dist/skills/scheduler.d.ts.map +1 -0
  273. package/dist/skills/scheduler.js.map +1 -0
  274. package/dist/skills/types.d.ts.map +1 -0
  275. package/dist/skills/types.js.map +1 -0
  276. package/dist/skills/webhook-trigger.d.ts.map +1 -0
  277. package/dist/skills/webhook-trigger.js.map +1 -0
  278. package/dist/studio/server.d.ts.map +1 -0
  279. package/dist/studio/server.js.map +1 -0
  280. package/dist/studio/templates-data.d.ts.map +1 -0
  281. package/dist/studio/templates-data.js.map +1 -0
  282. package/dist/telemetry/index.d.ts.map +1 -0
  283. package/dist/telemetry/index.js.map +1 -0
  284. package/dist/templates/code-reviewer.d.ts.map +1 -0
  285. package/dist/templates/code-reviewer.js.map +1 -0
  286. package/dist/templates/content-writer.d.ts.map +1 -0
  287. package/dist/templates/content-writer.js.map +1 -0
  288. package/dist/templates/customer-service.d.ts.map +1 -0
  289. package/dist/templates/customer-service.js.map +1 -0
  290. package/dist/templates/data-analyst.d.ts.map +1 -0
  291. package/dist/templates/data-analyst.js.map +1 -0
  292. package/dist/templates/executive-assistant.d.ts.map +1 -0
  293. package/dist/templates/executive-assistant.js.map +1 -0
  294. package/dist/templates/financial-advisor.d.ts.map +1 -0
  295. package/dist/templates/financial-advisor.js.map +1 -0
  296. package/dist/templates/hr-recruiter.d.ts.map +1 -0
  297. package/dist/templates/hr-recruiter.js.map +1 -0
  298. package/dist/templates/knowledge-base.d.ts.map +1 -0
  299. package/dist/templates/knowledge-base.js.map +1 -0
  300. package/dist/templates/legal-assistant.d.ts.map +1 -0
  301. package/dist/templates/legal-assistant.js.map +1 -0
  302. package/dist/templates/project-manager.d.ts.map +1 -0
  303. package/dist/templates/project-manager.js.map +1 -0
  304. package/dist/templates/sales-assistant.d.ts.map +1 -0
  305. package/dist/templates/sales-assistant.js.map +1 -0
  306. package/dist/templates/teacher.d.ts.map +1 -0
  307. package/dist/templates/teacher.js.map +1 -0
  308. package/dist/testing/index.d.ts.map +1 -0
  309. package/dist/testing/index.js.map +1 -0
  310. package/dist/tools/builtin/browser.d.ts.map +1 -0
  311. package/dist/tools/builtin/browser.js.map +1 -0
  312. package/dist/tools/builtin/datetime.d.ts.map +1 -0
  313. package/dist/tools/builtin/datetime.js.map +1 -0
  314. package/dist/tools/builtin/file.d.ts.map +1 -0
  315. package/dist/tools/builtin/file.js.map +1 -0
  316. package/dist/tools/builtin/home-assistant.d.ts.map +1 -0
  317. package/dist/tools/builtin/home-assistant.js.map +1 -0
  318. package/dist/tools/builtin/index.d.ts.map +1 -0
  319. package/dist/tools/builtin/index.js.map +1 -0
  320. package/dist/tools/builtin/rl-tools.d.ts.map +1 -0
  321. package/dist/tools/builtin/rl-tools.js.map +1 -0
  322. package/dist/tools/builtin/shell.d.ts.map +1 -0
  323. package/dist/tools/builtin/shell.js.map +1 -0
  324. package/dist/tools/builtin/vision.d.ts.map +1 -0
  325. package/dist/tools/builtin/vision.js.map +1 -0
  326. package/dist/tools/builtin/web-search.d.ts.map +1 -0
  327. package/dist/tools/builtin/web-search.js.map +1 -0
  328. package/dist/tools/builtin/web.d.ts.map +1 -0
  329. package/dist/tools/builtin/web.js.map +1 -0
  330. package/dist/tools/calculator.d.ts.map +1 -0
  331. package/dist/tools/calculator.js.map +1 -0
  332. package/dist/tools/datetime.d.ts.map +1 -0
  333. package/dist/tools/datetime.js.map +1 -0
  334. package/dist/tools/document-processor.d.ts.map +1 -0
  335. package/dist/tools/document-processor.js.map +1 -0
  336. package/dist/tools/gateway.d.ts.map +1 -0
  337. package/dist/tools/gateway.js.map +1 -0
  338. package/dist/tools/image-generator.d.ts.map +1 -0
  339. package/dist/tools/image-generator.js.map +1 -0
  340. package/dist/tools/integrations/calendar.d.ts.map +1 -0
  341. package/dist/tools/integrations/calendar.js.map +1 -0
  342. package/dist/tools/integrations/code-exec.d.ts.map +1 -0
  343. package/dist/tools/integrations/code-exec.js.map +1 -0
  344. package/dist/tools/integrations/csv-analyzer.d.ts.map +1 -0
  345. package/dist/tools/integrations/csv-analyzer.js.map +1 -0
  346. package/dist/tools/integrations/database.d.ts.map +1 -0
  347. package/dist/tools/integrations/database.js.map +1 -0
  348. package/dist/tools/integrations/email-send.d.ts.map +1 -0
  349. package/dist/tools/integrations/email-send.js.map +1 -0
  350. package/dist/tools/integrations/git-tool.d.ts.map +1 -0
  351. package/dist/tools/integrations/git-tool.js.map +1 -0
  352. package/dist/tools/integrations/github-tool.d.ts.map +1 -0
  353. package/dist/tools/integrations/github-tool.js.map +1 -0
  354. package/dist/tools/integrations/image-gen.d.ts.map +1 -0
  355. package/dist/tools/integrations/image-gen.js.map +1 -0
  356. package/dist/tools/integrations/index.d.ts.map +1 -0
  357. package/dist/tools/integrations/index.js.map +1 -0
  358. package/dist/tools/integrations/jira.d.ts.map +1 -0
  359. package/dist/tools/integrations/jira.js.map +1 -0
  360. package/dist/tools/integrations/notion.d.ts.map +1 -0
  361. package/dist/tools/integrations/notion.js.map +1 -0
  362. package/dist/tools/integrations/npm-tool.d.ts.map +1 -0
  363. package/dist/tools/integrations/npm-tool.js.map +1 -0
  364. package/dist/tools/integrations/pdf-reader.d.ts.map +1 -0
  365. package/dist/tools/integrations/pdf-reader.js.map +1 -0
  366. package/dist/tools/integrations/slack.d.ts.map +1 -0
  367. package/dist/tools/integrations/slack.js.map +1 -0
  368. package/dist/tools/integrations/summarizer.d.ts.map +1 -0
  369. package/dist/tools/integrations/summarizer.js.map +1 -0
  370. package/dist/tools/integrations/translator.d.ts.map +1 -0
  371. package/dist/tools/integrations/translator.js.map +1 -0
  372. package/dist/tools/integrations/trello.d.ts.map +1 -0
  373. package/dist/tools/integrations/trello.js.map +1 -0
  374. package/dist/tools/integrations/vector-search.d.ts.map +1 -0
  375. package/dist/tools/integrations/vector-search.js.map +1 -0
  376. package/dist/tools/integrations/web-scraper.d.ts.map +1 -0
  377. package/dist/tools/integrations/web-scraper.js.map +1 -0
  378. package/dist/tools/integrations/web-search.d.ts.map +1 -0
  379. package/dist/tools/integrations/web-search.js.map +1 -0
  380. package/dist/tools/integrations/webhook.d.ts.map +1 -0
  381. package/dist/tools/integrations/webhook.js.map +1 -0
  382. package/dist/tools/json-transform.d.ts.map +1 -0
  383. package/dist/tools/json-transform.js.map +1 -0
  384. package/dist/tools/mcp-client.d.ts.map +1 -0
  385. package/dist/tools/mcp-client.js.map +1 -0
  386. package/dist/tools/mcp.d.ts.map +1 -0
  387. package/dist/tools/mcp.js.map +1 -0
  388. package/dist/tools/text-analysis.d.ts.map +1 -0
  389. package/dist/tools/text-analysis.js.map +1 -0
  390. package/dist/tools/web-scraper.d.ts.map +1 -0
  391. package/dist/tools/web-scraper.js.map +1 -0
  392. package/dist/tools/web-search.d.ts.map +1 -0
  393. package/dist/tools/web-search.js.map +1 -0
  394. package/dist/traces/index.d.ts.map +1 -0
  395. package/dist/traces/index.js.map +1 -0
  396. package/dist/ui/components.d.ts.map +1 -0
  397. package/dist/ui/components.js.map +1 -0
  398. package/models.json +164 -0
  399. package/package.json +1 -1
  400. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -20
  401. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
  402. package/.github/PULL_REQUEST_TEMPLATE.md +0 -13
  403. package/.github/workflows/ci.yml +0 -24
  404. package/USABILITY-ISSUES.md +0 -73
  405. package/docs/.vitepress/config.ts +0 -103
  406. package/docs/api/cli.md +0 -48
  407. package/docs/api/oad-schema.md +0 -64
  408. package/docs/api/sdk.md +0 -80
  409. package/docs/guide/concepts.md +0 -51
  410. package/docs/guide/configuration.md +0 -79
  411. package/docs/guide/deployment.md +0 -42
  412. package/docs/guide/getting-started.md +0 -44
  413. package/docs/guide/templates.md +0 -28
  414. package/docs/guide/testing.md +0 -84
  415. package/docs/index.md +0 -27
  416. package/docs/zh/api/cli.md +0 -54
  417. package/docs/zh/api/oad-schema.md +0 -87
  418. package/docs/zh/api/sdk.md +0 -102
  419. package/docs/zh/guide/concepts.md +0 -104
  420. package/docs/zh/guide/configuration.md +0 -135
  421. package/docs/zh/guide/deployment.md +0 -81
  422. package/docs/zh/guide/getting-started.md +0 -82
  423. package/docs/zh/guide/templates.md +0 -84
  424. package/docs/zh/guide/testing.md +0 -88
  425. package/docs/zh/index.md +0 -27
  426. package/fix-sidebar.mjs +0 -188
  427. package/serve-studio.js +0 -13
  428. package/serve-test.js +0 -25
  429. package/src/analytics/index.ts +0 -66
  430. package/src/channels/dingtalk.ts +0 -46
  431. package/src/channels/discord.ts +0 -192
  432. package/src/channels/email.ts +0 -351
  433. package/src/channels/feishu.ts +0 -349
  434. package/src/channels/googlechat.ts +0 -42
  435. package/src/channels/imessage.ts +0 -32
  436. package/src/channels/index.ts +0 -15
  437. package/src/channels/irc.ts +0 -82
  438. package/src/channels/line.ts +0 -33
  439. package/src/channels/matrix.ts +0 -34
  440. package/src/channels/mattermost.ts +0 -57
  441. package/src/channels/msteams.ts +0 -33
  442. package/src/channels/nostr.ts +0 -33
  443. package/src/channels/qq.ts +0 -34
  444. package/src/channels/signal.ts +0 -33
  445. package/src/channels/slack.ts +0 -217
  446. package/src/channels/sms.ts +0 -34
  447. package/src/channels/telegram.ts +0 -616
  448. package/src/channels/twitch.ts +0 -65
  449. package/src/channels/voice-call.ts +0 -100
  450. package/src/channels/voice.ts +0 -471
  451. package/src/channels/web.ts +0 -638
  452. package/src/channels/webhook.ts +0 -199
  453. package/src/channels/websocket.ts +0 -399
  454. package/src/channels/wechat.ts +0 -329
  455. package/src/channels/whatsapp.ts +0 -33
  456. package/src/cli/chat.ts +0 -99
  457. package/src/cli/setup.ts +0 -314
  458. package/src/cli.ts +0 -2826
  459. package/src/core/a2a.ts +0 -203
  460. package/src/core/agent.ts +0 -476
  461. package/src/core/analytics-engine.ts +0 -186
  462. package/src/core/api-server.ts +0 -277
  463. package/src/core/audio.ts +0 -98
  464. package/src/core/auth.ts +0 -57
  465. package/src/core/cache.ts +0 -141
  466. package/src/core/collaboration.ts +0 -275
  467. package/src/core/compose.ts +0 -77
  468. package/src/core/config.ts +0 -14
  469. package/src/core/context-discovery.ts +0 -85
  470. package/src/core/context-refs.ts +0 -140
  471. package/src/core/errors.ts +0 -148
  472. package/src/core/gateway.ts +0 -106
  473. package/src/core/heartbeat.ts +0 -51
  474. package/src/core/hitl.ts +0 -138
  475. package/src/core/hooks.ts +0 -105
  476. package/src/core/ide-bridge.ts +0 -133
  477. package/src/core/knowledge.ts +0 -255
  478. package/src/core/logger.ts +0 -57
  479. package/src/core/node-network.ts +0 -86
  480. package/src/core/orchestrator.ts +0 -215
  481. package/src/core/performance.ts +0 -187
  482. package/src/core/profiles.ts +0 -122
  483. package/src/core/rate-limiter.ts +0 -128
  484. package/src/core/room.ts +0 -109
  485. package/src/core/runtime.ts +0 -435
  486. package/src/core/sandbox.ts +0 -344
  487. package/src/core/scheduler.ts +0 -187
  488. package/src/core/security.ts +0 -171
  489. package/src/core/session-manager.ts +0 -137
  490. package/src/core/streaming.ts +0 -195
  491. package/src/core/subagent.ts +0 -98
  492. package/src/core/types.ts +0 -68
  493. package/src/core/versioning.ts +0 -106
  494. package/src/core/vision.ts +0 -180
  495. package/src/core/watch.ts +0 -178
  496. package/src/core/workflow-graph.ts +0 -365
  497. package/src/core/workflow.ts +0 -235
  498. package/src/daemon.ts +0 -96
  499. package/src/deploy/hermes.ts +0 -156
  500. package/src/deploy/index.ts +0 -255
  501. package/src/deploy/openclaw.ts +0 -190
  502. package/src/doctor.ts +0 -243
  503. package/src/eval/index.ts +0 -211
  504. package/src/eval/suites/basic.json +0 -16
  505. package/src/eval/suites/memory.json +0 -12
  506. package/src/eval/suites/safety.json +0 -14
  507. package/src/hub/brain-seed.ts +0 -54
  508. package/src/hub/client.ts +0 -60
  509. package/src/i18n/index.ts +0 -216
  510. package/src/index.ts +0 -283
  511. package/src/mcp/servers/calculator-mcp.ts +0 -65
  512. package/src/mcp/servers/crypto-mcp.ts +0 -73
  513. package/src/mcp/servers/database-mcp.ts +0 -72
  514. package/src/mcp/servers/datetime-mcp.ts +0 -69
  515. package/src/mcp/servers/filesystem.ts +0 -66
  516. package/src/mcp/servers/github-mcp.ts +0 -58
  517. package/src/mcp/servers/index.ts +0 -63
  518. package/src/mcp/servers/json-mcp.ts +0 -102
  519. package/src/mcp/servers/memory-mcp.ts +0 -56
  520. package/src/mcp/servers/regex-mcp.ts +0 -53
  521. package/src/mcp/servers/web-mcp.ts +0 -49
  522. package/src/memory/context-compressor.ts +0 -189
  523. package/src/memory/deepbrain.ts +0 -202
  524. package/src/memory/index.ts +0 -41
  525. package/src/memory/seed-loader.ts +0 -212
  526. package/src/memory/user-profiler.ts +0 -215
  527. package/src/plugins/content-filter.ts +0 -23
  528. package/src/plugins/index.ts +0 -339
  529. package/src/plugins/logger.ts +0 -18
  530. package/src/plugins/rate-limiter.ts +0 -38
  531. package/src/protocols/a2a/client.ts +0 -132
  532. package/src/protocols/a2a/index.ts +0 -8
  533. package/src/protocols/a2a/server.ts +0 -333
  534. package/src/protocols/a2a/types.ts +0 -88
  535. package/src/protocols/a2a/utils.ts +0 -50
  536. package/src/protocols/agui/client.ts +0 -83
  537. package/src/protocols/agui/index.ts +0 -4
  538. package/src/protocols/agui/server.ts +0 -218
  539. package/src/protocols/agui/types.ts +0 -153
  540. package/src/protocols/index.ts +0 -2
  541. package/src/protocols/mcp/agent-tools.ts +0 -134
  542. package/src/protocols/mcp/index.ts +0 -8
  543. package/src/protocols/mcp/server.ts +0 -262
  544. package/src/protocols/mcp/types.ts +0 -69
  545. package/src/providers/index.ts +0 -632
  546. package/src/publish/index.ts +0 -376
  547. package/src/scheduler/cron-engine.ts +0 -191
  548. package/src/scheduler/index.ts +0 -2
  549. package/src/schema/oad.ts +0 -217
  550. package/src/security/approval.ts +0 -131
  551. package/src/security/approvals.ts +0 -143
  552. package/src/security/elevated.ts +0 -105
  553. package/src/security/guardrails.ts +0 -248
  554. package/src/security/index.ts +0 -9
  555. package/src/security/keys.ts +0 -87
  556. package/src/security/secrets.ts +0 -129
  557. package/src/skills/auto-learn.ts +0 -262
  558. package/src/skills/base.ts +0 -16
  559. package/src/skills/builtin/index.ts +0 -408
  560. package/src/skills/document.ts +0 -100
  561. package/src/skills/http.ts +0 -35
  562. package/src/skills/index.ts +0 -27
  563. package/src/skills/marketplace.ts +0 -113
  564. package/src/skills/scheduler.ts +0 -80
  565. package/src/skills/types.ts +0 -42
  566. package/src/skills/webhook-trigger.ts +0 -59
  567. package/src/studio/server.ts +0 -1791
  568. package/src/studio/templates-data.ts +0 -178
  569. package/src/studio-ui/index.html +0 -3076
  570. package/src/telemetry/index.ts +0 -324
  571. package/src/templates/code-reviewer.ts +0 -30
  572. package/src/templates/content-writer.ts +0 -58
  573. package/src/templates/customer-service.ts +0 -76
  574. package/src/templates/data-analyst.ts +0 -66
  575. package/src/templates/executive-assistant.ts +0 -71
  576. package/src/templates/financial-advisor.ts +0 -60
  577. package/src/templates/hr-recruiter.ts +0 -58
  578. package/src/templates/knowledge-base.ts +0 -27
  579. package/src/templates/legal-assistant.ts +0 -71
  580. package/src/templates/project-manager.ts +0 -58
  581. package/src/templates/sales-assistant.ts +0 -75
  582. package/src/templates/teacher.ts +0 -75
  583. package/src/testing/index.ts +0 -181
  584. package/src/tools/builtin/browser.ts +0 -299
  585. package/src/tools/builtin/datetime.ts +0 -41
  586. package/src/tools/builtin/file.ts +0 -107
  587. package/src/tools/builtin/home-assistant.ts +0 -116
  588. package/src/tools/builtin/index.ts +0 -37
  589. package/src/tools/builtin/rl-tools.ts +0 -243
  590. package/src/tools/builtin/shell.ts +0 -43
  591. package/src/tools/builtin/vision.ts +0 -64
  592. package/src/tools/builtin/web-search.ts +0 -126
  593. package/src/tools/builtin/web.ts +0 -35
  594. package/src/tools/calculator.ts +0 -73
  595. package/src/tools/datetime.ts +0 -149
  596. package/src/tools/document-processor.ts +0 -213
  597. package/src/tools/gateway.ts +0 -220
  598. package/src/tools/image-generator.ts +0 -150
  599. package/src/tools/integrations/calendar.ts +0 -73
  600. package/src/tools/integrations/code-exec.ts +0 -39
  601. package/src/tools/integrations/csv-analyzer.ts +0 -92
  602. package/src/tools/integrations/database.ts +0 -44
  603. package/src/tools/integrations/email-send.ts +0 -76
  604. package/src/tools/integrations/git-tool.ts +0 -42
  605. package/src/tools/integrations/github-tool.ts +0 -76
  606. package/src/tools/integrations/image-gen.ts +0 -56
  607. package/src/tools/integrations/index.ts +0 -92
  608. package/src/tools/integrations/jira.ts +0 -83
  609. package/src/tools/integrations/notion.ts +0 -71
  610. package/src/tools/integrations/npm-tool.ts +0 -48
  611. package/src/tools/integrations/pdf-reader.ts +0 -58
  612. package/src/tools/integrations/slack.ts +0 -65
  613. package/src/tools/integrations/summarizer.ts +0 -49
  614. package/src/tools/integrations/translator.ts +0 -48
  615. package/src/tools/integrations/trello.ts +0 -60
  616. package/src/tools/integrations/vector-search.ts +0 -42
  617. package/src/tools/integrations/web-scraper.ts +0 -47
  618. package/src/tools/integrations/web-search.ts +0 -58
  619. package/src/tools/integrations/webhook.ts +0 -38
  620. package/src/tools/json-transform.ts +0 -187
  621. package/src/tools/mcp-client.ts +0 -131
  622. package/src/tools/mcp.ts +0 -76
  623. package/src/tools/text-analysis.ts +0 -116
  624. package/src/tools/web-scraper.ts +0 -179
  625. package/src/tools/web-search.ts +0 -180
  626. package/src/traces/index.ts +0 -132
  627. package/src/types/agent-workstation.d.ts +0 -2
  628. package/src/ui/components.ts +0 -127
  629. package/srv-err.txt +0 -0
  630. package/srv-out.txt +0 -1
  631. package/test-agent/Dockerfile +0 -9
  632. package/test-agent/README.md +0 -50
  633. package/test-agent/agent.yaml +0 -23
  634. package/test-agent/docker-compose.yml +0 -11
  635. package/test-agent/oad.yaml +0 -31
  636. package/test-agent/package-lock.json +0 -1492
  637. package/test-agent/package.json +0 -18
  638. package/test-agent/src/index.ts +0 -24
  639. package/test-agent/src/skills/echo.ts +0 -15
  640. package/test-agent/tsconfig.json +0 -25
  641. package/test-full.js +0 -43
  642. package/test-sidebar.js +0 -22
  643. package/test-studio3.js +0 -75
  644. package/test-studio4.js +0 -41
  645. package/tests/a2a-protocol.test.ts +0 -285
  646. package/tests/a2a.test.ts +0 -66
  647. package/tests/agent.test.ts +0 -72
  648. package/tests/agui-protocol.test.ts +0 -246
  649. package/tests/analytics.test.ts +0 -50
  650. package/tests/api-server.test.ts +0 -148
  651. package/tests/approvals.test.ts +0 -89
  652. package/tests/audio.test.ts +0 -40
  653. package/tests/auto-learn.test.ts +0 -105
  654. package/tests/brain-seed-extended.test.ts +0 -490
  655. package/tests/brain-seed.test.ts +0 -239
  656. package/tests/browser.test.ts +0 -179
  657. package/tests/builtin-tools.test.ts +0 -83
  658. package/tests/channel.test.ts +0 -39
  659. package/tests/channels/discord.test.ts +0 -79
  660. package/tests/channels/email.test.ts +0 -148
  661. package/tests/channels/feishu.test.ts +0 -123
  662. package/tests/channels/telegram.test.ts +0 -129
  663. package/tests/channels/websocket.test.ts +0 -53
  664. package/tests/channels/wechat.test.ts +0 -170
  665. package/tests/channels-extra.test.ts +0 -45
  666. package/tests/chat-cli.test.ts +0 -160
  667. package/tests/cli.test.ts +0 -46
  668. package/tests/collaboration.test.ts +0 -319
  669. package/tests/context-compressor.test.ts +0 -172
  670. package/tests/context-refs.test.ts +0 -121
  671. package/tests/cron-engine.test.ts +0 -101
  672. package/tests/daemon.test.ts +0 -135
  673. package/tests/deepbrain-wire.test.ts +0 -234
  674. package/tests/deploy-and-dag.test.ts +0 -196
  675. package/tests/doctor.test.ts +0 -38
  676. package/tests/document-processor.test.ts +0 -69
  677. package/tests/e2e-nocode.test.ts +0 -442
  678. package/tests/e2e.test.ts +0 -134
  679. package/tests/elevated.test.ts +0 -69
  680. package/tests/errors.test.ts +0 -83
  681. package/tests/eval.test.ts +0 -173
  682. package/tests/gateway.test.ts +0 -63
  683. package/tests/guardrails.test.ts +0 -177
  684. package/tests/hitl.test.ts +0 -71
  685. package/tests/home-assistant.test.ts +0 -40
  686. package/tests/hooks.test.ts +0 -79
  687. package/tests/i18n.test.ts +0 -41
  688. package/tests/ide-bridge.test.ts +0 -38
  689. package/tests/image-generator.test.ts +0 -84
  690. package/tests/init-role.test.ts +0 -124
  691. package/tests/integrations.test.ts +0 -249
  692. package/tests/mcp-client.test.ts +0 -92
  693. package/tests/mcp-server.test.ts +0 -178
  694. package/tests/mcp-servers.test.ts +0 -260
  695. package/tests/mcp.test.ts +0 -54
  696. package/tests/node-network.test.ts +0 -74
  697. package/tests/oad.test.ts +0 -68
  698. package/tests/performance.test.ts +0 -115
  699. package/tests/plugin-a2a-enhanced.test.ts +0 -230
  700. package/tests/plugin.test.ts +0 -74
  701. package/tests/profiles.test.ts +0 -61
  702. package/tests/publish.test.ts +0 -231
  703. package/tests/rl-tools.test.ts +0 -93
  704. package/tests/room.test.ts +0 -106
  705. package/tests/runtime.test.ts +0 -42
  706. package/tests/sandbox-manager.test.ts +0 -46
  707. package/tests/sandbox.test.ts +0 -46
  708. package/tests/scheduler.test.ts +0 -200
  709. package/tests/secrets.test.ts +0 -107
  710. package/tests/security-enhanced.test.ts +0 -233
  711. package/tests/security.test.ts +0 -60
  712. package/tests/settings-api.test.ts +0 -148
  713. package/tests/setup.test.ts +0 -73
  714. package/tests/skill-learner.test.ts +0 -161
  715. package/tests/streaming.test.ts +0 -109
  716. package/tests/studio.test.ts +0 -402
  717. package/tests/subagent.test.ts +0 -193
  718. package/tests/telegram-discord.test.ts +0 -60
  719. package/tests/telemetry.test.ts +0 -186
  720. package/tests/templates.test.ts +0 -77
  721. package/tests/tools/builtin-extended.test.ts +0 -138
  722. package/tests/user-profiler.test.ts +0 -169
  723. package/tests/v070.test.ts +0 -76
  724. package/tests/v090-features.test.ts +0 -254
  725. package/tests/versioning.test.ts +0 -75
  726. package/tests/vision.test.ts +0 -61
  727. package/tests/voice-call.test.ts +0 -47
  728. package/tests/voice-enhanced.test.ts +0 -169
  729. package/tests/voice-interaction.test.ts +0 -38
  730. package/tests/voice.test.ts +0 -61
  731. package/tests/web-search.test.ts +0 -155
  732. package/tests/webhook.test.ts +0 -29
  733. package/tests/workflow-graph.test.ts +0 -279
  734. package/tests/workflow.test.ts +0 -143
  735. package/tmp-js-test.js +0 -1532
  736. package/tmp-sc.js +0 -1716
  737. package/tutorial/customer-service-agent/README.md +0 -612
  738. package/tutorial/customer-service-agent/SOUL.md +0 -26
  739. package/tutorial/customer-service-agent/agent.yaml +0 -63
  740. package/tutorial/customer-service-agent/package.json +0 -19
  741. package/tutorial/customer-service-agent/src/index.ts +0 -69
  742. package/tutorial/customer-service-agent/src/skills/faq.ts +0 -27
  743. package/tutorial/customer-service-agent/src/skills/ticket.ts +0 -22
  744. package/tutorial/customer-service-agent/tsconfig.json +0 -14
  745. package/vitest.config.ts +0 -9
@@ -1,1791 +0,0 @@
1
- import { createServer, IncomingMessage, ServerResponse, request as httpRequest } from 'http';
2
- import { readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from 'fs';
3
- import { join, extname } from 'path';
4
- import * as os from 'os';
5
- import * as net from 'net';
6
- import { Tracer } from '../telemetry';
7
- import { TEMPLATES, INDUSTRIES, AgentTemplate } from './templates-data';
8
- import { SkillMarketplace } from '../skills/marketplace';
9
- import { CronEngine } from '../scheduler/cron-engine';
10
- import { ImageGenerator } from '../tools/image-generator';
11
- import { DocumentProcessor, ProcessedDocument } from '../tools/document-processor';
12
-
13
- export interface WorkflowNode {
14
- id: string;
15
- type: 'agent' | 'tool' | 'condition' | 'loop' | 'parallel' | 'input' | 'output';
16
- name: string;
17
- x: number;
18
- y: number;
19
- config: Record<string, any>;
20
- }
21
-
22
- export interface WorkflowEdge {
23
- id: string;
24
- from: string;
25
- to: string;
26
- fromPort: string;
27
- toPort: string;
28
- }
29
-
30
- export interface WorkflowDefinition {
31
- id: string;
32
- name: string;
33
- nodes: WorkflowNode[];
34
- edges: WorkflowEdge[];
35
- created: string;
36
- updated: string;
37
- }
38
-
39
- interface StudioConfig {
40
- port: number;
41
- agentDir: string;
42
- staticDir: string;
43
- }
44
-
45
- interface ModuleInfo {
46
- name: string;
47
- path: string;
48
- port: number;
49
- icon: string;
50
- }
51
-
52
- const MODULE_REGISTRY: ModuleInfo[] = [
53
- { name: 'DeepBrain', path: 'brain', port: 4001, icon: '🧠' },
54
- { name: 'AgentKits', path: 'kits', port: 4002, icon: '📊' },
55
- { name: 'Workstation', path: 'workstation', port: 4003, icon: '👤' },
56
- ];
57
-
58
- // Settings config helpers
59
- function getSettingsConfigPath(): string {
60
- const dir = join(os.homedir(), '.opc');
61
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
62
- return join(dir, 'config.json');
63
- }
64
-
65
- function loadSettingsConfig(): any {
66
- const p = getSettingsConfigPath();
67
- if (existsSync(p)) {
68
- try { return JSON.parse(readFileSync(p, 'utf-8')); } catch { return {}; }
69
- }
70
- return {};
71
- }
72
-
73
- function saveSettingsConfig(config: any): void {
74
- writeFileSync(getSettingsConfigPath(), JSON.stringify(config, null, 2));
75
- }
76
-
77
- class StudioServer {
78
- private server: any;
79
- private config: StudioConfig;
80
- private tracer?: Tracer;
81
- private skillMarketplace: SkillMarketplace;
82
- private cronEngine: CronEngine;
83
- private imageGenerator: ImageGenerator;
84
-
85
- constructor(config: Partial<StudioConfig> = {}) {
86
- this.config = {
87
- port: config.port || 4000,
88
- agentDir: config.agentDir || process.cwd(),
89
- staticDir: config.staticDir || join(__dirname, '../studio-ui'),
90
- };
91
- this.cronEngine = new CronEngine();
92
- this.imageGenerator = new ImageGenerator();
93
- this.skillMarketplace = new SkillMarketplace();
94
- }
95
-
96
- setTracer(tracer: Tracer): void {
97
- this.tracer = tracer;
98
- }
99
-
100
- getTracer(): Tracer | undefined {
101
- return this.tracer;
102
- }
103
-
104
- getConfig(): StudioConfig {
105
- return { ...this.config };
106
- }
107
-
108
- async start(): Promise<void> {
109
- const opcDir = join(os.homedir(), '.opc');
110
- if (!existsSync(opcDir)) mkdirSync(opcDir, { recursive: true });
111
- const cfgPath = join(opcDir, 'config.json');
112
- if (!existsSync(cfgPath)) writeFileSync(cfgPath, JSON.stringify({}, null, 2));
113
-
114
- this.server = createServer((req, res) => this.handleRequest(req, res));
115
- this.server.listen(this.config.port, '0.0.0.0');
116
- this.cronEngine.start();
117
- console.log(`🎨 OPC Studio: http://localhost:${this.config.port}`);
118
- }
119
-
120
- async stop(): Promise<void> {
121
- this.cronEngine.stop();
122
- return new Promise((resolve) => {
123
- if (this.server) {
124
- this.server.close(() => resolve());
125
- } else {
126
- resolve();
127
- }
128
- });
129
- }
130
-
131
- async handleRequest(req: IncomingMessage, res: ServerResponse) {
132
- const url = new URL(req.url || '/', `http://localhost`);
133
-
134
- // Handle CORS preflight
135
- if (req.method === 'OPTIONS') {
136
- res.writeHead(204, {
137
- 'Access-Control-Allow-Origin': '*',
138
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
139
- 'Access-Control-Allow-Headers': 'Content-Type',
140
- });
141
- res.end();
142
- return;
143
- }
144
-
145
- // API routes
146
- if (url.pathname.startsWith('/api/')) {
147
- return this.handleAPI(req, res, url);
148
- }
149
-
150
- // Module proxy routes
151
- for (const mod of MODULE_REGISTRY) {
152
- if (url.pathname.startsWith(`/${mod.path}/`) || url.pathname === `/${mod.path}`) {
153
- return this.proxyToModule(req, res, mod, url);
154
- }
155
- }
156
-
157
- // Static files
158
- return this.serveStatic(req, res, url);
159
- }
160
-
161
- private async handleAPI(req: IncomingMessage, res: ServerResponse, url: URL) {
162
- const route = url.pathname.replace('/api/', '');
163
-
164
- try {
165
- let data: any;
166
-
167
- // Dynamic agent routes
168
- if (route === 'agents' && req.method === 'POST') {
169
- data = await this.createAgent(req);
170
- res.writeHead(201, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
171
- res.end(JSON.stringify(data));
172
- return;
173
- }
174
- if (route === 'agents' && req.method === 'GET') {
175
- data = this.listAgents();
176
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
177
- res.end(JSON.stringify(data));
178
- return;
179
- }
180
- if (route === 'templates' && req.method === 'GET') {
181
- const industry = url.searchParams.get('industry') || '';
182
- const search = url.searchParams.get('q') || '';
183
- data = this.getTemplates(industry, search);
184
- // Merge with real workstation templates
185
- try {
186
- const ws = require('agent-workstation');
187
- const categories = ws.getCategories();
188
- const wsTemplates: any[] = [];
189
- for (const cat of categories) {
190
- for (const roleName of cat.roles) {
191
- const role = ws.getRole(cat.name, roleName);
192
- if (!role) continue;
193
- let oad: any = {};
194
- try {
195
- if (role.files?.['oad.yaml']) {
196
- const yaml = require('js-yaml');
197
- oad = yaml.load(role.files['oad.yaml']) || {};
198
- }
199
- } catch {}
200
- const tpl = {
201
- id: `ws-${cat.name}-${roleName}`,
202
- name: oad.name || roleName.replace(/-/g, ' ').replace(/\b\w/g, (c: string) => c.toUpperCase()),
203
- nameZh: oad.nameZh || '',
204
- icon: oad.icon || '🤖',
205
- description: oad.description || '',
206
- descriptionZh: oad.descriptionZh || '',
207
- industry: cat.name,
208
- industryZh: cat.name,
209
- tags: [cat.name, 'workstation'],
210
- suggestedModel: 'auto',
211
- systemPrompt: oad.systemPrompt || role.files?.['brain-seed.md'] || '',
212
- source: 'workstation',
213
- ego: oad.ego || null,
214
- mission: oad.mission || null,
215
- skills: oad.skills || [],
216
- };
217
- if (!search || tpl.name.toLowerCase().includes(search.toLowerCase()) || tpl.nameZh.includes(search)) {
218
- if (!industry || tpl.industry === industry) {
219
- wsTemplates.push(tpl);
220
- }
221
- }
222
- }
223
- }
224
- data.templates = [...data.templates, ...wsTemplates];
225
- // Add workstation industries to list
226
- const existingIds = new Set(data.industries.map((i: any) => i.id));
227
- for (const cat of categories) {
228
- if (!existingIds.has(cat.name)) {
229
- data.industries.push({ id: cat.name, name: cat.name, nameZh: cat.name });
230
- }
231
- }
232
- } catch (wsErr: any) {
233
- // workstation not available, use built-in templates only
234
- }
235
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
236
- res.end(JSON.stringify(data));
237
- return;
238
- }
239
- if (route.match(/^templates\/[^/]+$/) && req.method === 'GET') {
240
- const tplId = route.split('/')[1];
241
- // Check workstation first
242
- if (tplId.startsWith('ws-')) {
243
- const parts = tplId.replace('ws-', '').split('-');
244
- const catName = parts[0];
245
- const roleName = parts.slice(1).join('-');
246
- try {
247
- const ws = require('agent-workstation');
248
- const role = ws.getRole(catName, roleName);
249
- if (role) {
250
- let oad: any = {};
251
- try {
252
- if (role.files?.['oad.yaml']) {
253
- const yaml = require('js-yaml');
254
- oad = yaml.load(role.files['oad.yaml']) || {};
255
- }
256
- } catch {}
257
- data = {
258
- id: tplId, name: oad.name || roleName, source: 'workstation',
259
- category: catName, role: roleName, files: role.files,
260
- ego: oad.ego, mission: oad.mission, skills: oad.skills,
261
- systemPrompt: oad.systemPrompt || role.files?.['brain-seed.md'] || '',
262
- };
263
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
264
- res.end(JSON.stringify(data));
265
- return;
266
- }
267
- } catch {}
268
- }
269
- data = this.getTemplateById(tplId);
270
- res.writeHead(data.error ? 404 : 200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
271
- res.end(JSON.stringify(data));
272
- return;
273
- }
274
- if (route.match(/^agents\/[^/]+\/memory$/) && req.method === 'GET') {
275
- const agentId = route.split('/')[1];
276
- data = this.getAgentMemory(agentId);
277
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
278
- res.end(JSON.stringify(data));
279
- return;
280
- }
281
- if (route.match(/^agents\/[^/]+\/chat$/) && req.method === 'POST') {
282
- const agentId = route.split('/')[1];
283
- return this.handleAgentChat(req, res, agentId);
284
- }
285
- if (route.match(/^agents\/[^/]+$/) && req.method === 'GET') {
286
- const agentId = route.split('/')[1];
287
- data = this.getAgentById(agentId);
288
- res.writeHead(data.error ? 404 : 200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
289
- res.end(JSON.stringify(data));
290
- return;
291
- }
292
- if (route.match(/^agents\/[^/]+$/) && req.method === 'PUT') {
293
- const agentId = route.split('/')[1];
294
- data = await this.updateAgent(agentId, req);
295
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
296
- res.end(JSON.stringify(data));
297
- return;
298
- }
299
- if (route.match(/^agents\/[^/]+$/) && req.method === 'DELETE') {
300
- const agentId = route.split('/')[1];
301
- data = this.deleteAgent(agentId);
302
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
303
- res.end(JSON.stringify(data));
304
- return;
305
- }
306
-
307
- // --- Document upload routes ---
308
- if (route.match(/^agents\/[^/]+\/upload$/) && req.method === 'POST') {
309
- const agentId = route.split('/')[1];
310
- return this.handleDocumentUpload(req, res, agentId);
311
- }
312
- if (route.match(/^agents\/[^/]+\/documents$/) && req.method === 'GET') {
313
- const agentId = route.split('/')[1];
314
- data = this.getDocumentList(agentId);
315
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
316
- res.end(JSON.stringify(data));
317
- return;
318
- }
319
- if (route.match(/^agents\/[^/]+\/documents\/[^/]+$/) && req.method === 'DELETE') {
320
- const parts = route.split('/');
321
- const agentId = parts[1];
322
- const docId = parts[3];
323
- data = this.deleteDocument(agentId, docId);
324
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
325
- res.end(JSON.stringify(data));
326
- return;
327
- }
328
-
329
- // --- Settings API routes ---
330
- if (route === 'settings/models' && req.method === 'GET') {
331
- const cfg = loadSettingsConfig();
332
- data = cfg.models || { mode: 'local', provider: 'ollama', chatModel: 'qwen2.5:7b', embeddingModel: 'nomic-embed-text', providers: {} };
333
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
334
- res.end(JSON.stringify(data));
335
- return;
336
- }
337
- if (route === 'settings/models' && req.method === 'PUT') {
338
- const body = JSON.parse(await this.readBody(req));
339
- const cfg = loadSettingsConfig();
340
- cfg.models = { ...(cfg.models || {}), ...body };
341
- saveSettingsConfig(cfg);
342
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
343
- res.end(JSON.stringify({ success: true, models: cfg.models }));
344
- return;
345
- }
346
- if (route === 'settings/models/test' && req.method === 'POST') {
347
- const body = JSON.parse(await this.readBody(req));
348
- const { provider, apiKey, baseUrl } = body;
349
- data = await this.testModelConnection(provider, apiKey, baseUrl);
350
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
351
- res.end(JSON.stringify(data));
352
- return;
353
- }
354
- if (route === 'settings/models/local' && req.method === 'GET') {
355
- data = await this.detectLocalOllama();
356
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
357
- res.end(JSON.stringify(data));
358
- return;
359
- }
360
- if (route === 'settings/channels' && req.method === 'GET') {
361
- const cfg = loadSettingsConfig();
362
- data = cfg.channels || {};
363
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
364
- res.end(JSON.stringify(data));
365
- return;
366
- }
367
- if (route.match(/^settings\/channels\/[^/]+$/) && req.method === 'PUT') {
368
- const channelName = route.split('/')[2];
369
- const body = JSON.parse(await this.readBody(req));
370
- const cfg = loadSettingsConfig();
371
- if (!cfg.channels) cfg.channels = {};
372
- cfg.channels[channelName] = { ...(cfg.channels[channelName] || {}), ...body, updated: new Date().toISOString() };
373
- saveSettingsConfig(cfg);
374
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
375
- res.end(JSON.stringify({ success: true, channel: cfg.channels[channelName] }));
376
- return;
377
- }
378
- // Web Search settings
379
- if (route === 'settings/search' && req.method === 'GET') {
380
- const cfg = loadSettingsConfig();
381
- data = cfg.webSearch || { defaultEngine: 'duckduckgo', enabled: true, engines: { duckduckgo: { enabled: true } } };
382
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
383
- res.end(JSON.stringify(data));
384
- return;
385
- }
386
- if (route === 'settings/search' && req.method === 'PUT') {
387
- const body = JSON.parse(await this.readBody(req));
388
- const cfg = loadSettingsConfig();
389
- cfg.webSearch = { ...(cfg.webSearch || {}), ...body, updated: new Date().toISOString() };
390
- saveSettingsConfig(cfg);
391
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
392
- res.end(JSON.stringify({ success: true, config: cfg.webSearch }));
393
- return;
394
- }
395
- if (route === 'settings/search/test' && req.method === 'POST') {
396
- try {
397
- const { webSearch: doSearch } = require('../tools/web-search');
398
- const body = JSON.parse(await this.readBody(req));
399
- const query = body.query || 'test search';
400
- const cfg = loadSettingsConfig();
401
- const searchCfg = { ...(cfg.webSearch || { defaultEngine: 'duckduckgo', enabled: true, engines: { duckduckgo: { enabled: true } } }), ...body.config };
402
- const results = await doSearch(query, searchCfg, { maxResults: 3 });
403
- data = { success: true, results, engine: searchCfg.defaultEngine };
404
- } catch (e: any) {
405
- data = { success: false, error: e.message };
406
- }
407
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
408
- res.end(JSON.stringify(data));
409
- return;
410
- }
411
- if (route === 'settings/status' && req.method === 'GET') {
412
- data = await this.getSettingsStatus();
413
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
414
- res.end(JSON.stringify(data));
415
- return;
416
- }
417
- if (route === 'settings/status/start' && req.method === 'POST') {
418
- data = { success: true, status: 'running', message: 'Agent started' };
419
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
420
- res.end(JSON.stringify(data));
421
- return;
422
- }
423
- if (route === 'settings/status/stop' && req.method === 'POST') {
424
- data = { success: true, status: 'stopped', message: 'Agent stopped' };
425
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
426
- res.end(JSON.stringify(data));
427
- return;
428
- }
429
- if (route === 'settings/usage' && req.method === 'GET') {
430
- data = await this.getUsageStats();
431
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
432
- res.end(JSON.stringify(data));
433
- return;
434
- }
435
-
436
- // Dynamic workflow routes (parameterized)
437
- if (route.match(/^workflows\/[^/]+\/run$/) && req.method === 'POST') {
438
- const wfId = route.split('/')[1];
439
- data = await this.runWorkflow(wfId);
440
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
441
- res.end(JSON.stringify(data));
442
- return;
443
- }
444
- if (route.match(/^workflows\/[^/]+$/) && req.method === 'GET') {
445
- const wfId = route.split('/')[1];
446
- data = this.getWorkflowById(wfId);
447
- res.writeHead(data.error ? 404 : 200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
448
- res.end(JSON.stringify(data));
449
- return;
450
- }
451
- if (route.match(/^workflows\/[^/]+$/) && req.method === 'DELETE') {
452
- const wfId = route.split('/')[1];
453
- data = this.deleteWorkflow(wfId);
454
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
455
- res.end(JSON.stringify(data));
456
- return;
457
- }
458
-
459
- // --- Schedules API ---
460
- if (route === 'schedules' && req.method === 'GET') {
461
- data = this.cronEngine.listTasks();
462
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
463
- res.end(JSON.stringify(data));
464
- return;
465
- }
466
- if (route === 'schedules' && req.method === 'POST') {
467
- const body = JSON.parse(await this.readBody(req));
468
- data = this.cronEngine.createTask(body);
469
- res.writeHead(201, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
470
- res.end(JSON.stringify(data));
471
- return;
472
- }
473
- if (route.match(/^schedules\/[^/]+$/) && req.method === 'PUT') {
474
- const id = route.split('/')[1];
475
- const body = JSON.parse(await this.readBody(req));
476
- data = this.cronEngine.updateTask(id, body);
477
- res.writeHead(data ? 200 : 404, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
478
- res.end(JSON.stringify(data || { error: 'Schedule not found' }));
479
- return;
480
- }
481
- if (route.match(/^schedules\/[^/]+$/) && req.method === 'DELETE') {
482
- const id = route.split('/')[1];
483
- const success = this.cronEngine.deleteTask(id);
484
- res.writeHead(success ? 200 : 404, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
485
- res.end(JSON.stringify({ success }));
486
- return;
487
- }
488
- if (route.match(/^schedules\/[^/]+\/run$/) && req.method === 'POST') {
489
- const id = route.split('/')[1];
490
- const success = await this.cronEngine.runTask(id);
491
- res.writeHead(success ? 200 : 404, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
492
- res.end(JSON.stringify({ success }));
493
- return;
494
- }
495
-
496
- // --- Image Generation API ---
497
- if (route === 'image-gen/status' && req.method === 'GET') {
498
- data = this.imageGenerator.getStatus();
499
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
500
- res.end(JSON.stringify(data));
501
- return;
502
- }
503
- if (route === 'image-gen/generate' && req.method === 'POST') {
504
- const body = JSON.parse(await this.readBody(req));
505
- data = await this.imageGenerator.generate(body.prompt, body);
506
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
507
- res.end(JSON.stringify(data));
508
- return;
509
- }
510
- if (route === 'image-gen/config' && req.method === 'PUT') {
511
- const body = JSON.parse(await this.readBody(req));
512
- const cfg = loadSettingsConfig();
513
- cfg.imageGen = { ...(cfg.imageGen || {}), ...body };
514
- saveSettingsConfig(cfg);
515
- this.imageGenerator = new ImageGenerator({
516
- openaiApiKey: body.openaiApiKey,
517
- replicateApiKey: body.replicateApiKey,
518
- sdApiUrl: body.sdApiUrl,
519
- });
520
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
521
- res.end(JSON.stringify({ success: true }));
522
- return;
523
- }
524
-
525
- if (route === 'first-run/status' && req.method === 'GET') {
526
- data = await this.getFirstRunStatus();
527
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
528
- res.end(JSON.stringify(data));
529
- return;
530
- }
531
- if (route === 'first-run/complete' && req.method === 'POST') {
532
- const body = JSON.parse(await this.readBody(req));
533
- data = await this.completeFirstRun(body);
534
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
535
- res.end(JSON.stringify(data));
536
- return;
537
- }
538
-
539
- // === Skill Marketplace API ===
540
- if (route === 'skills/marketplace' && req.method === 'GET') {
541
- const category = url.searchParams.get('category') || undefined;
542
- const search = url.searchParams.get('q') || undefined;
543
- data = this.skillMarketplace.listAll(category, search);
544
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
545
- res.end(JSON.stringify(data));
546
- return;
547
- }
548
- if (route === 'skills/installed' && req.method === 'GET') {
549
- data = this.skillMarketplace.getInstalled();
550
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
551
- res.end(JSON.stringify(data));
552
- return;
553
- }
554
- if (route.match(/^skills\/marketplace\/[^/]+$/) && req.method === 'GET') {
555
- const skillId = route.split('/')[2];
556
- data = this.skillMarketplace.getSkill(skillId);
557
- res.writeHead(data ? 200 : 404, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
558
- res.end(JSON.stringify(data || { error: 'Skill not found' }));
559
- return;
560
- }
561
- if (route.match(/^skills\/marketplace\/[^/]+\/install$/) && req.method === 'POST') {
562
- const skillId = route.split('/')[2];
563
- data = this.skillMarketplace.install(skillId);
564
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
565
- res.end(JSON.stringify(data));
566
- return;
567
- }
568
- if (route.match(/^skills\/marketplace\/[^/]+\/uninstall$/) && req.method === 'DELETE') {
569
- const skillId = route.split('/')[2];
570
- data = this.skillMarketplace.uninstall(skillId);
571
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
572
- res.end(JSON.stringify(data));
573
- return;
574
- }
575
-
576
- // === Global config API (reads/writes ~/.opc/config.json) ===
577
- if (route === 'config' && req.method === 'GET') {
578
- data = loadSettingsConfig();
579
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
580
- res.end(JSON.stringify(data));
581
- return;
582
- }
583
- if (route === 'config' && req.method === 'PUT') {
584
- const body = JSON.parse(await this.readBody(req));
585
- const cfg = loadSettingsConfig();
586
- Object.assign(cfg, body);
587
- saveSettingsConfig(cfg);
588
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
589
- res.end(JSON.stringify({ success: true, config: cfg }));
590
- return;
591
- }
592
-
593
- // === Models API (real agentkits integration) ===
594
- if (route === 'models' && req.method === 'GET') {
595
- try {
596
- const ak = await import('agentkits');
597
- const providers = ak.listLLMProviders();
598
- data = { providers };
599
- } catch (e: any) {
600
- data = { providers: [], error: 'agentkits not available: ' + e.message };
601
- }
602
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
603
- res.end(JSON.stringify(data));
604
- return;
605
- }
606
-
607
- // === Memory stats API (real deepbrain integration) ===
608
- if (route === 'memory/stats' && req.method === 'GET') {
609
- try {
610
- const { Brain } = require('deepbrain');
611
- const oad = this.loadOAD();
612
- const dbPath = oad?.spec?.memory?.longTerm?.database || './data/brain.db';
613
- const brain = new Brain({ database: dbPath, embedding_provider: 'ollama' });
614
- await brain.connect();
615
- const stats = await brain.stats();
616
- await brain.disconnect();
617
- data = { connected: true, ...stats };
618
- } catch {
619
- data = { connected: false, pages: 0, chunks: 0, error: 'DeepBrain not installed or not configured. Install with: npm i deepbrain' };
620
- }
621
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
622
- res.end(JSON.stringify(data));
623
- return;
624
- }
625
- if (route.match(/^memory\/[^/]+$/) && req.method === 'GET') {
626
- const agentId = route.split('/')[1];
627
- if (agentId !== 'stats' && agentId !== 'list' && agentId !== 'search') {
628
- data = this.getAgentMemory(agentId);
629
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
630
- res.end(JSON.stringify(data));
631
- return;
632
- }
633
- }
634
-
635
- switch (route) {
636
- case 'modules':
637
- data = await this.getModulesStatus();
638
- break;
639
- case 'agent/info':
640
- data = await this.getAgentInfo();
641
- break;
642
- case 'agent/config':
643
- if (req.method === 'GET') data = await this.getAgentConfig();
644
- else if (req.method === 'PUT') data = await this.saveConfig(req);
645
- break;
646
- case 'agent/chat':
647
- data = await this.handleChat(req);
648
- break;
649
- case 'memory/list':
650
- data = await this.getMemoryList();
651
- break;
652
- case 'memory/search':
653
- data = await this.searchMemory(url.searchParams.get('q') || '');
654
- break;
655
- case 'memory/stats':
656
- data = await this.getMemoryStats();
657
- break;
658
- case 'skills/list':
659
- data = await this.getSkills();
660
- break;
661
- case 'tools/list':
662
- data = await this.getTools();
663
- break;
664
- case 'workflows/list':
665
- data = this.listWorkflows();
666
- break;
667
- case 'workflows':
668
- if (req.method === 'POST') data = await this.saveWorkflow(req);
669
- else if (req.method === 'GET') data = this.listWorkflows();
670
- else { res.writeHead(405); res.end(); return; }
671
- break;
672
- case 'jobs/list':
673
- data = await this.getJobs();
674
- break;
675
- case 'logs/recent':
676
- data = await this.getRecentLogs();
677
- break;
678
- case 'analytics/overview':
679
- data = await this.getAnalytics();
680
- break;
681
- case 'doctor/check':
682
- data = await this.runDoctor();
683
- break;
684
- case 'channels/list':
685
- data = await this.getChannels();
686
- break;
687
- case 'plugins/list':
688
- data = await this.getPlugins();
689
- break;
690
- case 'security/approvals':
691
- data = await this.getPendingApprovals();
692
- break;
693
- case 'eval/suites':
694
- data = await this.getEvalSuites();
695
- break;
696
- case 'eval/run':
697
- if (req.method === 'POST') data = await this.runEvalSuite(req);
698
- else { res.writeHead(405); res.end(); return; }
699
- break;
700
- case 'a2a/card':
701
- data = this.getA2ACard();
702
- break;
703
- case 'a2a/tasks':
704
- data = this.getA2ATasks();
705
- break;
706
- case 'a2a/discover':
707
- if (req.method === 'POST') data = await this.discoverA2AAgent(req);
708
- else { res.writeHead(405); res.end(); return; }
709
- break;
710
- case 'protocols':
711
- data = await this.getProtocols();
712
- break;
713
- case 'protocols/mcp':
714
- data = this.getMCPServerStatus();
715
- break;
716
- case 'eval/reports':
717
- data = await this.getEvalReports();
718
- break;
719
- case 'telemetry/stats':
720
- data = this.tracer ? this.tracer.getStats() : { error: 'Telemetry not enabled' };
721
- break;
722
- case 'telemetry/traces':
723
- data = this.getTelemetryTraces(url);
724
- break;
725
- case 'telemetry/metrics':
726
- data = this.tracer ? this.tracer.getMetrics() : [];
727
- break;
728
- case 'playground/chat':
729
- if (req.method === 'POST') {
730
- return this.handlePlaygroundChat(req, res);
731
- }
732
- res.writeHead(405); res.end(); return;
733
- case 'playground/models':
734
- data = { models: ['gpt-4o', 'gpt-4o-mini', 'claude-sonnet-4', 'claude-haiku', 'gemini-2.0-flash', 'deepseek-v3'] };
735
- break;
736
- default:
737
- res.writeHead(404, { 'Content-Type': 'application/json' });
738
- res.end(JSON.stringify({ error: 'Not found' }));
739
- return;
740
- }
741
-
742
- res.writeHead(200, {
743
- 'Content-Type': 'application/json',
744
- 'Access-Control-Allow-Origin': '*',
745
- });
746
- res.end(JSON.stringify(data));
747
- } catch (e: any) {
748
- res.writeHead(500, { 'Content-Type': 'application/json' });
749
- res.end(JSON.stringify({ error: e.message }));
750
- }
751
- }
752
-
753
- // --- Agent CRUD & Templates ---
754
-
755
- private getAgentsDir(): string {
756
- const dir = join(os.homedir(), '.opc', 'agents');
757
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
758
- return dir;
759
- }
760
-
761
- private async createAgent(req: IncomingMessage): Promise<any> {
762
- const body = await this.readBody(req);
763
- const { name, templateId, description, model, language } = JSON.parse(body);
764
- const template = TEMPLATES.find(t => t.id === templateId);
765
- const id = `agent-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
766
- const agent = {
767
- id,
768
- name: name || template?.name || 'My Agent',
769
- templateId: templateId || null,
770
- templateName: template?.name || 'Custom',
771
- templateIcon: template?.icon || '🤖',
772
- description: description || template?.description || '',
773
- model: model || template?.suggestedModel || 'gpt-4o-mini',
774
- language: language || 'en',
775
- systemPrompt: template?.systemPrompt || 'You are a helpful assistant.',
776
- industry: template?.industry || 'general',
777
- created: new Date().toISOString(),
778
- updated: new Date().toISOString(),
779
- messageCount: 0,
780
- lastActive: new Date().toISOString(),
781
- };
782
- const filePath = join(this.getAgentsDir(), `${id}.json`);
783
- writeFileSync(filePath, JSON.stringify(agent, null, 2));
784
- return agent;
785
- }
786
-
787
- private listAgents(): { agents: any[] } {
788
- // 1. Load Studio-created agents from ~/.opc/agents/*.json
789
- const dir = this.getAgentsDir();
790
- const files = readdirSync(dir).filter(f => f.endsWith('.json'));
791
- const agents = files.map(f => {
792
- try { return JSON.parse(readFileSync(join(dir, f), 'utf-8')); } catch { return null; }
793
- }).filter(Boolean);
794
-
795
- // 2. Also detect current working directory agent (oad.yaml)
796
- const seenIds = new Set(agents.map((a: any) => a.id));
797
- const oadPath = join(this.config.agentDir, 'oad.yaml');
798
- if (existsSync(oadPath)) {
799
- try {
800
- const oadRaw = readFileSync(oadPath, 'utf-8');
801
- const yamlMod = require('js-yaml');
802
- const oad = yamlMod.load(oadRaw) as any;
803
- const name = oad?.name || oad?.metadata?.name || 'My Agent';
804
- const id = oad?.id || name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
805
- if (!seenIds.has(id)) {
806
- agents.push({
807
- id,
808
- name,
809
- description: oad?.description || oad?.spec?.description || '',
810
- icon: oad?.icon || '🤖',
811
- emoji: oad?.icon || '🤖',
812
- status: 'running',
813
- source: 'oad.yaml',
814
- model: oad?.spec?.model || oad?.spec?.provider?.model || 'auto',
815
- created: new Date().toISOString(),
816
- updated: new Date().toISOString(),
817
- });
818
- }
819
- } catch { /* ignore parse errors */ }
820
- }
821
-
822
- agents.sort((a: any, b: any) => new Date(b.updated).getTime() - new Date(a.updated).getTime());
823
- return { agents };
824
- }
825
-
826
- private getAgentById(id: string): any {
827
- const filePath = join(this.getAgentsDir(), `${id}.json`);
828
- if (!existsSync(filePath)) return { error: 'Agent not found' };
829
- return JSON.parse(readFileSync(filePath, 'utf-8'));
830
- }
831
-
832
- private async updateAgent(id: string, req: IncomingMessage): Promise<any> {
833
- const filePath = join(this.getAgentsDir(), `${id}.json`);
834
- if (!existsSync(filePath)) return { error: 'Agent not found' };
835
- const existing = JSON.parse(readFileSync(filePath, 'utf-8'));
836
- const body = await this.readBody(req);
837
- const updates = JSON.parse(body);
838
- const updated = { ...existing, ...updates, id, updated: new Date().toISOString() };
839
- writeFileSync(filePath, JSON.stringify(updated, null, 2));
840
- return updated;
841
- }
842
-
843
- private deleteAgent(id: string): { success: boolean } {
844
- const filePath = join(this.getAgentsDir(), `${id}.json`);
845
- if (existsSync(filePath)) unlinkSync(filePath);
846
- return { success: true };
847
- }
848
-
849
- private getTemplates(industry: string, search: string): { templates: AgentTemplate[]; industries: typeof INDUSTRIES } {
850
- let filtered = TEMPLATES;
851
- if (industry) filtered = filtered.filter(t => t.industry === industry);
852
- if (search) {
853
- const q = search.toLowerCase();
854
- filtered = filtered.filter(t =>
855
- t.name.toLowerCase().includes(q) || t.nameZh.includes(q) ||
856
- t.description.toLowerCase().includes(q) || t.descriptionZh.includes(q) ||
857
- t.tags.some(tag => tag.includes(q))
858
- );
859
- }
860
- return { templates: filtered, industries: INDUSTRIES };
861
- }
862
-
863
- private getTemplateById(id: string): AgentTemplate | { error: string } {
864
- const tpl = TEMPLATES.find(t => t.id === id);
865
- return tpl || { error: 'Template not found' };
866
- }
867
-
868
- private getAgentMemory(agentId: string): any {
869
- const memDir = join(this.getAgentsDir(), agentId + '-memory');
870
- if (!existsSync(memDir)) return { entries: [], timeline: [] };
871
- const files = readdirSync(memDir).filter(f => f.endsWith('.json'));
872
- const entries = files.map(f => {
873
- try { return JSON.parse(readFileSync(join(memDir, f), 'utf-8')); } catch { return null; }
874
- }).filter(Boolean).sort((a: any, b: any) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
875
- return { entries, timeline: entries.map((e: any) => ({ date: e.timestamp, summary: e.summary || e.content?.slice(0, 100) })) };
876
- }
877
-
878
- private async handleAgentChat(req: IncomingMessage, res: ServerResponse, agentId: string): Promise<void> {
879
- let body: any;
880
- try {
881
- body = JSON.parse(await this.readBody(req));
882
- } catch {
883
- res.writeHead(400, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
884
- res.end(JSON.stringify({ error: 'Invalid JSON body' }));
885
- return;
886
- }
887
-
888
- // Accept both { messages: [...] } and { message: "...", history: [...] }
889
- let messages: any[] = body.messages || [];
890
- if (body.message) {
891
- // Frontend sends { message, history }
892
- messages = [...(body.history || []), { role: 'user', content: body.message }];
893
- }
894
-
895
- const agent = this.getAgentById(agentId);
896
- if (agent.error) {
897
- res.writeHead(404, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
898
- res.end(JSON.stringify(agent));
899
- return;
900
- }
901
-
902
- // Update message count
903
- agent.messageCount = (agent.messageCount || 0) + 1;
904
- agent.lastActive = new Date().toISOString();
905
- agent.updated = new Date().toISOString();
906
- const agentFilePath = join(this.getAgentsDir(), `${agentId}.json`);
907
- writeFileSync(agentFilePath, JSON.stringify(agent, null, 2));
908
-
909
- // SSE streaming response
910
- res.writeHead(200, {
911
- 'Content-Type': 'text/event-stream',
912
- 'Cache-Control': 'no-cache',
913
- 'Connection': 'keep-alive',
914
- 'Access-Control-Allow-Origin': '*',
915
- });
916
-
917
- // Use createProvider directly to call LLM
918
- try {
919
- const { createProvider } = require('../providers');
920
- // Determine provider: agent config > OAD yaml > env > auto
921
- let providerName = agent.provider || process.env.OPC_LLM_PROVIDER;
922
- if (!providerName) {
923
- try {
924
- for (const fname of ['oad.yaml', 'agent.yaml']) {
925
- const oadPath = join(this.config.agentDir, fname);
926
- if (existsSync(oadPath)) {
927
- const yaml = require('js-yaml');
928
- const oad = yaml.load(readFileSync(oadPath, 'utf-8'));
929
- providerName = oad?.spec?.provider?.default;
930
- if (providerName) break;
931
- }
932
- }
933
- } catch {}
934
- }
935
- providerName = providerName || 'auto';
936
- const provider = createProvider(providerName, agent.model);
937
-
938
- let fullText = '';
939
- try {
940
- for await (const chunk of provider.chatStream(messages, agent.systemPrompt)) {
941
- const sseData = JSON.stringify({
942
- choices: [{ delta: { content: chunk }, index: 0 }],
943
- });
944
- res.write(`data: ${sseData}\n\n`);
945
- fullText += chunk;
946
- }
947
- } catch (streamErr: any) {
948
- if (!fullText) {
949
- const errData = JSON.stringify({
950
- choices: [{ delta: { content: `⚠️ LLM Error: ${streamErr.message}` }, index: 0 }],
951
- });
952
- res.write(`data: ${errData}\n\n`);
953
- }
954
- }
955
- res.write('data: [DONE]\n\n');
956
- res.end();
957
- } catch (err: any) {
958
- // Provider creation failed — send error as SSE so frontend can display it
959
- const errData = JSON.stringify({
960
- 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 }],
961
- });
962
- res.write(`data: ${errData}\n\n`);
963
- res.write('data: [DONE]\n\n');
964
- res.end();
965
- }
966
- }
967
-
968
- private sendSimulatedResponse(res: ServerResponse, lastMsg: string, agent: any): void {
969
- const response = `Hello! I'm ${agent.name}. You said: "${lastMsg}"\n\nI'm ready to help you. (Note: Connect a model provider for real AI responses)`;
970
- const words = response.split(' ');
971
- let i = 0;
972
- const interval = setInterval(() => {
973
- if (i >= words.length) {
974
- res.write('data: [DONE]\n\n');
975
- res.end();
976
- clearInterval(interval);
977
- return;
978
- }
979
- const chunk = (i === 0 ? '' : ' ') + words[i];
980
- res.write(`data: ${JSON.stringify({ choices: [{ delta: { content: chunk } }] })}\n\n`);
981
- i++;
982
- }, 50);
983
- }
984
-
985
- // --- Settings Implementations ---
986
-
987
- private async detectLocalOllama(): Promise<any> {
988
- return new Promise((resolve) => {
989
- const req = httpRequest({ hostname: 'localhost', port: 11434, path: '/api/tags', method: 'GET', timeout: 3000 }, (res) => {
990
- let body = '';
991
- res.on('data', (c: any) => body += c);
992
- res.on('end', () => {
993
- try {
994
- const data = JSON.parse(body);
995
- const models = (data.models || []).map((m: any) => ({
996
- name: m.name, size: m.size, modified: m.modified_at,
997
- details: m.details || {},
998
- }));
999
- resolve({ running: true, models });
1000
- } catch { resolve({ running: true, models: [] }); }
1001
- });
1002
- });
1003
- req.on('error', () => resolve({ running: false, models: [] }));
1004
- req.on('timeout', () => { req.destroy(); resolve({ running: false, models: [] }); });
1005
- req.end();
1006
- });
1007
- }
1008
-
1009
- private async testModelConnection(provider: string, apiKey: string, baseUrl?: string): Promise<any> {
1010
- const endpoints: Record<string, { url: string; path: string }> = {
1011
- openai: { url: 'api.openai.com', path: '/v1/models' },
1012
- deepseek: { url: 'api.deepseek.com', path: '/v1/models' },
1013
- anthropic: { url: 'api.anthropic.com', path: '/v1/models' },
1014
- openrouter: { url: 'openrouter.ai', path: '/api/v1/models' },
1015
- };
1016
- const ep = endpoints[provider];
1017
- if (!ep && !baseUrl) return { success: false, error: 'Unknown provider' };
1018
-
1019
- const hostname = baseUrl ? new URL(baseUrl).hostname : ep.url;
1020
- const path = baseUrl ? '/v1/models' : ep.path;
1021
- const headers: Record<string, string> = { 'Authorization': `Bearer ${apiKey}` };
1022
- if (provider === 'anthropic') {
1023
- headers['x-api-key'] = apiKey;
1024
- headers['anthropic-version'] = '2023-06-01';
1025
- delete headers['Authorization'];
1026
- }
1027
-
1028
- return new Promise((resolve) => {
1029
- const https = require('https');
1030
- const req = https.request({ hostname, path, method: 'GET', headers, timeout: 10000 }, (res: any) => {
1031
- resolve({ success: res.statusCode === 200, statusCode: res.statusCode });
1032
- });
1033
- req.on('error', (e: any) => resolve({ success: false, error: e.message }));
1034
- req.on('timeout', () => { req.destroy(); resolve({ success: false, error: 'Timeout' }); });
1035
- req.end();
1036
- });
1037
- }
1038
-
1039
- private async getSettingsStatus(): Promise<any> {
1040
- const uptime = process.uptime();
1041
- const mem = process.memoryUsage();
1042
- const modules = await this.getModulesStatus();
1043
- const logPath = join(this.config.agentDir, '.opc', 'agent.log');
1044
- let recentLogs: string[] = [];
1045
- if (existsSync(logPath)) {
1046
- const content = readFileSync(logPath, 'utf-8');
1047
- recentLogs = content.split('\n').slice(-50);
1048
- }
1049
- return {
1050
- status: 'running',
1051
- uptime,
1052
- memory: { rss: mem.rss, heapUsed: mem.heapUsed, heapTotal: mem.heapTotal },
1053
- cpu: os.loadavg(),
1054
- modules: modules.modules,
1055
- logs: recentLogs,
1056
- startedAt: new Date(Date.now() - uptime * 1000).toISOString(),
1057
- };
1058
- }
1059
-
1060
- private async getUsageStats(): Promise<any> {
1061
- const cfg = loadSettingsConfig();
1062
- const usage = cfg.usage || { totalTokens: 0, totalCost: 0, daily: [], byModel: {} };
1063
- return usage;
1064
- }
1065
-
1066
- // --- API Implementations ---
1067
-
1068
- private async getAgentInfo() {
1069
- const oad = this.loadOAD();
1070
- const pkg = this.loadPackageJson();
1071
- return {
1072
- name: oad?.metadata?.name || pkg?.name || 'unknown',
1073
- version: oad?.metadata?.version || pkg?.version || '0.0.0',
1074
- description: oad?.metadata?.description || pkg?.description || '',
1075
- model: oad?.spec?.model || 'unknown',
1076
- provider: oad?.spec?.provider?.default || 'unknown',
1077
- channels: oad?.spec?.channels?.map((c: any) => c.type) || [],
1078
- skills: oad?.spec?.skills?.map((s: any) => s.name) || [],
1079
- status: 'running',
1080
- };
1081
- }
1082
-
1083
- private async getAgentConfig() {
1084
- const yamlPath = join(this.config.agentDir, 'agent.yaml');
1085
- if (existsSync(yamlPath)) {
1086
- return { content: readFileSync(yamlPath, 'utf-8') };
1087
- }
1088
- return { content: '', error: 'agent.yaml not found' };
1089
- }
1090
-
1091
- private async saveConfig(req: IncomingMessage) {
1092
- const body = await this.readBody(req);
1093
- const { content } = JSON.parse(body);
1094
- const yamlPath = join(this.config.agentDir, 'agent.yaml');
1095
- const { writeFileSync } = require('fs');
1096
- writeFileSync(yamlPath, content, 'utf-8');
1097
- return { success: true };
1098
- }
1099
-
1100
- private async handleChat(req: IncomingMessage) {
1101
- const body = await this.readBody(req);
1102
- const { message, sessionId } = JSON.parse(body);
1103
- try {
1104
- const { BaseAgent, InMemoryStore } = require('../index');
1105
- const oad = this.loadOAD();
1106
- const agent = new BaseAgent({
1107
- name: oad?.metadata?.name || 'studio-agent',
1108
- systemPrompt: oad?.spec?.systemPrompt || 'You are a helpful assistant.',
1109
- provider: oad?.spec?.provider?.default || 'ollama',
1110
- model: oad?.spec?.model || 'qwen2.5',
1111
- memory: new InMemoryStore(),
1112
- });
1113
- await agent.init();
1114
- const response = await agent.handleMessage({
1115
- id: String(Date.now()),
1116
- content: message,
1117
- sender: 'studio-user',
1118
- channel: 'studio',
1119
- sessionId: sessionId || 'studio-session',
1120
- timestamp: new Date(),
1121
- });
1122
- return { response: response.content };
1123
- } catch (e: any) {
1124
- return { response: `Error: ${e.message}` };
1125
- }
1126
- }
1127
-
1128
- private async getMemoryList() {
1129
- try {
1130
- const { Brain } = require('deepbrain');
1131
- const oad = this.loadOAD();
1132
- const dbPath = oad?.spec?.memory?.longTerm?.database || './data/brain.db';
1133
- const brain = new Brain({ database: dbPath, embedding_provider: 'ollama' });
1134
- await brain.connect();
1135
- const pages = await brain.list({ limit: 50 });
1136
- await brain.disconnect();
1137
- return { pages };
1138
- } catch {
1139
- return { pages: [], error: 'DeepBrain not available' };
1140
- }
1141
- }
1142
-
1143
- private async searchMemory(query: string) {
1144
- try {
1145
- const { Brain } = require('deepbrain');
1146
- const oad = this.loadOAD();
1147
- const dbPath = oad?.spec?.memory?.longTerm?.database || './data/brain.db';
1148
- const brain = new Brain({ database: dbPath, embedding_provider: 'ollama' });
1149
- await brain.connect();
1150
- const results = await brain.search(query);
1151
- await brain.disconnect();
1152
- return { results };
1153
- } catch {
1154
- return { results: [], error: 'Search failed' };
1155
- }
1156
- }
1157
-
1158
- private async getMemoryStats() {
1159
- try {
1160
- const { Brain } = require('deepbrain');
1161
- const oad = this.loadOAD();
1162
- const dbPath = oad?.spec?.memory?.longTerm?.database || './data/brain.db';
1163
- const brain = new Brain({ database: dbPath, embedding_provider: 'ollama' });
1164
- await brain.connect();
1165
- const stats = await brain.stats();
1166
- await brain.disconnect();
1167
- return stats;
1168
- } catch {
1169
- return { pages: 0, chunks: 0, error: 'Stats unavailable' };
1170
- }
1171
- }
1172
-
1173
- private async getSkills() {
1174
- try {
1175
- const { SkillLearner } = require('../index');
1176
- const learner = new SkillLearner('.opc/skills');
1177
- const skills = learner.loadSkills();
1178
- return { skills };
1179
- } catch {
1180
- return { skills: [] };
1181
- }
1182
- }
1183
-
1184
- private async getTools() {
1185
- try {
1186
- const { getBuiltinTools } = require('../index');
1187
- const tools = getBuiltinTools(this.config.agentDir);
1188
- return { tools: tools.map((t: any) => ({ name: t.definition.name, description: t.definition.description })) };
1189
- } catch {
1190
- return { tools: [] };
1191
- }
1192
- }
1193
-
1194
- private getWorkflowsDir(): string {
1195
- const dir = join(this.config.agentDir, '.opc', 'workflows');
1196
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
1197
- return dir;
1198
- }
1199
-
1200
- private listWorkflows(): { workflows: WorkflowDefinition[] } {
1201
- const dir = this.getWorkflowsDir();
1202
- const files = require('fs').readdirSync(dir).filter((f: string) => f.endsWith('.json'));
1203
- const workflows = files.map((f: string) => {
1204
- try { return JSON.parse(readFileSync(join(dir, f), 'utf-8')); } catch { return null; }
1205
- }).filter(Boolean);
1206
- // Also include OAD-defined workflows
1207
- const oad = this.loadOAD();
1208
- const oadWorkflows = (oad?.spec?.workflows || []).map((w: any, i: number) => ({
1209
- id: `oad-${i}`,
1210
- name: w.name || `Workflow ${i + 1}`,
1211
- nodes: [],
1212
- edges: [],
1213
- steps: w.steps,
1214
- source: 'oad',
1215
- }));
1216
- return { workflows: [...workflows, ...oadWorkflows] };
1217
- }
1218
-
1219
- private getWorkflowById(id: string): WorkflowDefinition | { error: string } {
1220
- const filePath = join(this.getWorkflowsDir(), `${id}.json`);
1221
- if (!existsSync(filePath)) return { error: 'Workflow not found' };
1222
- return JSON.parse(readFileSync(filePath, 'utf-8'));
1223
- }
1224
-
1225
- private async saveWorkflow(req: IncomingMessage): Promise<{ success: boolean; id: string }> {
1226
- const body = await this.readBody(req);
1227
- const workflow = JSON.parse(body) as WorkflowDefinition;
1228
- if (!workflow.id) workflow.id = `wf-${Date.now()}`;
1229
- workflow.updated = new Date().toISOString();
1230
- if (!workflow.created) workflow.created = workflow.updated;
1231
- const filePath = join(this.getWorkflowsDir(), `${workflow.id}.json`);
1232
- writeFileSync(filePath, JSON.stringify(workflow, null, 2));
1233
- return { success: true, id: workflow.id };
1234
- }
1235
-
1236
- private deleteWorkflow(id: string): { success: boolean } {
1237
- const filePath = join(this.getWorkflowsDir(), `${id}.json`);
1238
- if (existsSync(filePath)) require('fs').unlinkSync(filePath);
1239
- return { success: true };
1240
- }
1241
-
1242
- private async runWorkflow(id: string): Promise<any> {
1243
- const wf = this.getWorkflowById(id);
1244
- if ('error' in wf) return wf;
1245
- // Basic topological execution simulation
1246
- const results: Record<string, any> = {};
1247
- const sorted = this.topoSort(wf.nodes, wf.edges);
1248
- for (const node of sorted) {
1249
- results[node.id] = { type: node.type, name: node.name, status: 'completed', output: `[simulated output for ${node.name}]` };
1250
- }
1251
- return { workflowId: id, status: 'completed', results };
1252
- }
1253
-
1254
- private topoSort(nodes: WorkflowNode[], edges: WorkflowEdge[]): WorkflowNode[] {
1255
- const nodeMap = new Map(nodes.map(n => [n.id, n]));
1256
- const inDegree = new Map<string, number>();
1257
- const adj = new Map<string, string[]>();
1258
- for (const n of nodes) { inDegree.set(n.id, 0); adj.set(n.id, []); }
1259
- for (const e of edges) { adj.get(e.from)?.push(e.to); inDegree.set(e.to, (inDegree.get(e.to) || 0) + 1); }
1260
- const queue = nodes.filter(n => (inDegree.get(n.id) || 0) === 0);
1261
- const result: WorkflowNode[] = [];
1262
- while (queue.length > 0) {
1263
- const node = queue.shift()!;
1264
- result.push(node);
1265
- for (const next of (adj.get(node.id) || [])) {
1266
- const d = (inDegree.get(next) || 1) - 1;
1267
- inDegree.set(next, d);
1268
- if (d === 0) queue.push(nodeMap.get(next)!);
1269
- }
1270
- }
1271
- return result;
1272
- }
1273
-
1274
- private async getJobs() {
1275
- const oad = this.loadOAD();
1276
- return { jobs: oad?.spec?.scheduler?.jobs || [] };
1277
- }
1278
-
1279
- private async getRecentLogs() {
1280
- const logPath = join(this.config.agentDir, '.opc', 'agent.log');
1281
- if (existsSync(logPath)) {
1282
- const content = readFileSync(logPath, 'utf-8');
1283
- const lines = content.split('\n').slice(-100);
1284
- return { lines };
1285
- }
1286
- return { lines: [] };
1287
- }
1288
-
1289
- private async getAnalytics() {
1290
- return {
1291
- totalMessages: 0,
1292
- totalSessions: 0,
1293
- avgResponseTime: 0,
1294
- topSkills: [],
1295
- note: 'Analytics tracking starts when agent is running via opc run/start',
1296
- };
1297
- }
1298
-
1299
- private async runDoctor() {
1300
- try {
1301
- const { runDoctor } = require('../doctor');
1302
- const results = await runDoctor();
1303
- return results;
1304
- } catch {
1305
- return { error: 'Doctor not available' };
1306
- }
1307
- }
1308
-
1309
- private async getChannels() {
1310
- const oad = this.loadOAD();
1311
- return { channels: oad?.spec?.channels || [] };
1312
- }
1313
-
1314
- private async getPlugins() {
1315
- const oad = this.loadOAD();
1316
- return { plugins: oad?.spec?.plugins || [] };
1317
- }
1318
-
1319
- private async getProtocols() {
1320
- const oad = this.loadOAD();
1321
- const protocols = (oad?.spec as any)?.protocols || {};
1322
- return {
1323
- protocols: [
1324
- { name: 'a2a', description: 'Agent-to-Agent', enabled: !!protocols.a2a?.enabled, config: protocols.a2a || {} },
1325
- { name: 'agui', description: 'AG-UI — Agent-User Interaction (SSE)', enabled: !!protocols.agui?.enabled, config: protocols.agui || {} },
1326
- { name: 'mcp', description: 'MCP Server — Expose as MCP tools', enabled: !!protocols.mcp?.enabled, config: protocols.mcp || {} },
1327
- ],
1328
- };
1329
- }
1330
-
1331
- private async getPendingApprovals() {
1332
- return { approvals: [] };
1333
- }
1334
-
1335
- private getMCPServerStatus() {
1336
- const oad = this.loadOAD();
1337
- const mcpConfig = (oad?.spec as any)?.protocols?.mcp;
1338
- const { agentToMCPTools } = require('../protocols/mcp/agent-tools');
1339
- const agentName = oad?.metadata?.name || 'opc-agent';
1340
- const tools = agentToMCPTools({ name: agentName });
1341
- return {
1342
- enabled: !!mcpConfig?.enabled,
1343
- mode: mcpConfig?.mode || 'stdio',
1344
- port: mcpConfig?.port || 3002,
1345
- tools: tools.map((t: any) => ({ name: t.name, description: t.description })),
1346
- toolCount: tools.length,
1347
- exposedTools: mcpConfig?.exposedTools || tools.map((t: any) => t.name),
1348
- };
1349
- }
1350
-
1351
- private getTelemetryTraces(url: URL) {
1352
- if (!this.tracer) return { traces: [] };
1353
- const traceId = url.searchParams.get('id');
1354
- if (traceId) {
1355
- return { spans: this.tracer.getTrace(traceId) };
1356
- }
1357
- const limit = parseInt(url.searchParams.get('limit') || '50');
1358
- const spans = this.tracer.getSpans({ limit });
1359
- // Group by traceId for trace list
1360
- const traceMap = new Map<string, { traceId: string; rootSpan: string; startTime: number; spanCount: number; status: string }>();
1361
- for (const s of spans) {
1362
- if (!traceMap.has(s.traceId)) {
1363
- traceMap.set(s.traceId, { traceId: s.traceId, rootSpan: s.name, startTime: s.startTime, spanCount: 0, status: s.status });
1364
- }
1365
- traceMap.get(s.traceId)!.spanCount++;
1366
- }
1367
- return { traces: Array.from(traceMap.values()) };
1368
- }
1369
-
1370
- private async getEvalSuites() {
1371
- const { AgentEvaluator } = require('../eval');
1372
- return { suites: AgentEvaluator.builtinSuites() };
1373
- }
1374
-
1375
- private async runEvalSuite(req: IncomingMessage): Promise<any> {
1376
- const body = await this.readBody(req);
1377
- const { suite: suiteName } = JSON.parse(body || '{}');
1378
- const { AgentEvaluator } = require('../eval');
1379
- const suite = AgentEvaluator.loadBuiltinSuite(suiteName || 'basic');
1380
- // Use a mock agent for studio eval (no real agent loaded)
1381
- const mockAgent = { chat: async (input: string) => `[mock response to: ${input}]` };
1382
- const evaluator = new AgentEvaluator(mockAgent);
1383
- const report = await evaluator.evalSuite(suite);
1384
- // Save report
1385
- const reportsDir = join(this.config.agentDir, '.eval-reports');
1386
- const reportPath = join(reportsDir, `${suiteName || 'basic'}-${Date.now()}.json`);
1387
- AgentEvaluator.saveReport(report, reportPath);
1388
- return report;
1389
- }
1390
-
1391
- private async getEvalReports() {
1392
- const reportsDir = join(this.config.agentDir, '.eval-reports');
1393
- if (!existsSync(reportsDir)) return { reports: [] };
1394
- const files = require('fs').readdirSync(reportsDir).filter((f: string) => f.endsWith('.json'));
1395
- return {
1396
- reports: files.map((f: string) => {
1397
- try {
1398
- return JSON.parse(readFileSync(join(reportsDir, f), 'utf-8'));
1399
- } catch { return null; }
1400
- }).filter(Boolean)
1401
- };
1402
- }
1403
-
1404
- // --- A2A Protocol ---
1405
-
1406
- private getA2ACard() {
1407
- try {
1408
- const { oadToAgentCard } = require('../protocols/a2a');
1409
- const yaml = require('js-yaml');
1410
- for (const name of ['agent.yaml', 'agent.yml']) {
1411
- const p = join(this.config.agentDir, name);
1412
- if (existsSync(p)) {
1413
- const oad = yaml.load(readFileSync(p, 'utf-8'));
1414
- return oadToAgentCard(oad, `http://localhost:${this.config.port}`);
1415
- }
1416
- }
1417
- return { error: 'No agent.yaml found' };
1418
- } catch { return { error: 'Failed to generate agent card' }; }
1419
- }
1420
-
1421
- private getA2ATasks() {
1422
- // In-memory tasks from A2A server if running
1423
- return { tasks: [] };
1424
- }
1425
-
1426
- private async discoverA2AAgent(req: IncomingMessage): Promise<any> {
1427
- const body = await this.readBody(req);
1428
- const { url } = JSON.parse(body || '{}');
1429
- if (!url) return { error: 'url required' };
1430
- try {
1431
- const { A2AClient } = require('../protocols/a2a');
1432
- const client = new A2AClient(url);
1433
- return await client.getAgentCard();
1434
- } catch (err: any) {
1435
- return { error: err.message };
1436
- }
1437
- }
1438
-
1439
- private async getFirstRunStatus(): Promise<any> {
1440
- const configPath = join(os.homedir(), '.opc', 'config.json');
1441
- const ollamaStatus = await this.detectLocalOllama();
1442
- if (!existsSync(configPath)) {
1443
- return { firstRun: true, ollamaDetected: ollamaStatus.running, ollamaModels: ollamaStatus.models || [] };
1444
- }
1445
- try {
1446
- const config = JSON.parse(readFileSync(configPath, 'utf-8'));
1447
- return { firstRun: !config.firstRunComplete, ollamaDetected: ollamaStatus.running, ollamaModels: ollamaStatus.models || [] };
1448
- } catch {
1449
- return { firstRun: true, ollamaDetected: ollamaStatus.running, ollamaModels: ollamaStatus.models || [] };
1450
- }
1451
- }
1452
-
1453
- private async completeFirstRun(choices: any): Promise<any> {
1454
- const cfg = loadSettingsConfig();
1455
- cfg.firstRunComplete = true;
1456
- if (choices) {
1457
- if (choices.templateId) cfg.firstRunTemplate = choices.templateId;
1458
- if (choices.model) cfg.models = { ...(cfg.models || {}), chatModel: choices.model };
1459
- }
1460
- saveSettingsConfig(cfg);
1461
- return { success: true };
1462
- }
1463
-
1464
- // --- Module Proxy & Health ---
1465
-
1466
- private proxyToModule(req: IncomingMessage, res: ServerResponse, mod: ModuleInfo, url: URL) {
1467
- const targetPath = url.pathname.slice(`/${mod.path}`.length) || '/';
1468
- const proxyReq = httpRequest(
1469
- {
1470
- hostname: 'localhost',
1471
- port: mod.port,
1472
- path: targetPath + (url.search || ''),
1473
- method: req.method,
1474
- headers: { ...req.headers, host: `localhost:${mod.port}` },
1475
- },
1476
- (proxyRes) => {
1477
- res.writeHead(proxyRes.statusCode || 502, proxyRes.headers);
1478
- proxyRes.pipe(res, { end: true });
1479
- },
1480
- );
1481
- proxyReq.on('error', () => {
1482
- res.writeHead(502, { 'Content-Type': 'text/html' });
1483
- res.end(`<html><body style="font-family:system-ui;padding:40px;color:#999;background:#1a1a2e;text-align:center"><h2>${mod.icon} ${mod.name}</h2><p>Module not running on port ${mod.port}</p></body></html>`);
1484
- });
1485
- req.pipe(proxyReq, { end: true });
1486
- }
1487
-
1488
- private checkPort(port: number): Promise<boolean> {
1489
- return new Promise((resolve) => {
1490
- const sock = new net.Socket();
1491
- sock.setTimeout(500);
1492
- sock.once('connect', () => { sock.destroy(); resolve(true); });
1493
- sock.once('error', () => { sock.destroy(); resolve(false); });
1494
- sock.once('timeout', () => { sock.destroy(); resolve(false); });
1495
- sock.connect(port, 'localhost');
1496
- });
1497
- }
1498
-
1499
- async getModulesStatus() {
1500
- const modules = await Promise.all(
1501
- MODULE_REGISTRY.map(async (mod) => ({
1502
- name: mod.name,
1503
- path: `/${mod.path}/`,
1504
- port: mod.port,
1505
- icon: mod.icon,
1506
- running: await this.checkPort(mod.port),
1507
- })),
1508
- );
1509
- return { modules };
1510
- }
1511
-
1512
- // --- Helpers ---
1513
-
1514
- private loadOAD(): any {
1515
- try {
1516
- const yamlPath = join(this.config.agentDir, 'agent.yaml');
1517
- if (!existsSync(yamlPath)) return null;
1518
- const content = readFileSync(yamlPath, 'utf-8');
1519
- try {
1520
- const { loadOAD } = require('../index');
1521
- return loadOAD(yamlPath);
1522
- } catch {
1523
- // Fallback: simple yaml parse
1524
- const yaml = require('js-yaml');
1525
- return yaml.load(content);
1526
- }
1527
- } catch {
1528
- return null;
1529
- }
1530
- }
1531
-
1532
- private loadPackageJson(): any {
1533
- try {
1534
- const pkgPath = join(this.config.agentDir, 'package.json');
1535
- if (!existsSync(pkgPath)) return null;
1536
- return JSON.parse(readFileSync(pkgPath, 'utf-8'));
1537
- } catch {
1538
- return null;
1539
- }
1540
- }
1541
-
1542
- serveStatic(req: IncomingMessage, res: ServerResponse, url: URL) {
1543
- let filePath = url.pathname === '/' ? '/index.html' : url.pathname;
1544
- const fullPath = join(this.config.staticDir, filePath);
1545
-
1546
- if (!existsSync(fullPath)) {
1547
- // SPA fallback
1548
- const indexPath = join(this.config.staticDir, 'index.html');
1549
- if (existsSync(indexPath)) {
1550
- const content = readFileSync(indexPath, 'utf-8');
1551
- res.writeHead(200, { 'Content-Type': 'text/html', 'Cache-Control': 'no-cache, no-store, must-revalidate' });
1552
- res.end(content);
1553
- return;
1554
- }
1555
- res.writeHead(404);
1556
- res.end('Not found');
1557
- return;
1558
- }
1559
-
1560
- const mimeTypes: Record<string, string> = {
1561
- '.html': 'text/html',
1562
- '.css': 'text/css',
1563
- '.js': 'application/javascript',
1564
- '.json': 'application/json',
1565
- '.png': 'image/png',
1566
- '.svg': 'image/svg+xml',
1567
- '.ico': 'image/x-icon',
1568
- };
1569
- const ext = extname(fullPath);
1570
- const contentType = mimeTypes[ext] || 'application/octet-stream';
1571
-
1572
- const content = readFileSync(fullPath);
1573
- res.writeHead(200, { 'Content-Type': contentType });
1574
- res.end(content);
1575
- }
1576
-
1577
- private async handlePlaygroundChat(req: IncomingMessage, res: ServerResponse): Promise<void> {
1578
- const body = JSON.parse(await this.readBody(req));
1579
- const { messages = [], model = 'gpt-4o', temperature = 0.7, systemPrompt } = body;
1580
-
1581
- res.writeHead(200, {
1582
- 'Content-Type': 'text/event-stream',
1583
- 'Cache-Control': 'no-cache',
1584
- 'Connection': 'keep-alive',
1585
- 'Access-Control-Allow-Origin': '*',
1586
- });
1587
-
1588
- // Simulated streaming response for playground demo
1589
- const allMsgs = systemPrompt ? [{ role: 'system', content: systemPrompt }, ...messages] : messages;
1590
- const lastMsg = allMsgs[allMsgs.length - 1]?.content || '';
1591
- const response = `This is a playground demo response to: "${lastMsg}"\n\nModel: ${model}, Temperature: ${temperature}\nMessages in context: ${allMsgs.length}`;
1592
-
1593
- const words = response.split(' ');
1594
- for (let i = 0; i < words.length; i++) {
1595
- const chunk = (i === 0 ? '' : ' ') + words[i];
1596
- res.write(`data: ${JSON.stringify({ content: chunk })}\n\n`);
1597
- }
1598
- res.write('data: [DONE]\n\n');
1599
- res.end();
1600
- }
1601
-
1602
- // --- Document upload handlers ---
1603
-
1604
- private getDocumentsDir(agentId: string): string {
1605
- const dir = join(this.getAgentsDir(), agentId + '-documents');
1606
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
1607
- return dir;
1608
- }
1609
-
1610
- private async handleDocumentUpload(req: IncomingMessage, res: ServerResponse, agentId: string): Promise<void> {
1611
- const corsHeaders = { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' };
1612
-
1613
- try {
1614
- // Parse multipart form data manually
1615
- const { buffer, filename } = await this.parseMultipart(req);
1616
-
1617
- if (!filename) {
1618
- res.writeHead(400, corsHeaders);
1619
- res.end(JSON.stringify({ error: 'No file uploaded' }));
1620
- return;
1621
- }
1622
-
1623
- if (buffer.length > 50 * 1024 * 1024) {
1624
- res.writeHead(413, corsHeaders);
1625
- res.end(JSON.stringify({ error: 'File too large (max 50MB)' }));
1626
- return;
1627
- }
1628
-
1629
- // Process document
1630
- const processor = new DocumentProcessor();
1631
- const doc = await processor.process(buffer, filename);
1632
-
1633
- // Store chunks via DeepBrain learn()
1634
- let learnedCount = 0;
1635
- try {
1636
- const { Brain } = require('deepbrain');
1637
- const oad = this.loadOAD();
1638
- const dbPath = oad?.spec?.memory?.longTerm?.database || './data/brain.db';
1639
- const brain = new Brain({ database: dbPath, embedding_provider: 'ollama' });
1640
- await brain.connect();
1641
-
1642
- for (const chunk of doc.chunks) {
1643
- const content = `[Source: ${filename}] ${chunk.title}\n\n${chunk.content}`;
1644
- if (typeof brain.store === 'function') {
1645
- await brain.store('documents', `${doc.id}-${chunk.metadata.chunkIndex}`, content, {
1646
- source: filename,
1647
- docId: doc.id,
1648
- chunkIndex: chunk.metadata.chunkIndex,
1649
- tags: ['document-upload', filename],
1650
- });
1651
- } else if (typeof brain.learn === 'function') {
1652
- await brain.learn(content, {
1653
- tags: ['document-upload', filename],
1654
- slug: `${doc.id}-${chunk.metadata.chunkIndex}`,
1655
- });
1656
- }
1657
- learnedCount++;
1658
- }
1659
-
1660
- await brain.disconnect();
1661
- } catch {
1662
- // If DeepBrain is not available, store in local memory files
1663
- const memDir = join(this.getAgentsDir(), agentId + '-memory');
1664
- if (!existsSync(memDir)) mkdirSync(memDir, { recursive: true });
1665
-
1666
- for (const chunk of doc.chunks) {
1667
- const entry = {
1668
- id: `${doc.id}-${chunk.metadata.chunkIndex}`,
1669
- content: chunk.content,
1670
- summary: `[${filename}] ${chunk.title}`,
1671
- timestamp: new Date().toISOString(),
1672
- source: filename,
1673
- docId: doc.id,
1674
- tags: ['document-upload'],
1675
- };
1676
- writeFileSync(join(memDir, `${entry.id}.json`), JSON.stringify(entry, null, 2));
1677
- learnedCount++;
1678
- }
1679
- }
1680
-
1681
- // Save document metadata
1682
- const docsDir = this.getDocumentsDir(agentId);
1683
- const docMeta = {
1684
- id: doc.id,
1685
- filename: doc.filename,
1686
- format: doc.format,
1687
- size: doc.size,
1688
- chunks: doc.chunks.length,
1689
- processedAt: doc.processedAt,
1690
- };
1691
- writeFileSync(join(docsDir, `${doc.id}.json`), JSON.stringify(docMeta, null, 2));
1692
-
1693
- res.writeHead(200, corsHeaders);
1694
- res.end(JSON.stringify({ success: true, document: docMeta, learnedCount }));
1695
- } catch (e: any) {
1696
- res.writeHead(500, corsHeaders);
1697
- res.end(JSON.stringify({ error: e.message || 'Upload failed' }));
1698
- }
1699
- }
1700
-
1701
- private async parseMultipart(req: IncomingMessage): Promise<{ buffer: Buffer; filename: string }> {
1702
- return new Promise((resolve, reject) => {
1703
- const contentType = req.headers['content-type'] || '';
1704
- const boundaryMatch = contentType.match(/boundary=(.+)/);
1705
-
1706
- if (!boundaryMatch) {
1707
- reject(new Error('Missing multipart boundary'));
1708
- return;
1709
- }
1710
-
1711
- const boundary = boundaryMatch[1];
1712
- const chunks: Buffer[] = [];
1713
-
1714
- req.on('data', (chunk: Buffer) => chunks.push(chunk));
1715
- req.on('error', reject);
1716
- req.on('end', () => {
1717
- const body = Buffer.concat(chunks);
1718
- const bodyStr = body.toString('latin1');
1719
- const parts = bodyStr.split('--' + boundary).filter(p => p.trim() && p.trim() !== '--');
1720
-
1721
- for (const part of parts) {
1722
- const headerEnd = part.indexOf('\r\n\r\n');
1723
- if (headerEnd === -1) continue;
1724
-
1725
- const headers = part.slice(0, headerEnd);
1726
- const filenameMatch = headers.match(/filename="([^"]+)"/);
1727
- if (!filenameMatch) continue;
1728
-
1729
- const filename = filenameMatch[1];
1730
- // Extract binary content properly
1731
- const contentStart = body.indexOf('\r\n\r\n', body.indexOf(Buffer.from(headers.slice(0, 40), 'latin1'))) + 4;
1732
- const nextBoundary = body.indexOf(Buffer.from('\r\n--' + boundary, 'latin1'), contentStart);
1733
- const fileBuffer = body.slice(contentStart, nextBoundary);
1734
-
1735
- resolve({ buffer: fileBuffer, filename });
1736
- return;
1737
- }
1738
-
1739
- reject(new Error('No file found in upload'));
1740
- });
1741
- });
1742
- }
1743
-
1744
- private getDocumentList(agentId: string): any {
1745
- const docsDir = this.getDocumentsDir(agentId);
1746
- const files = readdirSync(docsDir).filter(f => f.endsWith('.json'));
1747
- const documents = files.map(f => {
1748
- try { return JSON.parse(readFileSync(join(docsDir, f), 'utf-8')); } catch { return null; }
1749
- }).filter(Boolean).sort((a: any, b: any) =>
1750
- new Date(b.processedAt).getTime() - new Date(a.processedAt).getTime()
1751
- );
1752
- return { documents };
1753
- }
1754
-
1755
- private deleteDocument(agentId: string, docId: string): any {
1756
- const docsDir = this.getDocumentsDir(agentId);
1757
- const docPath = join(docsDir, `${docId}.json`);
1758
-
1759
- if (!existsSync(docPath)) {
1760
- return { error: 'Document not found' };
1761
- }
1762
-
1763
- // Delete document metadata
1764
- unlinkSync(docPath);
1765
-
1766
- // Try to delete from DeepBrain
1767
- try {
1768
- // Remove memory entries with this docId
1769
- const memDir = join(this.getAgentsDir(), agentId + '-memory');
1770
- if (existsSync(memDir)) {
1771
- const files = readdirSync(memDir).filter(f => f.startsWith(docId));
1772
- for (const f of files) {
1773
- unlinkSync(join(memDir, f));
1774
- }
1775
- }
1776
- } catch { /* best effort */ }
1777
-
1778
- return { success: true, deleted: docId };
1779
- }
1780
-
1781
- private readBody(req: IncomingMessage): Promise<string> {
1782
- return new Promise((resolve, reject) => {
1783
- let body = '';
1784
- req.on('data', (chunk: any) => (body += chunk));
1785
- req.on('end', () => resolve(body));
1786
- req.on('error', reject);
1787
- });
1788
- }
1789
- }
1790
-
1791
- export { StudioServer, StudioConfig };