opc-agent 2.0.0 → 2.0.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/dist/channels/email.d.ts +32 -26
  2. package/dist/channels/email.js +239 -62
  3. package/dist/channels/feishu.d.ts +21 -6
  4. package/dist/channels/feishu.js +225 -126
  5. package/dist/channels/websocket.d.ts +46 -3
  6. package/dist/channels/websocket.js +306 -37
  7. package/dist/channels/wechat.d.ts +33 -13
  8. package/dist/channels/wechat.js +229 -42
  9. package/dist/cli.js +712 -11
  10. package/dist/core/a2a.d.ts +17 -0
  11. package/dist/core/a2a.js +43 -1
  12. package/dist/core/agent.d.ts +16 -0
  13. package/dist/core/agent.js +108 -0
  14. package/dist/core/runtime.d.ts +6 -0
  15. package/dist/core/runtime.js +161 -2
  16. package/dist/core/sandbox.d.ts +26 -0
  17. package/dist/core/sandbox.js +117 -0
  18. package/dist/core/workflow-graph.d.ts +93 -0
  19. package/dist/core/workflow-graph.js +247 -0
  20. package/dist/doctor.d.ts +15 -0
  21. package/dist/doctor.js +183 -0
  22. package/dist/eval/index.d.ts +65 -0
  23. package/dist/eval/index.js +191 -0
  24. package/dist/index.d.ts +30 -6
  25. package/dist/index.js +60 -4
  26. package/dist/plugins/content-filter.d.ts +7 -0
  27. package/dist/plugins/content-filter.js +25 -0
  28. package/dist/plugins/index.d.ts +42 -0
  29. package/dist/plugins/index.js +108 -2
  30. package/dist/plugins/logger.d.ts +6 -0
  31. package/dist/plugins/logger.js +20 -0
  32. package/dist/plugins/rate-limiter.d.ts +7 -0
  33. package/dist/plugins/rate-limiter.js +35 -0
  34. package/dist/protocols/a2a/client.d.ts +25 -0
  35. package/dist/protocols/a2a/client.js +115 -0
  36. package/dist/protocols/a2a/index.d.ts +6 -0
  37. package/dist/protocols/a2a/index.js +12 -0
  38. package/dist/protocols/a2a/server.d.ts +41 -0
  39. package/dist/protocols/a2a/server.js +295 -0
  40. package/dist/protocols/a2a/types.d.ts +91 -0
  41. package/dist/protocols/a2a/types.js +15 -0
  42. package/dist/protocols/a2a/utils.d.ts +6 -0
  43. package/dist/protocols/a2a/utils.js +47 -0
  44. package/dist/protocols/agui/client.d.ts +10 -0
  45. package/dist/protocols/agui/client.js +75 -0
  46. package/dist/protocols/agui/index.d.ts +4 -0
  47. package/dist/protocols/agui/index.js +25 -0
  48. package/dist/protocols/agui/server.d.ts +37 -0
  49. package/dist/protocols/agui/server.js +191 -0
  50. package/dist/protocols/agui/types.d.ts +107 -0
  51. package/dist/protocols/agui/types.js +17 -0
  52. package/dist/protocols/index.d.ts +2 -0
  53. package/dist/protocols/index.js +19 -0
  54. package/dist/protocols/mcp/agent-tools.d.ts +11 -0
  55. package/dist/protocols/mcp/agent-tools.js +129 -0
  56. package/dist/protocols/mcp/index.d.ts +5 -0
  57. package/dist/protocols/mcp/index.js +11 -0
  58. package/dist/protocols/mcp/server.d.ts +31 -0
  59. package/dist/protocols/mcp/server.js +248 -0
  60. package/dist/protocols/mcp/types.d.ts +92 -0
  61. package/dist/protocols/mcp/types.js +17 -0
  62. package/dist/publish/index.d.ts +45 -0
  63. package/dist/publish/index.js +350 -0
  64. package/dist/schema/oad.d.ts +682 -65
  65. package/dist/schema/oad.js +36 -3
  66. package/dist/security/approval.d.ts +36 -0
  67. package/dist/security/approval.js +113 -0
  68. package/dist/security/index.d.ts +4 -0
  69. package/dist/security/index.js +8 -0
  70. package/dist/security/keys.d.ts +16 -0
  71. package/dist/security/keys.js +117 -0
  72. package/dist/studio/server.d.ts +63 -0
  73. package/dist/studio/server.js +625 -0
  74. package/dist/studio-ui/index.html +662 -0
  75. package/dist/telemetry/index.d.ts +93 -0
  76. package/dist/telemetry/index.js +285 -0
  77. package/package.json +5 -3
  78. package/scripts/install.ps1 +31 -0
  79. package/scripts/install.sh +40 -0
  80. package/src/channels/email.ts +351 -177
  81. package/src/channels/feishu.ts +349 -236
  82. package/src/channels/websocket.ts +399 -87
  83. package/src/channels/wechat.ts +329 -149
  84. package/src/cli.ts +783 -12
  85. package/src/core/a2a.ts +60 -0
  86. package/src/core/agent.ts +125 -0
  87. package/src/core/runtime.ts +127 -0
  88. package/src/core/sandbox.ts +143 -0
  89. package/src/core/workflow-graph.ts +365 -0
  90. package/src/doctor.ts +156 -0
  91. package/src/eval/index.ts +211 -0
  92. package/src/eval/suites/basic.json +16 -0
  93. package/src/eval/suites/memory.json +12 -0
  94. package/src/eval/suites/safety.json +14 -0
  95. package/src/index.ts +54 -6
  96. package/src/plugins/content-filter.ts +23 -0
  97. package/src/plugins/index.ts +133 -2
  98. package/src/plugins/logger.ts +18 -0
  99. package/src/plugins/rate-limiter.ts +38 -0
  100. package/src/protocols/a2a/client.ts +132 -0
  101. package/src/protocols/a2a/index.ts +8 -0
  102. package/src/protocols/a2a/server.ts +333 -0
  103. package/src/protocols/a2a/types.ts +88 -0
  104. package/src/protocols/a2a/utils.ts +50 -0
  105. package/src/protocols/agui/client.ts +83 -0
  106. package/src/protocols/agui/index.ts +4 -0
  107. package/src/protocols/agui/server.ts +218 -0
  108. package/src/protocols/agui/types.ts +153 -0
  109. package/src/protocols/index.ts +2 -0
  110. package/src/protocols/mcp/agent-tools.ts +134 -0
  111. package/src/protocols/mcp/index.ts +8 -0
  112. package/src/protocols/mcp/server.ts +262 -0
  113. package/src/protocols/mcp/types.ts +69 -0
  114. package/src/publish/index.ts +376 -0
  115. package/src/schema/oad.ts +39 -2
  116. package/src/security/approval.ts +131 -0
  117. package/src/security/index.ts +3 -0
  118. package/src/security/keys.ts +87 -0
  119. package/src/studio/server.ts +629 -0
  120. package/src/studio-ui/index.html +662 -0
  121. package/src/telemetry/index.ts +324 -0
  122. package/src/types/agent-workstation.d.ts +2 -0
  123. package/tests/a2a-protocol.test.ts +285 -0
  124. package/tests/agui-protocol.test.ts +246 -0
  125. package/tests/channels/discord.test.ts +79 -0
  126. package/tests/channels/email.test.ts +148 -0
  127. package/tests/channels/feishu.test.ts +123 -0
  128. package/tests/channels/telegram.test.ts +129 -0
  129. package/tests/channels/websocket.test.ts +53 -0
  130. package/tests/channels/wechat.test.ts +170 -0
  131. package/tests/chat-cli.test.ts +160 -0
  132. package/tests/daemon.test.ts +135 -0
  133. package/tests/deepbrain-wire.test.ts +234 -0
  134. package/tests/doctor.test.ts +38 -0
  135. package/tests/eval.test.ts +173 -0
  136. package/tests/init-role.test.ts +124 -0
  137. package/tests/mcp-client.test.ts +92 -0
  138. package/tests/mcp-server.test.ts +178 -0
  139. package/tests/plugin-a2a-enhanced.test.ts +230 -0
  140. package/tests/publish.test.ts +231 -0
  141. package/tests/scheduler.test.ts +200 -0
  142. package/tests/security-enhanced.test.ts +233 -0
  143. package/tests/skill-learner.test.ts +161 -0
  144. package/tests/studio.test.ts +229 -0
  145. package/tests/subagent.test.ts +63 -0
  146. package/tests/telemetry.test.ts +186 -0
  147. package/tests/tools/builtin-extended.test.ts +138 -0
  148. package/tests/workflow-graph.test.ts +279 -0
  149. package/tutorial/customer-service-agent/README.md +612 -0
  150. package/tutorial/customer-service-agent/SOUL.md +26 -0
  151. package/tutorial/customer-service-agent/agent.yaml +63 -0
  152. package/tutorial/customer-service-agent/package.json +19 -0
  153. package/tutorial/customer-service-agent/src/index.ts +69 -0
  154. package/tutorial/customer-service-agent/src/skills/faq.ts +27 -0
  155. package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -0
  156. package/tutorial/customer-service-agent/tsconfig.json +14 -0
@@ -0,0 +1,662 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>OPC Studio</title>
7
+ <style>
8
+ /* === Global === */
9
+ * { margin: 0; padding: 0; box-sizing: border-box; }
10
+ :root {
11
+ --bg: #0a0a0a;
12
+ --bg-card: #141414;
13
+ --bg-hover: #1a1a1a;
14
+ --border: #262626;
15
+ --text: #e5e5e5;
16
+ --text-muted: #737373;
17
+ --accent: #3b82f6;
18
+ --accent-hover: #2563eb;
19
+ --green: #22c55e;
20
+ --red: #ef4444;
21
+ --yellow: #eab308;
22
+ --purple: #a855f7;
23
+ --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
24
+ --mono: 'SF Mono', 'Fira Code', monospace;
25
+ --radius: 8px;
26
+ }
27
+ body { font-family: var(--font); background: var(--bg); color: var(--text); min-height: 100vh; }
28
+
29
+ /* === Layout === */
30
+ .app { display: flex; min-height: 100vh; }
31
+
32
+ /* Sidebar */
33
+ .sidebar {
34
+ width: 240px; background: var(--bg-card); border-right: 1px solid var(--border);
35
+ padding: 16px; display: flex; flex-direction: column; position: fixed; height: 100vh;
36
+ }
37
+ .sidebar-logo { font-size: 18px; font-weight: 700; padding: 8px 12px; margin-bottom: 24px; display: flex; align-items: center; gap: 8px; }
38
+ .sidebar-logo span { color: var(--accent); }
39
+ .sidebar-nav { flex: 1; }
40
+ .nav-item {
41
+ display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: var(--radius);
42
+ cursor: pointer; color: var(--text-muted); transition: all 0.15s; font-size: 14px; margin-bottom: 2px;
43
+ }
44
+ .nav-item:hover { background: var(--bg-hover); color: var(--text); }
45
+ .nav-item.active { background: var(--bg-hover); color: var(--text); font-weight: 500; }
46
+ .nav-item .icon { width: 18px; text-align: center; }
47
+
48
+ /* Main content */
49
+ .main { flex: 1; margin-left: 240px; padding: 32px; max-width: 1200px; }
50
+
51
+ /* Header */
52
+ .page-header { margin-bottom: 32px; }
53
+ .page-title { font-size: 24px; font-weight: 700; margin-bottom: 4px; }
54
+ .page-subtitle { color: var(--text-muted); font-size: 14px; }
55
+
56
+ /* Cards */
57
+ .card {
58
+ background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius);
59
+ padding: 20px; margin-bottom: 16px;
60
+ }
61
+ .card-title { font-size: 14px; font-weight: 600; margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
62
+ .card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px; margin-bottom: 24px; }
63
+
64
+ /* Stats */
65
+ .stat { text-align: center; padding: 16px; }
66
+ .stat-value { font-size: 32px; font-weight: 700; color: var(--accent); }
67
+ .stat-label { font-size: 12px; color: var(--text-muted); margin-top: 4px; text-transform: uppercase; letter-spacing: 0.5px; }
68
+
69
+ /* Status badge */
70
+ .badge { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px; font-size: 12px; font-weight: 500; }
71
+ .badge-green { background: rgba(34,197,94,0.1); color: var(--green); }
72
+ .badge-red { background: rgba(239,68,68,0.1); color: var(--red); }
73
+ .badge-yellow { background: rgba(234,179,8,0.1); color: var(--yellow); }
74
+ .badge-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
75
+
76
+ /* Table */
77
+ .table { width: 100%; border-collapse: collapse; }
78
+ .table th { text-align: left; padding: 10px 12px; font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid var(--border); }
79
+ .table td { padding: 10px 12px; font-size: 14px; border-bottom: 1px solid var(--border); }
80
+ .table tr:hover { background: var(--bg-hover); }
81
+
82
+ /* Chat */
83
+ .chat-container { height: 500px; display: flex; flex-direction: column; }
84
+ .chat-messages { flex: 1; overflow-y: auto; padding: 16px; }
85
+ .chat-input-row { display: flex; gap: 8px; padding: 16px; border-top: 1px solid var(--border); }
86
+ .chat-input { flex: 1; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 14px; color: var(--text); font-size: 14px; outline: none; }
87
+ .chat-input:focus { border-color: var(--accent); }
88
+ .chat-send { background: var(--accent); color: white; border: none; border-radius: var(--radius); padding: 10px 20px; font-weight: 500; cursor: pointer; }
89
+ .chat-send:hover { background: var(--accent-hover); }
90
+ .message { margin-bottom: 16px; }
91
+ .message-user { text-align: right; }
92
+ .message-user .bubble { background: var(--accent); color: white; display: inline-block; padding: 10px 14px; border-radius: 14px 14px 4px 14px; max-width: 70%; text-align: left; }
93
+ .message-agent .bubble { background: var(--bg-hover); display: inline-block; padding: 10px 14px; border-radius: 14px 14px 14px 4px; max-width: 70%; }
94
+ .message-label { font-size: 11px; color: var(--text-muted); margin-bottom: 4px; }
95
+
96
+ /* Config editor */
97
+ .editor { width: 100%; min-height: 400px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; color: var(--text); font-family: var(--mono); font-size: 13px; line-height: 1.6; resize: vertical; outline: none; }
98
+ .editor:focus { border-color: var(--accent); }
99
+
100
+ /* Button */
101
+ .btn { padding: 8px 16px; border-radius: var(--radius); font-size: 13px; font-weight: 500; cursor: pointer; border: 1px solid var(--border); background: var(--bg-card); color: var(--text); transition: all 0.15s; }
102
+ .btn:hover { background: var(--bg-hover); }
103
+ .btn-primary { background: var(--accent); border-color: var(--accent); color: white; }
104
+ .btn-primary:hover { background: var(--accent-hover); }
105
+
106
+ /* Doctor checks */
107
+ .check-item { display: flex; align-items: center; gap: 10px; padding: 8px 0; }
108
+ .check-icon { font-size: 16px; }
109
+ .check-name { font-weight: 500; min-width: 180px; }
110
+ .check-detail { color: var(--text-muted); font-size: 13px; }
111
+ .check-fix { color: var(--yellow); font-size: 12px; font-style: italic; }
112
+
113
+ /* Memory list */
114
+ .memory-item { padding: 12px; border-bottom: 1px solid var(--border); cursor: pointer; }
115
+ .memory-item:hover { background: var(--bg-hover); }
116
+ .memory-slug { font-weight: 500; font-family: var(--mono); font-size: 13px; }
117
+ .memory-preview { color: var(--text-muted); font-size: 13px; margin-top: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
118
+ .memory-meta { font-size: 11px; color: var(--text-muted); margin-top: 4px; }
119
+
120
+ /* Search */
121
+ .search-bar { display: flex; gap: 8px; margin-bottom: 16px; }
122
+ .search-input { flex: 1; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); padding: 8px 14px; color: var(--text); font-size: 14px; outline: none; }
123
+
124
+ /* Page sections (hidden by default) */
125
+ .page { display: none; }
126
+ .page.active { display: block; }
127
+
128
+ /* Scrollbar */
129
+ ::-webkit-scrollbar { width: 6px; }
130
+ ::-webkit-scrollbar-track { background: transparent; }
131
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
132
+
133
+ /* Loading */
134
+ .loading { color: var(--text-muted); text-align: center; padding: 40px; }
135
+ .spinner { display: inline-block; width: 20px; height: 20px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.8s linear infinite; }
136
+ @keyframes spin { to { transform: rotate(360deg); } }
137
+ </style>
138
+ </head>
139
+ <body>
140
+ <div class="app">
141
+ <!-- Sidebar -->
142
+ <nav class="sidebar">
143
+ <div class="sidebar-logo">⚡ <span>OPC</span> Studio</div>
144
+ <div class="sidebar-nav">
145
+ <div class="nav-item active" data-page="dashboard"><span class="icon">📊</span> Dashboard</div>
146
+ <div class="nav-item" data-page="chat"><span class="icon">💬</span> Chat</div>
147
+ <div class="nav-item" data-page="config"><span class="icon">⚙️</span> Config</div>
148
+ <div class="nav-item" data-page="memory"><span class="icon">🧠</span> Memory</div>
149
+ <div class="nav-item" data-page="skills"><span class="icon">🛠</span> Skills</div>
150
+ <div class="nav-item" data-page="tools"><span class="icon">🔧</span> Tools</div>
151
+ <div class="nav-item" data-page="channels"><span class="icon">📡</span> Channels</div>
152
+ <div class="nav-item" data-page="workflows"><span class="icon">🔀</span> Workflows</div>
153
+ <div class="nav-item" data-page="jobs"><span class="icon">⏰</span> Jobs</div>
154
+ <div class="nav-item" data-page="plugins"><span class="icon">🔌</span> Plugins</div>
155
+ <div class="nav-item" data-page="protocols"><span class="icon">📡</span> Protocols</div>
156
+ <div class="nav-item" data-page="doctor"><span class="icon">🩺</span> Doctor</div>
157
+ <div class="nav-item" data-page="evals"><span class="icon">🧪</span> Evals</div>
158
+ <div class="nav-item" data-page="telemetry"><span class="icon">📈</span> Telemetry</div>
159
+ <div class="nav-item" data-page="logs"><span class="icon">📜</span> Logs</div>
160
+ <div style="padding: 8px 12px; margin-top: 16px; font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1px;">Modules</div>
161
+ <div class="nav-item" data-page="modules"><span class="icon">🔌</span> Modules</div>
162
+ <div class="nav-item" data-page="brain-module"><span class="icon">🧠</span> DeepBrain</div>
163
+ <div class="nav-item" data-page="kits-module"><span class="icon">📊</span> AgentKits</div>
164
+ <div class="nav-item" data-page="workstation-module"><span class="icon">👤</span> Workstation</div>
165
+ </div>
166
+ <div style="padding: 8px 12px; font-size: 11px; color: var(--text-muted);">OPC Agent v2.1</div>
167
+ </nav>
168
+
169
+ <!-- Main Content -->
170
+ <main class="main">
171
+ <!-- Dashboard -->
172
+ <div class="page active" id="page-dashboard">
173
+ <div class="page-header">
174
+ <div class="page-title">Dashboard</div>
175
+ <div class="page-subtitle">Agent overview and health status</div>
176
+ </div>
177
+ <div class="card-grid">
178
+ <div class="card stat"><div class="stat-value" id="agent-name">—</div><div class="stat-label">Agent Name</div></div>
179
+ <div class="card stat"><div class="stat-value" id="agent-model">—</div><div class="stat-label">Model</div></div>
180
+ <div class="card stat"><div class="stat-value" id="agent-channels">—</div><div class="stat-label">Channels</div></div>
181
+ <div class="card stat"><div class="stat-value" id="agent-skills">—</div><div class="stat-label">Skills</div></div>
182
+ </div>
183
+ <div class="card">
184
+ <div class="card-title">🟢 Agent Status</div>
185
+ <div id="agent-status-detail">Loading...</div>
186
+ </div>
187
+ <div class="card">
188
+ <div class="card-title">🧠 Memory Stats</div>
189
+ <div id="memory-stats">Loading...</div>
190
+ </div>
191
+ </div>
192
+
193
+ <!-- Chat -->
194
+ <div class="page" id="page-chat">
195
+ <div class="page-header">
196
+ <div class="page-title">Chat</div>
197
+ <div class="page-subtitle">Test your agent in real-time</div>
198
+ </div>
199
+ <div class="card chat-container">
200
+ <div class="chat-messages" id="chat-messages">
201
+ <div class="message message-agent"><div class="message-label">Agent</div><div class="bubble">Hi! I'm ready to chat. Type a message below.</div></div>
202
+ </div>
203
+ <div class="chat-input-row">
204
+ <input class="chat-input" id="chat-input" placeholder="Type a message..." onkeydown="if(event.key==='Enter')sendChat()">
205
+ <button class="chat-send" onclick="sendChat()">Send</button>
206
+ </div>
207
+ </div>
208
+ </div>
209
+
210
+ <!-- Config -->
211
+ <div class="page" id="page-config">
212
+ <div class="page-header">
213
+ <div class="page-title">Configuration</div>
214
+ <div class="page-subtitle">Edit agent.yaml</div>
215
+ </div>
216
+ <div class="card">
217
+ <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:12px;">
218
+ <div class="card-title" style="margin:0">agent.yaml</div>
219
+ <button class="btn btn-primary" onclick="saveConfig()">Save</button>
220
+ </div>
221
+ <textarea class="editor" id="config-editor">Loading...</textarea>
222
+ </div>
223
+ </div>
224
+
225
+ <!-- Memory -->
226
+ <div class="page" id="page-memory">
227
+ <div class="page-header">
228
+ <div class="page-title">Memory</div>
229
+ <div class="page-subtitle">DeepBrain knowledge pages</div>
230
+ </div>
231
+ <div class="search-bar">
232
+ <input class="search-input" id="memory-search" placeholder="Search memories..." onkeydown="if(event.key==='Enter')searchMemory()">
233
+ <button class="btn" onclick="searchMemory()">Search</button>
234
+ </div>
235
+ <div class="card" id="memory-list">Loading...</div>
236
+ </div>
237
+
238
+ <!-- Skills -->
239
+ <div class="page" id="page-skills">
240
+ <div class="page-header">
241
+ <div class="page-title">Skills</div>
242
+ <div class="page-subtitle">Agent capabilities</div>
243
+ </div>
244
+ <div class="card" id="skills-list">Loading...</div>
245
+ </div>
246
+
247
+ <!-- Tools -->
248
+ <div class="page" id="page-tools">
249
+ <div class="page-header">
250
+ <div class="page-title">Tools</div>
251
+ <div class="page-subtitle">Built-in and MCP tools</div>
252
+ </div>
253
+ <div class="card" id="tools-list">Loading...</div>
254
+ </div>
255
+
256
+ <!-- Channels -->
257
+ <div class="page" id="page-channels">
258
+ <div class="page-header">
259
+ <div class="page-title">Channels</div>
260
+ <div class="page-subtitle">Communication endpoints</div>
261
+ </div>
262
+ <div class="card" id="channels-list">Loading...</div>
263
+ </div>
264
+
265
+ <!-- Workflows -->
266
+ <div class="page" id="page-workflows">
267
+ <div class="page-header">
268
+ <div class="page-title">Workflows</div>
269
+ <div class="page-subtitle">Agent workflow definitions</div>
270
+ </div>
271
+ <div class="card" id="workflows-list">Loading...</div>
272
+ </div>
273
+
274
+ <!-- Jobs -->
275
+ <div class="page" id="page-jobs">
276
+ <div class="page-header">
277
+ <div class="page-title">Scheduled Jobs</div>
278
+ <div class="page-subtitle">Cron tasks</div>
279
+ </div>
280
+ <div class="card" id="jobs-list">Loading...</div>
281
+ </div>
282
+
283
+ <!-- Plugins -->
284
+ <div class="page" id="page-plugins">
285
+ <div class="page-header">
286
+ <div class="page-title">Plugins</div>
287
+ <div class="page-subtitle">Middleware and extensions</div>
288
+ </div>
289
+ <div class="card" id="plugins-list">Loading...</div>
290
+ </div>
291
+
292
+ <!-- Doctor -->
293
+ <div class="page" id="page-doctor">
294
+ <div class="page-header">
295
+ <div class="page-title">Doctor</div>
296
+ <div class="page-subtitle">Environment health check</div>
297
+ </div>
298
+ <div class="card" id="doctor-results">
299
+ <button class="btn btn-primary" onclick="runDoctor()">Run Diagnostic</button>
300
+ </div>
301
+ </div>
302
+
303
+ <!-- Logs -->
304
+ <div class="page" id="page-evals">
305
+ <div class="page-header">
306
+ <div class="page-title">🧪 Evals</div>
307
+ <div class="page-subtitle">Agent quality evaluation</div>
308
+ </div>
309
+ <div class="card" id="eval-panel">
310
+ <div style="margin-bottom:16px">
311
+ <label>Suite: </label>
312
+ <select id="eval-suite-select" style="padding:6px 12px;border-radius:6px;border:1px solid var(--border);background:var(--bg-card);color:var(--text)">
313
+ <option value="basic">basic</option>
314
+ <option value="safety">safety</option>
315
+ <option value="memory">memory</option>
316
+ </select>
317
+ <button class="btn btn-primary" onclick="runEval()" style="margin-left:8px">Run Suite</button>
318
+ </div>
319
+ <div id="eval-results"></div>
320
+ </div>
321
+ </div>
322
+
323
+ <!-- Telemetry -->
324
+ <div class="page" id="page-telemetry">
325
+ <div class="page-header">
326
+ <div class="page-title">Telemetry</div>
327
+ <div class="page-subtitle">OTel-compatible tracing & metrics</div>
328
+ </div>
329
+ <div class="card-grid" style="grid-template-columns: repeat(4,1fr); margin-bottom:16px">
330
+ <div class="card" id="tel-total-spans"><div style="font-size:11px;color:var(--text-muted)">Total Spans</div><div style="font-size:24px;font-weight:700" id="tel-stat-spans">—</div></div>
331
+ <div class="card" id="tel-total-traces"><div style="font-size:11px;color:var(--text-muted)">Total Traces</div><div style="font-size:24px;font-weight:700" id="tel-stat-traces">—</div></div>
332
+ <div class="card" id="tel-error-rate"><div style="font-size:11px;color:var(--text-muted)">Error Rate</div><div style="font-size:24px;font-weight:700" id="tel-stat-errors">—</div></div>
333
+ <div class="card" id="tel-p95"><div style="font-size:11px;color:var(--text-muted)">P95 Latency</div><div style="font-size:24px;font-weight:700" id="tel-stat-p95">—</div></div>
334
+ </div>
335
+ <div class="card" style="margin-bottom:16px">
336
+ <h3 style="margin:0 0 12px">Recent Traces</h3>
337
+ <div id="tel-traces-list" style="font-family:var(--mono);font-size:12px">Loading...</div>
338
+ </div>
339
+ <div class="card">
340
+ <h3 style="margin:0 0 12px">Trace Waterfall</h3>
341
+ <div id="tel-waterfall" style="font-family:var(--mono);font-size:12px;min-height:100px;color:var(--text-muted)">Click a trace above to view spans</div>
342
+ </div>
343
+ </div>
344
+
345
+ <!-- Logs -->
346
+ <div class="page" id="page-logs">
347
+ <div class="page-header">
348
+ <div class="page-title">Logs</div>
349
+ <div class="page-subtitle">Recent agent activity</div>
350
+ </div>
351
+ <div class="card"><pre id="logs-content" style="font-family:var(--mono);font-size:12px;max-height:600px;overflow:auto;color:var(--text-muted)">Loading...</pre></div>
352
+ </div>
353
+
354
+ <!-- Modules Status -->
355
+ <div class="page" id="page-modules">
356
+ <div class="page-header">
357
+ <div class="page-title">Modules</div>
358
+ <div class="page-subtitle">Sub-module status and health</div>
359
+ </div>
360
+ <div id="modules-grid" class="card-grid">Loading...</div>
361
+ </div>
362
+
363
+ <!-- DeepBrain Module -->
364
+ <div class="page" id="page-brain-module">
365
+ <iframe src="/brain/" style="width:100%;height:calc(100vh - 32px);border:none;border-radius:8px;"></iframe>
366
+ </div>
367
+
368
+ <!-- AgentKits Module -->
369
+ <div class="page" id="page-kits-module">
370
+ <iframe src="/kits/" style="width:100%;height:calc(100vh - 32px);border:none;border-radius:8px;"></iframe>
371
+ </div>
372
+
373
+ <!-- Workstation Module -->
374
+ <div class="page" id="page-workstation-module">
375
+ <iframe src="/workstation/" style="width:100%;height:calc(100vh - 32px);border:none;border-radius:8px;"></iframe>
376
+ </div>
377
+ </main>
378
+ </div>
379
+
380
+ <script>
381
+ const API = ''; // same origin
382
+
383
+ // Navigation
384
+ document.querySelectorAll('.nav-item').forEach(item => {
385
+ item.addEventListener('click', () => {
386
+ document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
387
+ document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
388
+ item.classList.add('active');
389
+ const page = item.dataset.page;
390
+ document.getElementById('page-' + page).classList.add('active');
391
+ loadPage(page);
392
+ });
393
+ });
394
+
395
+ // Page loaders
396
+ async function loadPage(page) {
397
+ switch (page) {
398
+ case 'dashboard': loadDashboard(); break;
399
+ case 'config': loadConfig(); break;
400
+ case 'memory': loadMemory(); break;
401
+ case 'skills': loadSkills(); break;
402
+ case 'tools': loadTools(); break;
403
+ case 'channels': loadChannels(); break;
404
+ case 'workflows': loadWorkflows(); break;
405
+ case 'jobs': loadJobs(); break;
406
+ case 'plugins': loadPlugins(); break;
407
+ case 'protocols': loadProtocols(); break;
408
+ case 'logs': loadLogs(); break;
409
+ case 'modules': loadModules(); break;
410
+ case 'telemetry': loadTelemetry(); break;
411
+ case 'evals': break; // static page, run via button
412
+ }
413
+ }
414
+
415
+ async function api(path) {
416
+ const r = await fetch(API + '/api/' + path);
417
+ return r.json();
418
+ }
419
+
420
+ async function apiPost(path, body) {
421
+ const r = await fetch(API + '/api/' + path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
422
+ return r.json();
423
+ }
424
+
425
+ async function apiPut(path, body) {
426
+ const r = await fetch(API + '/api/' + path, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
427
+ return r.json();
428
+ }
429
+
430
+ // Dashboard
431
+ async function loadDashboard() {
432
+ try {
433
+ const info = await api('agent/info');
434
+ document.getElementById('agent-name').textContent = info.name || '—';
435
+ document.getElementById('agent-model').textContent = info.model || '—';
436
+ document.getElementById('agent-channels').textContent = info.channels?.length || 0;
437
+ document.getElementById('agent-skills').textContent = info.skills?.length || 0;
438
+ document.getElementById('agent-status-detail').innerHTML =
439
+ `<span class="badge badge-green"><span class="badge-dot"></span> ${info.status || 'unknown'}</span>` +
440
+ ` &nbsp; Provider: ${info.provider || '—'} &nbsp; Version: ${info.version || '—'}`;
441
+
442
+ const stats = await api('memory/stats');
443
+ document.getElementById('memory-stats').innerHTML =
444
+ `Pages: <strong>${stats.pages || 0}</strong> &nbsp; Chunks: <strong>${stats.chunks || 0}</strong>`;
445
+ } catch (e) {
446
+ document.getElementById('agent-status-detail').textContent = 'Failed to connect to agent';
447
+ }
448
+ }
449
+
450
+ // Chat
451
+ async function sendChat() {
452
+ const input = document.getElementById('chat-input');
453
+ const msg = input.value.trim();
454
+ if (!msg) return;
455
+ input.value = '';
456
+
457
+ const messages = document.getElementById('chat-messages');
458
+ messages.innerHTML += `<div class="message message-user"><div class="message-label">You</div><div class="bubble">${escapeHtml(msg)}</div></div>`;
459
+ messages.innerHTML += `<div class="message message-agent" id="pending"><div class="message-label">Agent</div><div class="bubble"><span class="spinner"></span> Thinking...</div></div>`;
460
+ messages.scrollTop = messages.scrollHeight;
461
+
462
+ try {
463
+ const result = await apiPost('agent/chat', { message: msg, sessionId: 'studio' });
464
+ document.getElementById('pending').innerHTML = `<div class="message-label">Agent</div><div class="bubble">${escapeHtml(result.response || 'No response')}</div>`;
465
+ document.getElementById('pending').id = '';
466
+ } catch (e) {
467
+ document.getElementById('pending').innerHTML = `<div class="message-label">Agent</div><div class="bubble" style="color:var(--red)">Error: ${e.message}</div>`;
468
+ document.getElementById('pending').id = '';
469
+ }
470
+ messages.scrollTop = messages.scrollHeight;
471
+ }
472
+
473
+ // Config
474
+ async function loadConfig() {
475
+ const data = await api('agent/config');
476
+ document.getElementById('config-editor').value = data.content || '# No config found';
477
+ }
478
+ async function saveConfig() {
479
+ const content = document.getElementById('config-editor').value;
480
+ await apiPut('agent/config', { content });
481
+ alert('Config saved!');
482
+ }
483
+
484
+ // Memory
485
+ async function loadMemory() {
486
+ const data = await api('memory/list');
487
+ const el = document.getElementById('memory-list');
488
+ if (!data.pages?.length) { el.innerHTML = '<div class="loading">No memories yet</div>'; return; }
489
+ el.innerHTML = data.pages.map(p => `
490
+ <div class="memory-item">
491
+ <div class="memory-slug">${escapeHtml(p.slug || p.title || '—')}</div>
492
+ <div class="memory-preview">${escapeHtml((p.compiled_truth || p.content || '').slice(0, 100))}</div>
493
+ <div class="memory-meta">Type: ${p.type || '—'} | Tags: ${(p.tags || []).join(', ') || '—'}</div>
494
+ </div>
495
+ `).join('');
496
+ }
497
+ async function searchMemory() {
498
+ const q = document.getElementById('memory-search').value;
499
+ if (!q) return loadMemory();
500
+ const data = await api('memory/search?q=' + encodeURIComponent(q));
501
+ const el = document.getElementById('memory-list');
502
+ if (!data.results?.length) { el.innerHTML = '<div class="loading">No results</div>'; return; }
503
+ el.innerHTML = data.results.map(r => `
504
+ <div class="memory-item">
505
+ <div class="memory-slug">${escapeHtml(r.slug || r.title || '—')}</div>
506
+ <div class="memory-preview">${escapeHtml((r.compiled_truth || r.content || '').slice(0, 100))}</div>
507
+ </div>
508
+ `).join('');
509
+ }
510
+
511
+ // Generic list loaders
512
+ async function loadSkills() {
513
+ const data = await api('skills/list');
514
+ renderList('skills-list', data.skills || [], s => `<strong>${s.name}</strong> — ${s.description || '—'} (used ${s.usageCount || 0} times)`);
515
+ }
516
+ async function loadTools() {
517
+ const data = await api('tools/list');
518
+ renderList('tools-list', data.tools || [], t => `<strong>🔧 ${t.name}</strong> — ${t.description || '—'}`);
519
+ }
520
+ async function loadChannels() {
521
+ const data = await api('channels/list');
522
+ renderList('channels-list', data.channels || [], c => `<span class="badge badge-green"><span class="badge-dot"></span> ${c.type}</span> ${c.port ? 'Port ' + c.port : ''} ${c.mode || ''}`);
523
+ }
524
+ async function loadWorkflows() {
525
+ const data = await api('workflows/list');
526
+ renderList('workflows-list', data.workflows || [], w => `<strong>${w.name}</strong> — ${w.steps?.length || 0} steps`);
527
+ }
528
+ async function loadJobs() {
529
+ const data = await api('jobs/list');
530
+ renderList('jobs-list', data.jobs || [], j => `<strong>⏰ ${j.name}</strong> — <code>${j.schedule}</code> — ${j.task || '—'}`);
531
+ }
532
+ async function loadPlugins() {
533
+ const data = await api('plugins/list');
534
+ renderList('plugins-list', data.plugins || [], p => `<strong>🔌 ${p.name}</strong> ${p.config ? JSON.stringify(p.config) : ''}`);
535
+ }
536
+ async function loadProtocols() {
537
+ const data = await api('protocols');
538
+ const list = data.protocols || [];
539
+ const content = document.getElementById('content');
540
+ content.innerHTML = `<h2>📡 Protocols</h2><div class="grid">${list.map(p =>
541
+ `<div class="card stat"><div class="stat-value">${p.enabled ? '🟢' : '⚫'} ${p.name}</div><div class="stat-label">${p.description}</div></div>`
542
+ ).join('')}</div>`;
543
+ }
544
+ async function loadLogs() {
545
+ const data = await api('logs/recent');
546
+ document.getElementById('logs-content').textContent = (data.lines || []).join('\n') || 'No logs';
547
+ }
548
+
549
+ // Modules
550
+ async function loadModules() {
551
+ const data = await api('modules');
552
+ const grid = document.getElementById('modules-grid');
553
+ if (data.modules) {
554
+ grid.innerHTML = data.modules.map(m => `
555
+ <div class="card stat" style="cursor:pointer" onclick="document.querySelector('[data-page=${m.path.replace(/\//g,'')}-module]')?.click()">
556
+ <div class="stat-value">${m.icon} ${m.name}</div>
557
+ <div class="stat-label">${m.running ? '🟢 Running on port ' + m.port : '⚫ Not running'}</div>
558
+ </div>`).join('');
559
+ } else {
560
+ grid.innerHTML = '<div class="card">Failed to load module status</div>';
561
+ }
562
+ }
563
+
564
+ // Telemetry
565
+ async function loadTelemetry() {
566
+ const stats = await api('telemetry/stats');
567
+ if (stats && !stats.error) {
568
+ document.getElementById('tel-stat-spans').textContent = stats.totalSpans;
569
+ document.getElementById('tel-stat-traces').textContent = stats.totalTraces;
570
+ document.getElementById('tel-stat-errors').textContent = (stats.errorRate * 100).toFixed(1) + '%';
571
+ document.getElementById('tel-stat-p95').textContent = stats.p95Latency.toFixed(0) + 'ms';
572
+ }
573
+ const tracesData = await api('telemetry/traces?limit=50');
574
+ const el = document.getElementById('tel-traces-list');
575
+ if (tracesData.traces && tracesData.traces.length > 0) {
576
+ el.innerHTML = '<table style="width:100%;border-collapse:collapse"><tr style="color:var(--text-muted);text-align:left"><th>Trace ID</th><th>Root Span</th><th>Time</th><th>Spans</th><th>Status</th></tr>' +
577
+ tracesData.traces.map(t => {
578
+ const time = new Date(t.startTime).toLocaleTimeString();
579
+ const statusColor = t.status === 'ok' ? '#4ade80' : t.status === 'error' ? '#f87171' : '#9ca3af';
580
+ return `<tr style="cursor:pointer;border-top:1px solid var(--border)" onclick="loadTraceWaterfall('${t.traceId}')"><td style="padding:6px;color:var(--accent)">${t.traceId.slice(0,12)}</td><td>${t.rootSpan}</td><td>${time}</td><td>${t.spanCount}</td><td style="color:${statusColor}">${t.status}</td></tr>`;
581
+ }).join('') + '</table>';
582
+ } else {
583
+ el.innerHTML = '<div style="color:var(--text-muted)">No traces yet. Enable telemetry: spec.telemetry.enabled: true</div>';
584
+ }
585
+ }
586
+
587
+ async function loadTraceWaterfall(traceId) {
588
+ const data = await api('telemetry/traces?id=' + traceId);
589
+ const el = document.getElementById('tel-waterfall');
590
+ if (!data.spans || data.spans.length === 0) { el.innerHTML = 'No spans found'; return; }
591
+ const spans = data.spans;
592
+ const minTime = Math.min(...spans.map(s => s.startTime));
593
+ const maxTime = Math.max(...spans.map(s => s.endTime || s.startTime));
594
+ const totalDur = maxTime - minTime || 1;
595
+ const depthMap = {};
596
+ spans.forEach(s => { depthMap[s.spanId] = s.parentSpanId && depthMap[s.parentSpanId] !== undefined ? depthMap[s.parentSpanId] + 1 : 0; });
597
+ el.innerHTML = spans.map(s => {
598
+ const left = ((s.startTime - minTime) / totalDur * 70).toFixed(1);
599
+ const width = Math.max(1, ((s.endTime || s.startTime) - s.startTime) / totalDur * 70).toFixed(1);
600
+ const color = s.status === 'ok' ? '#4ade80' : s.status === 'error' ? '#f87171' : '#60a5fa';
601
+ const depth = depthMap[s.spanId] || 0;
602
+ const dur = s.endTime ? (s.endTime - s.startTime) + 'ms' : '—';
603
+ return `<div style="display:flex;align-items:center;margin:2px 0;padding-left:${depth*20}px"><span style="width:180px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${s.name}</span><div style="flex:1;position:relative;height:18px;background:var(--bg-hover);border-radius:3px"><div style="position:absolute;left:${left}%;width:${width}%;height:100%;background:${color};border-radius:3px;opacity:0.8" title="${dur}"></div></div><span style="width:60px;text-align:right;flex-shrink:0;margin-left:8px">${dur}</span></div>`;
604
+ }).join('');
605
+ }
606
+ window.loadTraceWaterfall = loadTraceWaterfall;
607
+
608
+ // Doctor
609
+ async function runDoctor() {
610
+ const el = document.getElementById('doctor-results');
611
+ el.innerHTML = '<div class="loading"><span class="spinner"></span> Running checks...</div>';
612
+ const data = await api('doctor/check');
613
+ if (data.checks) {
614
+ el.innerHTML = data.checks.map(c => `
615
+ <div class="check-item">
616
+ <span class="check-icon">${c.ok ? '✅' : '❌'}</span>
617
+ <span class="check-name">${c.name}</span>
618
+ <span class="check-detail">${c.detail || ''}</span>
619
+ ${c.fix ? `<span class="check-fix">→ ${c.fix}</span>` : ''}
620
+ </div>
621
+ `).join('') + `<div style="margin-top:16px"><button class="btn btn-primary" onclick="runDoctor()">Re-run</button></div>`;
622
+ } else {
623
+ el.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre><button class="btn btn-primary" onclick="runDoctor()">Re-run</button>`;
624
+ }
625
+ }
626
+
627
+ // Helpers
628
+ async function runEval() {
629
+ const suite = document.getElementById('eval-suite-select').value;
630
+ const el = document.getElementById('eval-results');
631
+ el.innerHTML = '<div class="loading"><span class="spinner"></span> Running eval...</div>';
632
+ try {
633
+ const resp = await fetch('/api/eval/run', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ suite }) });
634
+ const report = await resp.json();
635
+ let html = `<div style="margin-bottom:12px;font-weight:600">${report.summary || ''}</div>`;
636
+ html += '<table style="width:100%;border-collapse:collapse">';
637
+ html += '<tr style="border-bottom:1px solid var(--border)"><th style="text-align:left;padding:6px">Case</th><th>Status</th><th>Latency</th></tr>';
638
+ for (const r of (report.results || [])) {
639
+ const color = r.passed ? '#4ade80' : '#f87171';
640
+ html += `<tr style="border-bottom:1px solid var(--border)"><td style="padding:6px">${r.caseId}</td><td style="color:${color};text-align:center">${r.passed ? 'PASS' : 'FAIL'}</td><td style="text-align:center">${r.scores?.latency_ms || 0}ms</td></tr>`;
641
+ }
642
+ html += '</table>';
643
+ el.innerHTML = html;
644
+ } catch (e) { el.innerHTML = `<div style="color:#f87171">Error: ${e.message}</div>`; }
645
+ }
646
+
647
+ function renderList(id, items, renderFn) {
648
+ const el = document.getElementById(id);
649
+ if (!items.length) { el.innerHTML = '<div class="loading">None configured</div>'; return; }
650
+ el.innerHTML = '<table class="table"><tbody>' +
651
+ items.map(i => `<tr><td>${renderFn(i)}</td></tr>`).join('') +
652
+ '</tbody></table>';
653
+ }
654
+ function escapeHtml(s) {
655
+ return String(s || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
656
+ }
657
+
658
+ // Initial load
659
+ loadDashboard();
660
+ </script>
661
+ </body>
662
+ </html>