@stackbilt/aegis-core 0.1.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/package.json +96 -0
- package/schema.sql +586 -0
- package/src/adapters/voice/cloudflare-agent.ts +34 -0
- package/src/auth.ts +124 -0
- package/src/bluesky.ts +464 -0
- package/src/claude-tools/content.ts +188 -0
- package/src/claude-tools/email.ts +69 -0
- package/src/claude-tools/github.ts +440 -0
- package/src/claude-tools/goals.ts +116 -0
- package/src/claude-tools/index.ts +353 -0
- package/src/claude-tools/web.ts +59 -0
- package/src/claude.ts +406 -0
- package/src/codebeast.ts +200 -0
- package/src/composite.ts +715 -0
- package/src/content/column.ts +80 -0
- package/src/content/hero-image.ts +47 -0
- package/src/content/index.ts +27 -0
- package/src/content/journal.ts +91 -0
- package/src/content/roundtable.ts +163 -0
- package/src/core.ts +309 -0
- package/src/dashboard.ts +620 -0
- package/src/decision-docs.ts +284 -0
- package/src/dispatch.ts +13 -0
- package/src/edge-env.ts +58 -0
- package/src/email.ts +850 -0
- package/src/exports.ts +156 -0
- package/src/github-projects.ts +312 -0
- package/src/github.ts +670 -0
- package/src/groq.ts +247 -0
- package/src/health-page.ts +578 -0
- package/src/index.ts +89 -0
- package/src/kernel/argus-actions.ts +397 -0
- package/src/kernel/argus-correlation.ts +639 -0
- package/src/kernel/board.ts +91 -0
- package/src/kernel/briefing.ts +177 -0
- package/src/kernel/classify-memory-topic.ts +166 -0
- package/src/kernel/cognition.ts +377 -0
- package/src/kernel/court-cards.ts +163 -0
- package/src/kernel/dispatch.ts +587 -0
- package/src/kernel/domain.ts +50 -0
- package/src/kernel/dynamic-tools.ts +322 -0
- package/src/kernel/executor-port.ts +45 -0
- package/src/kernel/executors/claude.ts +73 -0
- package/src/kernel/executors/direct.ts +237 -0
- package/src/kernel/executors/groq.ts +18 -0
- package/src/kernel/executors/index.ts +87 -0
- package/src/kernel/executors/tarotscript.ts +104 -0
- package/src/kernel/executors/workers-ai.ts +54 -0
- package/src/kernel/insight-cache.ts +76 -0
- package/src/kernel/memory/agenda.ts +200 -0
- package/src/kernel/memory/blocks.ts +188 -0
- package/src/kernel/memory/consolidation.ts +194 -0
- package/src/kernel/memory/episodic.ts +241 -0
- package/src/kernel/memory/goals.ts +156 -0
- package/src/kernel/memory/graph.ts +290 -0
- package/src/kernel/memory/index.ts +11 -0
- package/src/kernel/memory/insights.ts +316 -0
- package/src/kernel/memory/procedural.ts +467 -0
- package/src/kernel/memory/pruning.ts +67 -0
- package/src/kernel/memory/recall.ts +367 -0
- package/src/kernel/memory/semantic.ts +315 -0
- package/src/kernel/memory/synthesis.ts +161 -0
- package/src/kernel/memory-adapter.ts +369 -0
- package/src/kernel/memory-guardrails.ts +76 -0
- package/src/kernel/port.ts +23 -0
- package/src/kernel/resilience.ts +322 -0
- package/src/kernel/router.ts +471 -0
- package/src/kernel/scheduled/agent-dispatch.ts +252 -0
- package/src/kernel/scheduled/argus-analytics.ts +247 -0
- package/src/kernel/scheduled/argus-heartbeat.ts +320 -0
- package/src/kernel/scheduled/argus-notify.ts +348 -0
- package/src/kernel/scheduled/board-sync.ts +110 -0
- package/src/kernel/scheduled/ci-watcher.ts +125 -0
- package/src/kernel/scheduled/cognitive-metrics.ts +377 -0
- package/src/kernel/scheduled/consolidation.ts +229 -0
- package/src/kernel/scheduled/content-drip.ts +47 -0
- package/src/kernel/scheduled/content.ts +6 -0
- package/src/kernel/scheduled/conversation-facts.ts +204 -0
- package/src/kernel/scheduled/cost-report.ts +84 -0
- package/src/kernel/scheduled/curiosity.ts +219 -0
- package/src/kernel/scheduled/dev-activity.ts +44 -0
- package/src/kernel/scheduled/digest.ts +317 -0
- package/src/kernel/scheduled/dreaming/agenda-triage.ts +115 -0
- package/src/kernel/scheduled/dreaming/facts.ts +239 -0
- package/src/kernel/scheduled/dreaming/index.ts +8 -0
- package/src/kernel/scheduled/dreaming/llm.ts +33 -0
- package/src/kernel/scheduled/dreaming/pattern-synthesis.ts +124 -0
- package/src/kernel/scheduled/dreaming/persona.ts +75 -0
- package/src/kernel/scheduled/dreaming/symbolic.ts +31 -0
- package/src/kernel/scheduled/dreaming/task-proposals.ts +80 -0
- package/src/kernel/scheduled/dreaming.ts +66 -0
- package/src/kernel/scheduled/entropy.ts +149 -0
- package/src/kernel/scheduled/escalation.ts +192 -0
- package/src/kernel/scheduled/feed-watcher.ts +206 -0
- package/src/kernel/scheduled/goals.ts +214 -0
- package/src/kernel/scheduled/governance.ts +41 -0
- package/src/kernel/scheduled/heartbeat.ts +220 -0
- package/src/kernel/scheduled/inbox-processor.ts +174 -0
- package/src/kernel/scheduled/index.ts +245 -0
- package/src/kernel/scheduled/issue-proposer.ts +478 -0
- package/src/kernel/scheduled/issue-watcher.ts +128 -0
- package/src/kernel/scheduled/pr-automerge.ts +213 -0
- package/src/kernel/scheduled/product-health.ts +107 -0
- package/src/kernel/scheduled/reflection.ts +373 -0
- package/src/kernel/scheduled/self-improvement.ts +114 -0
- package/src/kernel/scheduled/social-engage.ts +175 -0
- package/src/kernel/scheduled/task-audit.ts +60 -0
- package/src/kernel/symbolic.ts +156 -0
- package/src/kernel/types.ts +145 -0
- package/src/landing.ts +1190 -0
- package/src/lib/audit-chain/chain.ts +28 -0
- package/src/lib/audit-chain/types.ts +12 -0
- package/src/lib/observability/errors.ts +55 -0
- package/src/markdown.ts +164 -0
- package/src/mcp/handlers.ts +647 -0
- package/src/mcp/server.ts +184 -0
- package/src/mcp/tools.ts +316 -0
- package/src/mcp-client.ts +275 -0
- package/src/mcp-server.ts +2 -0
- package/src/operator/config.example.ts +60 -0
- package/src/operator/config.ts +60 -0
- package/src/operator/index.ts +46 -0
- package/src/operator/persona.example.ts +34 -0
- package/src/operator/persona.ts +34 -0
- package/src/operator/prompt-builder.ts +190 -0
- package/src/operator/types.ts +43 -0
- package/src/pulse.ts +1179 -0
- package/src/routes/bluesky.ts +116 -0
- package/src/routes/cc-tasks.ts +328 -0
- package/src/routes/codebeast.ts +1 -0
- package/src/routes/content.ts +194 -0
- package/src/routes/conversations.ts +25 -0
- package/src/routes/dynamic-tools.ts +111 -0
- package/src/routes/feedback.ts +192 -0
- package/src/routes/health.ts +147 -0
- package/src/routes/messages.ts +228 -0
- package/src/routes/observability.ts +82 -0
- package/src/routes/operator-logs.ts +42 -0
- package/src/routes/pages.ts +96 -0
- package/src/routes/sessions.ts +54 -0
- package/src/sanitize.ts +73 -0
- package/src/schema-enums.ts +155 -0
- package/src/search.ts +112 -0
- package/src/task-intelligence.ts +497 -0
- package/src/types.ts +194 -0
- package/src/ui.ts +5 -0
- package/src/version.ts +3 -0
- package/src/workers-ai-chat.ts +333 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Stub — full implementation not yet extracted to OSS
|
|
2
|
+
|
|
3
|
+
import { Hono } from 'hono';
|
|
4
|
+
import type { Env } from '../types.js';
|
|
5
|
+
import { getAllProceduresWithDerivedStats, getActiveAgendaItems } from '../kernel/memory/index.js';
|
|
6
|
+
|
|
7
|
+
const observability = new Hono<{ Bindings: Env }>();
|
|
8
|
+
|
|
9
|
+
// ─── Shadow Write Stats ─────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
observability.get('/api/shadow-stats', async (c) => {
|
|
12
|
+
const days = parseInt(c.req.query('days') ?? '7', 10);
|
|
13
|
+
|
|
14
|
+
const summary = await c.env.DB.prepare(
|
|
15
|
+
"SELECT COUNT(*) as total_writes, SUM(CASE WHEN worker_ok THEN 1 ELSE 0 END) as worker_ok, SUM(CASE WHEN d1_ok THEN 1 ELSE 0 END) as d1_ok FROM shadow_writes WHERE created_at > datetime('now', '-' || ? || ' days')"
|
|
16
|
+
).bind(days).first();
|
|
17
|
+
|
|
18
|
+
const errors = await c.env.DB.prepare(
|
|
19
|
+
"SELECT error_source, COUNT(*) as count FROM shadow_write_errors WHERE created_at > datetime('now', '-' || ? || ' days') GROUP BY error_source"
|
|
20
|
+
).bind(days).all();
|
|
21
|
+
|
|
22
|
+
const recent = await c.env.DB.prepare(
|
|
23
|
+
"SELECT topic, worker_ok, d1_ok FROM shadow_writes WHERE created_at > datetime('now', '-' || ? || ' days') ORDER BY created_at DESC LIMIT 20"
|
|
24
|
+
).bind(days).all();
|
|
25
|
+
|
|
26
|
+
return c.json({ days, summary, errors: errors.results, recent: recent.results });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// ─── Shadow Read Stats ──────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
observability.get('/api/shadow-read-stats', async (c) => {
|
|
32
|
+
const days = parseInt(c.req.query('days') ?? '7', 10);
|
|
33
|
+
|
|
34
|
+
const summary = await c.env.DB.prepare(
|
|
35
|
+
"SELECT COUNT(*) as total_reads, AVG(overlap_ratio) as avg_overlap_ratio FROM shadow_reads WHERE created_at > datetime('now', '-' || ? || ' days')"
|
|
36
|
+
).bind(days).first();
|
|
37
|
+
|
|
38
|
+
const bySite = await c.env.DB.prepare(
|
|
39
|
+
"SELECT call_site, COUNT(*) as reads FROM shadow_reads WHERE created_at > datetime('now', '-' || ? || ' days') GROUP BY call_site"
|
|
40
|
+
).bind(days).all();
|
|
41
|
+
|
|
42
|
+
const recent = await c.env.DB.prepare(
|
|
43
|
+
"SELECT call_site, query_text FROM shadow_reads WHERE created_at > datetime('now', '-' || ? || ' days') ORDER BY created_at DESC LIMIT 20"
|
|
44
|
+
).bind(days).all();
|
|
45
|
+
|
|
46
|
+
return c.json({ days, summary, by_site: bySite.results, recent: recent.results });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// ─── Agenda ─────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
observability.get('/agenda', async (c) => {
|
|
52
|
+
const items = await getActiveAgendaItems(c.env.DB);
|
|
53
|
+
return c.json({ count: items.length, items });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// ─── Procedures ─────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
observability.get('/procedures', async (c) => {
|
|
59
|
+
const raw = await getAllProceduresWithDerivedStats(c.env.DB, { reader: 'observability' });
|
|
60
|
+
|
|
61
|
+
const procedures = raw.map((p: any) => {
|
|
62
|
+
const total = p.success_count + p.fail_count;
|
|
63
|
+
const successRate = total > 0 ? Math.round((p.success_count / total) * 100) / 100 : 0;
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
pattern: p.task_pattern,
|
|
67
|
+
executor: p.executor,
|
|
68
|
+
status: p.status,
|
|
69
|
+
successes: p.success_count,
|
|
70
|
+
failures: p.fail_count,
|
|
71
|
+
consecutiveFailures: p.consecutive_failures,
|
|
72
|
+
successRate,
|
|
73
|
+
avgLatencyMs: Math.round(p.avg_latency_ms),
|
|
74
|
+
avgCost: Math.round(p.avg_cost * 10000) / 10000,
|
|
75
|
+
lastUsed: p.last_used,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return c.json({ count: procedures.length, procedures });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export { observability };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Stub — full implementation not yet extracted to OSS
|
|
2
|
+
|
|
3
|
+
import { Hono } from 'hono';
|
|
4
|
+
import type { Env } from '../types.js';
|
|
5
|
+
|
|
6
|
+
const operatorLogs = new Hono<{ Bindings: Env }>();
|
|
7
|
+
|
|
8
|
+
// List operator logs with pagination
|
|
9
|
+
operatorLogs.get('/api/operator-logs', async (c) => {
|
|
10
|
+
const limit = Math.min(parseInt(c.req.query('limit') ?? '20', 10), 100);
|
|
11
|
+
const offset = parseInt(c.req.query('offset') ?? '0', 10);
|
|
12
|
+
|
|
13
|
+
const logs = await c.env.DB.prepare(
|
|
14
|
+
'SELECT id, content, episodes_count, created_at FROM operator_logs ORDER BY created_at DESC LIMIT ? OFFSET ?'
|
|
15
|
+
).bind(limit, offset).all();
|
|
16
|
+
|
|
17
|
+
const countRow = await c.env.DB.prepare(
|
|
18
|
+
'SELECT COUNT(*) as cnt FROM operator_logs'
|
|
19
|
+
).first<{ cnt: number }>();
|
|
20
|
+
|
|
21
|
+
return c.json({
|
|
22
|
+
logs: logs.results,
|
|
23
|
+
total: countRow?.cnt ?? 0,
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Get single operator log by ID
|
|
28
|
+
operatorLogs.get('/api/operator-logs/:id', async (c) => {
|
|
29
|
+
const id = parseInt(c.req.param('id'), 10);
|
|
30
|
+
|
|
31
|
+
const log = await c.env.DB.prepare(
|
|
32
|
+
'SELECT * FROM operator_logs WHERE id = ?'
|
|
33
|
+
).bind(id).first();
|
|
34
|
+
|
|
35
|
+
if (!log) {
|
|
36
|
+
return c.json({ error: 'Not found' }, 404);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return c.json(log);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export { operatorLogs };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Stub — full implementation not yet extracted to OSS
|
|
2
|
+
|
|
3
|
+
import { Hono } from 'hono';
|
|
4
|
+
import { bodyLimit } from 'hono/body-limit';
|
|
5
|
+
import type { Env } from '../types.js';
|
|
6
|
+
import { chatPage } from '../ui.js';
|
|
7
|
+
import { landingPage } from '../landing.js';
|
|
8
|
+
import { dashboardPage, getDashboardData } from '../dashboard.js';
|
|
9
|
+
import { pulsePage, getPulseData } from '../pulse.js';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_BODY_LIMIT = 100 * 1024;
|
|
12
|
+
|
|
13
|
+
const pages = new Hono<{ Bindings: Env }>();
|
|
14
|
+
|
|
15
|
+
// ─── Landing ────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
pages.get('/', (c) => {
|
|
18
|
+
return c.html(landingPage());
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// ─── Chat ───────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
pages.get('/chat', (c) => {
|
|
24
|
+
return c.html(chatPage());
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// ─── Pulse ──────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
pages.get('/pulse', async (c) => {
|
|
30
|
+
const data = await getPulseData(c.env.DB);
|
|
31
|
+
return c.html(pulsePage(data));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// ─── Dashboard ──────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
pages.get('/dashboard', async (c) => {
|
|
37
|
+
const data = await getDashboardData(c.env.DB);
|
|
38
|
+
return c.html(dashboardPage(data));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// ─── PWA Manifest ───────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
pages.get('/manifest.json', (c) => {
|
|
44
|
+
return c.json({
|
|
45
|
+
name: 'AEGIS',
|
|
46
|
+
short_name: 'AEGIS',
|
|
47
|
+
start_url: '/chat',
|
|
48
|
+
display: 'standalone',
|
|
49
|
+
background_color: '#0a0a0f',
|
|
50
|
+
theme_color: '#8b8bff',
|
|
51
|
+
icons: [],
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// ─── Service Worker ─────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
pages.get('/sw.js', (c) => {
|
|
58
|
+
const sw = `
|
|
59
|
+
self.addEventListener('install', () => { self.skipWaiting(); });
|
|
60
|
+
self.addEventListener('activate', (event) => { event.waitUntil(clients.claim()); });
|
|
61
|
+
`;
|
|
62
|
+
return new Response(sw.trim(), {
|
|
63
|
+
headers: { 'Content-Type': 'application/javascript; charset=utf-8' },
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// ─── Events ─────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
pages.post('/api/events', bodyLimit({ maxSize: DEFAULT_BODY_LIMIT }), async (c) => {
|
|
70
|
+
const body = await c.req.json<{ event_id?: string }>();
|
|
71
|
+
const eventId = body.event_id?.trim();
|
|
72
|
+
|
|
73
|
+
if (!eventId) {
|
|
74
|
+
return c.json({ error: 'event_id is required' }, 400);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await c.env.DB.prepare(
|
|
78
|
+
"INSERT OR REPLACE INTO web_events (event_id, received_at) VALUES (?, datetime('now'))"
|
|
79
|
+
).bind(eventId).run();
|
|
80
|
+
|
|
81
|
+
return c.json({ ok: true, event_id: eventId });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ─── Reading (TarotScript) ──────────────────────────────────
|
|
85
|
+
|
|
86
|
+
pages.post('/api/reading', bodyLimit({ maxSize: DEFAULT_BODY_LIMIT }), async (c) => {
|
|
87
|
+
const tarotBinding = (c.env as any).TAROTSCRIPT;
|
|
88
|
+
if (!tarotBinding) {
|
|
89
|
+
return c.json({ error: 'TarotScript service binding not configured' }, 503);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Full implementation not yet extracted
|
|
93
|
+
return c.json({ error: 'not implemented' }, 501);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export { pages };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { bodyLimit } from 'hono/body-limit';
|
|
3
|
+
import type { Env } from '../types.js';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_BODY_LIMIT = 100 * 1024;
|
|
6
|
+
|
|
7
|
+
export const sessions = new Hono<{ Bindings: Env }>();
|
|
8
|
+
|
|
9
|
+
interface CCSessionPayload {
|
|
10
|
+
summary: string;
|
|
11
|
+
commits?: Array<{ hash: string; message: string; repo?: string }>;
|
|
12
|
+
files_changed?: string[];
|
|
13
|
+
issues_opened?: Array<{ repo: string; number: number; title: string }>;
|
|
14
|
+
issues_closed?: Array<{ repo: string; number: number; title: string }>;
|
|
15
|
+
decisions?: string[];
|
|
16
|
+
repos?: string[];
|
|
17
|
+
duration_minutes?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
sessions.post('/api/cc-session', bodyLimit({ maxSize: DEFAULT_BODY_LIMIT }), async (c) => {
|
|
21
|
+
const body = await c.req.json<CCSessionPayload>();
|
|
22
|
+
if (!body.summary?.trim()) {
|
|
23
|
+
return c.json({ error: 'summary is required' }, 400);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const id = crypto.randomUUID();
|
|
27
|
+
const sessionDate = new Date().toISOString().slice(0, 10);
|
|
28
|
+
|
|
29
|
+
await c.env.DB.prepare(`
|
|
30
|
+
INSERT INTO cc_sessions (id, session_date, summary, commits, files_changed, issues_opened, issues_closed, decisions, repos, duration_minutes)
|
|
31
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
32
|
+
`).bind(
|
|
33
|
+
id,
|
|
34
|
+
sessionDate,
|
|
35
|
+
body.summary.trim(),
|
|
36
|
+
body.commits ? JSON.stringify(body.commits) : null,
|
|
37
|
+
body.files_changed ? JSON.stringify(body.files_changed) : null,
|
|
38
|
+
body.issues_opened ? JSON.stringify(body.issues_opened) : null,
|
|
39
|
+
body.issues_closed ? JSON.stringify(body.issues_closed) : null,
|
|
40
|
+
body.decisions ? JSON.stringify(body.decisions) : null,
|
|
41
|
+
body.repos ? JSON.stringify(body.repos) : null,
|
|
42
|
+
body.duration_minutes ?? null,
|
|
43
|
+
).run();
|
|
44
|
+
|
|
45
|
+
return c.json({ id, session_date: sessionDate, status: 'recorded' }, 201);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
sessions.get('/api/cc-sessions', async (c) => {
|
|
49
|
+
const days = parseInt(c.req.query('days') ?? '7', 10);
|
|
50
|
+
const sessions = await c.env.DB.prepare(
|
|
51
|
+
"SELECT * FROM cc_sessions WHERE created_at > datetime('now', '-' || ? || ' days') ORDER BY created_at DESC LIMIT 50"
|
|
52
|
+
).bind(days).all();
|
|
53
|
+
return c.json({ sessions: sessions.results });
|
|
54
|
+
});
|
package/src/sanitize.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// sanitize.ts — strip PII and internal identifiers from content before publishing
|
|
2
|
+
//
|
|
3
|
+
// Used by operator-log and reflection pipelines to make internal content
|
|
4
|
+
// safe for the public blog. Applies regex-based redactions in a single pass.
|
|
5
|
+
|
|
6
|
+
// ─── Known internal entities (replace with generic labels) ───
|
|
7
|
+
const ENTITY_MAP: [RegExp, string][] = [
|
|
8
|
+
// Company / product names that reveal internal structure
|
|
9
|
+
[/ExampleCo\s+LLC/gi, '[Company]'],
|
|
10
|
+
[/Citizens\s+Reunited,?\s*PBC/gi, '[Partner Org]'],
|
|
11
|
+
[/Citizens\s+Reunited/gi, '[Partner Org]'],
|
|
12
|
+
|
|
13
|
+
// People — full names only (first names are OK per journal guidelines)
|
|
14
|
+
[/Jane\s+Doe/gi, '[Operator]'],
|
|
15
|
+
[/John\s+Doe/gi, '[Client]'],
|
|
16
|
+
|
|
17
|
+
// Internal service names that shouldn't be public
|
|
18
|
+
[/bizops-copilot/gi, '[internal-service]'],
|
|
19
|
+
[/aegis-memory/gi, '[memory-service]'],
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
// ─── Regex patterns for sensitive data ───────────────────────
|
|
23
|
+
const SENSITIVE_PATTERNS: [RegExp, string][] = [
|
|
24
|
+
// EIN (XX-XXXXXXX)
|
|
25
|
+
[/\b\d{2}-\d{7}\b/g, '[EIN-REDACTED]'],
|
|
26
|
+
|
|
27
|
+
// UUIDs
|
|
28
|
+
[/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi, '[ID-REDACTED]'],
|
|
29
|
+
|
|
30
|
+
// API tokens / bearer tokens (aegis_*, memory_*, sk-*, etc.)
|
|
31
|
+
[/\b(?:aegis|memory|sk|Bearer)\s*[_-]?[a-zA-Z0-9]{16,}\b/gi, '[TOKEN-REDACTED]'],
|
|
32
|
+
|
|
33
|
+
// Email addresses
|
|
34
|
+
[/\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b/g, '[EMAIL-REDACTED]'],
|
|
35
|
+
|
|
36
|
+
// Phone numbers (US formats)
|
|
37
|
+
[/\b(?:\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}\b/g, '[PHONE-REDACTED]'],
|
|
38
|
+
|
|
39
|
+
// Credit card numbers (13-19 digits, possibly separated)
|
|
40
|
+
[/\b(?:\d[-\s]?){13,19}\b/g, '[CC-REDACTED]'],
|
|
41
|
+
|
|
42
|
+
// IP addresses
|
|
43
|
+
[/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '[IP-REDACTED]'],
|
|
44
|
+
|
|
45
|
+
// Dollar amounts with specific figures (keep general like "$500" but redact exact like "$10.42")
|
|
46
|
+
[/\$\d{1,3}(?:,\d{3})*\.\d{2}\b/g, '[AMOUNT-REDACTED]'],
|
|
47
|
+
|
|
48
|
+
// Cloudflare account IDs (32-char hex)
|
|
49
|
+
[/\b[0-9a-f]{32}\b/g, '[ACCOUNT-REDACTED]'],
|
|
50
|
+
|
|
51
|
+
// Internal URLs with tokens or auth paths
|
|
52
|
+
[/https?:\/\/[^\s]*(?:token|secret|key|auth)[^\s]*/gi, '[URL-REDACTED]'],
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Sanitize content for public blog publication.
|
|
57
|
+
* Strips PII, internal identifiers, and sensitive business data.
|
|
58
|
+
*/
|
|
59
|
+
export function sanitizeForBlog(content: string): string {
|
|
60
|
+
let result = content;
|
|
61
|
+
|
|
62
|
+
// Pass 1: Named entity replacements
|
|
63
|
+
for (const [pattern, replacement] of ENTITY_MAP) {
|
|
64
|
+
result = result.replace(pattern, replacement);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Pass 2: Sensitive data patterns
|
|
68
|
+
for (const [pattern, replacement] of SENSITIVE_PATTERNS) {
|
|
69
|
+
result = result.replace(pattern, replacement);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// ─── Centralized Schema Enums ────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Single source of truth for all D1 CHECK constraint values.
|
|
4
|
+
// SQL schema.sql defines the CHECK constraints; this file mirrors
|
|
5
|
+
// them for TypeScript. When adding a new enum value, update BOTH
|
|
6
|
+
// this file and the corresponding CHECK in schema.sql.
|
|
7
|
+
//
|
|
8
|
+
// Rule: no enum literal should appear inline in routes or kernel
|
|
9
|
+
// files. Import from here.
|
|
10
|
+
|
|
11
|
+
// ─── Helpers ─────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
/** Derive a union type from a const array. */
|
|
14
|
+
type EnumValues<T extends readonly string[]> = T[number];
|
|
15
|
+
|
|
16
|
+
// ─── Chat ────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
export const MESSAGE_ROLES = ['user', 'assistant'] as const;
|
|
19
|
+
export type MessageRole = EnumValues<typeof MESSAGE_ROLES>;
|
|
20
|
+
|
|
21
|
+
// ─── Memory ──────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export const VALIDATION_STAGES = ['candidate', 'validated', 'expert', 'canonical', 'refuted'] as const;
|
|
24
|
+
export type ValidationStage = EnumValues<typeof VALIDATION_STAGES>;
|
|
25
|
+
|
|
26
|
+
export const EPISODIC_OUTCOMES = ['success', 'failure'] as const;
|
|
27
|
+
export type EpisodicOutcome = EnumValues<typeof EPISODIC_OUTCOMES>;
|
|
28
|
+
|
|
29
|
+
export const PROCEDURAL_STATUSES = ['learning', 'learned', 'degraded', 'broken'] as const;
|
|
30
|
+
export type ProceduralStatus = EnumValues<typeof PROCEDURAL_STATUSES>;
|
|
31
|
+
|
|
32
|
+
// ─── Agenda ──────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export const AGENDA_PRIORITIES = ['low', 'medium', 'high'] as const;
|
|
35
|
+
export type AgendaPriority = EnumValues<typeof AGENDA_PRIORITIES>;
|
|
36
|
+
|
|
37
|
+
export const AGENDA_STATUSES = ['active', 'done', 'dismissed'] as const;
|
|
38
|
+
export type AgendaStatus = EnumValues<typeof AGENDA_STATUSES>;
|
|
39
|
+
|
|
40
|
+
// ─── Goals & Actions ─────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
export const GOAL_STATUSES = ['active', 'paused', 'completed', 'failed'] as const;
|
|
43
|
+
export type GoalStatus = EnumValues<typeof GOAL_STATUSES>;
|
|
44
|
+
|
|
45
|
+
export const AUTHORITY_LEVELS = ['propose', 'auto_low', 'auto_high'] as const;
|
|
46
|
+
export type AuthorityLevel = EnumValues<typeof AUTHORITY_LEVELS>;
|
|
47
|
+
|
|
48
|
+
export const ACTION_TYPES = ['proposed', 'executed', 'skipped'] as const;
|
|
49
|
+
export type ActionType = EnumValues<typeof ACTION_TYPES>;
|
|
50
|
+
|
|
51
|
+
export const ACTION_OUTCOMES = ['success', 'failure', 'pending'] as const;
|
|
52
|
+
export type ActionOutcome = EnumValues<typeof ACTION_OUTCOMES>;
|
|
53
|
+
|
|
54
|
+
// ─── Task Observability ─────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
export const HEARTBEAT_STATUSES = ['ok', 'error', 'skipped'] as const;
|
|
57
|
+
export type HeartbeatStatus = EnumValues<typeof HEARTBEAT_STATUSES>;
|
|
58
|
+
|
|
59
|
+
// ─── CC Tasks ────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
export const TASK_STATUSES = ['pending', 'running', 'completed', 'failed', 'cancelled'] as const;
|
|
62
|
+
export type TaskStatus = EnumValues<typeof TASK_STATUSES>;
|
|
63
|
+
|
|
64
|
+
export const TASK_AUTHORITIES = ['proposed', 'auto_safe', 'operator'] as const;
|
|
65
|
+
export type TaskAuthority = EnumValues<typeof TASK_AUTHORITIES>;
|
|
66
|
+
|
|
67
|
+
export const TASK_CATEGORIES = ['docs', 'tests', 'research', 'bugfix', 'feature', 'refactor', 'deploy'] as const;
|
|
68
|
+
export type TaskCategory = EnumValues<typeof TASK_CATEGORIES>;
|
|
69
|
+
|
|
70
|
+
/** Categories that execute without operator approval. */
|
|
71
|
+
export const AUTO_SAFE_CATEGORIES = new Set<TaskCategory>(['docs', 'tests', 'research', 'refactor']);
|
|
72
|
+
|
|
73
|
+
/** Categories that require approval before execution. */
|
|
74
|
+
export const PROPOSED_CATEGORIES = new Set<TaskCategory>(['bugfix', 'feature']);
|
|
75
|
+
|
|
76
|
+
/** Subset safe for PR automerge. */
|
|
77
|
+
export const AUTOMERGE_SAFE_CATEGORIES = new Set<TaskCategory>(['docs', 'tests', 'research']);
|
|
78
|
+
|
|
79
|
+
// ─── Narratives ─────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
export const MEMORY_GOAL_STATUSES = ['active', 'resolved', 'stalled', 'abandoned'] as const;
|
|
82
|
+
export type MemoryGoalStatus = EnumValues<typeof MEMORY_GOAL_STATUSES>;
|
|
83
|
+
|
|
84
|
+
// ─── Knowledge Graph ─────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
export const NODE_TYPES = [
|
|
87
|
+
'concept', 'project', 'person', 'decision', 'pattern', 'tool', 'event',
|
|
88
|
+
'file', 'module', 'function', 'class', 'interface', 'type_alias',
|
|
89
|
+
] as const;
|
|
90
|
+
export type NodeType = EnumValues<typeof NODE_TYPES>;
|
|
91
|
+
|
|
92
|
+
export const SOURCE_SYSTEMS = ['cognitive', 'code', 'manual'] as const;
|
|
93
|
+
export type SourceSystem = EnumValues<typeof SOURCE_SYSTEMS>;
|
|
94
|
+
|
|
95
|
+
export const EDGE_RELATIONS = ['depends_on', 'part_of', 'decided_by', 'blocks', 'uses', 'related_to', 'caused'] as const;
|
|
96
|
+
export type EdgeRelation = EnumValues<typeof EDGE_RELATIONS>;
|
|
97
|
+
|
|
98
|
+
// ─── Feedback ────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
export const FEEDBACK_CATEGORIES = ['general', 'bug', 'feature', 'question'] as const;
|
|
101
|
+
export type FeedbackCategory = EnumValues<typeof FEEDBACK_CATEGORIES>;
|
|
102
|
+
|
|
103
|
+
export const FEEDBACK_SOURCES = ['web', 'mcp', 'api'] as const;
|
|
104
|
+
export type FeedbackSource = EnumValues<typeof FEEDBACK_SOURCES>;
|
|
105
|
+
|
|
106
|
+
// ─── Board ───────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
export const BOARD_COLUMNS = ['backlog', 'queued', 'in_progress', 'blocked', 'shipped'] as const;
|
|
109
|
+
export type BoardColumn = EnumValues<typeof BOARD_COLUMNS>;
|
|
110
|
+
|
|
111
|
+
export const CONTENT_TYPES = ['issue', 'pr'] as const;
|
|
112
|
+
export type ContentType = EnumValues<typeof CONTENT_TYPES>;
|
|
113
|
+
|
|
114
|
+
// ─── Content Queue ───────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
export const CONTENT_QUEUE_STATUSES = ['scheduled', 'published', 'failed', 'cancelled'] as const;
|
|
117
|
+
export type ContentQueueStatus = EnumValues<typeof CONTENT_QUEUE_STATUSES>;
|
|
118
|
+
|
|
119
|
+
// ─── Dynamic Tools ───────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
export const TOOL_EXECUTORS = ['gpt_oss', 'workers_ai', 'groq'] as const;
|
|
122
|
+
export type ToolExecutor = EnumValues<typeof TOOL_EXECUTORS>;
|
|
123
|
+
|
|
124
|
+
export const TOOL_STATUSES = ['active', 'promoted', 'retired', 'draft'] as const;
|
|
125
|
+
export type ToolStatus = EnumValues<typeof TOOL_STATUSES>;
|
|
126
|
+
|
|
127
|
+
// ─── CodeBeast Findings ─────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
export const FINDING_SEVERITIES = ['HIGH', 'MID', 'LOW', 'INFO'] as const;
|
|
130
|
+
export type FindingSeverity = EnumValues<typeof FINDING_SEVERITIES>;
|
|
131
|
+
|
|
132
|
+
export const FINDING_CATEGORIES = ['SECURITY', 'LOGIC', 'STYLE', 'DEPENDENCY', 'BOUNDARY'] as const;
|
|
133
|
+
export type FindingCategory = EnumValues<typeof FINDING_CATEGORIES>;
|
|
134
|
+
|
|
135
|
+
export const FINDING_PRIORITIES = ['high', 'medium', 'low'] as const;
|
|
136
|
+
export type FindingPriority = EnumValues<typeof FINDING_PRIORITIES>;
|
|
137
|
+
|
|
138
|
+
export const FINDING_STATUSES = ['open', 'resolved'] as const;
|
|
139
|
+
export type FindingStatus = EnumValues<typeof FINDING_STATUSES>;
|
|
140
|
+
|
|
141
|
+
// ─── Validation Helpers ──────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
/** Check if a value is a valid member of a const enum array. */
|
|
144
|
+
export function isValidEnum<T extends readonly string[]>(values: T, input: unknown): input is T[number] {
|
|
145
|
+
return typeof input === 'string' && (values as readonly string[]).includes(input);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Validate and return the value, or fall back to a default. */
|
|
149
|
+
export function validateEnum<T extends readonly string[]>(
|
|
150
|
+
values: T,
|
|
151
|
+
input: unknown,
|
|
152
|
+
fallback: T[number],
|
|
153
|
+
): T[number] {
|
|
154
|
+
return isValidEnum(values, input) ? input : fallback;
|
|
155
|
+
}
|
package/src/search.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Fetch-based web search and URL fetching — no external SDK
|
|
2
|
+
// Follows the same pattern as groq.ts
|
|
3
|
+
|
|
4
|
+
import { operatorConfig } from './operator/index.js';
|
|
5
|
+
|
|
6
|
+
// ─── Brave Search ────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
export interface SearchResult {
|
|
9
|
+
title: string;
|
|
10
|
+
url: string;
|
|
11
|
+
snippet: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function braveSearch(
|
|
15
|
+
apiKey: string,
|
|
16
|
+
query: string,
|
|
17
|
+
maxResults: number = 5,
|
|
18
|
+
): Promise<SearchResult[]> {
|
|
19
|
+
const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${Math.min(maxResults, 10)}`;
|
|
20
|
+
|
|
21
|
+
let response: Response;
|
|
22
|
+
try {
|
|
23
|
+
response = await fetch(url, {
|
|
24
|
+
headers: {
|
|
25
|
+
'Accept': 'application/json',
|
|
26
|
+
'Accept-Encoding': 'gzip',
|
|
27
|
+
'X-Subscription-Token': apiKey,
|
|
28
|
+
},
|
|
29
|
+
signal: AbortSignal.timeout(10_000),
|
|
30
|
+
});
|
|
31
|
+
} catch (err) {
|
|
32
|
+
if (err instanceof DOMException && err.name === 'TimeoutError') {
|
|
33
|
+
console.warn('[search] Brave Search timed out after 10s');
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
const errText = await response.text();
|
|
41
|
+
throw new Error(`Brave Search API error ${response.status}: ${errText}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const data = await response.json<{
|
|
45
|
+
web?: {
|
|
46
|
+
results?: Array<{
|
|
47
|
+
title: string;
|
|
48
|
+
url: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
extra_snippets?: string[];
|
|
51
|
+
}>;
|
|
52
|
+
};
|
|
53
|
+
}>();
|
|
54
|
+
|
|
55
|
+
const results = data.web?.results ?? [];
|
|
56
|
+
return results.map(r => ({
|
|
57
|
+
title: r.title,
|
|
58
|
+
url: r.url,
|
|
59
|
+
snippet: r.description ?? r.extra_snippets?.[0] ?? '',
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── URL Fetch + Text Extraction ─────────────────────────────
|
|
64
|
+
|
|
65
|
+
const STRIP_TAGS = /<script[\s\S]*?<\/script>|<style[\s\S]*?<\/style>|<[^>]+>/gi;
|
|
66
|
+
const COLLAPSE_WHITESPACE = /\s{2,}/g;
|
|
67
|
+
|
|
68
|
+
export async function fetchUrlText(url: string, maxChars: number = 8000): Promise<string> {
|
|
69
|
+
let response: Response;
|
|
70
|
+
try {
|
|
71
|
+
response = await fetch(url, {
|
|
72
|
+
headers: {
|
|
73
|
+
'User-Agent': operatorConfig.userAgent,
|
|
74
|
+
'Accept': 'text/html,text/plain,application/json',
|
|
75
|
+
},
|
|
76
|
+
redirect: 'follow',
|
|
77
|
+
signal: AbortSignal.timeout(10_000),
|
|
78
|
+
});
|
|
79
|
+
} catch (err) {
|
|
80
|
+
if (err instanceof DOMException && err.name === 'TimeoutError') {
|
|
81
|
+
return `[Error: request to ${url} timed out after 10s]`;
|
|
82
|
+
}
|
|
83
|
+
throw err;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new Error(`HTTP ${response.status} fetching ${url}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
91
|
+
const raw = await response.text();
|
|
92
|
+
|
|
93
|
+
let text: string;
|
|
94
|
+
if (contentType.includes('application/json')) {
|
|
95
|
+
// Prettify JSON for readability
|
|
96
|
+
try {
|
|
97
|
+
text = JSON.stringify(JSON.parse(raw), null, 2);
|
|
98
|
+
} catch {
|
|
99
|
+
text = raw;
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
// Strip HTML tags, collapse whitespace
|
|
103
|
+
text = raw
|
|
104
|
+
.replace(STRIP_TAGS, ' ')
|
|
105
|
+
.replace(COLLAPSE_WHITESPACE, ' ')
|
|
106
|
+
.trim();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return text.length > maxChars
|
|
110
|
+
? text.slice(0, maxChars) + `\n\n[... truncated at ${maxChars} chars — full page is ${text.length} chars ...]`
|
|
111
|
+
: text;
|
|
112
|
+
}
|