opc-agent 1.2.1 → 1.3.0

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 (152) hide show
  1. package/CONTRIBUTING.md +75 -75
  2. package/README.md +235 -358
  3. package/README.zh-CN.md +415 -415
  4. package/dist/channels/web.js +256 -256
  5. package/dist/core/knowledge.d.ts +5 -0
  6. package/dist/core/knowledge.js +39 -2
  7. package/dist/deploy/hermes.js +22 -22
  8. package/dist/deploy/openclaw.js +31 -31
  9. package/dist/index.d.ts +0 -4
  10. package/dist/index.js +1 -7
  11. package/dist/providers/index.d.ts +1 -1
  12. package/dist/providers/index.js +158 -14
  13. package/dist/schema/oad.d.ts +3 -3
  14. package/dist/templates/code-reviewer.js +5 -5
  15. package/dist/templates/customer-service.js +2 -2
  16. package/dist/templates/data-analyst.js +5 -5
  17. package/dist/templates/knowledge-base.js +2 -2
  18. package/dist/templates/sales-assistant.js +4 -4
  19. package/dist/templates/teacher.js +6 -6
  20. package/docs/.vitepress/config.ts +103 -103
  21. package/docs/api/cli.md +48 -48
  22. package/docs/api/oad-schema.md +64 -64
  23. package/docs/api/sdk.md +80 -80
  24. package/docs/guide/concepts.md +51 -51
  25. package/docs/guide/configuration.md +79 -79
  26. package/docs/guide/deployment.md +42 -42
  27. package/docs/guide/getting-started.md +44 -44
  28. package/docs/guide/templates.md +28 -28
  29. package/docs/guide/testing.md +84 -84
  30. package/docs/index.md +27 -27
  31. package/docs/zh/api/cli.md +54 -54
  32. package/docs/zh/api/oad-schema.md +87 -87
  33. package/docs/zh/api/sdk.md +102 -102
  34. package/docs/zh/guide/concepts.md +104 -104
  35. package/docs/zh/guide/configuration.md +135 -135
  36. package/docs/zh/guide/deployment.md +81 -81
  37. package/docs/zh/guide/getting-started.md +82 -82
  38. package/docs/zh/guide/templates.md +84 -84
  39. package/docs/zh/guide/testing.md +88 -88
  40. package/docs/zh/index.md +27 -27
  41. package/examples/customer-service-demo/README.md +90 -90
  42. package/examples/customer-service-demo/oad.yaml +107 -107
  43. package/package.json +50 -50
  44. package/src/analytics/index.ts +66 -66
  45. package/src/channels/discord.ts +192 -192
  46. package/src/channels/email.ts +177 -177
  47. package/src/channels/feishu.ts +236 -236
  48. package/src/channels/index.ts +15 -15
  49. package/src/channels/slack.ts +160 -160
  50. package/src/channels/telegram.ts +90 -90
  51. package/src/channels/voice.ts +106 -106
  52. package/src/channels/webhook.ts +199 -199
  53. package/src/channels/websocket.ts +87 -87
  54. package/src/channels/wechat.ts +149 -149
  55. package/src/cli.ts +119 -1
  56. package/src/core/a2a.ts +143 -143
  57. package/src/core/agent.ts +152 -152
  58. package/src/core/analytics-engine.ts +186 -186
  59. package/src/core/auth.ts +57 -57
  60. package/src/core/cache.ts +141 -141
  61. package/src/core/compose.ts +77 -77
  62. package/src/core/config.ts +14 -14
  63. package/src/core/errors.ts +148 -148
  64. package/src/core/hitl.ts +138 -138
  65. package/src/core/logger.ts +57 -57
  66. package/src/core/orchestrator.ts +215 -215
  67. package/src/core/performance.ts +187 -187
  68. package/src/core/rate-limiter.ts +128 -128
  69. package/src/core/room.ts +109 -109
  70. package/src/core/runtime.ts +152 -152
  71. package/src/core/sandbox.ts +101 -101
  72. package/src/core/security.ts +171 -171
  73. package/src/core/types.ts +68 -68
  74. package/src/core/versioning.ts +106 -106
  75. package/src/core/watch.ts +178 -178
  76. package/src/core/workflow.ts +235 -235
  77. package/src/deploy/hermes.ts +156 -156
  78. package/src/deploy/openclaw.ts +200 -200
  79. package/src/i18n/index.ts +216 -216
  80. package/src/index.ts +6 -2
  81. package/src/memory/deepbrain.ts +108 -108
  82. package/src/memory/index.ts +34 -34
  83. package/src/plugins/index.ts +208 -208
  84. package/src/schema/oad.ts +154 -155
  85. package/src/skills/base.ts +16 -16
  86. package/src/skills/document.ts +100 -100
  87. package/src/skills/http.ts +35 -35
  88. package/src/skills/index.ts +27 -27
  89. package/src/skills/scheduler.ts +80 -80
  90. package/src/skills/webhook-trigger.ts +59 -59
  91. package/src/templates/code-reviewer.ts +30 -34
  92. package/src/templates/customer-service.ts +76 -80
  93. package/src/templates/data-analyst.ts +66 -70
  94. package/src/templates/executive-assistant.ts +71 -71
  95. package/src/templates/financial-advisor.ts +60 -60
  96. package/src/templates/knowledge-base.ts +27 -31
  97. package/src/templates/legal-assistant.ts +71 -71
  98. package/src/templates/sales-assistant.ts +75 -79
  99. package/src/templates/teacher.ts +75 -79
  100. package/src/testing/index.ts +181 -181
  101. package/src/tools/calculator.ts +73 -73
  102. package/src/tools/datetime.ts +149 -149
  103. package/src/tools/json-transform.ts +187 -187
  104. package/src/tools/mcp.ts +76 -76
  105. package/src/tools/text-analysis.ts +116 -116
  106. package/src/traces/index.ts +132 -0
  107. package/templates/Dockerfile +15 -15
  108. package/templates/code-reviewer/README.md +27 -27
  109. package/templates/code-reviewer/oad.yaml +41 -41
  110. package/templates/customer-service/README.md +22 -22
  111. package/templates/customer-service/oad.yaml +36 -36
  112. package/templates/docker-compose.yml +21 -21
  113. package/templates/ecommerce-assistant/README.md +45 -45
  114. package/templates/ecommerce-assistant/oad.yaml +47 -47
  115. package/templates/knowledge-base/README.md +28 -28
  116. package/templates/knowledge-base/oad.yaml +38 -38
  117. package/templates/sales-assistant/README.md +26 -26
  118. package/templates/sales-assistant/oad.yaml +43 -43
  119. package/templates/tech-support/README.md +43 -43
  120. package/templates/tech-support/oad.yaml +45 -45
  121. package/tests/a2a.test.ts +66 -66
  122. package/tests/agent.test.ts +72 -72
  123. package/tests/analytics.test.ts +50 -50
  124. package/tests/channel.test.ts +39 -39
  125. package/tests/e2e.test.ts +134 -134
  126. package/tests/errors.test.ts +83 -83
  127. package/tests/hitl.test.ts +71 -71
  128. package/tests/i18n.test.ts +41 -41
  129. package/tests/mcp.test.ts +54 -54
  130. package/tests/oad.test.ts +68 -68
  131. package/tests/performance.test.ts +115 -115
  132. package/tests/plugin.test.ts +74 -74
  133. package/tests/room.test.ts +106 -106
  134. package/tests/runtime.test.ts +42 -42
  135. package/tests/sandbox.test.ts +46 -46
  136. package/tests/security.test.ts +60 -60
  137. package/tests/templates.test.ts +77 -77
  138. package/tests/v070.test.ts +76 -76
  139. package/tests/versioning.test.ts +75 -75
  140. package/tests/voice.test.ts +61 -61
  141. package/tests/webhook.test.ts +29 -29
  142. package/tests/workflow.test.ts +143 -143
  143. package/tsconfig.json +19 -19
  144. package/vitest.config.ts +9 -9
  145. package/dist/core/streaming.d.ts +0 -56
  146. package/dist/core/streaming.js +0 -160
  147. package/dist/tools/gateway.d.ts +0 -28
  148. package/dist/tools/gateway.js +0 -177
  149. package/src/dtv/data.ts +0 -29
  150. package/src/dtv/trust.ts +0 -43
  151. package/src/dtv/value.ts +0 -47
  152. package/src/marketplace/index.ts +0 -223
@@ -20,264 +20,264 @@ const AGENT_TEMPLATES = [
20
20
  { id: 'hr-recruiter', name: 'HR Recruiter', description: 'Resume screening, interview scheduling, candidate comms', icon: '👥', category: 'HR' },
21
21
  { id: 'legal-assistant', name: 'Legal Assistant', description: 'Contract review, compliance checks, legal research', icon: '⚖️', category: 'Legal' },
22
22
  ];
23
- const TEMPLATES_HTML = `<!DOCTYPE html>
24
- <html lang="en">
25
- <head>
26
- <meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
27
- <title>Agent Templates</title>
28
- <style>
29
- *{margin:0;padding:0;box-sizing:border-box}
30
- body{background:#0a0a0f;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px}
31
- h1{font-size:28px;margin-bottom:8px;color:#fff}
32
- .sub{color:#888;margin-bottom:32px;font-size:14px}
33
- nav{margin-bottom:24px}
34
- nav a{color:#818cf8;text-decoration:none;margin-right:16px;font-size:14px}
35
- .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
36
- .card{background:#12121a;border:1px solid #1e1e2e;border-radius:12px;padding:24px;cursor:pointer;transition:all .2s}
37
- .card:hover{border-color:#818cf8;transform:translateY(-2px)}
38
- .card .icon{font-size:32px;margin-bottom:12px}
39
- .card h3{font-size:16px;color:#fff;margin-bottom:8px}
40
- .card p{font-size:13px;color:#888;line-height:1.5}
41
- .card .cat{font-size:11px;color:#818cf8;text-transform:uppercase;letter-spacing:1px;margin-top:12px}
42
- .btn{display:inline-block;background:#2563eb;color:#fff;border:none;border-radius:8px;padding:8px 16px;font-size:13px;cursor:pointer;margin-top:12px}
43
- .btn:hover{background:#1d4ed8}
44
- </style>
45
- </head>
46
- <body>
47
- <nav><a href="/">← Chat</a><a href="/dashboard">Dashboard</a><a href="/templates">Templates</a></nav>
48
- <h1>🧩 Agent Templates</h1>
49
- <p class="sub">Create a new agent from a pre-built template in one click.</p>
50
- <div class="grid" id="grid"></div>
51
- <script>
52
- fetch('/api/templates').then(r=>r.json()).then(d=>{
53
- const g=document.getElementById('grid');
54
- d.templates.forEach(t=>{
55
- g.innerHTML+=\`<div class="card"><div class="icon">\${t.icon}</div><h3>\${t.name}</h3><p>\${t.description}</p><div class="cat">\${t.category}</div><button class="btn" onclick="alert('Creating agent from template: '+'\${t.id}'+'\\\\nRun: opc init --template \${t.id}')">Use Template</button></div>\`;
56
- });
57
- });
58
- </script>
59
- </body>
23
+ const TEMPLATES_HTML = `<!DOCTYPE html>
24
+ <html lang="en">
25
+ <head>
26
+ <meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
27
+ <title>Agent Templates</title>
28
+ <style>
29
+ *{margin:0;padding:0;box-sizing:border-box}
30
+ body{background:#0f0f23;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px}
31
+ h1{font-size:28px;margin-bottom:8px;color:#fff}
32
+ .sub{color:#8a8aa0;margin-bottom:32px;font-size:14px}
33
+ nav{margin-bottom:24px}
34
+ nav a{color:#818cf8;text-decoration:none;margin-right:16px;font-size:14px}
35
+ .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
36
+ .card{background:#1a1a3a;border:1px solid #2d2d4e;border-radius:14px;padding:24px;cursor:pointer;transition:all .2s}
37
+ .card:hover{border-color:#818cf8;transform:translateY(-2px);box-shadow:0 4px 20px rgba(129,140,248,.15)}
38
+ .card .icon{font-size:32px;margin-bottom:12px}
39
+ .card h3{font-size:16px;color:#fff;margin-bottom:8px}
40
+ .card p{font-size:13px;color:#888;line-height:1.5}
41
+ .card .cat{font-size:11px;color:#818cf8;text-transform:uppercase;letter-spacing:1px;margin-top:12px}
42
+ .btn{display:inline-block;background:#2563eb;color:#fff;border:none;border-radius:8px;padding:8px 16px;font-size:13px;cursor:pointer;margin-top:12px}
43
+ .btn:hover{background:#1d4ed8}
44
+ </style>
45
+ </head>
46
+ <body>
47
+ <nav><a href="/">← Chat</a><a href="/dashboard">Dashboard</a><a href="/templates">Templates</a></nav>
48
+ <h1>🧩 Agent Templates</h1>
49
+ <p class="sub">Create a new agent from a pre-built template in one click.</p>
50
+ <div class="grid" id="grid"></div>
51
+ <script>
52
+ fetch('/api/templates').then(r=>r.json()).then(d=>{
53
+ const g=document.getElementById('grid');
54
+ d.templates.forEach(t=>{
55
+ g.innerHTML+=\`<div class="card"><div class="icon">\${t.icon}</div><h3>\${t.name}</h3><p>\${t.description}</p><div class="cat">\${t.category}</div><button class="btn" onclick="alert('Creating agent from template: '+'\${t.id}'+'\\\\nRun: opc init --template \${t.id}')">Use Template</button></div>\`;
56
+ });
57
+ });
58
+ </script>
59
+ </body>
60
60
  </html>`;
61
- const CHAT_HTML = `<!DOCTYPE html>
62
- <html lang="en">
63
- <head>
64
- <meta charset="UTF-8">
65
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
66
- <title>OPC Agent</title>
67
- <style>
68
- :root{--bg:#0a0a0f;--surface:#12121a;--border:#1e1e2e;--text:#e0e0e0;--text-dim:#888;--accent:#818cf8;--accent-hover:#6366f1;--user-bg:#2563eb;--user-hover:#1d4ed8;--error-bg:#7f1d1d;--error-text:#fca5a5;--success:#22c55e;--radius:12px}
69
- *{margin:0;padding:0;box-sizing:border-box}
70
- body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',sans-serif;height:100vh;height:100dvh;display:flex;flex-direction:column;overflow:hidden}
71
- header{background:var(--surface);padding:14px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;flex-shrink:0;backdrop-filter:blur(12px)}
72
- header .avatar{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--accent),#6366f1);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0}
73
- header .info{flex:1;min-width:0}
74
- header h1{font-size:16px;font-weight:600;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
75
- header .status{font-size:12px;color:var(--success);display:flex;align-items:center;gap:4px}
76
- header .status .dot{width:6px;height:6px;border-radius:50%;background:var(--success);animation:pulse 2s infinite}
77
- nav.header-nav{display:flex;gap:4px}
78
- nav.header-nav a{color:var(--text-dim);text-decoration:none;font-size:12px;padding:4px 10px;border-radius:6px;transition:all .2s}
79
- nav.header-nav a:hover{color:#fff;background:rgba(255,255,255,.06)}
80
- @keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
81
- @keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
82
- @keyframes slideIn{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}
83
- #messages{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:12px;scroll-behavior:smooth}
84
- #messages::-webkit-scrollbar{width:4px}
85
- #messages::-webkit-scrollbar-track{background:transparent}
86
- #messages::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
87
- .msg-wrap{display:flex;flex-direction:column;animation:fadeIn .3s ease-out}
88
- .msg-wrap.user{align-items:flex-end}
89
- .msg-wrap.assistant{align-items:flex-start}
90
- .msg{max-width:min(720px,85%);padding:10px 14px;border-radius:var(--radius);line-height:1.7;font-size:14px;word-break:break-word;position:relative;transition:all .2s}
91
- .msg.user{background:var(--user-bg);color:#fff;border-bottom-right-radius:4px}
92
- .msg.assistant{background:var(--surface);color:var(--text);border:1px solid var(--border);border-bottom-left-radius:4px}
93
- .msg.error{background:var(--error-bg);color:var(--error-text);border:1px solid rgba(239,68,68,.3)}
94
- .msg pre{background:rgba(0,0,0,.4);padding:12px;border-radius:8px;overflow-x:auto;margin:8px 0;font-size:13px;font-family:'JetBrains Mono','Fira Code','Cascadia Code',monospace;line-height:1.5}
95
- .msg code{font-family:'JetBrains Mono','Fira Code','Cascadia Code',monospace;font-size:13px;background:rgba(0,0,0,.3);padding:1px 5px;border-radius:4px}
96
- .msg pre code{background:none;padding:0}
97
- .msg .cursor{display:inline-block;width:2px;height:14px;background:var(--accent);animation:blink .6s infinite;vertical-align:text-bottom;margin-left:2px}
98
- @keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
99
- .typing{display:flex;gap:4px;padding:12px 16px;align-items:center}
100
- .typing span{width:6px;height:6px;border-radius:50%;background:var(--text-dim);animation:typingDot 1.4s infinite}
101
- .typing span:nth-child(2){animation-delay:.2s}
102
- .typing span:nth-child(3){animation-delay:.4s}
103
- @keyframes typingDot{0%,60%,100%{opacity:.3;transform:scale(.8)}30%{opacity:1;transform:scale(1)}}
104
- .reactions{display:flex;gap:4px;margin-top:4px}
105
- .reactions button{background:rgba(255,255,255,.06);border:1px solid transparent;border-radius:16px;padding:2px 8px;font-size:13px;cursor:pointer;transition:all .15s;color:var(--text-dim)}
106
- .reactions button:hover{background:rgba(255,255,255,.12);border-color:var(--border)}
107
- .reactions button.active{background:rgba(99,102,241,.2);border-color:var(--accent);color:var(--accent)}
108
- .msg-time{font-size:11px;color:var(--text-dim);margin-top:2px;opacity:0;transition:opacity .2s}
109
- .msg-wrap:hover .msg-time{opacity:1}
110
- .attachment{display:flex;align-items:center;gap:8px;background:rgba(0,0,0,.3);padding:8px 12px;border-radius:8px;margin-top:6px;font-size:13px}
111
- .attachment .icon{font-size:18px}
112
- #input-area{background:var(--surface);padding:12px 20px 16px;border-top:1px solid var(--border);display:flex;gap:10px;align-items:flex-end;flex-shrink:0}
113
- #input{flex:1;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:10px 14px;color:#fff;font-size:14px;outline:none;resize:none;max-height:150px;min-height:42px;font-family:inherit;line-height:1.5;transition:border-color .2s}
114
- #input:focus{border-color:var(--accent)}
115
- #input::placeholder{color:var(--text-dim)}
116
- #send{background:var(--user-bg);color:#fff;border:none;border-radius:var(--radius);width:42px;height:42px;font-size:18px;cursor:pointer;transition:all .2s;display:flex;align-items:center;justify-content:center;flex-shrink:0}
117
- #send:hover{background:var(--user-hover);transform:scale(1.05)}
118
- #send:disabled{background:#334155;cursor:not-allowed;transform:none}
119
- .empty-state{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-dim);gap:12px;padding:40px;text-align:center}
120
- .empty-state .logo{font-size:48px;opacity:.6}
121
- .empty-state h2{color:var(--text);font-size:20px;font-weight:500}
122
- .empty-state p{font-size:14px;max-width:400px;line-height:1.6}
123
- @media(max-width:640px){
124
- header{padding:10px 14px}
125
- #messages{padding:12px}
126
- #input-area{padding:10px 14px 14px}
127
- .msg{max-width:90%;font-size:14px}
128
- nav.header-nav{display:none}
129
- }
130
- </style>
131
- </head>
132
- <body>
133
- <header>
134
- <div class="avatar" id="avatar">🤖</div>
135
- <div class="info"><h1 id="title">OPC Agent</h1><div class="status"><span class="dot"></span>Online</div></div>
136
- <nav class="header-nav"><a href="/dashboard">Dashboard</a><a href="/templates">Templates</a></nav>
137
- </header>
138
- <div id="messages">
139
- <div class="empty-state" id="empty"><div class="logo">💬</div><h2>Start a conversation</h2><p>Type a message below to chat with your AI agent.</p></div>
140
- </div>
141
- <div id="input-area">
142
- <textarea id="input" rows="1" placeholder="Type a message…" autocomplete="off"></textarea>
143
- <button id="send" aria-label="Send">↑</button>
144
- </div>
145
- <script>
146
- const msgs=document.getElementById('messages'),input=document.getElementById('input'),btn=document.getElementById('send'),empty=document.getElementById('empty');
147
- let sessionId=crypto.randomUUID(),sending=false;
148
-
149
- function esc(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML}
150
- function fmtTime(){return new Date().toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'})}
151
- function renderMd(text){
152
- let h=esc(text);
153
- h=h.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g,'<pre><code>$2</code></pre>');
154
- h=h.replace(/\`([^\`]+)\`/g,'<code>$1</code>');
155
- h=h.replace(/\\*\\*(.+?)\\*\\*/g,'<strong>$1</strong>');
156
- h=h.replace(/\\n/g,'<br>');
157
- return h;
158
- }
159
-
160
- function addMsg(role,text,opts){
161
- if(empty)empty.remove();
162
- const wrap=document.createElement('div');
163
- wrap.className='msg-wrap '+role;
164
- const d=document.createElement('div');
165
- d.className='msg '+role;
166
- if(opts?.html)d.innerHTML=text;else if(role==='assistant'&&text)d.innerHTML=renderMd(text);else d.textContent=text;
167
- wrap.appendChild(d);
168
- const time=document.createElement('div');
169
- time.className='msg-time';
170
- time.textContent=fmtTime();
171
- wrap.appendChild(time);
172
- if(role==='assistant'&&text){
173
- const rx=document.createElement('div');rx.className='reactions';
174
- rx.innerHTML='<button data-r="👍" onclick="react(this)">👍</button><button data-r="👎" onclick="react(this)">👎</button>';
175
- wrap.appendChild(rx);
176
- }
177
- msgs.appendChild(wrap);
178
- msgs.scrollTop=msgs.scrollHeight;
179
- return d;
180
- }
181
-
182
- window.react=function(el){el.classList.toggle('active')};
183
-
184
- input.addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send()}});
185
- input.addEventListener('input',()=>{input.style.height='auto';input.style.height=Math.min(input.scrollHeight,150)+'px'});
186
- btn.addEventListener('click',send);
187
-
188
- async function send(){
189
- const text=input.value.trim();
190
- if(!text||sending)return;
191
- sending=true;btn.disabled=true;
192
- input.value='';input.style.height='auto';
193
- addMsg('user',text);
194
- const wrap=document.createElement('div');wrap.className='msg-wrap assistant';
195
- const d=document.createElement('div');d.className='msg assistant';
196
- d.innerHTML='<div class="typing"><span></span><span></span><span></span></div>';
197
- wrap.appendChild(d);
198
- const time=document.createElement('div');time.className='msg-time';time.textContent=fmtTime();
199
- wrap.appendChild(time);
200
- msgs.appendChild(wrap);msgs.scrollTop=msgs.scrollHeight;
201
- try{
202
- const res=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:text,sessionId})});
203
- if(!res.ok)throw new Error('HTTP '+res.status);
204
- const reader=res.body.getReader(),dec=new TextDecoder();
205
- let full='';
206
- while(true){
207
- const{done,value}=await reader.read();
208
- if(done)break;
209
- const chunk=dec.decode(value,{stream:true});
210
- const lines=chunk.split('\\n');
211
- for(const line of lines){
212
- if(!line.startsWith('data: '))continue;
213
- const dd=line.slice(6);if(dd==='[DONE]')continue;
214
- try{const j=JSON.parse(dd);if(j.content)full+=j.content;if(j.error)full='Error: '+j.error;}catch{}
215
- }
216
- d.innerHTML=renderMd(full)+'<span class="cursor"></span>';
217
- msgs.scrollTop=msgs.scrollHeight;
218
- }
219
- if(!full){d.textContent='(empty response)';}else{d.innerHTML=renderMd(full);}
220
- const rx=document.createElement('div');rx.className='reactions';
221
- rx.innerHTML='<button data-r="👍" onclick="react(this)">👍</button><button data-r="👎" onclick="react(this)">👎</button>';
222
- wrap.appendChild(rx);
223
- }catch(e){d.className='msg error';d.textContent='Error: '+e.message;}
224
- sending=false;btn.disabled=false;input.focus();
225
- }
226
-
227
- fetch('/api/info').then(r=>r.json()).then(d=>{if(d.name)document.getElementById('title').textContent=d.name}).catch(()=>{});
228
- </script>
229
- </body>
61
+ const CHAT_HTML = `<!DOCTYPE html>
62
+ <html lang="en">
63
+ <head>
64
+ <meta charset="UTF-8">
65
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
66
+ <title>OPC Agent</title>
67
+ <style>
68
+ :root{--bg:#0f0f23;--surface:#1a1a3a;--border:#2d2d4e;--text:#e0e0e0;--text-dim:#8a8aa0;--accent:#818cf8;--accent-hover:#6366f1;--user-bg:#667eea;--user-hover:#5a6fd6;--error-bg:#7f1d1d;--error-text:#fca5a5;--success:#22c55e;--radius:14px}
69
+ *{margin:0;padding:0;box-sizing:border-box}
70
+ body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',sans-serif;height:100vh;height:100dvh;display:flex;flex-direction:column;overflow:hidden}
71
+ header{background:var(--surface);padding:14px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;flex-shrink:0;backdrop-filter:blur(12px)}
72
+ header .avatar{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--accent),#6366f1);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0}
73
+ header .info{flex:1;min-width:0}
74
+ header h1{font-size:16px;font-weight:600;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
75
+ header .status{font-size:12px;color:var(--success);display:flex;align-items:center;gap:4px}
76
+ header .status .dot{width:6px;height:6px;border-radius:50%;background:var(--success);animation:pulse 2s infinite}
77
+ nav.header-nav{display:flex;gap:4px}
78
+ nav.header-nav a{color:var(--text-dim);text-decoration:none;font-size:12px;padding:4px 10px;border-radius:6px;transition:all .2s}
79
+ nav.header-nav a:hover{color:#fff;background:rgba(255,255,255,.06)}
80
+ @keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
81
+ @keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
82
+ @keyframes slideIn{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}
83
+ #messages{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:12px;scroll-behavior:smooth}
84
+ #messages::-webkit-scrollbar{width:4px}
85
+ #messages::-webkit-scrollbar-track{background:transparent}
86
+ #messages::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
87
+ .msg-wrap{display:flex;flex-direction:column;animation:fadeIn .3s ease-out}
88
+ .msg-wrap.user{align-items:flex-end}
89
+ .msg-wrap.assistant{align-items:flex-start}
90
+ .msg{max-width:min(720px,85%);padding:10px 14px;border-radius:var(--radius);line-height:1.7;font-size:14px;word-break:break-word;position:relative;transition:all .2s}
91
+ .msg.user{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;border-bottom-right-radius:4px;box-shadow:0 2px 8px rgba(102,126,234,.35)}
92
+ .msg.assistant{background:#2a2a4a;color:var(--text);border:1px solid var(--border);border-bottom-left-radius:4px;box-shadow:0 2px 8px rgba(0,0,0,.25)}
93
+ .msg.error{background:var(--error-bg);color:var(--error-text);border:1px solid rgba(239,68,68,.3)}
94
+ .msg pre{background:rgba(0,0,0,.4);padding:12px;border-radius:8px;overflow-x:auto;margin:8px 0;font-size:13px;font-family:'JetBrains Mono','Fira Code','Cascadia Code',monospace;line-height:1.5}
95
+ .msg code{font-family:'JetBrains Mono','Fira Code','Cascadia Code',monospace;font-size:13px;background:rgba(0,0,0,.3);padding:1px 5px;border-radius:4px}
96
+ .msg pre code{background:none;padding:0}
97
+ .msg .cursor{display:inline-block;width:2px;height:14px;background:var(--accent);animation:blink .6s infinite;vertical-align:text-bottom;margin-left:2px}
98
+ @keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
99
+ .typing{display:flex;gap:4px;padding:12px 16px;align-items:center}
100
+ .typing span{width:6px;height:6px;border-radius:50%;background:var(--text-dim);animation:typingDot 1.4s infinite}
101
+ .typing span:nth-child(2){animation-delay:.2s}
102
+ .typing span:nth-child(3){animation-delay:.4s}
103
+ @keyframes typingDot{0%,60%,100%{opacity:.3;transform:scale(.8)}30%{opacity:1;transform:scale(1)}}
104
+ .reactions{display:flex;gap:4px;margin-top:4px}
105
+ .reactions button{background:rgba(255,255,255,.06);border:1px solid transparent;border-radius:16px;padding:2px 8px;font-size:13px;cursor:pointer;transition:all .15s;color:var(--text-dim)}
106
+ .reactions button:hover{background:rgba(255,255,255,.12);border-color:var(--border)}
107
+ .reactions button.active{background:rgba(99,102,241,.2);border-color:var(--accent);color:var(--accent)}
108
+ .msg-time{font-size:11px;color:var(--text-dim);margin-top:2px;opacity:0;transition:opacity .2s}
109
+ .msg-wrap:hover .msg-time{opacity:1}
110
+ .attachment{display:flex;align-items:center;gap:8px;background:rgba(0,0,0,.3);padding:8px 12px;border-radius:8px;margin-top:6px;font-size:13px}
111
+ .attachment .icon{font-size:18px}
112
+ #input-area{background:var(--surface);padding:12px 20px 16px;border-top:1px solid var(--border);display:flex;gap:10px;align-items:flex-end;flex-shrink:0}
113
+ #input{flex:1;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:10px 14px;color:#fff;font-size:14px;outline:none;resize:none;max-height:150px;min-height:42px;font-family:inherit;line-height:1.5;transition:border-color .2s}
114
+ #input:focus{border-color:var(--accent)}
115
+ #input::placeholder{color:var(--text-dim)}
116
+ #send{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;border:none;border-radius:var(--radius);padding:0 16px;height:42px;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s;display:flex;align-items:center;justify-content:center;flex-shrink:0;letter-spacing:.5px}
117
+ #send:hover{background:var(--user-hover);transform:scale(1.05)}
118
+ #send:disabled{background:#334155;cursor:not-allowed;transform:none}
119
+ .empty-state{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-dim);gap:12px;padding:40px;text-align:center}
120
+ .empty-state .logo{font-size:48px;opacity:.6}
121
+ .empty-state h2{color:var(--text);font-size:20px;font-weight:500}
122
+ .empty-state p{font-size:14px;max-width:400px;line-height:1.6}
123
+ @media(max-width:640px){
124
+ header{padding:10px 14px}
125
+ #messages{padding:12px}
126
+ #input-area{padding:10px 14px 14px}
127
+ .msg{max-width:90%;font-size:14px}
128
+ nav.header-nav{display:none}
129
+ }
130
+ </style>
131
+ </head>
132
+ <body>
133
+ <header>
134
+ <div class="avatar" id="avatar">🤖</div>
135
+ <div class="info"><h1 id="title">OPC Agent</h1><div class="status"><span class="dot"></span>在线</div></div>
136
+ <nav class="header-nav"><a href="/dashboard">Dashboard</a><a href="/templates">Templates</a></nav>
137
+ </header>
138
+ <div id="messages">
139
+ <div class="empty-state" id="empty"><div class="logo">💬</div><h2>开始对话</h2><p>在下方输入消息与 AI 助手对话。</p></div>
140
+ </div>
141
+ <div id="input-area">
142
+ <textarea id="input" rows="1" placeholder="输入消息…" autocomplete="off"></textarea>
143
+ <button id="send" aria-label="发送">发送</button>
144
+ </div>
145
+ <script>
146
+ const msgs=document.getElementById('messages'),input=document.getElementById('input'),btn=document.getElementById('send'),empty=document.getElementById('empty');
147
+ let sessionId=crypto.randomUUID(),sending=false;
148
+
149
+ function esc(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML}
150
+ function fmtTime(){return new Date().toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'})}
151
+ function renderMd(text){
152
+ let h=esc(text);
153
+ h=h.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g,'<pre><code>$2</code></pre>');
154
+ h=h.replace(/\`([^\`]+)\`/g,'<code>$1</code>');
155
+ h=h.replace(/\\*\\*(.+?)\\*\\*/g,'<strong>$1</strong>');
156
+ h=h.replace(/\\n/g,'<br>');
157
+ return h;
158
+ }
159
+
160
+ function addMsg(role,text,opts){
161
+ if(empty)empty.remove();
162
+ const wrap=document.createElement('div');
163
+ wrap.className='msg-wrap '+role;
164
+ const d=document.createElement('div');
165
+ d.className='msg '+role;
166
+ if(opts?.html)d.innerHTML=text;else if(role==='assistant'&&text)d.innerHTML=renderMd(text);else d.textContent=text;
167
+ wrap.appendChild(d);
168
+ const time=document.createElement('div');
169
+ time.className='msg-time';
170
+ time.textContent=fmtTime();
171
+ wrap.appendChild(time);
172
+ if(role==='assistant'&&text){
173
+ const rx=document.createElement('div');rx.className='reactions';
174
+ rx.innerHTML='<button data-r="👍" onclick="react(this)">👍</button><button data-r="👎" onclick="react(this)">👎</button>';
175
+ wrap.appendChild(rx);
176
+ }
177
+ msgs.appendChild(wrap);
178
+ msgs.scrollTop=msgs.scrollHeight;
179
+ return d;
180
+ }
181
+
182
+ window.react=function(el){el.classList.toggle('active')};
183
+
184
+ input.addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send()}});
185
+ input.addEventListener('input',()=>{input.style.height='auto';input.style.height=Math.min(input.scrollHeight,150)+'px'});
186
+ btn.addEventListener('click',send);
187
+
188
+ async function send(){
189
+ const text=input.value.trim();
190
+ if(!text||sending)return;
191
+ sending=true;btn.disabled=true;
192
+ input.value='';input.style.height='auto';
193
+ addMsg('user',text);
194
+ const wrap=document.createElement('div');wrap.className='msg-wrap assistant';
195
+ const d=document.createElement('div');d.className='msg assistant';
196
+ d.innerHTML='<div class="typing"><span></span><span></span><span></span><small style="margin-left:6px;font-size:12px;color:#8a8aa0">思考中…</small></div>';
197
+ wrap.appendChild(d);
198
+ const time=document.createElement('div');time.className='msg-time';time.textContent=fmtTime();
199
+ wrap.appendChild(time);
200
+ msgs.appendChild(wrap);msgs.scrollTop=msgs.scrollHeight;
201
+ try{
202
+ const res=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:text,sessionId})});
203
+ if(!res.ok)throw new Error('HTTP '+res.status);
204
+ const reader=res.body.getReader(),dec=new TextDecoder();
205
+ let full='';
206
+ while(true){
207
+ const{done,value}=await reader.read();
208
+ if(done)break;
209
+ const chunk=dec.decode(value,{stream:true});
210
+ const lines=chunk.split('\\n');
211
+ for(const line of lines){
212
+ if(!line.startsWith('data: '))continue;
213
+ const dd=line.slice(6);if(dd==='[DONE]')continue;
214
+ try{const j=JSON.parse(dd);if(j.content)full+=j.content;if(j.error)full='Error: '+j.error;}catch{}
215
+ }
216
+ d.innerHTML=renderMd(full)+'<span class="cursor"></span>';
217
+ msgs.scrollTop=msgs.scrollHeight;
218
+ }
219
+ if(!full){d.textContent='(empty response)';}else{d.innerHTML=renderMd(full);}
220
+ const rx=document.createElement('div');rx.className='reactions';
221
+ rx.innerHTML='<button data-r="👍" onclick="react(this)">👍</button><button data-r="👎" onclick="react(this)">👎</button>';
222
+ wrap.appendChild(rx);
223
+ }catch(e){d.className='msg error';d.textContent='Error: '+e.message;}
224
+ sending=false;btn.disabled=false;input.focus();
225
+ }
226
+
227
+ fetch('/api/info').then(r=>r.json()).then(d=>{if(d.name)document.getElementById('title').textContent=d.name}).catch(()=>{});
228
+ </script>
229
+ </body>
230
230
  </html>`;
231
- const DASHBOARD_HTML = `<!DOCTYPE html>
232
- <html lang="en">
233
- <head>
234
- <meta charset="UTF-8">
235
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
236
- <title>OPC Dashboard</title>
237
- <style>
238
- *{margin:0;padding:0;box-sizing:border-box}
239
- body{background:#0a0a0f;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px}
240
- h1{font-size:24px;margin-bottom:24px;color:#fff}
241
- .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-bottom:32px}
242
- .card{background:#12121a;border:1px solid #1e1e2e;border-radius:12px;padding:20px}
243
- .card .label{font-size:12px;color:#888;text-transform:uppercase;letter-spacing:1px}
244
- .card .value{font-size:32px;font-weight:700;color:#818cf8;margin-top:4px}
245
- .card .sub{font-size:12px;color:#555;margin-top:4px}
246
- nav{margin-bottom:24px}
247
- nav a{color:#818cf8;text-decoration:none;margin-right:16px;font-size:14px}
248
- nav a:hover{text-decoration:underline}
249
- .chart{background:#12121a;border:1px solid #1e1e2e;border-radius:12px;padding:20px;margin-bottom:16px}
250
- .chart h3{font-size:14px;color:#888;margin-bottom:12px}
251
- </style>
252
- </head>
253
- <body>
254
- <nav><a href="/">← Chat</a><a href="/dashboard">Dashboard</a></nav>
255
- <h1>📊 Agent Dashboard</h1>
256
- <div class="grid">
257
- <div class="card"><div class="label">Sessions</div><div class="value" id="sessions">0</div></div>
258
- <div class="card"><div class="label">Messages</div><div class="value" id="messages">0</div></div>
259
- <div class="card"><div class="label">Avg Response</div><div class="value" id="avgMs">0ms</div></div>
260
- <div class="card"><div class="label">Token Usage</div><div class="value" id="tokens">0</div></div>
261
- <div class="card"><div class="label">Uptime</div><div class="value" id="uptime">0m</div></div>
262
- <div class="card"><div class="label">Knowledge Files</div><div class="value" id="kb">0</div></div>
263
- </div>
264
- <div class="chart"><h3>Messages Over Time</h3><svg id="chart" width="100%" height="120" viewBox="0 0 600 120"></svg></div>
265
- <script>
266
- async function refresh(){
267
- try{
268
- const r=await fetch('/api/dashboard');const d=await r.json();
269
- document.getElementById('sessions').textContent=d.sessions;
270
- document.getElementById('messages').textContent=d.messages;
271
- document.getElementById('avgMs').textContent=d.messages>0?Math.round(d.totalResponseMs/d.messages)+'ms':'0ms';
272
- document.getElementById('tokens').textContent=d.tokenUsage.toLocaleString();
273
- document.getElementById('kb').textContent=d.knowledgeFiles;
274
- const mins=Math.round((Date.now()-d.startedAt)/60000);
275
- document.getElementById('uptime').textContent=mins<60?mins+'m':Math.round(mins/60)+'h '+mins%60+'m';
276
- }catch{}
277
- }
278
- refresh();setInterval(refresh,5000);
279
- </script>
280
- </body>
231
+ const DASHBOARD_HTML = `<!DOCTYPE html>
232
+ <html lang="en">
233
+ <head>
234
+ <meta charset="UTF-8">
235
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
236
+ <title>OPC Dashboard</title>
237
+ <style>
238
+ *{margin:0;padding:0;box-sizing:border-box}
239
+ body{background:#0f0f23;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px}
240
+ h1{font-size:24px;margin-bottom:24px;color:#fff}
241
+ .grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-bottom:32px}
242
+ .card{background:#1a1a3a;border:1px solid #2d2d4e;border-radius:14px;padding:20px}
243
+ .card .label{font-size:12px;color:#8a8aa0;text-transform:uppercase;letter-spacing:1px}
244
+ .card .value{font-size:32px;font-weight:700;color:#818cf8;margin-top:4px}
245
+ .card .sub{font-size:12px;color:#555;margin-top:4px}
246
+ nav{margin-bottom:24px}
247
+ nav a{color:#818cf8;text-decoration:none;margin-right:16px;font-size:14px}
248
+ nav a:hover{text-decoration:underline}
249
+ .chart{background:#1a1a3a;border:1px solid #2d2d4e;border-radius:14px;padding:20px;margin-bottom:16px}
250
+ .chart h3{font-size:14px;color:#888;margin-bottom:12px}
251
+ </style>
252
+ </head>
253
+ <body>
254
+ <nav><a href="/">← Chat</a><a href="/dashboard">Dashboard</a></nav>
255
+ <h1>📊 Agent Dashboard</h1>
256
+ <div class="grid">
257
+ <div class="card"><div class="label">Sessions</div><div class="value" id="sessions">0</div></div>
258
+ <div class="card"><div class="label">Messages</div><div class="value" id="messages">0</div></div>
259
+ <div class="card"><div class="label">Avg Response</div><div class="value" id="avgMs">0ms</div></div>
260
+ <div class="card"><div class="label">Token Usage</div><div class="value" id="tokens">0</div></div>
261
+ <div class="card"><div class="label">Uptime</div><div class="value" id="uptime">0m</div></div>
262
+ <div class="card"><div class="label">Knowledge Files</div><div class="value" id="kb">0</div></div>
263
+ </div>
264
+ <div class="chart"><h3>Messages Over Time</h3><svg id="chart" width="100%" height="120" viewBox="0 0 600 120"></svg></div>
265
+ <script>
266
+ async function refresh(){
267
+ try{
268
+ const r=await fetch('/api/dashboard');const d=await r.json();
269
+ document.getElementById('sessions').textContent=d.sessions;
270
+ document.getElementById('messages').textContent=d.messages;
271
+ document.getElementById('avgMs').textContent=d.messages>0?Math.round(d.totalResponseMs/d.messages)+'ms':'0ms';
272
+ document.getElementById('tokens').textContent=d.tokenUsage.toLocaleString();
273
+ document.getElementById('kb').textContent=d.knowledgeFiles;
274
+ const mins=Math.round((Date.now()-d.startedAt)/60000);
275
+ document.getElementById('uptime').textContent=mins<60?mins+'m':Math.round(mins/60)+'h '+mins%60+'m';
276
+ }catch{}
277
+ }
278
+ refresh();setInterval(refresh,5000);
279
+ </script>
280
+ </body>
281
281
  </html>`;
282
282
  class WebChannel extends index_1.BaseChannel {
283
283
  type = 'web';
@@ -17,6 +17,11 @@ export declare class KnowledgeBase {
17
17
  }>>;
18
18
  /** Build context string for injection into LLM calls */
19
19
  getContext(query: string, topK?: number, minScore?: number): Promise<string>;
20
+ /**
21
+ * Query DeepBrain for semantic search enhancement.
22
+ * Activated when OPC_DEEPBRAIN_ENABLED=true and deepbrain CLI is globally installed.
23
+ */
24
+ private queryDeepBrain;
20
25
  getStats(): {
21
26
  totalEntries: number;
22
27
  sources: string[];
@@ -36,10 +36,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.KnowledgeBase = void 0;
37
37
  /**
38
38
  * Knowledge Base / RAG - Local vector storage with semantic search
39
+ * Supports optional DeepBrain semantic search enhancement via OPC_DEEPBRAIN_ENABLED=true
39
40
  */
40
41
  const fs = __importStar(require("fs"));
41
42
  const path = __importStar(require("path"));
42
43
  const crypto = __importStar(require("crypto"));
44
+ const child_process_1 = require("child_process");
43
45
  const CHUNK_SIZE = 500; // chars per chunk
44
46
  const CHUNK_OVERLAP = 50;
45
47
  const STORE_FILE = '.opc-knowledge.json';
@@ -185,9 +187,44 @@ class KnowledgeBase {
185
187
  async getContext(query, topK = 3, minScore = 0.1) {
186
188
  const results = await this.search(query, topK);
187
189
  const relevant = results.filter(r => r.score >= minScore);
188
- if (relevant.length === 0)
190
+ let context = '';
191
+ if (relevant.length > 0) {
192
+ context = `\n\n--- Relevant Knowledge ---\n${relevant.map((r, i) => `[${i + 1}] (source: ${r.source}, relevance: ${(r.score * 100).toFixed(0)}%)\n${r.content}`).join('\n\n')}\n--- End Knowledge ---\n`;
193
+ }
194
+ // Enhance with DeepBrain semantic search if enabled
195
+ const deepBrainCtx = this.queryDeepBrain(query, topK);
196
+ return context + deepBrainCtx;
197
+ }
198
+ /**
199
+ * Query DeepBrain for semantic search enhancement.
200
+ * Activated when OPC_DEEPBRAIN_ENABLED=true and deepbrain CLI is globally installed.
201
+ */
202
+ queryDeepBrain(query, topK) {
203
+ if (process.env.OPC_DEEPBRAIN_ENABLED !== 'true')
189
204
  return '';
190
- return `\n\n--- Relevant Knowledge ---\n${relevant.map((r, i) => `[${i + 1}] (source: ${r.source}, relevance: ${(r.score * 100).toFixed(0)}%)\n${r.content}`).join('\n\n')}\n--- End Knowledge ---\n`;
205
+ // Verify deepbrain is installed
206
+ const check = (0, child_process_1.spawnSync)('deepbrain', ['--version'], { encoding: 'utf-8', timeout: 3000 });
207
+ if (check.error || check.status !== 0)
208
+ return '';
209
+ try {
210
+ const result = (0, child_process_1.spawnSync)('deepbrain', ['query', query, '--top', String(topK), '--format', 'json'], { encoding: 'utf-8', timeout: 5000 });
211
+ if (result.status !== 0 || !result.stdout?.trim())
212
+ return '';
213
+ const parsed = JSON.parse(result.stdout);
214
+ const items = Array.isArray(parsed)
215
+ ? parsed
216
+ : (parsed.results ?? []);
217
+ if (items.length === 0)
218
+ return '';
219
+ return `\n\n--- DeepBrain Knowledge ---\n${items.map((r, i) => {
220
+ const relevance = r.score != null ? `${(r.score * 100).toFixed(0)}%` : 'n/a';
221
+ const text = r.content ?? r.text ?? '';
222
+ return `[${i + 1}] (source: ${r.source ?? 'deepbrain'}, relevance: ${relevance})\n${text}`;
223
+ }).join('\n\n')}\n--- End DeepBrain Knowledge ---\n`;
224
+ }
225
+ catch {
226
+ return '';
227
+ }
191
228
  }
192
229
  getStats() {
193
230
  const sources = [...new Set(this.store.entries.map(e => String(e.metadata.source)))];