@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,18 @@
|
|
|
1
|
+
import { askGroq } from '../../groq.js';
|
|
2
|
+
import { buildGroqSystemPrompt } from '../../operator/prompt-builder.js';
|
|
3
|
+
import type { KernelIntent } from '../types.js';
|
|
4
|
+
import type { EdgeEnv } from '../dispatch.js';
|
|
5
|
+
|
|
6
|
+
export async function executeGroq(
|
|
7
|
+
intent: KernelIntent,
|
|
8
|
+
env: EdgeEnv,
|
|
9
|
+
): Promise<{ text: string; cost: number }> {
|
|
10
|
+
const text = await askGroq(
|
|
11
|
+
env.groqApiKey,
|
|
12
|
+
env.groqResponseModel, // 8B model for greetings — fast + cheap
|
|
13
|
+
buildGroqSystemPrompt(),
|
|
14
|
+
intent.raw,
|
|
15
|
+
env.groqBaseUrl,
|
|
16
|
+
);
|
|
17
|
+
return { text, cost: 0.0001 };
|
|
18
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { McpClient, McpRegistry } from '../../mcp-client.js';
|
|
2
|
+
import { operatorConfig } from '../../operator/index.js';
|
|
3
|
+
import type { Executor } from '../types.js';
|
|
4
|
+
import type { EdgeEnv } from '../dispatch.js';
|
|
5
|
+
import { executeGptOss } from './workers-ai.js';
|
|
6
|
+
|
|
7
|
+
// Re-export all executors
|
|
8
|
+
export { executeClaude, executeClaudeOpus, executeClaudeStream } from './claude.js';
|
|
9
|
+
export { executeGroq } from './groq.js';
|
|
10
|
+
export { executeWorkersAi, executeGptOss } from './workers-ai.js';
|
|
11
|
+
export { executeDirect, executeCodeTask } from './direct.js';
|
|
12
|
+
export { executeTarotScript } from './tarotscript.js';
|
|
13
|
+
|
|
14
|
+
// ─── MCP Registry ────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
const CF_OBSERVABILITY_MCP_URL = 'https://observability.mcp.cloudflare.com/mcp';
|
|
17
|
+
|
|
18
|
+
export function buildMcpRegistry(env: EdgeEnv): McpRegistry {
|
|
19
|
+
const registry = new McpRegistry();
|
|
20
|
+
|
|
21
|
+
// Always register BizOps (Service Binding)
|
|
22
|
+
registry.register(new McpClient({
|
|
23
|
+
url: operatorConfig.integrations.bizops.fallbackUrl,
|
|
24
|
+
token: env.bizopsToken,
|
|
25
|
+
prefix: 'bizops',
|
|
26
|
+
fetcher: env.bizopsFetcher,
|
|
27
|
+
rpcPath: '/rpc',
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
// Colony OS MARA — Service Binding (Governor MCP, 8 tools)
|
|
31
|
+
if (env.maraToken) {
|
|
32
|
+
registry.register(new McpClient({
|
|
33
|
+
url: 'https://your-mara-worker.your-subdomain.workers.dev/mara/mcp',
|
|
34
|
+
token: env.maraToken,
|
|
35
|
+
prefix: 'mara',
|
|
36
|
+
fetcher: env.maraFetcher,
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// CodeBeast — adversarial code review + fix drain (Service Binding, stateless RPC)
|
|
41
|
+
if (env.codebeastFetcher) {
|
|
42
|
+
registry.register(new McpClient({
|
|
43
|
+
url: 'https://your-codebeast-worker.your-subdomain.workers.dev',
|
|
44
|
+
token: '', // Service Binding — no auth needed
|
|
45
|
+
prefix: 'codebeast',
|
|
46
|
+
fetcher: env.codebeastFetcher,
|
|
47
|
+
rpcPath: '/rpc',
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// CF Observability — direct fetch (no Service Binding, external MCP server)
|
|
52
|
+
if (env.cfAnalyticsToken && operatorConfig.integrations.cfObservability.enabled) {
|
|
53
|
+
registry.register(new McpClient({
|
|
54
|
+
url: CF_OBSERVABILITY_MCP_URL,
|
|
55
|
+
token: env.cfAnalyticsToken,
|
|
56
|
+
prefix: 'cf_obs',
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return registry;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── Anthropic Failover ─────────────────────────────────────
|
|
64
|
+
// When Anthropic API is unavailable (billing, auth, rate limit),
|
|
65
|
+
// fall back to GPT-OSS-120B which has tool-calling capability.
|
|
66
|
+
|
|
67
|
+
export async function executeWithAnthropicFailover(
|
|
68
|
+
executor: 'claude' | 'claude_opus',
|
|
69
|
+
intent: import('../types.js').KernelIntent,
|
|
70
|
+
env: EdgeEnv,
|
|
71
|
+
): Promise<{ text: string; cost: number; meta?: unknown; actualExecutor: Executor }> {
|
|
72
|
+
try {
|
|
73
|
+
const { executeClaude, executeClaudeOpus } = await import('./claude.js');
|
|
74
|
+
const result = executor === 'claude_opus'
|
|
75
|
+
? await executeClaudeOpus(intent, env)
|
|
76
|
+
: await executeClaude(intent, env);
|
|
77
|
+
return { ...result, actualExecutor: executor };
|
|
78
|
+
} catch (err) {
|
|
79
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
80
|
+
if (msg.includes('Anthropic API error') || msg.includes('credit balance')) {
|
|
81
|
+
console.warn(`[dispatch] ${executor} failed, failover → gpt_oss: ${msg.slice(0, 120)}`);
|
|
82
|
+
const result = await executeGptOss(intent, env);
|
|
83
|
+
return { ...result, actualExecutor: 'gpt_oss' };
|
|
84
|
+
}
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// TarotScript Executor — symbolic consultation via Service Binding
|
|
2
|
+
//
|
|
3
|
+
// Calls the TarotScript worker's POST /run endpoint. Zero inference tokens
|
|
4
|
+
// (pure JS execution + optional Oracle call on the TarotScript side).
|
|
5
|
+
// Service binding = same colo, no network hop.
|
|
6
|
+
|
|
7
|
+
import type { KernelIntent } from '../types.js';
|
|
8
|
+
import type { EdgeEnv } from '../dispatch.js';
|
|
9
|
+
|
|
10
|
+
// Explicit tarot_* classifications (for internal triggers)
|
|
11
|
+
const MODE_MAP: Record<string, string> = {
|
|
12
|
+
tarot_pulse: 'pulse',
|
|
13
|
+
tarot_trajectory: 'trajectory',
|
|
14
|
+
tarot_multi_angle: 'multi-angle',
|
|
15
|
+
tarot_deep: 'deep',
|
|
16
|
+
tarot_shadow: 'shadow',
|
|
17
|
+
tarot_orchestration: 'orchestration',
|
|
18
|
+
tarot_planning: 'planning',
|
|
19
|
+
support_triage: 'triage',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Mode → spread type (mirrors TarotScript's spread-selector.ts)
|
|
23
|
+
const SPREAD_MAP: Record<string, string> = {
|
|
24
|
+
pulse: 'single',
|
|
25
|
+
trajectory: 'three-card',
|
|
26
|
+
'multi-angle': 'star',
|
|
27
|
+
deep: 'celtic-cross',
|
|
28
|
+
shadow: 'shadow-work',
|
|
29
|
+
orchestration: 'pipeline-cast',
|
|
30
|
+
planning: 'objective-cast',
|
|
31
|
+
triage: 'triage-cast',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function detectMode(intent: KernelIntent): string {
|
|
35
|
+
// Check explicit tarot_* classification first
|
|
36
|
+
if (intent.classified && MODE_MAP[intent.classified]) {
|
|
37
|
+
return MODE_MAP[intent.classified];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Complexity-based fallback for symbolic_consultation
|
|
41
|
+
const complexity = intent.complexity ?? 2;
|
|
42
|
+
if (complexity <= 1) return 'pulse';
|
|
43
|
+
if (complexity >= 3) return 'deep';
|
|
44
|
+
return 'trajectory';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function executeTarotScript(
|
|
48
|
+
intent: KernelIntent,
|
|
49
|
+
env: EdgeEnv,
|
|
50
|
+
): Promise<{ text: string; cost: number; meta?: unknown }> {
|
|
51
|
+
if (!env.tarotscriptFetcher) {
|
|
52
|
+
return { text: 'TarotScript service binding not available.', cost: 0 };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const mode = detectMode(intent);
|
|
56
|
+
const spreadType = SPREAD_MAP[mode] ?? 'three-card';
|
|
57
|
+
|
|
58
|
+
const response = await env.tarotscriptFetcher.fetch('https://tarotscript-worker/run', {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: { 'Content-Type': 'application/json' },
|
|
61
|
+
body: JSON.stringify({
|
|
62
|
+
spreadType,
|
|
63
|
+
querent: {
|
|
64
|
+
id: intent.source.threadId,
|
|
65
|
+
intention: intent.raw,
|
|
66
|
+
state: {
|
|
67
|
+
classification: intent.classified ?? 'symbolic_consultation',
|
|
68
|
+
complexity: String(intent.complexity ?? 2),
|
|
69
|
+
confidence: String(intent.confidence ?? 0.8),
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
inscribe: true,
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const err = await response.text().catch(() => `HTTP ${response.status}`);
|
|
78
|
+
return { text: `TarotScript execution failed: ${err}`, cost: 0 };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const result = await response.json() as {
|
|
82
|
+
output: string[];
|
|
83
|
+
facts: Record<string, unknown>;
|
|
84
|
+
analysis?: { dominantElement?: string; shadowDensity?: { ratio: number } };
|
|
85
|
+
receipt: { hash: string; seed: number };
|
|
86
|
+
spread?: { name: string };
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const text = result.output.join('\n');
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
text,
|
|
93
|
+
cost: 0.001, // Nominal bookkeeping — no inference tokens, pure JS + optional Oracle
|
|
94
|
+
meta: {
|
|
95
|
+
source: 'tarotScript',
|
|
96
|
+
spreadType: result.spread?.name ?? spreadType,
|
|
97
|
+
mode,
|
|
98
|
+
receipt: result.receipt,
|
|
99
|
+
dominantElement: result.analysis?.dominantElement,
|
|
100
|
+
shadowDensity: result.analysis?.shadowDensity?.ratio,
|
|
101
|
+
facts: result.facts,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { executeWorkersAiChat } from '../../workers-ai-chat.js';
|
|
2
|
+
import { McpClient } from '../../mcp-client.js';
|
|
3
|
+
import { operatorConfig } from '../../operator/index.js';
|
|
4
|
+
import { buildGroqSystemPrompt } from '../../operator/prompt-builder.js';
|
|
5
|
+
import type { KernelIntent } from '../types.js';
|
|
6
|
+
import type { EdgeEnv } from '../dispatch.js';
|
|
7
|
+
import { buildMcpRegistry } from './index.js';
|
|
8
|
+
|
|
9
|
+
export async function executeWorkersAi(
|
|
10
|
+
intent: KernelIntent,
|
|
11
|
+
env: EdgeEnv,
|
|
12
|
+
): Promise<{ text: string; cost: number }> {
|
|
13
|
+
if (!env.ai) throw new Error('Workers AI binding not available');
|
|
14
|
+
const result = await env.ai.run('@cf/meta/llama-3.3-70b-instruct-fp8-fast', {
|
|
15
|
+
messages: [
|
|
16
|
+
{ role: 'system', content: buildGroqSystemPrompt() },
|
|
17
|
+
{ role: 'user', content: intent.raw },
|
|
18
|
+
],
|
|
19
|
+
}) as { response?: string };
|
|
20
|
+
return { text: result.response ?? '(no response)', cost: 0.005 };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function executeGptOss(
|
|
24
|
+
intent: KernelIntent,
|
|
25
|
+
env: EdgeEnv,
|
|
26
|
+
): Promise<{ text: string; cost: number }> {
|
|
27
|
+
if (!env.ai) throw new Error('Workers AI binding not available');
|
|
28
|
+
const registry = buildMcpRegistry(env);
|
|
29
|
+
const mcpClient = new McpClient({
|
|
30
|
+
url: operatorConfig.integrations.bizops.fallbackUrl,
|
|
31
|
+
token: env.bizopsToken,
|
|
32
|
+
prefix: 'bizops',
|
|
33
|
+
fetcher: env.bizopsFetcher,
|
|
34
|
+
rpcPath: '/rpc',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return executeWorkersAiChat(
|
|
38
|
+
{
|
|
39
|
+
ai: env.ai,
|
|
40
|
+
model: env.gptOssModel,
|
|
41
|
+
mcpClient,
|
|
42
|
+
mcpRegistry: registry,
|
|
43
|
+
db: env.db,
|
|
44
|
+
channel: 'web',
|
|
45
|
+
conversationId: intent.source.threadId,
|
|
46
|
+
githubToken: env.githubToken,
|
|
47
|
+
githubRepo: env.githubRepo,
|
|
48
|
+
braveApiKey: env.braveApiKey,
|
|
49
|
+
memoryBinding: env.memoryBinding,
|
|
50
|
+
resendApiKeys: { resendApiKey: env.resendApiKey, resendApiKeyPersonal: env.resendApiKeyPersonal },
|
|
51
|
+
},
|
|
52
|
+
intent.raw,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Cross-Repo Insight Cache (CRIX #106)
|
|
2
|
+
// In-memory TTL cache for validated insights from the Memory Worker.
|
|
3
|
+
// Keyword-scored matching injects relevant cross-repo intelligence into dispatch context.
|
|
4
|
+
|
|
5
|
+
import type { EdgeEnv } from './dispatch.js';
|
|
6
|
+
|
|
7
|
+
interface InsightEntry {
|
|
8
|
+
fact: string;
|
|
9
|
+
type: string;
|
|
10
|
+
origin: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let cache: { entries: InsightEntry[]; fetchedAt: number } | null = null;
|
|
14
|
+
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
15
|
+
const MAX_PER_DISPATCH = 3;
|
|
16
|
+
|
|
17
|
+
export async function fetchRelevantInsights(
|
|
18
|
+
env: EdgeEnv,
|
|
19
|
+
classification: string,
|
|
20
|
+
rawQuery: string,
|
|
21
|
+
): Promise<string | null> {
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
if (!cache || (now - cache.fetchedAt) > CACHE_TTL_MS) {
|
|
24
|
+
try {
|
|
25
|
+
const fragments = await env.memoryBinding!.recall('aegis', {
|
|
26
|
+
topic: 'cross_repo_insights',
|
|
27
|
+
limit: 20,
|
|
28
|
+
min_confidence: 0.75,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
cache = {
|
|
32
|
+
entries: fragments.map(f => {
|
|
33
|
+
// Parse "[type:pattern] (from repo-name) actual fact text"
|
|
34
|
+
const prefixMatch = f.content.match(/^\[([^\]]+)\]\s*\(from\s+([^)]+)\)\s*(.+)$/s);
|
|
35
|
+
return {
|
|
36
|
+
fact: prefixMatch ? prefixMatch[3] : f.content,
|
|
37
|
+
type: prefixMatch ? prefixMatch[1] : 'pattern',
|
|
38
|
+
origin: prefixMatch ? prefixMatch[2] : 'unknown',
|
|
39
|
+
};
|
|
40
|
+
}),
|
|
41
|
+
fetchedAt: now,
|
|
42
|
+
};
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!cache || cache.entries.length === 0) return null;
|
|
49
|
+
|
|
50
|
+
// Keyword matching
|
|
51
|
+
const queryWords = new Set(
|
|
52
|
+
rawQuery.toLowerCase()
|
|
53
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
54
|
+
.split(/\s+/)
|
|
55
|
+
.filter(w => w.length > 3)
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const scored = cache.entries.map(entry => {
|
|
59
|
+
const factWords = entry.fact.toLowerCase().split(/\s+/);
|
|
60
|
+
const matches = factWords.filter(w => queryWords.has(w)).length;
|
|
61
|
+
return { ...entry, relevance: matches };
|
|
62
|
+
}).filter(e => e.relevance > 0);
|
|
63
|
+
|
|
64
|
+
scored.sort((a, b) => b.relevance - a.relevance);
|
|
65
|
+
const top = scored.slice(0, MAX_PER_DISPATCH);
|
|
66
|
+
|
|
67
|
+
if (top.length === 0) return null;
|
|
68
|
+
|
|
69
|
+
const lines = top.map(i => `- [${i.type}] (from ${i.origin}) ${i.fact}`);
|
|
70
|
+
return `[Cross-Repo Intelligence — validated patterns]\n${lines.join('\n')}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Reset cache — useful for testing */
|
|
74
|
+
export function resetInsightCache(): void {
|
|
75
|
+
cache = null;
|
|
76
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// ─── Agent Agenda ────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Proposed Actions convention:
|
|
4
|
+
// An agenda item is a [PROPOSED ACTION] when AEGIS has identified a specific,
|
|
5
|
+
// executable action it can perform and is requesting approval before proceeding.
|
|
6
|
+
//
|
|
7
|
+
// Format: item = "[PROPOSED ACTION] <description>"
|
|
8
|
+
// context = full reasoning — what it would do, why, expected outcome
|
|
9
|
+
//
|
|
10
|
+
// AEGIS should use this prefix when:
|
|
11
|
+
// - It has identified a specific step it can execute in-session (not just a flag)
|
|
12
|
+
// - The action has real consequences (filing, creating records, sending emails)
|
|
13
|
+
// - It has enough context to act without further clarification
|
|
14
|
+
//
|
|
15
|
+
// Proposed actions surface before regular agenda items at session start,
|
|
16
|
+
// with an explicit numbered approval prompt. The operator responds "approve N",
|
|
17
|
+
// "reject N", or "defer N". Approved = AEGIS executes + resolves. Rejected =
|
|
18
|
+
// dismissed. Deferred = stays active for next session.
|
|
19
|
+
|
|
20
|
+
import type { AgendaPriority, AgendaStatus } from '../../schema-enums.js';
|
|
21
|
+
|
|
22
|
+
export interface AgendaItem {
|
|
23
|
+
id: number;
|
|
24
|
+
item: string;
|
|
25
|
+
context: string | null;
|
|
26
|
+
priority: AgendaPriority;
|
|
27
|
+
status: AgendaStatus;
|
|
28
|
+
created_at: string;
|
|
29
|
+
resolved_at: string | null;
|
|
30
|
+
business_unit: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const DEFAULT_BUSINESS_UNIT = 'stackbilt';
|
|
34
|
+
|
|
35
|
+
// ─── Heartbeat History (#6) ──────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export interface HeartbeatResult {
|
|
38
|
+
id: number;
|
|
39
|
+
actionable: boolean;
|
|
40
|
+
severity: string;
|
|
41
|
+
summary: string;
|
|
42
|
+
checks_json: string;
|
|
43
|
+
created_at: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function getRecentHeartbeats(db: D1Database, limit = 5): Promise<HeartbeatResult[]> {
|
|
47
|
+
const result = await db.prepare(
|
|
48
|
+
'SELECT * FROM heartbeat_results ORDER BY created_at DESC LIMIT ?'
|
|
49
|
+
).bind(limit).all();
|
|
50
|
+
return result.results as unknown as HeartbeatResult[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function getActiveAgendaItems(
|
|
54
|
+
db: D1Database,
|
|
55
|
+
businessUnit?: string,
|
|
56
|
+
): Promise<AgendaItem[]> {
|
|
57
|
+
const sql = businessUnit
|
|
58
|
+
? "SELECT * FROM agent_agenda WHERE status = 'active' AND business_unit = ? ORDER BY CASE priority WHEN 'high' THEN 0 WHEN 'medium' THEN 1 ELSE 2 END, created_at ASC"
|
|
59
|
+
: "SELECT * FROM agent_agenda WHERE status = 'active' ORDER BY CASE priority WHEN 'high' THEN 0 WHEN 'medium' THEN 1 ELSE 2 END, created_at ASC";
|
|
60
|
+
const stmt = businessUnit
|
|
61
|
+
? db.prepare(sql).bind(businessUnit)
|
|
62
|
+
: db.prepare(sql);
|
|
63
|
+
const result = await stmt.all();
|
|
64
|
+
return result.results as unknown as AgendaItem[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const PRIORITY_RANK: Record<string, number> = { low: 0, medium: 1, high: 2 };
|
|
68
|
+
|
|
69
|
+
function extractAgendaKeyPhrase(text: string): string {
|
|
70
|
+
return text
|
|
71
|
+
.replace(/^\[PROPOSED ACTION\]\s*/i, '')
|
|
72
|
+
.toLowerCase()
|
|
73
|
+
.replace(/[^a-z0-9 ]/g, ' ')
|
|
74
|
+
.replace(/\s+/g, ' ')
|
|
75
|
+
.trim();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function addAgendaItem(
|
|
79
|
+
db: D1Database,
|
|
80
|
+
item: string,
|
|
81
|
+
context: string | undefined,
|
|
82
|
+
priority: AgendaPriority,
|
|
83
|
+
businessUnit: string = DEFAULT_BUSINESS_UNIT,
|
|
84
|
+
): Promise<number> {
|
|
85
|
+
// Dedup: check for existing active OR recently resolved items with similar text (#72)
|
|
86
|
+
// Include items resolved within 7 days to prevent zombie re-creation loops
|
|
87
|
+
// where a compliance item is re-flagged before the upstream source is updated.
|
|
88
|
+
const keyPhrase = extractAgendaKeyPhrase(item);
|
|
89
|
+
const words = keyPhrase.split(' ').filter(w => w.length > 3);
|
|
90
|
+
if (words.length > 0) {
|
|
91
|
+
// Dedup scoped to the same business_unit — different BUs can have
|
|
92
|
+
// legitimately overlapping item text without being duplicates.
|
|
93
|
+
const candidates = await db.prepare(
|
|
94
|
+
"SELECT id, item, priority, status FROM agent_agenda WHERE business_unit = ? AND (status = 'active' OR (status IN ('done', 'dismissed') AND resolved_at >= datetime('now', '-7 days')))"
|
|
95
|
+
).bind(businessUnit).all<{ id: number; item: string; priority: string; status: string }>();
|
|
96
|
+
|
|
97
|
+
for (const existing of candidates.results) {
|
|
98
|
+
const existingKey = extractAgendaKeyPhrase(existing.item);
|
|
99
|
+
// Match if >50% of significant words overlap
|
|
100
|
+
const matches = words.filter(w => existingKey.includes(w)).length;
|
|
101
|
+
if (matches / words.length > 0.5) {
|
|
102
|
+
if (existing.status !== 'active') {
|
|
103
|
+
// Recently resolved — suppress re-creation entirely
|
|
104
|
+
return existing.id;
|
|
105
|
+
}
|
|
106
|
+
// Upgrade priority if new is higher
|
|
107
|
+
if ((PRIORITY_RANK[priority] ?? 0) > (PRIORITY_RANK[existing.priority] ?? 0)) {
|
|
108
|
+
await db.prepare(
|
|
109
|
+
'UPDATE agent_agenda SET priority = ? WHERE id = ?'
|
|
110
|
+
).bind(priority, existing.id).run();
|
|
111
|
+
}
|
|
112
|
+
return existing.id;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const result = await db.prepare(
|
|
118
|
+
'INSERT INTO agent_agenda (item, context, priority, business_unit) VALUES (?, ?, ?, ?)'
|
|
119
|
+
).bind(item, context ?? null, priority, businessUnit).run();
|
|
120
|
+
return result.meta.last_row_id as number;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function resolveAgendaItem(
|
|
124
|
+
db: D1Database,
|
|
125
|
+
id: number,
|
|
126
|
+
status: 'done' | 'dismissed',
|
|
127
|
+
): Promise<void> {
|
|
128
|
+
await db.prepare(
|
|
129
|
+
"UPDATE agent_agenda SET status = ?, resolved_at = datetime('now') WHERE id = ?"
|
|
130
|
+
).bind(status, id).run();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const PROPOSED_ACTION_PREFIX = '[PROPOSED ACTION]';
|
|
134
|
+
export const PROPOSED_TASK_PREFIX = '[PROPOSED TASK]';
|
|
135
|
+
|
|
136
|
+
export async function createProposedTaskAgendaItem(
|
|
137
|
+
db: D1Database,
|
|
138
|
+
taskId: string,
|
|
139
|
+
title: string,
|
|
140
|
+
repo: string,
|
|
141
|
+
category: string,
|
|
142
|
+
rationale: string,
|
|
143
|
+
): Promise<number> {
|
|
144
|
+
const summary = `${PROPOSED_TASK_PREFIX} ${title} (${repo}) — ${category}`;
|
|
145
|
+
const context = JSON.stringify({ task_id: taskId, repo, category, rationale });
|
|
146
|
+
return addAgendaItem(db, summary, context, 'medium');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function getAgendaContext(db: D1Database): Promise<string> {
|
|
150
|
+
const items = await getActiveAgendaItems(db);
|
|
151
|
+
if (items.length === 0) return '';
|
|
152
|
+
|
|
153
|
+
const proposed = items.filter(i => i.item.startsWith(PROPOSED_ACTION_PREFIX));
|
|
154
|
+
const regular = items.filter(i => !i.item.startsWith(PROPOSED_ACTION_PREFIX));
|
|
155
|
+
|
|
156
|
+
let ctx = '';
|
|
157
|
+
|
|
158
|
+
if (proposed.length > 0) {
|
|
159
|
+
ctx += `\n## Pending Proposed Actions\n`;
|
|
160
|
+
ctx += `I have ${proposed.length} proposed action${proposed.length !== 1 ? 's' : ''} awaiting approval:\n\n`;
|
|
161
|
+
proposed.forEach((item, idx) => {
|
|
162
|
+
const description = item.item.slice(PROPOSED_ACTION_PREFIX.length).trim();
|
|
163
|
+
ctx += `${idx + 1}. **[PROPOSED ACTION #${item.id}]** ${description}\n`;
|
|
164
|
+
if (item.context) ctx += ` Reasoning: ${item.context}\n`;
|
|
165
|
+
});
|
|
166
|
+
ctx += `\nRespond "approve N", "reject N", or "defer N" for each — or "approve all".\n`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (regular.length > 0) {
|
|
170
|
+
const lines = regular.map(i =>
|
|
171
|
+
`- [${i.priority.toUpperCase()} #${i.id}] ${i.item}${i.context ? ` — ${i.context}` : ''}`
|
|
172
|
+
);
|
|
173
|
+
ctx += `\n## Active Agenda\n${lines.join('\n')}\n`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return ctx;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ─── Heartbeat Context ───────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
function formatAge(createdAt: string): string {
|
|
182
|
+
const ts = createdAt.endsWith('Z') ? createdAt : createdAt + 'Z';
|
|
183
|
+
const diffMs = Date.now() - new Date(ts).getTime();
|
|
184
|
+
const hours = Math.floor(diffMs / 3_600_000);
|
|
185
|
+
if (hours < 1) return 'just now';
|
|
186
|
+
if (hours < 24) return `${hours}h ago`;
|
|
187
|
+
return `${Math.floor(hours / 24)}d ago`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function getRecentHeartbeatContext(db: D1Database): Promise<string> {
|
|
191
|
+
const result = await db.prepare(
|
|
192
|
+
'SELECT severity, summary, actionable, created_at FROM heartbeat_results ORDER BY created_at DESC LIMIT 2'
|
|
193
|
+
).all();
|
|
194
|
+
const rows = result.results as unknown as { severity: string; summary: string; actionable: number; created_at: string }[];
|
|
195
|
+
if (rows.length === 0) return '';
|
|
196
|
+
const lines = rows.map(r =>
|
|
197
|
+
`- [${r.severity.toUpperCase()}${r.actionable ? ' ⚡' : ''}] ${r.summary} (${formatAge(r.created_at)})`
|
|
198
|
+
);
|
|
199
|
+
return `\n## Recent Heartbeat Status\n${lines.join('\n')}\n`;
|
|
200
|
+
}
|