@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,156 @@
|
|
|
1
|
+
// ─── Autonomous Goals (#14) ──────────────────────────────────
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ACTION_OUTCOMES, isValidEnum,
|
|
5
|
+
type GoalStatus, type AuthorityLevel, type ActionType, type ActionOutcome,
|
|
6
|
+
} from '../../schema-enums.js';
|
|
7
|
+
|
|
8
|
+
export interface AgentGoal {
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
description: string | null;
|
|
12
|
+
status: GoalStatus;
|
|
13
|
+
authority_level: AuthorityLevel;
|
|
14
|
+
schedule_hours: number;
|
|
15
|
+
created_at: string;
|
|
16
|
+
last_run_at: string | null;
|
|
17
|
+
next_run_at: string | null;
|
|
18
|
+
completed_at: string | null;
|
|
19
|
+
run_count: number;
|
|
20
|
+
context_json: string | null;
|
|
21
|
+
business_unit: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const DEFAULT_BUSINESS_UNIT = 'stackbilt';
|
|
25
|
+
|
|
26
|
+
export interface AgentAction {
|
|
27
|
+
id: string;
|
|
28
|
+
goal_id: string | null;
|
|
29
|
+
action_type: ActionType;
|
|
30
|
+
description: string;
|
|
31
|
+
tool_called: string | null;
|
|
32
|
+
tool_args_json: string | null;
|
|
33
|
+
tool_result_json: string | null;
|
|
34
|
+
outcome: ActionOutcome | null;
|
|
35
|
+
auto_executed: number; // 0 or 1 (SQLite boolean)
|
|
36
|
+
authority_level: string;
|
|
37
|
+
created_at: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function getActiveGoals(
|
|
41
|
+
db: D1Database,
|
|
42
|
+
businessUnit?: string,
|
|
43
|
+
): Promise<AgentGoal[]> {
|
|
44
|
+
const stmt = businessUnit
|
|
45
|
+
? db.prepare(
|
|
46
|
+
"SELECT * FROM agent_goals WHERE status = 'active' AND business_unit = ? ORDER BY created_at ASC"
|
|
47
|
+
).bind(businessUnit)
|
|
48
|
+
: db.prepare(
|
|
49
|
+
"SELECT * FROM agent_goals WHERE status = 'active' ORDER BY created_at ASC"
|
|
50
|
+
);
|
|
51
|
+
const result = await stmt.all();
|
|
52
|
+
return result.results as unknown as AgentGoal[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function addGoal(
|
|
56
|
+
db: D1Database,
|
|
57
|
+
title: string,
|
|
58
|
+
description?: string,
|
|
59
|
+
scheduleHours = 6,
|
|
60
|
+
businessUnit: string = DEFAULT_BUSINESS_UNIT,
|
|
61
|
+
): Promise<string> {
|
|
62
|
+
const id = crypto.randomUUID();
|
|
63
|
+
await db.prepare(
|
|
64
|
+
'INSERT INTO agent_goals (id, title, description, schedule_hours, business_unit) VALUES (?, ?, ?, ?, ?)'
|
|
65
|
+
).bind(id, title, description ?? null, scheduleHours, businessUnit).run();
|
|
66
|
+
return id;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function updateGoalStatus(
|
|
70
|
+
db: D1Database,
|
|
71
|
+
id: string,
|
|
72
|
+
status: AgentGoal['status'],
|
|
73
|
+
): Promise<void> {
|
|
74
|
+
const completedAt = status === 'completed' || status === 'failed'
|
|
75
|
+
? "datetime('now')"
|
|
76
|
+
: 'NULL';
|
|
77
|
+
await db.prepare(
|
|
78
|
+
`UPDATE agent_goals SET status = ?, completed_at = ${completedAt} WHERE id = ?`
|
|
79
|
+
).bind(status, id).run();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function touchGoal(db: D1Database, id: string, scheduleHours: number): Promise<void> {
|
|
83
|
+
await db.prepare(`
|
|
84
|
+
UPDATE agent_goals SET
|
|
85
|
+
last_run_at = datetime('now'),
|
|
86
|
+
next_run_at = datetime('now', '+' || ? || ' hours'),
|
|
87
|
+
run_count = run_count + 1
|
|
88
|
+
WHERE id = ?
|
|
89
|
+
`).bind(scheduleHours, id).run();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ─── Outcome Sanitization ───────────────────────────────────
|
|
93
|
+
// D1 CHECK: outcome IN ('success', 'failure', 'pending')
|
|
94
|
+
// Guard at the DB boundary so rogue values (partial_failure, error, blocked, "") never reach SQLite.
|
|
95
|
+
|
|
96
|
+
/** Map any outcome to a valid agent_actions value. */
|
|
97
|
+
export function sanitizeActionOutcome(raw: string | null | undefined): ActionOutcome | null {
|
|
98
|
+
if (raw === null || raw === undefined) return null;
|
|
99
|
+
if (isValidEnum(ACTION_OUTCOMES, raw)) return raw;
|
|
100
|
+
// partial_failure, error, blocked, empty string → failure
|
|
101
|
+
return 'failure';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function recordGoalAction(
|
|
105
|
+
db: D1Database,
|
|
106
|
+
goalId: string | null,
|
|
107
|
+
actionType: AgentAction['action_type'],
|
|
108
|
+
description: string,
|
|
109
|
+
outcome: AgentAction['outcome'] = 'success',
|
|
110
|
+
opts?: {
|
|
111
|
+
autoExecuted?: boolean;
|
|
112
|
+
authorityLevel?: string;
|
|
113
|
+
toolCalled?: string;
|
|
114
|
+
toolArgsJson?: string;
|
|
115
|
+
toolResultJson?: string;
|
|
116
|
+
},
|
|
117
|
+
): Promise<void> {
|
|
118
|
+
const id = crypto.randomUUID();
|
|
119
|
+
const safeOutcome = sanitizeActionOutcome(outcome);
|
|
120
|
+
await db.prepare(
|
|
121
|
+
'INSERT INTO agent_actions (id, goal_id, action_type, description, outcome, auto_executed, authority_level, tool_called, tool_args_json, tool_result_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
|
122
|
+
).bind(
|
|
123
|
+
id, goalId, actionType, description, safeOutcome,
|
|
124
|
+
(opts?.autoExecuted ?? false) ? 1 : 0,
|
|
125
|
+
opts?.authorityLevel ?? 'propose',
|
|
126
|
+
opts?.toolCalled ?? null,
|
|
127
|
+
opts?.toolArgsJson ?? null,
|
|
128
|
+
opts?.toolResultJson ?? null,
|
|
129
|
+
).run();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function downgradeGoalAuthority(
|
|
133
|
+
db: D1Database,
|
|
134
|
+
goalId: string,
|
|
135
|
+
reason: string,
|
|
136
|
+
): Promise<void> {
|
|
137
|
+
await db.prepare(
|
|
138
|
+
"UPDATE agent_goals SET authority_level = 'propose' WHERE id = ?"
|
|
139
|
+
).bind(goalId).run();
|
|
140
|
+
console.warn(`[goals] Downgraded goal ${goalId} to propose: ${reason}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function getGoalActions(
|
|
144
|
+
db: D1Database,
|
|
145
|
+
goalId?: string,
|
|
146
|
+
limit = 20,
|
|
147
|
+
): Promise<AgentAction[]> {
|
|
148
|
+
const result = goalId
|
|
149
|
+
? await db.prepare(
|
|
150
|
+
'SELECT * FROM agent_actions WHERE goal_id = ? ORDER BY created_at DESC LIMIT ?'
|
|
151
|
+
).bind(goalId, limit).all()
|
|
152
|
+
: await db.prepare(
|
|
153
|
+
'SELECT * FROM agent_actions ORDER BY created_at DESC LIMIT ?'
|
|
154
|
+
).bind(limit).all();
|
|
155
|
+
return result.results as unknown as AgentAction[];
|
|
156
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// ─── Knowledge Graph: Node Extraction, Edge Inference, Spreading Activation ──
|
|
2
|
+
//
|
|
3
|
+
// Phase 2 of the cognitive layer. Populates kg_nodes/kg_edges tables using
|
|
4
|
+
// zero-cost regex/heuristic NER (no LLM calls). Provides spreading activation
|
|
5
|
+
// for context-aware retrieval.
|
|
6
|
+
|
|
7
|
+
// ─── Node Type Classification ────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
import type { NodeType, SourceSystem } from '../../schema-enums.js';
|
|
10
|
+
export type { SourceSystem } from '../../schema-enums.js';
|
|
11
|
+
|
|
12
|
+
const KNOWN_PROJECTS: Set<string> = new Set([
|
|
13
|
+
'aegis', 'bizops',
|
|
14
|
+
// Add your project names here
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
const KNOWN_TOOLS: Set<string> = new Set([
|
|
18
|
+
'cloudflare', 'wrangler', 'd1', 'kv', 'r2', 'workers ai',
|
|
19
|
+
'groq', 'claude', 'anthropic', 'openai', 'sonnet', 'opus', 'haiku',
|
|
20
|
+
'stripe', 'resend', 'github', 'better auth', 'oauth', 'mcp',
|
|
21
|
+
'brave search', 'astro', 'react', 'typescript', 'pnpm', 'npm',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const KNOWN_PEOPLE: Set<string> = new Set([
|
|
25
|
+
'alex',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const DECISION_MARKERS = /\b(decided|decision|chose|chosen|approved|rejected|switched to|migrated to|adopted)\b/i;
|
|
29
|
+
const EVENT_MARKERS = /\b(launched|shipped|deployed|completed|released|merged|filed|signed|created|failed|broke|fixed)\b/i;
|
|
30
|
+
const PATTERN_MARKERS = /\b(pattern|anti-?pattern|best practice|convention|approach|strategy|architecture)\b/i;
|
|
31
|
+
|
|
32
|
+
function classifyNode(label: string, context: string): NodeType {
|
|
33
|
+
const lower = label.toLowerCase();
|
|
34
|
+
if (KNOWN_PEOPLE.has(lower)) return 'person';
|
|
35
|
+
if (KNOWN_PROJECTS.has(lower)) return 'project';
|
|
36
|
+
if (KNOWN_TOOLS.has(lower)) return 'tool';
|
|
37
|
+
if (DECISION_MARKERS.test(context)) return 'decision';
|
|
38
|
+
if (EVENT_MARKERS.test(context)) return 'event';
|
|
39
|
+
if (PATTERN_MARKERS.test(context)) return 'pattern';
|
|
40
|
+
return 'concept';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Entity Extraction (regex/heuristic NER — zero LLM cost) ─────────────────
|
|
44
|
+
|
|
45
|
+
// Matches capitalized multi-word phrases (2+ words starting with uppercase)
|
|
46
|
+
const CAPITALIZED_PHRASE = /\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)\b/g;
|
|
47
|
+
|
|
48
|
+
// Matches known technical terms that may not be capitalized
|
|
49
|
+
const TECH_TERMS = /\b(OAuth\s*2\.?1?|MCP|D1|KV|R2|CRIX|EIN|LLC|PBC|API|CLI|PKCE|SSO|JWT|RRF)\b/g;
|
|
50
|
+
|
|
51
|
+
// Matches version references like v1.9.0, Phase 2, etc.
|
|
52
|
+
const VERSION_REFS = /\b(v\d+\.\d+(?:\.\d+)?|Phase\s+\d+)\b/gi;
|
|
53
|
+
|
|
54
|
+
// Matches product/project names with hyphens like my-project, demo-app-v2
|
|
55
|
+
const HYPHENATED_NAMES = /\b([a-z]+-[a-z]+(?:-[a-z0-9]+)?)\b/gi;
|
|
56
|
+
|
|
57
|
+
function extractEntities(fact: string): string[] {
|
|
58
|
+
const entities = new Set<string>();
|
|
59
|
+
|
|
60
|
+
// Capitalized multi-word phrases
|
|
61
|
+
for (const match of fact.matchAll(CAPITALIZED_PHRASE)) {
|
|
62
|
+
const phrase = match[1].trim();
|
|
63
|
+
// Skip common English phrases that aren't entities
|
|
64
|
+
if (phrase.split(/\s+/).length <= 4 && phrase.length >= 4) {
|
|
65
|
+
entities.add(phrase);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Known tech terms
|
|
70
|
+
for (const match of fact.matchAll(TECH_TERMS)) {
|
|
71
|
+
entities.add(match[1].toUpperCase());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Version references (useful for tracking evolution)
|
|
75
|
+
for (const match of fact.matchAll(VERSION_REFS)) {
|
|
76
|
+
entities.add(match[1]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Hyphenated project names
|
|
80
|
+
for (const match of fact.matchAll(HYPHENATED_NAMES)) {
|
|
81
|
+
const name = match[1].toLowerCase();
|
|
82
|
+
if (KNOWN_PROJECTS.has(name) || KNOWN_TOOLS.has(name)) {
|
|
83
|
+
entities.add(name);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Single-word known projects/tools/people from the fact text
|
|
88
|
+
const words = fact.toLowerCase().split(/[\s,;:()\[\]]+/);
|
|
89
|
+
for (const word of words) {
|
|
90
|
+
const cleaned = word.replace(/[^a-z0-9-]/g, '');
|
|
91
|
+
if (cleaned.length >= 3) {
|
|
92
|
+
if (KNOWN_PROJECTS.has(cleaned) || KNOWN_TOOLS.has(cleaned) || KNOWN_PEOPLE.has(cleaned)) {
|
|
93
|
+
entities.add(cleaned);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return [...entities].filter(e => e.length >= 2);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── extractNodes ────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
export async function extractNodes(
|
|
104
|
+
db: D1Database,
|
|
105
|
+
fact: string,
|
|
106
|
+
topic: string,
|
|
107
|
+
): Promise<number[]> {
|
|
108
|
+
const entities = extractEntities(fact);
|
|
109
|
+
if (entities.length === 0) return [];
|
|
110
|
+
|
|
111
|
+
// Also add the topic itself as a concept node if not already extracted
|
|
112
|
+
const topicLabel = topic.replace(/_/g, ' ');
|
|
113
|
+
if (!entities.some(e => e.toLowerCase() === topicLabel.toLowerCase())) {
|
|
114
|
+
entities.push(topicLabel);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const nodeIds: number[] = [];
|
|
118
|
+
|
|
119
|
+
for (const entity of entities) {
|
|
120
|
+
const label = entity.trim();
|
|
121
|
+
if (!label) continue;
|
|
122
|
+
|
|
123
|
+
const nodeType = classifyNode(label, fact);
|
|
124
|
+
|
|
125
|
+
// Upsert: check if node with this label already exists (case-insensitive)
|
|
126
|
+
const existing = await db.prepare(
|
|
127
|
+
'SELECT id, memory_ids FROM kg_nodes WHERE LOWER(label) = LOWER(?) LIMIT 1'
|
|
128
|
+
).bind(label).first<{ id: number; memory_ids: string }>();
|
|
129
|
+
|
|
130
|
+
if (existing) {
|
|
131
|
+
// Update activation timestamp
|
|
132
|
+
await db.prepare(
|
|
133
|
+
"UPDATE kg_nodes SET last_activated_at = datetime('now'), activation = MIN(activation + 0.1, 1.0), updated_at = datetime('now') WHERE id = ?"
|
|
134
|
+
).bind(existing.id).run();
|
|
135
|
+
nodeIds.push(existing.id);
|
|
136
|
+
} else {
|
|
137
|
+
// Insert new node
|
|
138
|
+
const result = await db.prepare(
|
|
139
|
+
"INSERT INTO kg_nodes (label, node_type, activation, last_activated_at, memory_ids, source_system) VALUES (?, ?, 0.5, datetime('now'), '[]', 'cognitive')"
|
|
140
|
+
).bind(label, nodeType).run();
|
|
141
|
+
|
|
142
|
+
if (result.meta.last_row_id) {
|
|
143
|
+
nodeIds.push(result.meta.last_row_id as number);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return nodeIds;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ─── createEdges ─────────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
export async function createEdges(
|
|
154
|
+
db: D1Database,
|
|
155
|
+
nodeIds: number[],
|
|
156
|
+
_factId?: number,
|
|
157
|
+
): Promise<void> {
|
|
158
|
+
if (nodeIds.length < 2) return;
|
|
159
|
+
|
|
160
|
+
// Create co-occurrence edges for all pairs
|
|
161
|
+
for (let i = 0; i < nodeIds.length; i++) {
|
|
162
|
+
for (let j = i + 1; j < nodeIds.length; j++) {
|
|
163
|
+
const sourceId = nodeIds[i];
|
|
164
|
+
const targetId = nodeIds[j];
|
|
165
|
+
|
|
166
|
+
// Check for existing edge (either direction)
|
|
167
|
+
const existing = await db.prepare(
|
|
168
|
+
'SELECT id, co_activation_count, weight FROM kg_edges WHERE (source_id = ? AND target_id = ?) OR (source_id = ? AND target_id = ?) LIMIT 1'
|
|
169
|
+
).bind(sourceId, targetId, targetId, sourceId).first<{ id: number; co_activation_count: number; weight: number }>();
|
|
170
|
+
|
|
171
|
+
if (existing) {
|
|
172
|
+
// Increment co-activation, increase weight (cap at 1.0), update last_seen_at
|
|
173
|
+
const newWeight = Math.min(existing.weight + 0.1, 1.0);
|
|
174
|
+
await db.prepare(
|
|
175
|
+
"UPDATE kg_edges SET co_activation_count = co_activation_count + 1, weight = ?, updated_at = datetime('now'), last_seen_at = datetime('now') WHERE id = ?"
|
|
176
|
+
).bind(newWeight, existing.id).run();
|
|
177
|
+
} else {
|
|
178
|
+
// Create new co-occurrence edge
|
|
179
|
+
await db.prepare(
|
|
180
|
+
"INSERT INTO kg_edges (source_id, target_id, relation, weight, confidence, source_system, first_seen_at, last_seen_at) VALUES (?, ?, 'related_to', 0.5, 0.7, 'cognitive', datetime('now'), datetime('now'))"
|
|
181
|
+
).bind(sourceId, targetId).run();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ─── activateGraph (spreading activation) ────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
interface ActivatedNode {
|
|
190
|
+
label: string;
|
|
191
|
+
type: string;
|
|
192
|
+
activation: number;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function activateGraph(
|
|
196
|
+
db: D1Database,
|
|
197
|
+
query: string,
|
|
198
|
+
hops: number = 2,
|
|
199
|
+
): Promise<ActivatedNode[]> {
|
|
200
|
+
if (!query || query.trim().length < 2) return [];
|
|
201
|
+
|
|
202
|
+
// Find seed nodes by keyword matching against labels
|
|
203
|
+
const keywords = query.toLowerCase().split(/[\s,;:()\[\]]+/).filter(w => w.length >= 3);
|
|
204
|
+
if (keywords.length === 0) return [];
|
|
205
|
+
|
|
206
|
+
// Build LIKE conditions for seed node matching
|
|
207
|
+
const conditions = keywords.map(() => 'LOWER(label) LIKE ?').join(' OR ');
|
|
208
|
+
const binds = keywords.map(k => `%${k}%`);
|
|
209
|
+
|
|
210
|
+
const seedResult = await db.prepare(
|
|
211
|
+
`SELECT id, label, node_type FROM kg_nodes WHERE ${conditions} LIMIT 20`
|
|
212
|
+
).bind(...binds).all<{ id: number; label: string; node_type: string }>();
|
|
213
|
+
|
|
214
|
+
if (seedResult.results.length === 0) return [];
|
|
215
|
+
|
|
216
|
+
// Activation map: nodeId -> activation score
|
|
217
|
+
const activations = new Map<number, number>();
|
|
218
|
+
const nodeInfo = new Map<number, { label: string; type: string }>();
|
|
219
|
+
|
|
220
|
+
// Seed nodes get activation 1.0
|
|
221
|
+
for (const seed of seedResult.results) {
|
|
222
|
+
activations.set(seed.id, 1.0);
|
|
223
|
+
nodeInfo.set(seed.id, { label: seed.label, type: seed.node_type });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Spread activation through edges
|
|
227
|
+
const DECAY = 0.7;
|
|
228
|
+
let frontier = [...seedResult.results.map(s => s.id)];
|
|
229
|
+
|
|
230
|
+
for (let hop = 0; hop < hops && frontier.length > 0; hop++) {
|
|
231
|
+
const nextFrontier: number[] = [];
|
|
232
|
+
|
|
233
|
+
for (const nodeId of frontier) {
|
|
234
|
+
const sourceActivation = activations.get(nodeId) ?? 0;
|
|
235
|
+
if (sourceActivation < 0.05) continue; // Prune weak activations
|
|
236
|
+
|
|
237
|
+
// Find neighbors via edges (both directions)
|
|
238
|
+
const neighbors = await db.prepare(
|
|
239
|
+
`SELECT
|
|
240
|
+
CASE WHEN source_id = ? THEN target_id ELSE source_id END as neighbor_id,
|
|
241
|
+
weight,
|
|
242
|
+
e.id as edge_id
|
|
243
|
+
FROM kg_edges e
|
|
244
|
+
WHERE source_id = ? OR target_id = ?
|
|
245
|
+
LIMIT 20`
|
|
246
|
+
).bind(nodeId, nodeId, nodeId).all<{ neighbor_id: number; weight: number; edge_id: number }>();
|
|
247
|
+
|
|
248
|
+
for (const neighbor of neighbors.results) {
|
|
249
|
+
const spreadActivation = sourceActivation * neighbor.weight * DECAY;
|
|
250
|
+
const current = activations.get(neighbor.neighbor_id) ?? 0;
|
|
251
|
+
|
|
252
|
+
if (spreadActivation > current) {
|
|
253
|
+
activations.set(neighbor.neighbor_id, spreadActivation);
|
|
254
|
+
nextFrontier.push(neighbor.neighbor_id);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
frontier = nextFrontier;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Fetch info for any activated nodes we don't have info for yet
|
|
263
|
+
const missingIds = [...activations.keys()].filter(id => !nodeInfo.has(id));
|
|
264
|
+
if (missingIds.length > 0) {
|
|
265
|
+
// Batch fetch in chunks to avoid overly long queries
|
|
266
|
+
for (let i = 0; i < missingIds.length; i += 20) {
|
|
267
|
+
const chunk = missingIds.slice(i, i + 20);
|
|
268
|
+
const placeholders = chunk.map(() => '?').join(',');
|
|
269
|
+
const infoResult = await db.prepare(
|
|
270
|
+
`SELECT id, label, node_type FROM kg_nodes WHERE id IN (${placeholders})`
|
|
271
|
+
).bind(...chunk).all<{ id: number; label: string; node_type: string }>();
|
|
272
|
+
|
|
273
|
+
for (const row of infoResult.results) {
|
|
274
|
+
nodeInfo.set(row.id, { label: row.label, type: row.node_type });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Build result: top 10 by activation score
|
|
280
|
+
const results: ActivatedNode[] = [];
|
|
281
|
+
for (const [nodeId, activation] of activations.entries()) {
|
|
282
|
+
const info = nodeInfo.get(nodeId);
|
|
283
|
+
if (info) {
|
|
284
|
+
results.push({ label: info.label, type: info.type, activation });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
results.sort((a, b) => b.activation - a.activation);
|
|
289
|
+
return results.slice(0, 10);
|
|
290
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { recordEpisode, retrogradeEpisode, sanitizeEpisodicOutcome, getRecentEpisodes, getEpisodeStats, getEpisodeStatsByComplexity, getAllEpisodeStatsByComplexity, type EpisodeStatsAggregate, getConversationHistory, estimateTokens, budgetConversationHistory } from './episodic.js';
|
|
2
|
+
export { PROCEDURE_MIN_SUCCESSES, PROCEDURE_MIN_SUCCESS_RATE, complexityTier, procedureKey, getProcedure, getAllProcedures, getProcedureWithDerivedStats, getAllProceduresWithDerivedStats, type DriftLogOpts, findNearMiss, upsertProcedure, addRefinement, degradeProcedure, maintainProcedures } from './procedural.js';
|
|
3
|
+
export { normalizeTopic, tokenize, jaccardSimilarity, recordMemory, searchMemoryByKeywords, getMemoryEntries, recallMemory, computeEwaScore, getAllMemoryForContext } from './semantic.js';
|
|
4
|
+
export { pruneMemory } from './pruning.js';
|
|
5
|
+
export { consolidateEpisodicToSemantic } from './consolidation.js';
|
|
6
|
+
export { publishInsight, validateInsight, promoteInsight, type InsightType, type InsightPayload } from './insights.js';
|
|
7
|
+
export { type ValidationStage } from '../../schema-enums.js';
|
|
8
|
+
export { listInsights, getInsightDetail, getInsightStats, archiveInsight } from './insights.js';
|
|
9
|
+
export { type AgendaItem, type HeartbeatResult, getRecentHeartbeats, getActiveAgendaItems, addAgendaItem, resolveAgendaItem, PROPOSED_ACTION_PREFIX, getAgendaContext, getRecentHeartbeatContext } from './agenda.js';
|
|
10
|
+
export { type AgentGoal, type AgentAction, getActiveGoals, addGoal, updateGoalStatus, touchGoal, recordGoalAction, sanitizeActionOutcome, downgradeGoalAuthority, getGoalActions } from './goals.js';
|
|
11
|
+
export { extractNodes, createEdges, activateGraph } from './graph.js';
|