total-recall-brain 3.1.2 → 3.2.2

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 (63) hide show
  1. package/extension/background.js +99 -0
  2. package/extension/content-script.css +16 -0
  3. package/extension/content-script.js +134 -0
  4. package/extension/icons/README.md +27 -0
  5. package/extension/icons/icon-128.png +0 -0
  6. package/extension/icons/icon-16.png +0 -0
  7. package/extension/icons/icon-48.png +0 -0
  8. package/extension/lib/brain-client.js +47 -0
  9. package/extension/manifest.json +39 -0
  10. package/extension/options/options.css +218 -0
  11. package/extension/options/options.html +72 -0
  12. package/extension/options/options.js +99 -0
  13. package/extension/popup/popup.css +221 -0
  14. package/extension/popup/popup.html +44 -0
  15. package/extension/popup/popup.js +107 -0
  16. package/extension/sidepanel/sidepanel.css +340 -0
  17. package/extension/sidepanel/sidepanel.html +38 -0
  18. package/extension/sidepanel/sidepanel.js +229 -0
  19. package/frontend/README.md +73 -0
  20. package/frontend/index.html +17 -0
  21. package/frontend/package.json +31 -0
  22. package/frontend/src/App.css +2 -0
  23. package/frontend/src/App.tsx +390 -0
  24. package/frontend/src/api.ts +688 -0
  25. package/frontend/src/assets/hero.png +0 -0
  26. package/frontend/src/assets/react.svg +1 -0
  27. package/frontend/src/assets/vite.svg +1 -0
  28. package/frontend/src/components/BrainSelector.tsx +272 -0
  29. package/frontend/src/components/DaemonLogsTab.tsx +54 -0
  30. package/frontend/src/components/Graph3D.tsx +850 -0
  31. package/frontend/src/components/MarkdownUtils.tsx +99 -0
  32. package/frontend/src/components/ResearchAgendaTab.tsx +707 -0
  33. package/frontend/src/components/TaskQueueTab.tsx +185 -0
  34. package/frontend/src/index.css +1589 -0
  35. package/frontend/src/main.tsx +10 -0
  36. package/frontend/src/pages/ApiKeysPage.tsx +288 -0
  37. package/frontend/src/pages/AutomationsPage.tsx +354 -0
  38. package/frontend/src/pages/ChatPage.tsx +831 -0
  39. package/frontend/src/pages/DeploymentsPage.tsx +275 -0
  40. package/frontend/src/pages/FilesPage.tsx +384 -0
  41. package/frontend/src/pages/HealthPage.tsx +91 -0
  42. package/frontend/src/pages/IntegrationsPage.tsx +382 -0
  43. package/frontend/src/pages/LoginPage.tsx +237 -0
  44. package/frontend/src/pages/MemoryPage.tsx +1192 -0
  45. package/frontend/src/pages/SandboxPage.tsx +113 -0
  46. package/frontend/src/pages/SettingsPage.tsx +662 -0
  47. package/frontend/src/pages/SkillsPage.tsx +618 -0
  48. package/frontend/src/pages/TasksPage.tsx +392 -0
  49. package/frontend/src/pages/UsagePage.tsx +260 -0
  50. package/frontend/src/types.ts +163 -0
  51. package/frontend/tsconfig.app.json +25 -0
  52. package/frontend/tsconfig.json +7 -0
  53. package/frontend/tsconfig.node.json +24 -0
  54. package/frontend/vite.config.ts +24 -0
  55. package/package.json +8 -1
  56. package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +35 -8
  57. package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +13 -8
  58. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/always-websearch-gap.md +28 -1
  59. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/topic-research-sop.md +28 -1
  60. package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +830 -81
  61. package/src/cli/backup.mjs +172 -1
  62. package/src/cli/setup.mjs +24 -0
  63. package/src/server/rest.mjs +56 -7
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.tsx'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
@@ -0,0 +1,288 @@
1
+ import { useState, useEffect, useCallback } from 'react'
2
+ import { listApiKeys, issueApiKey, revokeApiKey } from '../api'
3
+ import type { ApiKey, IssuedApiKey } from '../api'
4
+
5
+ export default function ApiKeysPage() {
6
+ const [keys, setKeys] = useState<ApiKey[]>([])
7
+ const [availableScopes, setAvailableScopes] = useState<string[]>(['*'])
8
+ const [loading, setLoading] = useState(true)
9
+ const [error, setError] = useState<string | null>(null)
10
+ const [newKeyName, setNewKeyName] = useState('')
11
+ const [selectedScopes, setSelectedScopes] = useState<string[]>(['*'])
12
+ const [expiresAt, setExpiresAt] = useState('')
13
+ const [issuing, setIssuing] = useState(false)
14
+ const [newlyIssued, setNewlyIssued] = useState<IssuedApiKey | null>(null)
15
+ const [copied, setCopied] = useState(false)
16
+
17
+ const load = useCallback(async (showLoading = false) => {
18
+ if (showLoading) setLoading(true)
19
+ setError(null)
20
+ try {
21
+ const result = await listApiKeys()
22
+ setKeys(result.keys || [])
23
+ setAvailableScopes(result.available_scopes || ['*'])
24
+ } catch (e: unknown) {
25
+ setError((e as Error).message)
26
+ } finally {
27
+ setLoading(false)
28
+ }
29
+ }, [])
30
+
31
+ // eslint-disable-next-line react-hooks/set-state-in-effect -- legitimate one-time data fetch on mount
32
+ useEffect(() => { void load(false) }, [load])
33
+
34
+ const handleIssue = async () => {
35
+ if (!newKeyName.trim()) return
36
+ setIssuing(true)
37
+ setError(null)
38
+ try {
39
+ const key = await issueApiKey(newKeyName.trim(), selectedScopes, expiresAt || null)
40
+ setNewlyIssued(key)
41
+ setNewKeyName('')
42
+ setSelectedScopes(['*'])
43
+ setExpiresAt('')
44
+ await load(true)
45
+ } catch (e: unknown) {
46
+ setError((e as Error).message)
47
+ } finally {
48
+ setIssuing(false)
49
+ }
50
+ }
51
+
52
+ const handleRevoke = async (id: string, name: string) => {
53
+ if (!confirm(`Revoke key "${name}"? This cannot be undone.`)) return
54
+ try {
55
+ await revokeApiKey(id)
56
+ await load(true)
57
+ } catch (e: unknown) {
58
+ setError((e as Error).message)
59
+ }
60
+ }
61
+
62
+ const copyToken = () => {
63
+ if (!newlyIssued) return
64
+ navigator.clipboard.writeText(newlyIssued.token)
65
+ setCopied(true)
66
+ setTimeout(() => setCopied(false), 2000)
67
+ }
68
+
69
+ const activeKeys = keys.filter(k => !k.revoked)
70
+ const revokedKeys = keys.filter(k => k.revoked)
71
+
72
+ const toggleScope = (scope: string) => {
73
+ setSelectedScopes(current => {
74
+ if (scope === '*') return ['*']
75
+ const withoutAll = current.filter(s => s !== '*')
76
+ const next = withoutAll.includes(scope)
77
+ ? withoutAll.filter(s => s !== scope)
78
+ : [...withoutAll, scope]
79
+ return next.length ? next : ['*']
80
+ })
81
+ }
82
+
83
+ const formatScopes = (scopes: string[]) => {
84
+ if (!scopes || scopes.length === 0 || scopes.includes('*')) return 'Full access'
85
+ return scopes.join(', ')
86
+ }
87
+
88
+ return (
89
+ <div className="page">
90
+ <div className="page-header">
91
+ <h1>API Keys</h1>
92
+ <p>Issue and manage Personal Access Tokens to use Total Recall anywhere</p>
93
+ </div>
94
+
95
+ {/* New token banner — shown once after issuing */}
96
+ {newlyIssued && (
97
+ <div style={{
98
+ background: 'linear-gradient(135deg, rgba(16, 185, 129, 0.15), rgba(16, 185, 129, 0.05))',
99
+ border: '1px solid rgba(16, 185, 129, 0.4)',
100
+ borderRadius: 12,
101
+ padding: '20px 24px',
102
+ marginBottom: 24,
103
+ }}>
104
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8 }}>
105
+ <span style={{ fontSize: 18 }}>🔑</span>
106
+ <strong style={{ color: 'var(--accent)' }}>New key issued — copy it now! It won't be shown again.</strong>
107
+ </div>
108
+ <div style={{
109
+ display: 'flex', alignItems: 'center', gap: 12,
110
+ background: 'var(--bg-primary)', borderRadius: 8, padding: '10px 16px',
111
+ border: '1px solid var(--border)', fontFamily: 'monospace', fontSize: 13,
112
+ wordBreak: 'break-all',
113
+ }}>
114
+ <span style={{ flex: 1 }}>{newlyIssued.token}</span>
115
+ <button className="btn btn-sm btn-primary" onClick={copyToken} style={{ flexShrink: 0, minWidth: 70 }}>
116
+ {copied ? '✓ Copied' : 'Copy'}
117
+ </button>
118
+ </div>
119
+ <div style={{ marginTop: 8, fontSize: 12, color: 'var(--text-tertiary)' }}>
120
+ Usage: <code>Authorization: Bearer {newlyIssued.token.slice(0, 12)}…</code>
121
+ <span style={{ marginLeft: 12 }}>Scopes: {formatScopes(newlyIssued.scopes)}</span>
122
+ </div>
123
+ <button className="btn btn-ghost btn-sm" style={{ marginTop: 8 }} onClick={() => setNewlyIssued(null)}>
124
+ Dismiss
125
+ </button>
126
+ </div>
127
+ )}
128
+
129
+ {/* Issue new key */}
130
+ <div style={{
131
+ background: 'var(--bg-secondary)', borderRadius: 12,
132
+ border: '1px solid var(--border)', padding: '20px 24px', marginBottom: 24,
133
+ }}>
134
+ <h3 style={{ margin: '0 0 14px' }}>Issue New Key</h3>
135
+ <div style={{ display: 'flex', gap: 12, marginBottom: 14 }}>
136
+ <input
137
+ id="new-api-key-name"
138
+ type="text"
139
+ placeholder="e.g. Cursor IDE, iOS Shortcut, CI Pipeline…"
140
+ value={newKeyName}
141
+ onChange={e => setNewKeyName(e.target.value)}
142
+ onKeyDown={e => { if (e.key === 'Enter') handleIssue() }}
143
+ style={{
144
+ flex: 1, padding: '10px 14px', borderRadius: 8, fontSize: 14,
145
+ background: 'var(--bg-primary)', color: 'var(--text-primary)',
146
+ border: '1px solid var(--border)', outline: 'none',
147
+ }}
148
+ />
149
+ <button
150
+ className="btn btn-primary"
151
+ onClick={handleIssue}
152
+ disabled={issuing || !newKeyName.trim()}
153
+ style={{ minWidth: 120 }}
154
+ >
155
+ {issuing ? 'Issuing…' : '+ Issue Key'}
156
+ </button>
157
+ </div>
158
+ <div style={{ display: 'grid', gridTemplateColumns: 'minmax(180px, 240px) 1fr', gap: 16, alignItems: 'start' }}>
159
+ <label style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 12, color: 'var(--text-secondary)' }}>
160
+ Expires
161
+ <input
162
+ type="date"
163
+ value={expiresAt}
164
+ onChange={e => setExpiresAt(e.target.value)}
165
+ style={{
166
+ padding: '8px 10px', borderRadius: 8, fontSize: 13,
167
+ background: 'var(--bg-primary)', color: 'var(--text-primary)',
168
+ border: '1px solid var(--border)', outline: 'none',
169
+ }}
170
+ />
171
+ </label>
172
+ <div>
173
+ <div style={{ fontSize: 12, color: 'var(--text-secondary)', marginBottom: 8 }}>Scopes</div>
174
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
175
+ {availableScopes.map(scope => (
176
+ <label
177
+ key={scope}
178
+ style={{
179
+ display: 'inline-flex', alignItems: 'center', gap: 6,
180
+ padding: '6px 9px', borderRadius: 8, border: '1px solid var(--border)',
181
+ background: selectedScopes.includes(scope) ? 'rgba(59, 130, 246, 0.12)' : 'var(--bg-primary)',
182
+ color: 'var(--text-secondary)', fontSize: 12, cursor: 'pointer',
183
+ }}
184
+ >
185
+ <input
186
+ type="checkbox"
187
+ checked={selectedScopes.includes(scope)}
188
+ onChange={() => toggleScope(scope)}
189
+ style={{ margin: 0 }}
190
+ />
191
+ <span>{scope === '*' ? 'Full access' : scope}</span>
192
+ </label>
193
+ ))}
194
+ </div>
195
+ </div>
196
+ </div>
197
+ </div>
198
+
199
+ {error && <div className="badge badge-warning" style={{ marginBottom: 16, alignSelf: 'flex-start' }}>⚠️ {error}</div>}
200
+
201
+ {/* Active keys table */}
202
+ <div style={{ marginBottom: 32 }}>
203
+ <h3 style={{ marginBottom: 12 }}>Active Keys ({activeKeys.length})</h3>
204
+ {loading ? (
205
+ <div style={{ color: 'var(--text-tertiary)', padding: 20 }}>Loading…</div>
206
+ ) : activeKeys.length === 0 ? (
207
+ <div style={{
208
+ textAlign: 'center', padding: '40px 20px', color: 'var(--text-tertiary)',
209
+ border: '1px dashed var(--border)', borderRadius: 12,
210
+ }}>
211
+ No active keys yet. Issue one above to connect Total Recall to any tool.
212
+ </div>
213
+ ) : (
214
+ <div style={{ overflowX: 'auto' }}>
215
+ <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
216
+ <thead>
217
+ <tr style={{ borderBottom: '1px solid var(--border)', color: 'var(--text-secondary)', textAlign: 'left' }}>
218
+ <th style={{ padding: '8px 12px' }}>Name</th>
219
+ <th style={{ padding: '8px 12px' }}>Token</th>
220
+ <th style={{ padding: '8px 12px' }}>Scopes</th>
221
+ <th style={{ padding: '8px 12px' }}>Requests</th>
222
+ <th style={{ padding: '8px 12px' }}>Last Used</th>
223
+ <th style={{ padding: '8px 12px' }}>Expires</th>
224
+ <th style={{ padding: '8px 12px' }}>Created</th>
225
+ <th style={{ padding: '8px 12px' }}></th>
226
+ </tr>
227
+ </thead>
228
+ <tbody>
229
+ {activeKeys.map(k => (
230
+ <tr key={k.id} style={{ borderBottom: '1px solid var(--border)' }}>
231
+ <td style={{ padding: '10px 12px', fontWeight: 500 }}>{k.name}</td>
232
+ <td style={{ padding: '10px 12px', fontFamily: 'monospace', color: 'var(--text-secondary)' }}>{k.token_preview}</td>
233
+ <td style={{ padding: '10px 12px', color: 'var(--text-secondary)', maxWidth: 220 }}>
234
+ <span title={formatScopes(k.scopes)} style={{ display: 'inline-block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '100%' }}>
235
+ {formatScopes(k.scopes)}
236
+ </span>
237
+ </td>
238
+ <td style={{ padding: '10px 12px' }}>
239
+ <span className="badge badge-accent">{k.hit_count.toLocaleString()}</span>
240
+ </td>
241
+ <td style={{ padding: '10px 12px', color: 'var(--text-secondary)' }}>
242
+ {k.last_used_at ? new Date(k.last_used_at).toLocaleString() : '—'}
243
+ </td>
244
+ <td style={{ padding: '10px 12px', color: 'var(--text-secondary)' }}>
245
+ {k.expires_at ? new Date(k.expires_at).toLocaleDateString() : 'Never'}
246
+ </td>
247
+ <td style={{ padding: '10px 12px', color: 'var(--text-secondary)' }}>
248
+ {new Date(k.created_at).toLocaleDateString()}
249
+ </td>
250
+ <td style={{ padding: '10px 12px' }}>
251
+ <button
252
+ className="btn btn-ghost btn-sm"
253
+ style={{ color: 'var(--error, #ef4444)', borderColor: 'var(--error, #ef4444)' }}
254
+ onClick={() => handleRevoke(k.id, k.name)}
255
+ >
256
+ Revoke
257
+ </button>
258
+ </td>
259
+ </tr>
260
+ ))}
261
+ </tbody>
262
+ </table>
263
+ </div>
264
+ )}
265
+ </div>
266
+
267
+ {/* Revoked keys (collapsed) */}
268
+ {revokedKeys.length > 0 && (
269
+ <details style={{ color: 'var(--text-tertiary)', fontSize: 13 }}>
270
+ <summary style={{ cursor: 'pointer', marginBottom: 8 }}>
271
+ Revoked Keys ({revokedKeys.length})
272
+ </summary>
273
+ {revokedKeys.map(k => (
274
+ <div key={k.id} style={{
275
+ display: 'flex', gap: 16, padding: '8px 12px',
276
+ borderBottom: '1px solid var(--border)', opacity: 0.5,
277
+ }}>
278
+ <span style={{ flex: 1, textDecoration: 'line-through' }}>{k.name}</span>
279
+ <span style={{ fontFamily: 'monospace' }}>{k.token_preview}</span>
280
+ <span>{formatScopes(k.scopes)}</span>
281
+ <span>{k.hit_count} requests</span>
282
+ </div>
283
+ ))}
284
+ </details>
285
+ )}
286
+ </div>
287
+ )
288
+ }
@@ -0,0 +1,354 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { listTasks, createTask, triggerDream, triggerRecompile } from '../api';
3
+ import type { Task } from '../types';
4
+
5
+ export default function AutomationsPage() {
6
+ const [tasks, setTasks] = useState<Task[]>([]);
7
+ const [loading, setLoading] = useState(true);
8
+ const [error, setError] = useState<string | null>(null);
9
+ const [success, setSuccess] = useState<string | null>(null);
10
+
11
+ // Running Actions UI state
12
+ const [dreaming, setDreaming] = useState(false);
13
+ const [compiling, setCompiling] = useState(false);
14
+
15
+ // Custom Task Form state
16
+ const [category, setCategory] = useState('fact-seeker');
17
+ const [target, setTarget] = useState('');
18
+ const [body, setBody] = useState('');
19
+ const [priority, setPriority] = useState(5);
20
+ const [submitting, setSubmitting] = useState(false);
21
+
22
+ const fetchActiveTasks = async () => {
23
+ try {
24
+ const data = await listTasks();
25
+ setTasks(data || []);
26
+ } catch (err: unknown) {
27
+ setError((err as Error).message || 'Failed to fetch task queue.');
28
+ } finally {
29
+ setLoading(false);
30
+ }
31
+ };
32
+
33
+ useEffect(() => {
34
+ // eslint-disable-next-line react-hooks/set-state-in-effect -- legitimate task queue polling on mount
35
+ void fetchActiveTasks();
36
+ const interval = setInterval(fetchActiveTasks, 4000);
37
+ return () => clearInterval(interval);
38
+ }, []);
39
+
40
+ const handleTriggerDream = async () => {
41
+ setDreaming(true);
42
+ setError(null);
43
+ setSuccess(null);
44
+ try {
45
+ const result = await triggerDream();
46
+ setSuccess(`Dream consolidator complete! REM Consolidation cycle status: ${result.status}`);
47
+ setTimeout(() => setSuccess(null), 5000);
48
+ void fetchActiveTasks();
49
+ } catch (err: unknown) {
50
+ setError((err as Error).message || 'Dream cycle execution failed.');
51
+ } finally {
52
+ setDreaming(false);
53
+ }
54
+ };
55
+
56
+ const handleTriggerRecompile = async () => {
57
+ setCompiling(true);
58
+ setError(null);
59
+ setSuccess(null);
60
+ try {
61
+ const result = await triggerRecompile();
62
+ setSuccess(result.message || 'Brain surfaces recompiled successfully!');
63
+ setTimeout(() => setSuccess(null), 5000);
64
+ } catch (err: unknown) {
65
+ setError((err as Error).message || 'Surface recompilation failed.');
66
+ } finally {
67
+ setCompiling(false);
68
+ }
69
+ };
70
+
71
+ const handleEnqueueTask = async (e: React.FormEvent) => {
72
+ e.preventDefault();
73
+ if (!target.trim()) return;
74
+ setSubmitting(true);
75
+ setError(null);
76
+ setSuccess(null);
77
+ try {
78
+ await createTask(category, target.trim(), body.trim(), priority);
79
+ setTarget('');
80
+ setBody('');
81
+ setSuccess('Cognitive System 2 task successfully enqueued!');
82
+ setTimeout(() => setSuccess(null), 4000);
83
+ void fetchActiveTasks();
84
+ } catch (err: unknown) {
85
+ setError((err as Error).message || 'Enqueuing task failed.');
86
+ } finally {
87
+ setSubmitting(false);
88
+ }
89
+ };
90
+
91
+ const CRON_JOBS = [
92
+ { name: 'Upstream Git Synchronizer', cron: '*/5 * * * *', interval: 'Every 5m', desc: 'Syncs rule shims, merges remote memory nodes, compiles rules', status: 'active', emoji: '🔄' },
93
+ { name: 'REM Dream Consolidator', cron: '0 * * * *', interval: 'Every 60m', desc: 'Runs cognitive dream cycles (decaying confidence, deduplicating inbox)', status: 'active', emoji: '🌙' },
94
+ { name: 'Watchdog Subsystem Probe', cron: '*/1 * * * *', interval: 'Every 60s', desc: 'Probes Express gateways and quarantines compromised blocks', status: 'active', emoji: '🛡️' },
95
+ { name: 'Encrypted VFS Backup System', cron: '0 0 * * *', interval: 'Daily', desc: 'Creates secure point-in-time encrypted tarball snapshots of the VFS', status: 'active', emoji: '🗄️' },
96
+ ];
97
+
98
+ return (
99
+ <div className="page" style={{ display: 'flex', flexDirection: 'column', height: '100%', overflowY: 'auto' }}>
100
+ <div className="page-header" style={{ marginBottom: 24 }}>
101
+ <h1>Automations & Cron Schedulers</h1>
102
+ <p>Orchestrate background task daemons, trigger REM sleep dream loops, and manage Cron intervals</p>
103
+ </div>
104
+
105
+ {error && (
106
+ <div className="badge badge-error" style={{ marginBottom: 20, display: 'inline-flex', padding: '6px 12px', background: 'rgba(239, 68, 68, 0.1)', border: '1px solid #ef4444', color: '#f87171' }}>
107
+ ⚠️ {error}
108
+ </div>
109
+ )}
110
+ {success && (
111
+ <div className="badge badge-success" style={{ marginBottom: 20, display: 'inline-flex', padding: '6px 12px', background: 'rgba(16, 185, 129, 0.1)', border: '1px solid #10b981', color: '#34d399' }}>
112
+ ✓ {success}
113
+ </div>
114
+ )}
115
+
116
+ {/* Grid panels */}
117
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))', gap: 24, marginBottom: 24 }}>
118
+
119
+ {/* Panel A: Core Daemon Loops & Cron triggers */}
120
+ <div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column', gap: 20 }}>
121
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
122
+ <span style={{ fontSize: 20 }}>🕒</span>
123
+ <div>
124
+ <h3 style={{ fontSize: 15, fontWeight: 600 }}>Daemon Scheduler Triggers</h3>
125
+ <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Review active cron loops running under System Cron triggers</p>
126
+ </div>
127
+ </div>
128
+
129
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
130
+ {CRON_JOBS.map((job, idx) => (
131
+ <div key={idx} style={{ display: 'flex', alignItems: 'center', gap: 12, background: 'var(--bg-tertiary)', padding: 12, borderRadius: 8, border: '1px solid var(--border)' }}>
132
+ <span style={{ fontSize: 18 }}>{job.emoji}</span>
133
+ <div style={{ flex: 1 }}>
134
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
135
+ <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)' }}>{job.name}</span>
136
+ <span className="badge badge-accent" style={{ fontSize: 9, padding: '2px 6px', background: 'rgba(0, 206, 201, 0.1)', color: '#00cec9', border: '1px solid rgba(0, 206, 201, 0.2)' }}>
137
+ {job.interval}
138
+ </span>
139
+ </div>
140
+ <div style={{ fontSize: 11, color: 'var(--text-secondary)', marginTop: 2 }}>{job.desc}</div>
141
+ <div style={{ fontSize: 10, color: 'var(--text-tertiary)', fontFamily: 'monospace', marginTop: 4 }}>Cron: {job.cron}</div>
142
+ </div>
143
+ </div>
144
+ ))}
145
+ </div>
146
+
147
+ <div style={{ display: 'flex', gap: 12, borderTop: '1px solid var(--border)', paddingTop: 16 }}>
148
+ <button
149
+ className="btn btn-sm"
150
+ onClick={handleTriggerDream}
151
+ disabled={dreaming}
152
+ style={{
153
+ flex: 1,
154
+ background: dreaming ? 'var(--bg-secondary)' : 'linear-gradient(135deg, #a855f7, #6b21a8)',
155
+ color: '#fff',
156
+ border: 'none',
157
+ fontWeight: 500
158
+ }}
159
+ >
160
+ {dreaming ? '⏳ Consolidating...' : '🌙 Run REM Sleep Now'}
161
+ </button>
162
+ <button
163
+ className="btn btn-sm"
164
+ onClick={handleTriggerRecompile}
165
+ disabled={compiling}
166
+ style={{
167
+ flex: 1,
168
+ background: compiling ? 'var(--bg-secondary)' : 'linear-gradient(135deg, #3b82f6, #1d4ed8)',
169
+ color: '#fff',
170
+ border: 'none',
171
+ fontWeight: 500
172
+ }}
173
+ >
174
+ {compiling ? '⏳ Compiling...' : '🔄 Rebuild Indexes'}
175
+ </button>
176
+ </div>
177
+ </div>
178
+
179
+ {/* Panel B: Deliberate Task SSSS Creator */}
180
+ <div className="card" style={{ padding: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column', gap: 20 }}>
181
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderBottom: '1px solid var(--border)', paddingBottom: 12 }}>
182
+ <span style={{ fontSize: 20 }}>🤖</span>
183
+ <div>
184
+ <h3 style={{ fontSize: 15, fontWeight: 600 }}>Deliberative Task Creator</h3>
185
+ <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Trigger System 2 deep reasoning loops in the background queue</p>
186
+ </div>
187
+ </div>
188
+
189
+ <form onSubmit={handleEnqueueTask} style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
190
+ <div style={{ display: 'flex', gap: 12 }}>
191
+ <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 4 }}>
192
+ <label htmlFor="task_cat" style={{ fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)' }}>Category</label>
193
+ <select
194
+ id="task_cat"
195
+ value={category}
196
+ onChange={(e) => setCategory(e.target.value)}
197
+ style={{
198
+ background: 'var(--bg-tertiary)',
199
+ color: 'var(--text-primary)',
200
+ border: '1px solid var(--border)',
201
+ padding: '8px 10px',
202
+ borderRadius: 6,
203
+ outline: 'none',
204
+ fontSize: 12
205
+ }}
206
+ >
207
+ <option value="fact-seeker">🌐 Fact Seeker (Web retrieval)</option>
208
+ <option value="clarity-rewriter">📝 Clarity Rewriter (Synthesizer)</option>
209
+ <option value="post-mortem-daily">🧠 Post-Mortem daily logs</option>
210
+ <option value="code-optimization">💻 Code Optimizer agent</option>
211
+ </select>
212
+ </div>
213
+
214
+ <div style={{ width: 100, display: 'flex', flexDirection: 'column', gap: 4 }}>
215
+ <label htmlFor="task_priority" style={{ fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)' }}>Priority</label>
216
+ <select
217
+ id="task_priority"
218
+ value={priority}
219
+ onChange={(e) => setPriority(Number(e.target.value))}
220
+ style={{
221
+ background: 'var(--bg-tertiary)',
222
+ color: 'var(--text-primary)',
223
+ border: '1px solid var(--border)',
224
+ padding: '8px 10px',
225
+ borderRadius: 6,
226
+ outline: 'none',
227
+ fontSize: 12
228
+ }}
229
+ >
230
+ <option value={1}>1 (Absolute)</option>
231
+ <option value={3}>3 (High)</option>
232
+ <option value={5}>5 (Normal)</option>
233
+ <option value={8}>8 (Low)</option>
234
+ </select>
235
+ </div>
236
+ </div>
237
+
238
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
239
+ <label htmlFor="task_target" style={{ fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)' }}>Task Goal / Objective</label>
240
+ <input
241
+ id="task_target"
242
+ type="text"
243
+ placeholder="e.g. Audit security.yml files for weak configuration hashes"
244
+ value={target}
245
+ onChange={(e) => setTarget(e.target.value)}
246
+ required
247
+ style={{
248
+ background: 'var(--bg-tertiary)',
249
+ color: 'var(--text-primary)',
250
+ border: '1px solid var(--border)',
251
+ padding: '8px 12px',
252
+ borderRadius: 6,
253
+ outline: 'none',
254
+ fontSize: 12
255
+ }}
256
+ />
257
+ </div>
258
+
259
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
260
+ <label htmlFor="task_body" style={{ fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)' }}>Deliberation Payload (Context / Instructions)</label>
261
+ <textarea
262
+ id="task_body"
263
+ rows={3}
264
+ placeholder="Detail specific context, files to search, and desired output formats..."
265
+ value={body}
266
+ onChange={(e) => setBody(e.target.value)}
267
+ style={{
268
+ background: 'var(--bg-tertiary)',
269
+ color: 'var(--text-primary)',
270
+ border: '1px solid var(--border)',
271
+ padding: '8px 12px',
272
+ borderRadius: 6,
273
+ outline: 'none',
274
+ fontSize: 12,
275
+ resize: 'none'
276
+ }}
277
+ />
278
+ </div>
279
+
280
+ <button
281
+ type="submit"
282
+ disabled={submitting || !target.trim()}
283
+ style={{
284
+ width: '100%',
285
+ background: 'linear-gradient(135deg, var(--accent), var(--accent-hover))',
286
+ color: '#fff',
287
+ padding: '10px',
288
+ borderRadius: 6,
289
+ fontWeight: 500,
290
+ border: 'none',
291
+ marginTop: 8,
292
+ cursor: submitting || !target.trim() ? 'not-allowed' : 'pointer'
293
+ }}
294
+ >
295
+ {submitting ? '⏳ Dispatching Task...' : '🚀 Dispatch to Queue'}
296
+ </button>
297
+ </form>
298
+ </div>
299
+
300
+ </div>
301
+
302
+ {/* SSSS Queue List Panel */}
303
+ <div className="card" style={{ flex: 1, minHeight: 250, padding: 24, background: 'rgba(18, 18, 26, 0.6)', backdropFilter: 'blur(8px)', border: '1px solid var(--border)', display: 'flex', flexDirection: 'column' }}>
304
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderBottom: '1px solid var(--border)', paddingBottom: 12, marginBottom: 16 }}>
305
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
306
+ <span style={{ fontSize: 20 }}>📋</span>
307
+ <div>
308
+ <h3 style={{ fontSize: 15, fontWeight: 600 }}>Active SSSS Background Queue</h3>
309
+ <p style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>Real-time verification of enqueued System 2 task nodes</p>
310
+ </div>
311
+ </div>
312
+ <span className="badge badge-accent" style={{ background: 'var(--accent-muted)', color: 'var(--accent)' }}>{tasks.length} enqueued</span>
313
+ </div>
314
+
315
+ {loading ? (
316
+ <div style={{ padding: 20, textAlign: 'center', color: 'var(--text-secondary)' }}>Loading task list...</div>
317
+ ) : tasks.length === 0 ? (
318
+ <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-secondary)', fontStyle: 'italic' }}>
319
+ 🎉 No enqueued tasks! SSSS Queue is fully clear.
320
+ </div>
321
+ ) : (
322
+ <div style={{ overflowY: 'auto', flex: 1, display: 'flex', flexDirection: 'column', gap: 12 }}>
323
+ {tasks.map((task) => {
324
+ const isPending = task.status === 'pending';
325
+ const statusColor = isPending ? 'var(--warning)' : '#10b981';
326
+ return (
327
+ <div key={task.slug} style={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', padding: 16, borderRadius: 8, display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 12 }}>
328
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 4, flex: 1, minWidth: 260 }}>
329
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
330
+ <span className="badge" style={{ fontSize: 10, background: 'var(--bg-elevated)', color: 'var(--text-secondary)' }}>{task.category}</span>
331
+ <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)' }}>{task.target}</span>
332
+ </div>
333
+ {task.body && <div style={{ fontSize: 11, color: 'var(--text-secondary)', fontStyle: 'italic', marginTop: 2 }}>{task.body}</div>}
334
+ <div style={{ fontSize: 10, color: 'var(--text-tertiary)', fontFamily: 'monospace', marginTop: 4 }}>Node ID: {task.slug} • Priority: {task.priority || 5}</div>
335
+ </div>
336
+ <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
337
+ <div style={{ textAlign: 'right' }}>
338
+ <div style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 11, fontWeight: 600, color: statusColor, textTransform: 'capitalize' }}>
339
+ {isPending && <span className="pulse" style={{ background: 'var(--warning)', width: 6, height: 6, borderRadius: '50%' }} />}
340
+ {task.status}
341
+ </div>
342
+ <div style={{ fontSize: 10, color: 'var(--text-tertiary)' }}>Progress: {task.progress || 0}%</div>
343
+ </div>
344
+ </div>
345
+ </div>
346
+ );
347
+ })}
348
+ </div>
349
+ )}
350
+ </div>
351
+
352
+ </div>
353
+ );
354
+ }