@stackbilt/aegis-core 0.7.0 → 0.8.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 (98) hide show
  1. package/package.json +13 -3
  2. package/public/assets/index-CQHn03rW.css +1 -0
  3. package/public/assets/index-CTKpNJEr.js +74 -0
  4. package/public/index.html +14 -0
  5. package/src/adapters/voice/cloudflare-agent.ts +0 -0
  6. package/src/agent-routing.ts +38 -0
  7. package/src/assets.ts +6 -0
  8. package/src/auth.ts +1 -0
  9. package/src/bluesky.ts +0 -0
  10. package/src/claude-tools/content.ts +0 -0
  11. package/src/claude-tools/email.ts +0 -0
  12. package/src/codebeast.ts +0 -0
  13. package/src/content/column.ts +0 -0
  14. package/src/content/hero-image.ts +0 -0
  15. package/src/content/index.ts +0 -0
  16. package/src/content/journal.ts +0 -0
  17. package/src/content/roundtable.ts +0 -0
  18. package/src/contracts/agenda-item.contract.ts +0 -0
  19. package/src/contracts/cc-task.contract.ts +0 -0
  20. package/src/contracts/goal.contract.ts +0 -0
  21. package/src/contracts/memory-entry.contract.ts +0 -0
  22. package/src/dashboard.ts +0 -0
  23. package/src/decision-docs.ts +0 -0
  24. package/src/dispatch.ts +0 -0
  25. package/src/edge-env.ts +0 -0
  26. package/src/exports.ts +0 -0
  27. package/src/github-projects.ts +0 -0
  28. package/src/index.ts +7 -1
  29. package/src/kernel/argus-actions.ts +0 -0
  30. package/src/kernel/argus-correlation.ts +0 -0
  31. package/src/kernel/board.ts +0 -0
  32. package/src/kernel/classify-memory-topic.ts +0 -0
  33. package/src/kernel/executor-port.ts +0 -0
  34. package/src/kernel/executor-router.ts +0 -0
  35. package/src/kernel/grounding/fabrication-detector.ts +0 -0
  36. package/src/kernel/grounding/fanout.ts +0 -0
  37. package/src/kernel/grounding/semantic-sanhedrin.ts +0 -0
  38. package/src/kernel/grounding/verify.ts +0 -0
  39. package/src/kernel/grounding-layer.ts +0 -0
  40. package/src/kernel/insight-cache.ts +0 -0
  41. package/src/kernel/memory/insights.ts +0 -0
  42. package/src/kernel/memory-guardrails.ts +0 -0
  43. package/src/kernel/memory-service.ts +0 -0
  44. package/src/kernel/patterns.ts +0 -0
  45. package/src/kernel/port.ts +0 -0
  46. package/src/kernel/provider-factory.ts +0 -0
  47. package/src/kernel/resilience.ts +0 -0
  48. package/src/kernel/scheduled/agent-dispatch.ts +0 -0
  49. package/src/kernel/scheduled/argus-analytics.ts +0 -0
  50. package/src/kernel/scheduled/argus-heartbeat.ts +0 -0
  51. package/src/kernel/scheduled/argus-notify.ts +0 -0
  52. package/src/kernel/scheduled/board-sync.ts +0 -0
  53. package/src/kernel/scheduled/ci-watcher.ts +0 -0
  54. package/src/kernel/scheduled/content-drip.ts +0 -0
  55. package/src/kernel/scheduled/content.ts +0 -0
  56. package/src/kernel/scheduled/cost-report.ts +0 -0
  57. package/src/kernel/scheduled/dev-activity.ts +0 -0
  58. package/src/kernel/scheduled/dreaming/agenda-triage.ts +0 -0
  59. package/src/kernel/scheduled/dreaming/facts.ts +0 -0
  60. package/src/kernel/scheduled/dreaming/index.ts +0 -0
  61. package/src/kernel/scheduled/dreaming/pattern-synthesis.ts +0 -0
  62. package/src/kernel/scheduled/dreaming/persona.ts +0 -0
  63. package/src/kernel/scheduled/dreaming/symbolic.ts +0 -0
  64. package/src/kernel/scheduled/dreaming/task-proposals.ts +0 -0
  65. package/src/kernel/scheduled/entropy.ts +0 -0
  66. package/src/kernel/scheduled/feed-watcher.ts +0 -0
  67. package/src/kernel/scheduled/inbox-processor.ts +0 -0
  68. package/src/kernel/scheduled/issue-proposer.ts +0 -0
  69. package/src/kernel/scheduled/issue-watcher.ts +0 -0
  70. package/src/kernel/scheduled/pr-automerge.ts +0 -0
  71. package/src/kernel/scheduled/product-health.ts +0 -0
  72. package/src/kernel/scheduled/self-improvement.ts +0 -0
  73. package/src/kernel/scheduled/task-audit.ts +0 -0
  74. package/src/landing.ts +0 -0
  75. package/src/lib/audit-chain/chain.ts +0 -0
  76. package/src/lib/audit-chain/types.ts +0 -0
  77. package/src/lib/observability/errors.ts +0 -0
  78. package/src/operator/config.ts +0 -0
  79. package/src/operator/persona.ts +0 -0
  80. package/src/pulse.ts +0 -0
  81. package/src/routes/bluesky.ts +0 -0
  82. package/src/routes/codebeast.ts +0 -0
  83. package/src/routes/content.ts +0 -0
  84. package/src/routes/dynamic-tools.ts +0 -0
  85. package/src/routes/health.ts +1 -0
  86. package/src/routes/messages.ts +11 -6
  87. package/src/routes/observability.ts +0 -0
  88. package/src/routes/operator-logs.ts +0 -0
  89. package/src/routes/pages.ts +9 -1
  90. package/src/schema-enums.ts +0 -0
  91. package/src/task-intelligence.ts +0 -0
  92. package/src/types.ts +2 -0
  93. package/src/ui/index.html +13 -0
  94. package/src/ui/main.tsx +356 -0
  95. package/src/ui/styles.css +391 -0
  96. package/src/version.ts +1 -1
  97. package/src/wiki/client.ts +0 -0
  98. package/src/wiki/types.ts +0 -0
@@ -0,0 +1,356 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { useVoiceAgent } from '@cloudflare/voice/react';
4
+ import './styles.css';
5
+
6
+ type Conversation = {
7
+ id: string;
8
+ title: string | null;
9
+ created_at?: string;
10
+ updated_at?: string;
11
+ };
12
+
13
+ type Message = {
14
+ id: string;
15
+ role: 'user' | 'assistant' | string;
16
+ content: string;
17
+ metadata?: Record<string, unknown> | null;
18
+ };
19
+
20
+ type HealthPayload = {
21
+ status: string;
22
+ version: string;
23
+ mode: string;
24
+ kernel?: Record<string, number>;
25
+ tasks_24h?: Array<{ task_name: string; runs: number; ok: number; errors: number }>;
26
+ docs_sync_status?: { status: string; lastSyncAge: number | null };
27
+ };
28
+
29
+ const TOKEN_KEY = 'aegis_token';
30
+
31
+ function App() {
32
+ const [token, setToken] = useState(() => localStorage.getItem(TOKEN_KEY) ?? '');
33
+ const [tokenDraft, setTokenDraft] = useState(token);
34
+ const [conversations, setConversations] = useState<Conversation[]>([]);
35
+ const [conversationId, setConversationId] = useState<string | null>(null);
36
+ const [messages, setMessages] = useState<Message[]>([]);
37
+ const [prompt, setPrompt] = useState('');
38
+ const [status, setStatus] = useState('Ready');
39
+ const [health, setHealth] = useState<HealthPayload | null>(null);
40
+ const [voiceText, setVoiceText] = useState('');
41
+ const messagesRef = useRef<HTMLDivElement | null>(null);
42
+
43
+ const voice = useVoiceAgent({
44
+ agent: 'AegisVoiceAdapter',
45
+ name: 'operator',
46
+ query: token ? { token } : undefined,
47
+ });
48
+
49
+ const authHeaders = useMemo<Record<string, string>>(
50
+ () => {
51
+ const headers: Record<string, string> = {};
52
+ if (token) headers.Authorization = `Bearer ${token}`;
53
+ return headers;
54
+ },
55
+ [token],
56
+ );
57
+
58
+ useEffect(() => {
59
+ document.cookie = token
60
+ ? `aegis_token=${encodeURIComponent(token)};path=/;max-age=31536000;SameSite=Strict;Secure`
61
+ : 'aegis_token=;path=/;max-age=0;SameSite=Strict;Secure';
62
+ }, [token]);
63
+
64
+ useEffect(() => {
65
+ void refreshHealth();
66
+ if (token) void refreshConversations();
67
+ }, [token]);
68
+
69
+ useEffect(() => {
70
+ messagesRef.current?.scrollTo({ top: messagesRef.current.scrollHeight });
71
+ }, [messages]);
72
+
73
+ async function refreshHealth() {
74
+ const res = await fetch('/health?format=json', {
75
+ headers: { Accept: 'application/json' },
76
+ });
77
+ if (res.ok) {
78
+ setHealth(await res.json());
79
+ }
80
+ }
81
+
82
+ async function refreshConversations() {
83
+ const res = await fetch('/api/conversations', { headers: authHeaders });
84
+ if (!res.ok) {
85
+ setStatus(res.status === 401 ? 'Add your AEGIS token' : `Conversations failed: ${res.status}`);
86
+ return;
87
+ }
88
+ const data = await res.json() as { conversations: Conversation[] };
89
+ setConversations(data.conversations);
90
+ }
91
+
92
+ async function loadConversation(id: string) {
93
+ setConversationId(id);
94
+ const res = await fetch(`/api/conversations/${encodeURIComponent(id)}/messages`, {
95
+ headers: authHeaders,
96
+ });
97
+ if (!res.ok) {
98
+ setStatus(`History failed: ${res.status}`);
99
+ return;
100
+ }
101
+ const data = await res.json() as { messages: Message[] };
102
+ setMessages(data.messages);
103
+ }
104
+
105
+ function saveToken() {
106
+ const next = tokenDraft.trim();
107
+ localStorage.setItem(TOKEN_KEY, next);
108
+ setToken(next);
109
+ setStatus(next ? 'Token saved' : 'Token cleared');
110
+ }
111
+
112
+ async function sendMessage(text = prompt.trim()) {
113
+ if (!text) return;
114
+ if (!token) {
115
+ setStatus('Add your AEGIS token');
116
+ return;
117
+ }
118
+
119
+ const optimisticUser: Message = { id: crypto.randomUUID(), role: 'user', content: text };
120
+ const assistantId = crypto.randomUUID();
121
+ setMessages((current) => [
122
+ ...current,
123
+ optimisticUser,
124
+ { id: assistantId, role: 'assistant', content: '' },
125
+ ]);
126
+ setPrompt('');
127
+ setStatus('Streaming');
128
+
129
+ const res = await fetch('/api/message/stream', {
130
+ method: 'POST',
131
+ headers: {
132
+ ...authHeaders,
133
+ 'Content-Type': 'application/json',
134
+ },
135
+ body: JSON.stringify({ text, conversationId, executor: 'workers_ai' }),
136
+ });
137
+
138
+ if (!res.ok || !res.body) {
139
+ setStatus(`Message failed: ${res.status}`);
140
+ return;
141
+ }
142
+
143
+ let assistantText = '';
144
+ for await (const frame of readSse(res.body)) {
145
+ if (frame.type === 'start' && typeof frame.conversationId === 'string') {
146
+ setConversationId(frame.conversationId);
147
+ }
148
+ if (frame.type === 'delta' && typeof frame.text === 'string') {
149
+ assistantText += frame.text;
150
+ setMessages((current) => current.map((message) => (
151
+ message.id === assistantId ? { ...message, content: assistantText } : message
152
+ )));
153
+ }
154
+ if (frame.type === 'error') {
155
+ setStatus(typeof frame.error === 'string' ? frame.error : 'Stream error');
156
+ }
157
+ if (frame.type === 'done') {
158
+ setStatus('Ready');
159
+ void refreshConversations();
160
+ }
161
+ }
162
+ }
163
+
164
+ function sendVoiceText() {
165
+ const text = voiceText.trim();
166
+ if (!text) return;
167
+ voice.sendText(text);
168
+ setVoiceText('');
169
+ }
170
+
171
+ const activeConversation = conversations.find((conversation) => conversation.id === conversationId);
172
+ const taskErrors = health?.tasks_24h?.reduce((sum, task) => sum + Number(task.errors ?? 0), 0) ?? 0;
173
+
174
+ return (
175
+ <main className="shell">
176
+ <aside className="rail" aria-label="AEGIS navigation">
177
+ <section className="brand">
178
+ <div className="brand-mark" aria-hidden="true">A</div>
179
+ <div>
180
+ <h1>AEGIS</h1>
181
+ <p>Edge-native operator console</p>
182
+ </div>
183
+ </section>
184
+
185
+ <section className="token-panel" aria-label="Access token">
186
+ <label htmlFor="token">Access token</label>
187
+ <div className="token-row">
188
+ <input
189
+ id="token"
190
+ type="password"
191
+ value={tokenDraft}
192
+ onChange={(event) => setTokenDraft(event.target.value)}
193
+ autoComplete="off"
194
+ />
195
+ <button type="button" onClick={saveToken} aria-label="Save token">OK</button>
196
+ </div>
197
+ </section>
198
+
199
+ <section className="health-panel" aria-label="Health dashboard">
200
+ <div className="panel-title">
201
+ <span>Health</span>
202
+ <button type="button" onClick={refreshHealth} aria-label="Refresh health">R</button>
203
+ </div>
204
+ <dl className="health-grid">
205
+ <div>
206
+ <dt>Status</dt>
207
+ <dd>{health?.status ?? 'unknown'}</dd>
208
+ </div>
209
+ <div>
210
+ <dt>Version</dt>
211
+ <dd>{health?.version ?? '-'}</dd>
212
+ </div>
213
+ <div>
214
+ <dt>Kernel</dt>
215
+ <dd>{health?.kernel ? Object.values(health.kernel).reduce((a, b) => a + b, 0) : '-'}</dd>
216
+ </div>
217
+ <div>
218
+ <dt>Task errors</dt>
219
+ <dd>{taskErrors}</dd>
220
+ </div>
221
+ </dl>
222
+ </section>
223
+
224
+ <section className="conversation-panel" aria-label="Conversations">
225
+ <div className="panel-title">
226
+ <span>Conversations</span>
227
+ <button type="button" onClick={refreshConversations} aria-label="Refresh conversations">R</button>
228
+ </div>
229
+ <button
230
+ type="button"
231
+ className="new-chat"
232
+ onClick={() => {
233
+ setConversationId(null);
234
+ setMessages([]);
235
+ }}
236
+ >
237
+ New session
238
+ </button>
239
+ <div className="conversation-list">
240
+ {conversations.map((conversation) => (
241
+ <button
242
+ type="button"
243
+ key={conversation.id}
244
+ className={conversation.id === conversationId ? 'conversation active' : 'conversation'}
245
+ onClick={() => loadConversation(conversation.id)}
246
+ >
247
+ <span>{conversation.title || 'Untitled'}</span>
248
+ <small>{conversation.updated_at ? new Date(conversation.updated_at).toLocaleString() : 'recent'}</small>
249
+ </button>
250
+ ))}
251
+ </div>
252
+ </section>
253
+ </aside>
254
+
255
+ <section className="workspace">
256
+ <header className="topbar">
257
+ <div>
258
+ <p className="eyebrow">Self-sufficient deploy</p>
259
+ <h2>{activeConversation?.title || 'Operator session'}</h2>
260
+ </div>
261
+ <div className="runtime-status" aria-live="polite">
262
+ <span className={token ? 'dot online' : 'dot'} aria-hidden="true" />
263
+ {status}
264
+ </div>
265
+ </header>
266
+
267
+ <section className="message-surface" ref={messagesRef} aria-label="Messages">
268
+ {messages.length === 0 ? (
269
+ <div className="empty-state">
270
+ <p>Ask the agent to inspect memory, explain its health, or plan the next deployment step.</p>
271
+ </div>
272
+ ) : messages.map((message) => (
273
+ <article key={message.id} className={`message ${message.role}`}>
274
+ <div className="message-role">{message.role}</div>
275
+ <div className="message-content">{message.content || '...'}</div>
276
+ </article>
277
+ ))}
278
+ </section>
279
+
280
+ <form
281
+ className="composer"
282
+ onSubmit={(event) => {
283
+ event.preventDefault();
284
+ void sendMessage();
285
+ }}
286
+ >
287
+ <textarea
288
+ value={prompt}
289
+ onChange={(event) => setPrompt(event.target.value)}
290
+ placeholder="Message AEGIS"
291
+ rows={3}
292
+ />
293
+ <button type="submit" aria-label="Send message">Send</button>
294
+ </form>
295
+ </section>
296
+
297
+ <aside className="voice-panel" aria-label="Voice call">
298
+ <div className="panel-title">
299
+ <span>Voice</span>
300
+ <span className="voice-state">{voice.status}</span>
301
+ </div>
302
+ <div className="meter" aria-label="Audio level">
303
+ <span style={{ width: `${Math.round(voice.audioLevel * 100)}%` }} />
304
+ </div>
305
+ <div className="voice-actions">
306
+ <button type="button" onClick={() => voice.connected ? voice.endCall() : void voice.startCall()}>
307
+ {voice.connected ? 'End call' : 'Start call'}
308
+ </button>
309
+ <button type="button" onClick={voice.toggleMute} disabled={!voice.connected}>
310
+ {voice.isMuted ? 'Unmute' : 'Mute'}
311
+ </button>
312
+ </div>
313
+ {voice.error ? <p className="error">{voice.error}</p> : null}
314
+ <div className="voice-transcript">
315
+ {voice.transcript.slice(-8).map((entry, index) => (
316
+ <p key={`${entry.role}-${index}`}>
317
+ <strong>{entry.role}</strong>
318
+ <span>{entry.text}</span>
319
+ </p>
320
+ ))}
321
+ {voice.interimTranscript ? <p><strong>interim</strong><span>{voice.interimTranscript}</span></p> : null}
322
+ </div>
323
+ <div className="voice-text">
324
+ <input
325
+ value={voiceText}
326
+ onChange={(event) => setVoiceText(event.target.value)}
327
+ placeholder="Send text to voice agent"
328
+ />
329
+ <button type="button" onClick={sendVoiceText} disabled={!voice.connected}>Send</button>
330
+ </div>
331
+ </aside>
332
+ </main>
333
+ );
334
+ }
335
+
336
+ async function* readSse(body: ReadableStream<Uint8Array>): AsyncGenerator<Record<string, unknown>> {
337
+ const reader = body.getReader();
338
+ const decoder = new TextDecoder();
339
+ let buffer = '';
340
+
341
+ while (true) {
342
+ const { value, done } = await reader.read();
343
+ if (done) break;
344
+ buffer += decoder.decode(value, { stream: true });
345
+ const frames = buffer.split('\n\n');
346
+ buffer = frames.pop() ?? '';
347
+
348
+ for (const frame of frames) {
349
+ const dataLine = frame.split('\n').find((line) => line.startsWith('data: '));
350
+ if (!dataLine) continue;
351
+ yield JSON.parse(dataLine.slice(6));
352
+ }
353
+ }
354
+ }
355
+
356
+ createRoot(document.getElementById('root') as HTMLElement).render(<App />);