aiden-runtime 3.16.2 → 3.17.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.
- package/README.md +137 -7
- package/config/devos.config.json +1 -1
- package/config/hardware.json +2 -2
- package/dist/api/dashboard.js +480 -0
- package/dist/api/server.js +45 -1
- package/dist/core/agentLoop.js +30 -3
- package/dist/core/pluginLoader.js +142 -0
- package/dist/core/version.js +1 -1
- package/dist-bundle/cli.js +26960 -6383
- package/dist-bundle/index.js +5338 -4700
- package/package.json +2 -2
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// api/dashboard.ts — Aiden local web dashboard
|
|
3
|
+
// Served at GET /ui — single self-contained HTML, no external build step.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.getDashboardHTML = getDashboardHTML;
|
|
6
|
+
function getDashboardHTML() {
|
|
7
|
+
return `<!DOCTYPE html>
|
|
8
|
+
<html lang="en">
|
|
9
|
+
<head>
|
|
10
|
+
<meta charset="UTF-8"/>
|
|
11
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
12
|
+
<title>Aiden Dashboard</title>
|
|
13
|
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
14
|
+
<style>
|
|
15
|
+
:root {
|
|
16
|
+
--bg: #0D0D0D;
|
|
17
|
+
--surface: #1A1A1A;
|
|
18
|
+
--surface2: #222222;
|
|
19
|
+
--border: #2A2A2A;
|
|
20
|
+
--orange: #FF6B35;
|
|
21
|
+
--orange-dim: #c0521e;
|
|
22
|
+
--text: #E8E8E8;
|
|
23
|
+
--text-dim: #888;
|
|
24
|
+
--green: #4CAF50;
|
|
25
|
+
--red: #F44336;
|
|
26
|
+
--yellow: #FFC107;
|
|
27
|
+
--radius: 8px;
|
|
28
|
+
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
29
|
+
}
|
|
30
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
31
|
+
body{background:var(--bg);color:var(--text);font-family:var(--font);font-size:14px;height:100vh;display:flex;flex-direction:column;overflow:hidden}
|
|
32
|
+
|
|
33
|
+
/* ── Top bar ── */
|
|
34
|
+
#topbar{display:flex;align-items:center;gap:12px;padding:0 20px;height:48px;border-bottom:1px solid var(--border);background:var(--surface);flex-shrink:0}
|
|
35
|
+
#logo{font-weight:700;font-size:16px;color:var(--orange);letter-spacing:.5px}
|
|
36
|
+
#status-dot{width:8px;height:8px;border-radius:50%;background:var(--red);transition:background .4s}
|
|
37
|
+
#status-dot.ok{background:var(--green)}
|
|
38
|
+
#status-text{color:var(--text-dim);font-size:12px}
|
|
39
|
+
#topbar-right{margin-left:auto;display:flex;gap:8px;align-items:center}
|
|
40
|
+
#version-label{color:var(--text-dim);font-size:11px}
|
|
41
|
+
|
|
42
|
+
/* ── Tab bar ── */
|
|
43
|
+
#tabbar{display:flex;gap:2px;padding:8px 20px 0;background:var(--surface);border-bottom:1px solid var(--border);flex-shrink:0}
|
|
44
|
+
.tab{padding:8px 18px;border-radius:6px 6px 0 0;cursor:pointer;font-size:13px;font-weight:500;color:var(--text-dim);border:1px solid transparent;border-bottom:none;background:transparent;transition:all .15s}
|
|
45
|
+
.tab:hover{color:var(--text);background:var(--surface2)}
|
|
46
|
+
.tab.active{color:var(--orange);background:var(--bg);border-color:var(--border)}
|
|
47
|
+
|
|
48
|
+
/* ── Main content ── */
|
|
49
|
+
#panels{flex:1;overflow:hidden;display:flex;flex-direction:column}
|
|
50
|
+
.panel{display:none;flex:1;overflow:hidden;flex-direction:column}
|
|
51
|
+
.panel.active{display:flex}
|
|
52
|
+
|
|
53
|
+
/* ── Shared card ── */
|
|
54
|
+
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px;margin-bottom:12px}
|
|
55
|
+
.card-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.8px;color:var(--text-dim);margin-bottom:10px}
|
|
56
|
+
|
|
57
|
+
/* ── Scrollable inner ── */
|
|
58
|
+
.scroll{overflow-y:auto;flex:1;padding:16px 20px}
|
|
59
|
+
.scroll::-webkit-scrollbar{width:6px}
|
|
60
|
+
.scroll::-webkit-scrollbar-track{background:transparent}
|
|
61
|
+
.scroll::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
|
|
62
|
+
|
|
63
|
+
/* ─────────────────── CHAT ─────────────────── */
|
|
64
|
+
#chat-wrap{display:flex;flex:1;flex-direction:column;overflow:hidden}
|
|
65
|
+
#chat-messages{flex:1;overflow-y:auto;padding:16px 20px;display:flex;flex-direction:column;gap:10px}
|
|
66
|
+
#chat-messages::-webkit-scrollbar{width:6px}
|
|
67
|
+
#chat-messages::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
|
|
68
|
+
.msg{max-width:80%;border-radius:10px;padding:10px 14px;line-height:1.55;font-size:13.5px}
|
|
69
|
+
.msg.user{align-self:flex-end;background:var(--orange);color:#fff;border-bottom-right-radius:2px}
|
|
70
|
+
.msg.aiden{align-self:flex-start;background:var(--surface);border:1px solid var(--border);color:var(--text);border-bottom-left-radius:2px}
|
|
71
|
+
.msg.aiden pre{background:var(--bg);border:1px solid var(--border);border-radius:5px;padding:8px 10px;overflow-x:auto;font-size:12px;margin-top:8px}
|
|
72
|
+
.msg.aiden code{font-size:12px;background:var(--bg);padding:1px 4px;border-radius:3px}
|
|
73
|
+
.msg.system{align-self:center;color:var(--text-dim);font-size:12px;font-style:italic}
|
|
74
|
+
.msg.thinking{align-self:flex-start;color:var(--text-dim);font-size:12px;font-style:italic;padding:6px 10px}
|
|
75
|
+
#chat-input-bar{padding:12px 20px;border-top:1px solid var(--border);display:flex;gap:8px;flex-shrink:0;background:var(--surface)}
|
|
76
|
+
#chat-input{flex:1;background:var(--surface2);border:1px solid var(--border);border-radius:8px;padding:9px 14px;color:var(--text);font-size:14px;resize:none;min-height:40px;max-height:120px;line-height:1.4;font-family:var(--font)}
|
|
77
|
+
#chat-input:focus{outline:none;border-color:var(--orange)}
|
|
78
|
+
#send-btn{background:var(--orange);color:#fff;border:none;border-radius:8px;padding:9px 18px;font-size:14px;font-weight:600;cursor:pointer;flex-shrink:0;transition:background .15s}
|
|
79
|
+
#send-btn:hover{background:var(--orange-dim)}
|
|
80
|
+
#send-btn:disabled{opacity:.5;cursor:not-allowed}
|
|
81
|
+
#session-bar{padding:6px 20px;font-size:11px;color:var(--text-dim);border-bottom:1px solid var(--border);display:flex;gap:16px;align-items:center;flex-shrink:0;background:var(--surface)}
|
|
82
|
+
.sess-btn{background:transparent;border:1px solid var(--border);color:var(--text-dim);border-radius:5px;padding:2px 8px;font-size:11px;cursor:pointer}
|
|
83
|
+
.sess-btn:hover{border-color:var(--orange);color:var(--orange)}
|
|
84
|
+
|
|
85
|
+
/* ─────────────────── PROVIDERS ─────────────────── */
|
|
86
|
+
.provider-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px}
|
|
87
|
+
.pcard{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px}
|
|
88
|
+
.pcard-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
|
|
89
|
+
.pcard-name{font-weight:600;font-size:13px}
|
|
90
|
+
.badge{font-size:10px;padding:2px 7px;border-radius:20px;font-weight:600;text-transform:uppercase;letter-spacing:.4px}
|
|
91
|
+
.badge.ok{background:#1a3a1a;color:var(--green)}
|
|
92
|
+
.badge.err{background:#3a1a1a;color:var(--red)}
|
|
93
|
+
.badge.warn{background:#3a2a00;color:var(--yellow)}
|
|
94
|
+
.pcard-model{font-size:11px;color:var(--text-dim);margin-top:2px}
|
|
95
|
+
.pcard-latency{font-size:11px;color:var(--text-dim);margin-top:4px}
|
|
96
|
+
|
|
97
|
+
/* ─────────────────── MEMORY ─────────────────── */
|
|
98
|
+
.mem-item{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:10px 14px;margin-bottom:8px;display:flex;gap:10px;align-items:flex-start}
|
|
99
|
+
.mem-icon{color:var(--orange);font-size:16px;flex-shrink:0;margin-top:1px}
|
|
100
|
+
.mem-text{font-size:13px;line-height:1.5;color:var(--text);flex:1}
|
|
101
|
+
.mem-meta{font-size:10px;color:var(--text-dim);margin-top:3px}
|
|
102
|
+
#mem-search{width:100%;background:var(--surface2);border:1px solid var(--border);border-radius:7px;padding:8px 13px;color:var(--text);font-size:13px;margin-bottom:14px}
|
|
103
|
+
#mem-search:focus{outline:none;border-color:var(--orange)}
|
|
104
|
+
.mem-section-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.8px;color:var(--text-dim);margin:14px 0 8px}
|
|
105
|
+
|
|
106
|
+
/* ─────────────────── SKILLS ─────────────────── */
|
|
107
|
+
.skill-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px}
|
|
108
|
+
.scard{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px}
|
|
109
|
+
.scard-name{font-weight:600;font-size:13px;color:var(--orange);margin-bottom:4px}
|
|
110
|
+
.scard-desc{font-size:12px;color:var(--text-dim);line-height:1.4}
|
|
111
|
+
.scard-trigger{font-size:10px;color:var(--text-dim);margin-top:5px;font-family:monospace}
|
|
112
|
+
|
|
113
|
+
/* ─────────────────── REFRESH ─────────────────── */
|
|
114
|
+
.refresh-btn{background:transparent;border:1px solid var(--border);color:var(--text-dim);border-radius:6px;padding:5px 12px;font-size:12px;cursor:pointer;transition:all .15s}
|
|
115
|
+
.refresh-btn:hover{border-color:var(--orange);color:var(--orange)}
|
|
116
|
+
.panel-toolbar{display:flex;justify-content:space-between;align-items:center;padding:12px 20px 0;flex-shrink:0}
|
|
117
|
+
.panel-toolbar h2{font-size:14px;font-weight:600;color:var(--text)}
|
|
118
|
+
</style>
|
|
119
|
+
</head>
|
|
120
|
+
<body>
|
|
121
|
+
|
|
122
|
+
<!-- Top bar -->
|
|
123
|
+
<div id="topbar">
|
|
124
|
+
<span id="logo">⬡ Aiden</span>
|
|
125
|
+
<span id="status-dot"></span>
|
|
126
|
+
<span id="status-text">connecting…</span>
|
|
127
|
+
<div id="topbar-right">
|
|
128
|
+
<span id="version-label"></span>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<!-- Tab bar -->
|
|
133
|
+
<div id="tabbar">
|
|
134
|
+
<div class="tab active" data-tab="chat">💬 Chat</div>
|
|
135
|
+
<div class="tab" data-tab="providers">⚡ Providers</div>
|
|
136
|
+
<div class="tab" data-tab="memory">🧠 Memory</div>
|
|
137
|
+
<div class="tab" data-tab="skills">🎯 Skills</div>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<!-- Panels -->
|
|
141
|
+
<div id="panels">
|
|
142
|
+
|
|
143
|
+
<!-- ── CHAT ── -->
|
|
144
|
+
<div class="panel active" id="panel-chat">
|
|
145
|
+
<div id="session-bar">
|
|
146
|
+
<span id="session-label">Session: —</span>
|
|
147
|
+
<button class="sess-btn" id="new-session-btn">+ New session</button>
|
|
148
|
+
<button class="sess-btn" id="clear-chat-btn">✕ Clear</button>
|
|
149
|
+
</div>
|
|
150
|
+
<div id="chat-wrap">
|
|
151
|
+
<div id="chat-messages">
|
|
152
|
+
<div class="msg system">Aiden is running locally. Ask anything.</div>
|
|
153
|
+
</div>
|
|
154
|
+
<div id="chat-input-bar">
|
|
155
|
+
<textarea id="chat-input" placeholder="Message Aiden…" rows="1"></textarea>
|
|
156
|
+
<button id="send-btn">Send</button>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<!-- ── PROVIDERS ── -->
|
|
162
|
+
<div class="panel" id="panel-providers">
|
|
163
|
+
<div class="panel-toolbar">
|
|
164
|
+
<h2>LLM Providers</h2>
|
|
165
|
+
<button class="refresh-btn" id="refresh-providers">↻ Refresh</button>
|
|
166
|
+
</div>
|
|
167
|
+
<div class="scroll">
|
|
168
|
+
<div id="providers-content"><p style="color:var(--text-dim);padding:20px 0">Loading…</p></div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<!-- ── MEMORY ── -->
|
|
173
|
+
<div class="panel" id="panel-memory">
|
|
174
|
+
<div class="panel-toolbar">
|
|
175
|
+
<h2>Memory</h2>
|
|
176
|
+
<button class="refresh-btn" id="refresh-memory">↻ Refresh</button>
|
|
177
|
+
</div>
|
|
178
|
+
<div class="scroll">
|
|
179
|
+
<input id="mem-search" type="text" placeholder="Search memory…" autocomplete="off"/>
|
|
180
|
+
<div id="memory-content"><p style="color:var(--text-dim)">Loading…</p></div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<!-- ── SKILLS ── -->
|
|
185
|
+
<div class="panel" id="panel-skills">
|
|
186
|
+
<div class="panel-toolbar">
|
|
187
|
+
<h2>Skills</h2>
|
|
188
|
+
<button class="refresh-btn" id="refresh-skills">↻ Refresh</button>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="scroll">
|
|
191
|
+
<div id="skills-content"><p style="color:var(--text-dim);padding:20px 0">Loading…</p></div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
</div><!-- /panels -->
|
|
196
|
+
|
|
197
|
+
<script>
|
|
198
|
+
// ── Config ──────────────────────────────────────────────────
|
|
199
|
+
const BASE = window.location.origin // e.g. http://localhost:4200
|
|
200
|
+
|
|
201
|
+
// ── Tabs ────────────────────────────────────────────────────
|
|
202
|
+
const tabs = document.querySelectorAll('.tab')
|
|
203
|
+
const panels = document.querySelectorAll('.panel')
|
|
204
|
+
tabs.forEach(tab => {
|
|
205
|
+
tab.addEventListener('click', () => {
|
|
206
|
+
const id = tab.dataset.tab
|
|
207
|
+
tabs.forEach(t => t.classList.remove('active'))
|
|
208
|
+
panels.forEach(p => p.classList.remove('active'))
|
|
209
|
+
tab.classList.add('active')
|
|
210
|
+
document.getElementById('panel-' + id).classList.add('active')
|
|
211
|
+
if (id === 'providers') loadProviders()
|
|
212
|
+
if (id === 'memory') loadMemory()
|
|
213
|
+
if (id === 'skills') loadSkills()
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// ── Status bar ──────────────────────────────────────────────
|
|
218
|
+
async function ping() {
|
|
219
|
+
try {
|
|
220
|
+
const r = await fetch(BASE + '/api/ping', { cache: 'no-store' })
|
|
221
|
+
const d = await r.json()
|
|
222
|
+
document.getElementById('status-dot').className = 'ok'
|
|
223
|
+
document.getElementById('status-text').textContent = 'online'
|
|
224
|
+
document.getElementById('version-label').textContent = 'v' + (d.version || '')
|
|
225
|
+
} catch {
|
|
226
|
+
document.getElementById('status-dot').className = ''
|
|
227
|
+
document.getElementById('status-text').textContent = 'offline'
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
ping()
|
|
231
|
+
setInterval(ping, 10000)
|
|
232
|
+
|
|
233
|
+
// ── Chat ─────────────────────────────────────────────────────
|
|
234
|
+
let sessionId = null
|
|
235
|
+
const chatBox = document.getElementById('chat-messages')
|
|
236
|
+
const chatInput = document.getElementById('chat-input')
|
|
237
|
+
const sendBtn = document.getElementById('send-btn')
|
|
238
|
+
|
|
239
|
+
function addMsg(role, html) {
|
|
240
|
+
const el = document.createElement('div')
|
|
241
|
+
el.className = 'msg ' + role
|
|
242
|
+
el.innerHTML = html
|
|
243
|
+
chatBox.appendChild(el)
|
|
244
|
+
chatBox.scrollTop = chatBox.scrollHeight
|
|
245
|
+
return el
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function mdToHtml(text) {
|
|
249
|
+
try { return marked.parse(text) } catch { return escHtml(text) }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function escHtml(s) {
|
|
253
|
+
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
chatInput.addEventListener('keydown', e => {
|
|
257
|
+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage() }
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
document.getElementById('send-btn').addEventListener('click', sendMessage)
|
|
261
|
+
|
|
262
|
+
document.getElementById('clear-chat-btn').addEventListener('click', () => {
|
|
263
|
+
chatBox.innerHTML = '<div class="msg system">Cleared.</div>'
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
document.getElementById('new-session-btn').addEventListener('click', () => {
|
|
267
|
+
sessionId = null
|
|
268
|
+
document.getElementById('session-label').textContent = 'Session: —'
|
|
269
|
+
chatBox.innerHTML = '<div class="msg system">New session started.</div>'
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
async function sendMessage() {
|
|
273
|
+
const text = chatInput.value.trim()
|
|
274
|
+
if (!text) return
|
|
275
|
+
chatInput.value = ''
|
|
276
|
+
chatInput.style.height = ''
|
|
277
|
+
sendBtn.disabled = true
|
|
278
|
+
addMsg('user', escHtml(text))
|
|
279
|
+
|
|
280
|
+
const thinkEl = addMsg('thinking', '⋯ thinking')
|
|
281
|
+
let aiEl = null
|
|
282
|
+
let buffer = ''
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
const body = { message: text }
|
|
286
|
+
if (sessionId) body.sessionId = sessionId
|
|
287
|
+
|
|
288
|
+
const resp = await fetch(BASE + '/api/chat', {
|
|
289
|
+
method: 'POST',
|
|
290
|
+
headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' },
|
|
291
|
+
body: JSON.stringify(body)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
const reader = resp.body.getReader()
|
|
295
|
+
const dec = new TextDecoder()
|
|
296
|
+
let partial = ''
|
|
297
|
+
|
|
298
|
+
while (true) {
|
|
299
|
+
const { done, value } = await reader.read()
|
|
300
|
+
if (done) break
|
|
301
|
+
partial += dec.decode(value, { stream: true })
|
|
302
|
+
const lines = partial.split('\\n')
|
|
303
|
+
partial = lines.pop()
|
|
304
|
+
for (const line of lines) {
|
|
305
|
+
if (!line.startsWith('data:')) continue
|
|
306
|
+
const raw = line.slice(5).trim()
|
|
307
|
+
if (!raw || raw === '[DONE]') continue
|
|
308
|
+
let ev
|
|
309
|
+
try { ev = JSON.parse(raw) } catch { continue }
|
|
310
|
+
|
|
311
|
+
if (ev.sessionId && !sessionId) {
|
|
312
|
+
sessionId = ev.sessionId
|
|
313
|
+
document.getElementById('session-label').textContent = 'Session: ' + sessionId.slice(0,8)
|
|
314
|
+
}
|
|
315
|
+
// server sends { token, done: false } — no type field
|
|
316
|
+
if (ev.token || ev.delta) {
|
|
317
|
+
if (thinkEl && thinkEl.parentNode) thinkEl.remove()
|
|
318
|
+
if (!aiEl) aiEl = addMsg('aiden', '')
|
|
319
|
+
buffer += (ev.delta || ev.token || '')
|
|
320
|
+
aiEl.innerHTML = mdToHtml(buffer)
|
|
321
|
+
chatBox.scrollTop = chatBox.scrollHeight
|
|
322
|
+
}
|
|
323
|
+
// server sends { tool: "name", message: "...", timestamp } for tool progress
|
|
324
|
+
if (ev.tool) {
|
|
325
|
+
if (thinkEl && thinkEl.parentNode) thinkEl.innerHTML = '🔧 ' + escHtml(ev.tool)
|
|
326
|
+
}
|
|
327
|
+
// server sends { activity: { icon, message, style, rawTool? }, done: false }
|
|
328
|
+
if (ev.activity) {
|
|
329
|
+
const act = ev.activity
|
|
330
|
+
if (thinkEl && thinkEl.parentNode) {
|
|
331
|
+
thinkEl.innerHTML = (act.icon ? act.icon + ' ' : '🔧 ') + escHtml(act.message || act.rawTool || 'working…')
|
|
332
|
+
}
|
|
333
|
+
if (act.style === 'error' && !buffer) {
|
|
334
|
+
if (thinkEl && thinkEl.parentNode) thinkEl.remove()
|
|
335
|
+
addMsg('system', '⚠ ' + escHtml(act.message || 'Error'))
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// server sends { thinking: { stage, message } }
|
|
339
|
+
if (ev.thinking) {
|
|
340
|
+
if (thinkEl && thinkEl.parentNode) thinkEl.innerHTML = '⋯ ' + escHtml(ev.thinking.message || 'thinking…')
|
|
341
|
+
}
|
|
342
|
+
// server sends { done: true } — no type field
|
|
343
|
+
if (ev.done === true) {
|
|
344
|
+
if (thinkEl && thinkEl.parentNode) thinkEl.remove()
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (thinkEl && thinkEl.parentNode) thinkEl.remove()
|
|
349
|
+
if (!aiEl && !buffer) addMsg('system', '(no response)')
|
|
350
|
+
} catch (err) {
|
|
351
|
+
if (thinkEl && thinkEl.parentNode) thinkEl.remove()
|
|
352
|
+
addMsg('system', '⚠ ' + escHtml(err.message))
|
|
353
|
+
} finally {
|
|
354
|
+
sendBtn.disabled = false
|
|
355
|
+
chatInput.focus()
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Auto-resize textarea
|
|
360
|
+
chatInput.addEventListener('input', () => {
|
|
361
|
+
chatInput.style.height = 'auto'
|
|
362
|
+
chatInput.style.height = Math.min(chatInput.scrollHeight, 120) + 'px'
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
// ── Providers ────────────────────────────────────────────────
|
|
366
|
+
async function loadProviders() {
|
|
367
|
+
const el = document.getElementById('providers-content')
|
|
368
|
+
try {
|
|
369
|
+
const [stateRes, statusRes] = await Promise.all([
|
|
370
|
+
fetch(BASE + '/api/providers/state').then(r => r.json()),
|
|
371
|
+
fetch(BASE + '/api/providers/status').then(r => r.json()).catch(() => ({}))
|
|
372
|
+
])
|
|
373
|
+
|
|
374
|
+
const providers = Array.isArray(stateRes) ? stateRes
|
|
375
|
+
: (stateRes.providers || Object.entries(stateRes).map(([k,v]) => ({ name: k, ...v })))
|
|
376
|
+
|
|
377
|
+
if (!providers.length) { el.innerHTML = '<p style="color:var(--text-dim)">No providers configured.</p>'; return }
|
|
378
|
+
|
|
379
|
+
const statusMap = {}
|
|
380
|
+
if (statusRes && typeof statusRes === 'object') {
|
|
381
|
+
const arr = Array.isArray(statusRes) ? statusRes : Object.entries(statusRes).map(([k,v])=>({name:k,...v}))
|
|
382
|
+
arr.forEach(p => { statusMap[p.name || p.id] = p })
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
el.innerHTML = '<div class="provider-grid">' + providers.map(p => {
|
|
386
|
+
const name = p.name || p.id || '?'
|
|
387
|
+
const status = p.status || statusMap[name]?.status || 'unknown'
|
|
388
|
+
const badgeCls = status === 'ok' || status === 'healthy' || status === 'active' ? 'ok'
|
|
389
|
+
: status === 'error' || status === 'down' ? 'err' : 'warn'
|
|
390
|
+
const model = p.currentModel || p.model || p.defaultModel || ''
|
|
391
|
+
const latency = p.latency || p.avgLatency || statusMap[name]?.latency || ''
|
|
392
|
+
return \`<div class="pcard">
|
|
393
|
+
<div class="pcard-header">
|
|
394
|
+
<span class="pcard-name">\${escHtml(name)}</span>
|
|
395
|
+
<span class="badge \${badgeCls}">\${escHtml(status)}</span>
|
|
396
|
+
</div>
|
|
397
|
+
\${model ? \`<div class="pcard-model">Model: \${escHtml(model)}</div>\` : ''}
|
|
398
|
+
\${latency ? \`<div class="pcard-latency">Latency: \${typeof latency==='number' ? latency+'ms' : escHtml(String(latency))}</div>\` : ''}
|
|
399
|
+
</div>\`
|
|
400
|
+
}).join('') + '</div>'
|
|
401
|
+
} catch (err) {
|
|
402
|
+
el.innerHTML = '<p style="color:var(--red)">Failed to load providers: ' + escHtml(err.message) + '</p>'
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
document.getElementById('refresh-providers').addEventListener('click', loadProviders)
|
|
406
|
+
|
|
407
|
+
// ── Memory ───────────────────────────────────────────────────
|
|
408
|
+
let allMemories = []
|
|
409
|
+
|
|
410
|
+
async function loadMemory() {
|
|
411
|
+
const el = document.getElementById('memory-content')
|
|
412
|
+
try {
|
|
413
|
+
const data = await fetch(BASE + '/api/memory').then(r => r.json())
|
|
414
|
+
allMemories = Array.isArray(data) ? data : (data.memories || data.items || [])
|
|
415
|
+
renderMemory(allMemories)
|
|
416
|
+
} catch (err) {
|
|
417
|
+
el.innerHTML = '<p style="color:var(--red)">Failed to load memory: ' + escHtml(err.message) + '</p>'
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function renderMemory(items) {
|
|
422
|
+
const el = document.getElementById('memory-content')
|
|
423
|
+
if (!items.length) { el.innerHTML = '<p style="color:var(--text-dim)">No memories stored yet.</p>'; return }
|
|
424
|
+
el.innerHTML = items.map(m => {
|
|
425
|
+
const text = m.content || m.text || m.value || JSON.stringify(m)
|
|
426
|
+
const meta = [m.type, m.category, m.created_at || m.createdAt].filter(Boolean).join(' · ')
|
|
427
|
+
return \`<div class="mem-item">
|
|
428
|
+
<span class="mem-icon">◈</span>
|
|
429
|
+
<div>
|
|
430
|
+
<div class="mem-text">\${escHtml(String(text))}</div>
|
|
431
|
+
\${meta ? \`<div class="mem-meta">\${escHtml(meta)}</div>\` : ''}
|
|
432
|
+
</div>
|
|
433
|
+
</div>\`
|
|
434
|
+
}).join('')
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
let memSearchTimer = null
|
|
438
|
+
document.getElementById('mem-search').addEventListener('input', e => {
|
|
439
|
+
clearTimeout(memSearchTimer)
|
|
440
|
+
const q = e.target.value.trim()
|
|
441
|
+
memSearchTimer = setTimeout(async () => {
|
|
442
|
+
if (!q) { renderMemory(allMemories); return }
|
|
443
|
+
try {
|
|
444
|
+
const data = await fetch(BASE + '/api/memory/search?q=' + encodeURIComponent(q)).then(r => r.json())
|
|
445
|
+
const results = Array.isArray(data) ? data : (data.results || data.memories || [])
|
|
446
|
+
renderMemory(results)
|
|
447
|
+
} catch { renderMemory(allMemories.filter(m => JSON.stringify(m).toLowerCase().includes(q.toLowerCase()))) }
|
|
448
|
+
}, 300)
|
|
449
|
+
})
|
|
450
|
+
document.getElementById('refresh-memory').addEventListener('click', loadMemory)
|
|
451
|
+
|
|
452
|
+
// ── Skills ───────────────────────────────────────────────────
|
|
453
|
+
async function loadSkills() {
|
|
454
|
+
const el = document.getElementById('skills-content')
|
|
455
|
+
try {
|
|
456
|
+
const data = await fetch(BASE + '/api/skills/learned').then(r => r.json())
|
|
457
|
+
const skills = Array.isArray(data) ? data : (data.skills || data.items || [])
|
|
458
|
+
if (!skills.length) { el.innerHTML = '<p style="color:var(--text-dim)">No skills loaded.</p>'; return }
|
|
459
|
+
el.innerHTML = '<div class="skill-grid">' + skills.map(s => {
|
|
460
|
+
const name = s.name || s.id || '?'
|
|
461
|
+
const desc = s.description || s.desc || ''
|
|
462
|
+
const trigger = s.trigger || s.command || ''
|
|
463
|
+
return \`<div class="scard">
|
|
464
|
+
<div class="scard-name">\${escHtml(name)}</div>
|
|
465
|
+
\${desc ? \`<div class="scard-desc">\${escHtml(desc)}</div>\` : ''}
|
|
466
|
+
\${trigger ? \`<div class="scard-trigger">trigger: \${escHtml(trigger)}</div>\` : ''}
|
|
467
|
+
</div>\`
|
|
468
|
+
}).join('') + '</div>'
|
|
469
|
+
} catch (err) {
|
|
470
|
+
el.innerHTML = '<p style="color:var(--red)">Failed to load skills: ' + escHtml(err.message) + '</p>'
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
document.getElementById('refresh-skills').addEventListener('click', loadSkills)
|
|
474
|
+
|
|
475
|
+
// ── Init ─────────────────────────────────────────────────────
|
|
476
|
+
chatInput.focus()
|
|
477
|
+
</script>
|
|
478
|
+
</body>
|
|
479
|
+
</html>`;
|
|
480
|
+
}
|
package/dist/api/server.js
CHANGED
|
@@ -124,6 +124,7 @@ const costTracker_1 = require("../core/costTracker");
|
|
|
124
124
|
const sessionMemory_1 = require("../core/sessionMemory");
|
|
125
125
|
const memoryExtractor_1 = require("../core/memoryExtractor");
|
|
126
126
|
const pluginSystem_1 = require("../core/pluginSystem");
|
|
127
|
+
const pluginLoader_1 = require("../core/pluginLoader");
|
|
127
128
|
const aidenIdentity_1 = require("../core/aidenIdentity");
|
|
128
129
|
const eventBus_1 = require("../core/eventBus");
|
|
129
130
|
const workflowTracker_1 = require("../core/workflowTracker");
|
|
@@ -150,6 +151,7 @@ const signal_1 = require("../core/channels/signal");
|
|
|
150
151
|
const twilio_1 = require("../core/channels/twilio");
|
|
151
152
|
const imessage_1 = require("../core/channels/imessage");
|
|
152
153
|
const email_1 = require("../core/channels/email");
|
|
154
|
+
const dashboard_1 = require("./dashboard");
|
|
153
155
|
// —— Sprint 25: module-level WebSocket clients registry (shared between createApiServer routes and startApiServer WS setup)
|
|
154
156
|
let wsBroadcastClients = new Set();
|
|
155
157
|
let activeTelegramBot = null;
|
|
@@ -542,6 +544,15 @@ function createApiServer() {
|
|
|
542
544
|
next();
|
|
543
545
|
});
|
|
544
546
|
// ── Core routes ──────────────────────────────────────────────
|
|
547
|
+
// GET /ui — local web dashboard
|
|
548
|
+
app.get('/ui', (_req, res) => {
|
|
549
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
550
|
+
res.send((0, dashboard_1.getDashboardHTML)());
|
|
551
|
+
});
|
|
552
|
+
// GET /api/ping — lightweight status probe for dashboard
|
|
553
|
+
app.get('/api/ping', (_req, res) => {
|
|
554
|
+
res.json({ ok: true, version: version_1.VERSION, ts: Date.now() });
|
|
555
|
+
});
|
|
545
556
|
// GET /api/health — liveness probe (no auth required)
|
|
546
557
|
app.get('/api/health', (_req, res) => {
|
|
547
558
|
res.json({ status: 'ok', version: version_1.VERSION, timestamp: new Date().toISOString() });
|
|
@@ -1380,6 +1391,10 @@ function createApiServer() {
|
|
|
1380
1391
|
// ── Callback system — additive layer alongside existing SSE sends ──
|
|
1381
1392
|
const sid = sessionId || 'default';
|
|
1382
1393
|
callbackSystem_1.callbacks.emit('session_start', sid, { message }).catch(() => { });
|
|
1394
|
+
// Fire flat-plugin session hooks
|
|
1395
|
+
for (const fn of pluginLoader_1.pluginHooks.onSessionStart) {
|
|
1396
|
+
fn(sid, { message }).catch(() => { });
|
|
1397
|
+
}
|
|
1383
1398
|
// Forward callback events from other sessions to this SSE connection.
|
|
1384
1399
|
// The sessionId guard prevents re-sending this session's own emitted events.
|
|
1385
1400
|
const unsubscribeSSE = callbackSystem_1.callbacks.onAny((payload) => {
|
|
@@ -1393,6 +1408,9 @@ function createApiServer() {
|
|
|
1393
1408
|
(0, toolRegistry_1.setProgressEmitter)(null);
|
|
1394
1409
|
unsubscribeSSE();
|
|
1395
1410
|
callbackSystem_1.callbacks.emit('session_end', sid, {}).catch(() => { });
|
|
1411
|
+
for (const fn of pluginLoader_1.pluginHooks.onSessionEnd) {
|
|
1412
|
+
fn(sid, {}).catch(() => { });
|
|
1413
|
+
}
|
|
1396
1414
|
(0, memoryDistiller_1.distillSession)(sid).catch(() => { });
|
|
1397
1415
|
});
|
|
1398
1416
|
// Sprint 6: tiered model selection
|
|
@@ -2661,7 +2679,7 @@ function createApiServer() {
|
|
|
2661
2679
|
res.status(500).json({ error: err.message });
|
|
2662
2680
|
}
|
|
2663
2681
|
});
|
|
2664
|
-
// GET /api/plugins — list loaded community plugins
|
|
2682
|
+
// GET /api/plugins — list loaded community plugins (subdirectory format)
|
|
2665
2683
|
app.get('/api/plugins', (_req, res) => {
|
|
2666
2684
|
try {
|
|
2667
2685
|
res.json({ plugins: pluginSystem_1.pluginManager.list() });
|
|
@@ -2670,6 +2688,29 @@ function createApiServer() {
|
|
|
2670
2688
|
res.status(500).json({ error: e.message });
|
|
2671
2689
|
}
|
|
2672
2690
|
});
|
|
2691
|
+
// GET /api/plugins/list — list all loaded plugins (subdirectory + flat)
|
|
2692
|
+
app.get('/api/plugins/list', (_req, res) => {
|
|
2693
|
+
try {
|
|
2694
|
+
res.json({
|
|
2695
|
+
subdirectory: pluginSystem_1.pluginManager.list(),
|
|
2696
|
+
flat: (0, pluginLoader_1.listFlatPlugins)(),
|
|
2697
|
+
});
|
|
2698
|
+
}
|
|
2699
|
+
catch (e) {
|
|
2700
|
+
res.status(500).json({ error: e.message });
|
|
2701
|
+
}
|
|
2702
|
+
});
|
|
2703
|
+
// POST /api/plugins/reload — hot-reload all flat .js plugins
|
|
2704
|
+
app.post('/api/plugins/reload', async (_req, res) => {
|
|
2705
|
+
try {
|
|
2706
|
+
const dir = path.join(process.cwd(), 'workspace', 'plugins');
|
|
2707
|
+
await (0, pluginLoader_1.reloadPlugins)(dir);
|
|
2708
|
+
res.json({ ok: true, plugins: (0, pluginLoader_1.listFlatPlugins)() });
|
|
2709
|
+
}
|
|
2710
|
+
catch (e) {
|
|
2711
|
+
res.status(500).json({ error: e.message });
|
|
2712
|
+
}
|
|
2713
|
+
});
|
|
2673
2714
|
// GET /api/telegram/config — load Telegram bot config
|
|
2674
2715
|
app.get('/api/telegram/config', (_req, res) => {
|
|
2675
2716
|
try {
|
|
@@ -5851,6 +5892,9 @@ function startApiServer(portArg) {
|
|
|
5851
5892
|
}
|
|
5852
5893
|
// Load community plugins from workspace/plugins/
|
|
5853
5894
|
pluginSystem_1.pluginManager.loadAll().catch(e => console.error('[Plugins] Load failed:', e.message));
|
|
5895
|
+
// Load flat .js plugins from workspace/plugins/*.js
|
|
5896
|
+
const flatPluginDir = path.join(process.cwd(), 'workspace', 'plugins');
|
|
5897
|
+
(0, pluginLoader_1.loadPlugins)(flatPluginDir).catch(e => console.error('[PluginLoader] Load failed:', e.message));
|
|
5854
5898
|
// Start background license refresh (12-hour interval, silent)
|
|
5855
5899
|
(0, licenseManager_1.startLicenseRefresh)();
|
|
5856
5900
|
// Log provider chain before listening so it's visible in startup log
|
package/dist/core/agentLoop.js
CHANGED
|
@@ -80,6 +80,7 @@ const semanticMemory_1 = require("./semanticMemory");
|
|
|
80
80
|
const sessionMemory_1 = require("./sessionMemory");
|
|
81
81
|
const goalTracker_1 = require("./goalTracker");
|
|
82
82
|
const hooks_1 = require("./hooks");
|
|
83
|
+
const pluginLoader_1 = require("./pluginLoader");
|
|
83
84
|
const instinctSystem_1 = require("./instinctSystem");
|
|
84
85
|
const workflowTracker_1 = require("./workflowTracker");
|
|
85
86
|
const parallelExecutor_1 = require("./parallelExecutor");
|
|
@@ -1667,11 +1668,25 @@ const NO_RETRY_TOOLS = new Set([
|
|
|
1667
1668
|
async function executeToolWithRetry(tool, input, maxRetries = 2) {
|
|
1668
1669
|
const retryable = !NO_RETRY_TOOLS.has(tool);
|
|
1669
1670
|
const effectiveMax = retryable ? maxRetries : 0;
|
|
1671
|
+
// ── Plugin preTool hooks ──────────────────────────────────────
|
|
1672
|
+
let effectiveInput = input;
|
|
1673
|
+
for (const hook of pluginLoader_1.pluginHooks.preTool) {
|
|
1674
|
+
try {
|
|
1675
|
+
const r = await hook(tool, effectiveInput);
|
|
1676
|
+
if (r.skip)
|
|
1677
|
+
return { success: true, output: '[skipped by plugin]', skippedByPlugin: true };
|
|
1678
|
+
if (r.input)
|
|
1679
|
+
effectiveInput = r.input;
|
|
1680
|
+
}
|
|
1681
|
+
catch (e) {
|
|
1682
|
+
console.warn(`[PluginHook] preTool error for ${tool}:`, e.message);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1670
1685
|
for (let attempt = 0; attempt <= effectiveMax; attempt++) {
|
|
1671
1686
|
try {
|
|
1672
|
-
const result = await (0, toolRegistry_1.executeTool)(tool,
|
|
1687
|
+
const result = await (0, toolRegistry_1.executeTool)(tool, effectiveInput);
|
|
1673
1688
|
if (result.success) {
|
|
1674
|
-
const quality = validateResultQuality(tool,
|
|
1689
|
+
const quality = validateResultQuality(tool, effectiveInput, result.output || result);
|
|
1675
1690
|
if (!quality.valid) {
|
|
1676
1691
|
console.log(`[Quality] ${tool} returned but quality check failed: ${quality.reason}`);
|
|
1677
1692
|
if (attempt < effectiveMax) {
|
|
@@ -1683,7 +1698,19 @@ async function executeToolWithRetry(tool, input, maxRetries = 2) {
|
|
|
1683
1698
|
console.log(`[Quality] ${tool} — accepting low-quality result after ${effectiveMax} retries`);
|
|
1684
1699
|
appendLesson(`${tool} produced low-quality output (${quality.reason}) after ${effectiveMax} retries — consider alternative approach for this tool.`);
|
|
1685
1700
|
}
|
|
1686
|
-
|
|
1701
|
+
// ── Plugin postTool hooks ─────────────────────────────
|
|
1702
|
+
let finalResult = result;
|
|
1703
|
+
for (const hook of pluginLoader_1.pluginHooks.postTool) {
|
|
1704
|
+
try {
|
|
1705
|
+
const r = await hook(tool, effectiveInput, finalResult);
|
|
1706
|
+
if (r.result)
|
|
1707
|
+
finalResult = r.result;
|
|
1708
|
+
}
|
|
1709
|
+
catch (e) {
|
|
1710
|
+
console.warn(`[PluginHook] postTool error for ${tool}:`, e.message);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
return finalResult;
|
|
1687
1714
|
}
|
|
1688
1715
|
if (attempt < effectiveMax) {
|
|
1689
1716
|
const delay = Math.min(1000 * Math.pow(2, attempt), 5000);
|