opc-agent 1.1.3 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/CONTRIBUTING.md +75 -75
  3. package/README.md +429 -429
  4. package/README.zh-CN.md +415 -415
  5. package/dist/channels/web.js +256 -256
  6. package/dist/core/streaming.d.ts +56 -0
  7. package/dist/core/streaming.js +160 -0
  8. package/dist/deploy/hermes.js +22 -22
  9. package/dist/deploy/openclaw.js +31 -31
  10. package/dist/index.d.ts +4 -0
  11. package/dist/index.js +7 -1
  12. package/dist/providers/index.d.ts +1 -1
  13. package/dist/providers/index.js +13 -148
  14. package/dist/schema/oad.d.ts +3 -3
  15. package/dist/templates/code-reviewer.js +5 -5
  16. package/dist/templates/customer-service.js +2 -2
  17. package/dist/templates/data-analyst.js +5 -5
  18. package/dist/templates/knowledge-base.js +2 -2
  19. package/dist/templates/sales-assistant.js +4 -4
  20. package/dist/templates/teacher.js +6 -6
  21. package/dist/tools/gateway.d.ts +28 -0
  22. package/dist/tools/gateway.js +177 -0
  23. package/docs/.vitepress/config.ts +103 -103
  24. package/docs/api/cli.md +48 -48
  25. package/docs/api/oad-schema.md +64 -64
  26. package/docs/api/sdk.md +80 -80
  27. package/docs/guide/concepts.md +51 -51
  28. package/docs/guide/configuration.md +79 -79
  29. package/docs/guide/deployment.md +42 -42
  30. package/docs/guide/getting-started.md +44 -44
  31. package/docs/guide/templates.md +28 -28
  32. package/docs/guide/testing.md +84 -84
  33. package/docs/index.md +27 -27
  34. package/docs/zh/api/cli.md +54 -54
  35. package/docs/zh/api/oad-schema.md +87 -87
  36. package/docs/zh/api/sdk.md +102 -102
  37. package/docs/zh/guide/concepts.md +104 -104
  38. package/docs/zh/guide/configuration.md +135 -135
  39. package/docs/zh/guide/deployment.md +81 -81
  40. package/docs/zh/guide/getting-started.md +82 -82
  41. package/docs/zh/guide/templates.md +84 -84
  42. package/docs/zh/guide/testing.md +88 -88
  43. package/docs/zh/index.md +27 -27
  44. package/examples/customer-service-demo/README.md +90 -90
  45. package/examples/customer-service-demo/oad.yaml +107 -107
  46. package/package.json +1 -1
  47. package/src/analytics/index.ts +66 -66
  48. package/src/channels/discord.ts +192 -192
  49. package/src/channels/email.ts +177 -177
  50. package/src/channels/feishu.ts +236 -236
  51. package/src/channels/index.ts +15 -15
  52. package/src/channels/slack.ts +160 -160
  53. package/src/channels/telegram.ts +90 -90
  54. package/src/channels/voice.ts +106 -106
  55. package/src/channels/web.ts +17 -17
  56. package/src/channels/webhook.ts +199 -199
  57. package/src/channels/websocket.ts +87 -87
  58. package/src/channels/wechat.ts +149 -149
  59. package/src/core/a2a.ts +143 -143
  60. package/src/core/agent.ts +152 -152
  61. package/src/core/analytics-engine.ts +186 -186
  62. package/src/core/auth.ts +57 -57
  63. package/src/core/cache.ts +141 -141
  64. package/src/core/compose.ts +77 -77
  65. package/src/core/config.ts +14 -14
  66. package/src/core/errors.ts +148 -148
  67. package/src/core/hitl.ts +138 -138
  68. package/src/core/knowledge.ts +49 -4
  69. package/src/core/logger.ts +57 -57
  70. package/src/core/orchestrator.ts +215 -215
  71. package/src/core/performance.ts +187 -187
  72. package/src/core/rate-limiter.ts +128 -128
  73. package/src/core/room.ts +109 -109
  74. package/src/core/runtime.ts +152 -152
  75. package/src/core/sandbox.ts +101 -101
  76. package/src/core/security.ts +171 -171
  77. package/src/core/streaming.ts +195 -0
  78. package/src/core/types.ts +68 -68
  79. package/src/core/versioning.ts +106 -106
  80. package/src/core/watch.ts +178 -178
  81. package/src/core/workflow.ts +235 -235
  82. package/src/deploy/hermes.ts +156 -156
  83. package/src/deploy/openclaw.ts +200 -200
  84. package/src/dtv/data.ts +29 -29
  85. package/src/dtv/trust.ts +43 -43
  86. package/src/dtv/value.ts +47 -47
  87. package/src/i18n/index.ts +216 -216
  88. package/src/index.ts +6 -0
  89. package/src/marketplace/index.ts +223 -223
  90. package/src/memory/deepbrain.ts +108 -108
  91. package/src/memory/index.ts +34 -34
  92. package/src/plugins/index.ts +208 -208
  93. package/src/providers/index.ts +12 -3
  94. package/src/schema/oad.ts +155 -155
  95. package/src/skills/base.ts +16 -16
  96. package/src/skills/document.ts +100 -100
  97. package/src/skills/http.ts +35 -35
  98. package/src/skills/index.ts +27 -27
  99. package/src/skills/scheduler.ts +80 -80
  100. package/src/skills/webhook-trigger.ts +59 -59
  101. package/src/templates/code-reviewer.ts +34 -34
  102. package/src/templates/customer-service.ts +80 -80
  103. package/src/templates/data-analyst.ts +70 -70
  104. package/src/templates/executive-assistant.ts +71 -71
  105. package/src/templates/financial-advisor.ts +60 -60
  106. package/src/templates/knowledge-base.ts +31 -31
  107. package/src/templates/legal-assistant.ts +71 -71
  108. package/src/templates/sales-assistant.ts +79 -79
  109. package/src/templates/teacher.ts +79 -79
  110. package/src/testing/index.ts +181 -181
  111. package/src/tools/calculator.ts +73 -73
  112. package/src/tools/datetime.ts +149 -149
  113. package/src/tools/gateway.ts +220 -0
  114. package/src/tools/json-transform.ts +187 -187
  115. package/src/tools/mcp.ts +76 -76
  116. package/src/tools/text-analysis.ts +116 -116
  117. package/templates/Dockerfile +15 -15
  118. package/templates/code-reviewer/README.md +27 -27
  119. package/templates/code-reviewer/oad.yaml +41 -41
  120. package/templates/customer-service/README.md +22 -22
  121. package/templates/customer-service/oad.yaml +36 -36
  122. package/templates/docker-compose.yml +21 -21
  123. package/templates/ecommerce-assistant/README.md +45 -0
  124. package/templates/ecommerce-assistant/oad.yaml +47 -0
  125. package/templates/knowledge-base/README.md +28 -28
  126. package/templates/knowledge-base/oad.yaml +38 -38
  127. package/templates/sales-assistant/README.md +26 -26
  128. package/templates/sales-assistant/oad.yaml +43 -43
  129. package/templates/tech-support/README.md +43 -0
  130. package/templates/tech-support/oad.yaml +45 -0
  131. package/tests/a2a.test.ts +66 -66
  132. package/tests/agent.test.ts +72 -72
  133. package/tests/analytics.test.ts +50 -50
  134. package/tests/channel.test.ts +39 -39
  135. package/tests/e2e.test.ts +134 -134
  136. package/tests/errors.test.ts +83 -83
  137. package/tests/gateway.test.ts +71 -0
  138. package/tests/hitl.test.ts +71 -71
  139. package/tests/i18n.test.ts +41 -41
  140. package/tests/mcp.test.ts +54 -54
  141. package/tests/oad.test.ts +68 -68
  142. package/tests/performance.test.ts +115 -115
  143. package/tests/plugin.test.ts +74 -74
  144. package/tests/room.test.ts +106 -106
  145. package/tests/runtime.test.ts +42 -42
  146. package/tests/sandbox.test.ts +46 -46
  147. package/tests/security.test.ts +60 -60
  148. package/tests/streaming.test.ts +109 -0
  149. package/tests/templates.test.ts +77 -77
  150. package/tests/v070.test.ts +76 -76
  151. package/tests/versioning.test.ts +75 -75
  152. package/tests/voice.test.ts +61 -61
  153. package/tests/webhook.test.ts +29 -29
  154. package/tests/workflow.test.ts +143 -143
  155. package/tsconfig.json +19 -19
  156. package/vitest.config.ts +9 -9
@@ -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:#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>
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:#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>
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:#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>
281
281
  </html>`;
282
282
  class WebChannel extends index_1.BaseChannel {
283
283
  type = 'web';
@@ -0,0 +1,56 @@
1
+ import { EventEmitter } from 'events';
2
+ export interface StreamChunk {
3
+ id: string;
4
+ type: 'text' | 'tool_call' | 'error' | 'done';
5
+ data: string;
6
+ timestamp: number;
7
+ metadata?: Record<string, unknown>;
8
+ }
9
+ export interface StreamOptions {
10
+ /** High-water mark for backpressure (default 64 chunks). */
11
+ highWaterMark?: number;
12
+ /** Heartbeat interval in ms to keep connection alive (default 15000). */
13
+ heartbeatInterval?: number;
14
+ }
15
+ export declare class StreamableResponse extends EventEmitter {
16
+ readonly id: string;
17
+ private chunks;
18
+ private ended;
19
+ private paused;
20
+ private buffer;
21
+ private highWaterMark;
22
+ constructor(id: string, options?: StreamOptions);
23
+ /** Push a chunk. Returns false if backpressure threshold reached. */
24
+ push(chunk: StreamChunk): boolean;
25
+ /** Resume after backpressure — flush buffered chunks. */
26
+ resume(): void;
27
+ end(): void;
28
+ getChunks(): StreamChunk[];
29
+ /** Collect all text chunks into a single string. */
30
+ getText(): string;
31
+ get isEnded(): boolean;
32
+ get isPaused(): boolean;
33
+ get length(): number;
34
+ }
35
+ export declare class StreamingManager {
36
+ private streams;
37
+ private counter;
38
+ /** Create a new stream. */
39
+ createStream(options?: StreamOptions): StreamableResponse;
40
+ /** Write a text chunk to a stream. */
41
+ writeChunk(streamId: string, data: string, metadata?: Record<string, unknown>): boolean;
42
+ /** End a stream. */
43
+ endStream(streamId: string): void;
44
+ /** Get an existing stream. */
45
+ getStream(streamId: string): StreamableResponse | undefined;
46
+ /** Format a chunk as an SSE event string. */
47
+ static formatSSE(chunk: StreamChunk): string;
48
+ /** Pipe a stream to an SSE-compatible HTTP response (Express-style). */
49
+ static pipeSSE(stream: StreamableResponse, res: {
50
+ write(data: string): boolean;
51
+ end(): void;
52
+ setHeader?(name: string, value: string): void;
53
+ }, options?: StreamOptions): void;
54
+ get activeCount(): number;
55
+ }
56
+ //# sourceMappingURL=streaming.d.ts.map