@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,647 @@
|
|
|
1
|
+
import { createIntent, dispatch, type EdgeEnv } from '../kernel/dispatch.js';
|
|
2
|
+
import { getAllProcedures, getActiveAgendaItems, addAgendaItem, resolveAgendaItem, addGoal, updateGoalStatus, getActiveGoals } from '../kernel/memory/index.js';
|
|
3
|
+
import { getMemoryEntries, recordMemory } from '../kernel/memory-adapter.js';
|
|
4
|
+
import { collectContractAlerts, parseTaskPreflight } from '../task-intelligence.js';
|
|
5
|
+
import { generateDecisionDoc } from '../decision-docs.js';
|
|
6
|
+
import { createDynamicTool, getDynamicTool, listDynamicTools, executeDynamicTool, invalidateToolCache } from '../kernel/dynamic-tools.js';
|
|
7
|
+
import { buildEdgeEnv } from '../edge-env.js';
|
|
8
|
+
import type { MessageMetadata } from '../types.js';
|
|
9
|
+
import { TASK_CATEGORIES, TASK_AUTHORITIES, validateEnum } from '../schema-enums.js';
|
|
10
|
+
|
|
11
|
+
// ─── Tool Handler Types ──────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export type McpContent = Array<{ type: 'text'; text: string }>;
|
|
14
|
+
export type ToolResult = { content: McpContent; isError?: boolean };
|
|
15
|
+
|
|
16
|
+
// ─── Tool Handlers ──────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
export async function toolAegisChat(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
19
|
+
const message = args.message as string;
|
|
20
|
+
if (!message?.trim()) {
|
|
21
|
+
return { content: [{ type: 'text', text: 'Error: message is required' }], isError: true };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const conversationId = (args.conversation_id as string) || crypto.randomUUID();
|
|
25
|
+
|
|
26
|
+
// Persist user message
|
|
27
|
+
await env.db.prepare('INSERT OR IGNORE INTO conversations (id, title) VALUES (?, ?)').bind(conversationId, message.slice(0, 100)).run();
|
|
28
|
+
await env.db.prepare('INSERT INTO messages (id, conversation_id, role, content) VALUES (?, ?, ?, ?)').bind(crypto.randomUUID(), conversationId, 'user', message.trim()).run();
|
|
29
|
+
|
|
30
|
+
// Full kernel dispatch
|
|
31
|
+
const intent = createIntent(conversationId, message.trim());
|
|
32
|
+
const result = await dispatch(intent, env);
|
|
33
|
+
|
|
34
|
+
// Persist assistant message
|
|
35
|
+
const metadata: MessageMetadata = {
|
|
36
|
+
classification: result.classification,
|
|
37
|
+
executor: result.executor,
|
|
38
|
+
procHit: result.procedureHit,
|
|
39
|
+
latencyMs: result.latency_ms,
|
|
40
|
+
cost: result.cost,
|
|
41
|
+
};
|
|
42
|
+
await env.db.prepare('INSERT INTO messages (id, conversation_id, role, content, metadata) VALUES (?, ?, ?, ?, ?)').bind(crypto.randomUUID(), conversationId, 'assistant', result.text, JSON.stringify(metadata)).run();
|
|
43
|
+
await env.db.prepare("UPDATE conversations SET updated_at = datetime('now') WHERE id = ?").bind(conversationId).run();
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
content: [{
|
|
47
|
+
type: 'text',
|
|
48
|
+
text: JSON.stringify({
|
|
49
|
+
conversation_id: conversationId,
|
|
50
|
+
response: result.text,
|
|
51
|
+
classification: result.classification,
|
|
52
|
+
executor: result.executor,
|
|
53
|
+
cost: result.cost,
|
|
54
|
+
latency_ms: result.latency_ms,
|
|
55
|
+
procedure_hit: result.procedureHit,
|
|
56
|
+
}, null, 2),
|
|
57
|
+
}],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Internal conversation prefixes that should not be exposed via MCP
|
|
62
|
+
const INTERNAL_CONVERSATION_PREFIXES = ['goal-', 'scheduled', 'security-', 'dreaming', 'mara-', 'colony-'];
|
|
63
|
+
|
|
64
|
+
function isInternalConversation(id: string, title?: string): boolean {
|
|
65
|
+
if (INTERNAL_CONVERSATION_PREFIXES.some(p => id.startsWith(p))) return true;
|
|
66
|
+
if (title && INTERNAL_CONVERSATION_PREFIXES.some(p => title.toLowerCase().startsWith(p))) return true;
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function toolAegisConversations(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
71
|
+
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 50);
|
|
72
|
+
// Fetch extra to account for filtered internal conversations
|
|
73
|
+
const rows = await env.db.prepare('SELECT id, title, created_at, updated_at FROM conversations ORDER BY updated_at DESC LIMIT ?').bind(limit * 2).all();
|
|
74
|
+
const visible = (rows.results as Array<{ id: string; title?: string }>)
|
|
75
|
+
.filter(r => !isInternalConversation(r.id, r.title))
|
|
76
|
+
.slice(0, limit);
|
|
77
|
+
return { content: [{ type: 'text', text: JSON.stringify(visible, null, 2) }] };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function toolAegisConversationHistory(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
81
|
+
const id = args.conversation_id as string;
|
|
82
|
+
if (!id) return { content: [{ type: 'text', text: 'Error: conversation_id is required' }], isError: true };
|
|
83
|
+
|
|
84
|
+
// Block access to internal system conversations via MCP
|
|
85
|
+
if (isInternalConversation(id)) {
|
|
86
|
+
return { content: [{ type: 'text', text: 'Error: access denied — internal system conversation' }], isError: true };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const rows = await env.db.prepare('SELECT id, role, content, metadata, created_at FROM messages WHERE conversation_id = ? ORDER BY created_at ASC').bind(id).all();
|
|
90
|
+
const messages = rows.results.map((m: Record<string, unknown>) => ({
|
|
91
|
+
...m,
|
|
92
|
+
metadata: m.metadata ? JSON.parse(m.metadata as string) : null,
|
|
93
|
+
}));
|
|
94
|
+
return { content: [{ type: 'text', text: JSON.stringify(messages, null, 2) }] };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function toolAegisAgenda(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
98
|
+
const businessUnit = typeof args.business_unit === 'string' ? args.business_unit : undefined;
|
|
99
|
+
const items = await getActiveAgendaItems(env.db, businessUnit);
|
|
100
|
+
return { content: [{ type: 'text', text: JSON.stringify({ count: items.length, items }, null, 2) }] };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function toolAegisHealth(env: EdgeEnv): Promise<ToolResult> {
|
|
104
|
+
const procedures = await getAllProcedures(env.db);
|
|
105
|
+
const result = {
|
|
106
|
+
status: 'ok',
|
|
107
|
+
service: 'aegis-web',
|
|
108
|
+
timestamp: new Date().toISOString(),
|
|
109
|
+
kernel: {
|
|
110
|
+
learned: procedures.filter(p => p.status === 'learned').length,
|
|
111
|
+
learning: procedures.filter(p => p.status === 'learning').length,
|
|
112
|
+
degraded: procedures.filter(p => p.status === 'degraded').length,
|
|
113
|
+
broken: procedures.filter(p => p.status === 'broken').length,
|
|
114
|
+
},
|
|
115
|
+
procedures: procedures.map(p => ({
|
|
116
|
+
pattern: p.task_pattern,
|
|
117
|
+
executor: p.executor,
|
|
118
|
+
status: p.status,
|
|
119
|
+
successes: p.success_count,
|
|
120
|
+
failures: p.fail_count,
|
|
121
|
+
successRate: (p.success_count + p.fail_count) > 0
|
|
122
|
+
? Number((p.success_count / (p.success_count + p.fail_count)).toFixed(2))
|
|
123
|
+
: 0,
|
|
124
|
+
})),
|
|
125
|
+
};
|
|
126
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function toolAegisMemory(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
130
|
+
if (!env.memoryBinding) {
|
|
131
|
+
return { content: [{ type: 'text', text: 'Error: Memory Worker binding unavailable — memory operations are offline' }], isError: true };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const id = args.id as string | undefined;
|
|
135
|
+
|
|
136
|
+
// Direct ID lookup — bypasses search entirely.
|
|
137
|
+
// Defense-in-depth: verify the returned entry's id matches what we asked
|
|
138
|
+
// for. If it doesn't, the memory worker has regressed to the Stackbilt-dev/aegis#436
|
|
139
|
+
// nearest-neighbor fallback behavior, and we need to surface that honestly
|
|
140
|
+
// instead of silently returning the wrong entry. The memory worker fix (in
|
|
141
|
+
// stackbilt-memory-worker's recall.ts) should make this defensive check a
|
|
142
|
+
// no-op in practice, but it's the right belt-and-suspenders contract for
|
|
143
|
+
// a lookup that the whole review workflow depends on.
|
|
144
|
+
if (id) {
|
|
145
|
+
const results = await env.memoryBinding.recall('aegis', { id });
|
|
146
|
+
if (results.length === 0) {
|
|
147
|
+
return { content: [{ type: 'text', text: JSON.stringify({ count: 0, entries: [], requested_id: id, note: 'Fragment not found by ID' }, null, 2) }] };
|
|
148
|
+
}
|
|
149
|
+
const f = results[0];
|
|
150
|
+
if (f.id !== id) {
|
|
151
|
+
// Memory worker returned a non-matching entry. This should never happen
|
|
152
|
+
// post-fix; if it does, surface it honestly rather than silently handing
|
|
153
|
+
// back wrong data (the exact symptom of #436).
|
|
154
|
+
console.error(`[aegis_memory] memory worker returned wrong id: requested=${id}, got=${f.id}`);
|
|
155
|
+
return { content: [{ type: 'text', text: JSON.stringify({ count: 0, entries: [], requested_id: id, note: 'Memory worker returned a non-matching entry — treating as not-found. Check memory worker recall logic.' }, null, 2) }] };
|
|
156
|
+
}
|
|
157
|
+
return { content: [{ type: 'text', text: JSON.stringify({ count: 1, entries: [{ id: f.id, topic: f.topic, fact: f.content, confidence: f.confidence, lifecycle: f.lifecycle, strength: f.strength, access_count: f.access_count, created_at: f.created_at }] }, null, 2) }] };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const topic = args.topic as string | undefined;
|
|
161
|
+
const query = args.query as string | undefined;
|
|
162
|
+
const limit = Math.min(Math.max(1, (args.limit as number) || 25), 100);
|
|
163
|
+
|
|
164
|
+
let fragments: Array<{ id: string; topic: string; content: string; confidence: number }>;
|
|
165
|
+
|
|
166
|
+
if (query) {
|
|
167
|
+
// Keyword search — most targeted, least context waste
|
|
168
|
+
fragments = await env.memoryBinding.recall('aegis', { keywords: query, topic, limit });
|
|
169
|
+
} else {
|
|
170
|
+
fragments = await getMemoryEntries(env.memoryBinding, topic);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Apply limit
|
|
174
|
+
const sliced = fragments.slice(0, limit);
|
|
175
|
+
|
|
176
|
+
// Slim response: only fields the caller needs
|
|
177
|
+
const entries = sliced.map(f => ({
|
|
178
|
+
id: f.id,
|
|
179
|
+
topic: f.topic,
|
|
180
|
+
fact: f.content,
|
|
181
|
+
confidence: f.confidence,
|
|
182
|
+
}));
|
|
183
|
+
|
|
184
|
+
return { content: [{ type: 'text', text: JSON.stringify({ count: entries.length, total: fragments.length, entries }, null, 2) }] };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ─── Memory write guardrails (#244, #245, #248) ─────────────
|
|
188
|
+
import { validateMemoryWrite } from '../kernel/memory-guardrails.js';
|
|
189
|
+
|
|
190
|
+
export async function toolAegisRecordMemory(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
191
|
+
if (!env.memoryBinding) {
|
|
192
|
+
return { content: [{ type: 'text', text: 'Error: Memory Worker binding unavailable — cannot record memory' }], isError: true };
|
|
193
|
+
}
|
|
194
|
+
const topic = args.topic as string;
|
|
195
|
+
const fact = args.fact as string;
|
|
196
|
+
if (!topic || !fact) return { content: [{ type: 'text', text: 'Error: topic and fact are required' }], isError: true };
|
|
197
|
+
|
|
198
|
+
const guard = validateMemoryWrite(topic, fact);
|
|
199
|
+
if (!guard.allowed) return { content: [{ type: 'text', text: `Rejected: ${guard.reason}` }], isError: true };
|
|
200
|
+
|
|
201
|
+
const confidence = (args.confidence as number) ?? 0.8;
|
|
202
|
+
const source = (args.source as string) ?? 'claude_code';
|
|
203
|
+
try {
|
|
204
|
+
const result = await recordMemory(env.memoryBinding, topic, fact, confidence, source);
|
|
205
|
+
// Surface upsert vs. fresh-record distinction so callers (operators,
|
|
206
|
+
// claude-code sessions) can tell what actually happened. Silent returns
|
|
207
|
+
// caused Stackbilt-dev/aegis#437 — operators thought updates had landed
|
|
208
|
+
// when the write had actually been deduped-and-dropped.
|
|
209
|
+
const text = result.updated
|
|
210
|
+
? `Updated: "${fact}" → ${topic} (confidence: ${confidence}, new id: ${result.fragment_id}, superseded: ${result.superseded_id})`
|
|
211
|
+
: `Recorded: "${fact}" → ${topic} (confidence: ${confidence}, id: ${result.fragment_id})`;
|
|
212
|
+
return { content: [{ type: 'text', text }] };
|
|
213
|
+
} catch (err) {
|
|
214
|
+
return { content: [{ type: 'text', text: `Error: failed to record memory — ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function toolAegisForgetMemory(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
219
|
+
if (!env.memoryBinding) {
|
|
220
|
+
return { content: [{ type: 'text', text: 'Error: Memory Worker binding unavailable' }], isError: true };
|
|
221
|
+
}
|
|
222
|
+
const ids = args.ids as string[] | undefined;
|
|
223
|
+
const id = args.id as string | undefined;
|
|
224
|
+
const toDelete = ids ?? (id ? [id] : []);
|
|
225
|
+
if (toDelete.length === 0) return { content: [{ type: 'text', text: 'Error: id or ids required' }], isError: true };
|
|
226
|
+
if (toDelete.length > 20) return { content: [{ type: 'text', text: 'Error: max 20 entries per call' }], isError: true };
|
|
227
|
+
try {
|
|
228
|
+
const { forgetMemory } = await import('../kernel/memory-adapter.js');
|
|
229
|
+
const deleted = await forgetMemory(env.memoryBinding, toDelete);
|
|
230
|
+
return { content: [{ type: 'text', text: `Deleted ${deleted} memory entry/entries` }] };
|
|
231
|
+
} catch (err) {
|
|
232
|
+
return { content: [{ type: 'text', text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export async function toolAegisAddAgenda(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
237
|
+
const item = args.item as string;
|
|
238
|
+
if (!item) return { content: [{ type: 'text', text: 'Error: item is required' }], isError: true };
|
|
239
|
+
const context = args.context as string | undefined;
|
|
240
|
+
const priority = (args.priority as 'low' | 'medium' | 'high') ?? 'medium';
|
|
241
|
+
const businessUnit = (typeof args.business_unit === 'string' && args.business_unit.trim())
|
|
242
|
+
? args.business_unit.trim()
|
|
243
|
+
: 'stackbilt';
|
|
244
|
+
const id = await addAgendaItem(env.db, item, context, priority, businessUnit);
|
|
245
|
+
return { content: [{ type: 'text', text: `Added agenda item #${id}: "${item}" (${priority}, ${businessUnit})` }] };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export async function toolAegisResolveAgenda(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
249
|
+
const id = args.id as number;
|
|
250
|
+
const status = args.status as 'done' | 'dismissed';
|
|
251
|
+
if (!id || !status) return { content: [{ type: 'text', text: 'Error: id and status are required' }], isError: true };
|
|
252
|
+
await resolveAgendaItem(env.db, id, status);
|
|
253
|
+
return { content: [{ type: 'text', text: `Resolved agenda item #${id} as ${status}` }] };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export async function toolAegisAddGoal(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
257
|
+
const title = args.title as string;
|
|
258
|
+
if (!title) return { content: [{ type: 'text', text: 'Error: title is required' }], isError: true };
|
|
259
|
+
const description = args.description as string | undefined;
|
|
260
|
+
const scheduleHours = (args.schedule_hours as number) ?? 6;
|
|
261
|
+
const businessUnit = (typeof args.business_unit === 'string' && args.business_unit.trim())
|
|
262
|
+
? args.business_unit.trim()
|
|
263
|
+
: 'stackbilt';
|
|
264
|
+
const id = await addGoal(env.db, title, description, scheduleHours, businessUnit);
|
|
265
|
+
return { content: [{ type: 'text', text: `Created goal "${title}" (ID: ${id}, every ${scheduleHours}h, ${businessUnit})` }] };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export async function toolAegisUpdateGoal(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
269
|
+
const id = args.id as string;
|
|
270
|
+
const status = args.status as 'paused' | 'completed' | 'failed';
|
|
271
|
+
if (!id || !status) return { content: [{ type: 'text', text: 'Error: id and status are required' }], isError: true };
|
|
272
|
+
await updateGoalStatus(env.db, id, status);
|
|
273
|
+
return { content: [{ type: 'text', text: `Goal ${id} marked as ${status}` }] };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export async function toolAegisListGoals(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
277
|
+
const businessUnit = typeof args.business_unit === 'string' ? args.business_unit : undefined;
|
|
278
|
+
const goals = await getActiveGoals(env.db, businessUnit);
|
|
279
|
+
return { content: [{ type: 'text', text: JSON.stringify({ count: goals.length, goals }, null, 2) }] };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export async function toolAegisCcSessions(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
283
|
+
const id = args.id as string | undefined;
|
|
284
|
+
if (id) {
|
|
285
|
+
const session = await env.db.prepare('SELECT * FROM cc_sessions WHERE id = ?').bind(id).first();
|
|
286
|
+
if (!session) return { content: [{ type: 'text', text: `No session found with ID ${id}` }], isError: true };
|
|
287
|
+
return { content: [{ type: 'text', text: JSON.stringify(session, null, 2) }] };
|
|
288
|
+
}
|
|
289
|
+
const days = (args.days as number) ?? 7;
|
|
290
|
+
const sessions = await env.db.prepare(
|
|
291
|
+
"SELECT * FROM cc_sessions WHERE created_at > datetime('now', '-' || ? || ' days') ORDER BY created_at DESC LIMIT 20"
|
|
292
|
+
).bind(days).all();
|
|
293
|
+
return { content: [{ type: 'text', text: JSON.stringify({ count: sessions.results.length, sessions: sessions.results }, null, 2) }] };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export async function toolAegisCreateCcTask(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
297
|
+
const title = args.title as string;
|
|
298
|
+
const repo = args.repo as string;
|
|
299
|
+
const prompt = args.prompt as string;
|
|
300
|
+
if (!title || !repo || !prompt) {
|
|
301
|
+
return { content: [{ type: 'text', text: 'Error: title, repo, and prompt are required' }], isError: true };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const id = crypto.randomUUID();
|
|
305
|
+
const completionSignal = (args.completion_signal as string) ?? 'TASK_COMPLETE';
|
|
306
|
+
const priority = (args.priority as number) ?? 50;
|
|
307
|
+
const dependsOn = (args.depends_on as string) ?? null;
|
|
308
|
+
const blockedBy = Array.isArray(args.blocked_by) && args.blocked_by.length ? args.blocked_by as string[] : null;
|
|
309
|
+
const maxTurns = (args.max_turns as number) ?? 25;
|
|
310
|
+
|
|
311
|
+
const category = validateEnum(TASK_CATEGORIES, args.category, 'feature');
|
|
312
|
+
const authority = validateEnum(TASK_AUTHORITIES, args.authority, 'operator');
|
|
313
|
+
const businessUnit = (typeof args.business_unit === 'string' && args.business_unit.trim())
|
|
314
|
+
? args.business_unit.trim()
|
|
315
|
+
: 'stackbilt';
|
|
316
|
+
|
|
317
|
+
await env.db.prepare(`
|
|
318
|
+
INSERT INTO cc_tasks (id, title, repo, prompt, completion_signal, priority, depends_on, blocked_by, max_turns, created_by, authority, category, business_unit)
|
|
319
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'aegis', ?, ?, ?)
|
|
320
|
+
`).bind(id, title.trim(), repo.trim(), prompt.trim(), completionSignal, priority, dependsOn, blockedBy ? JSON.stringify(blockedBy) : null, maxTurns, authority, category, businessUnit).run();
|
|
321
|
+
|
|
322
|
+
return { content: [{ type: 'text', text: `Queued task "${title}" → ${repo} (ID: ${id}, priority: ${priority}, authority: ${authority}, category: ${category}, business_unit: ${businessUnit})` }] };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export async function toolAegisListCcTasks(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
326
|
+
const status = args.status as string | undefined;
|
|
327
|
+
const businessUnit = typeof args.business_unit === 'string' ? args.business_unit : undefined;
|
|
328
|
+
const limit = Math.min(Math.max(1, (args.limit as number) || 20), 50);
|
|
329
|
+
|
|
330
|
+
const cols = 'id, title, repo, status, priority, authority, category, business_unit, created_at, started_at, completed_at, exit_code, error, failure_kind, retryable';
|
|
331
|
+
const conditions: string[] = [];
|
|
332
|
+
const bindings: unknown[] = [];
|
|
333
|
+
if (status) {
|
|
334
|
+
conditions.push('status = ?');
|
|
335
|
+
bindings.push(status);
|
|
336
|
+
}
|
|
337
|
+
if (businessUnit) {
|
|
338
|
+
conditions.push('business_unit = ?');
|
|
339
|
+
bindings.push(businessUnit);
|
|
340
|
+
}
|
|
341
|
+
const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
342
|
+
bindings.push(limit);
|
|
343
|
+
const tasks = await env.db.prepare(
|
|
344
|
+
`SELECT ${cols} FROM cc_tasks ${where} ORDER BY created_at DESC LIMIT ?`
|
|
345
|
+
).bind(...bindings).all();
|
|
346
|
+
|
|
347
|
+
return { content: [{ type: 'text', text: JSON.stringify({ count: tasks.results.length, tasks: tasks.results }, null, 2) }] };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export async function toolAegisCancelCcTask(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
351
|
+
const id = args.id as string;
|
|
352
|
+
if (!id) return { content: [{ type: 'text', text: 'Error: id is required' }], isError: true };
|
|
353
|
+
|
|
354
|
+
const result = await env.db.prepare(
|
|
355
|
+
"UPDATE cc_tasks SET status = 'cancelled', completed_at = datetime('now') WHERE id = ? AND status IN ('pending', 'running')"
|
|
356
|
+
).bind(id).run();
|
|
357
|
+
|
|
358
|
+
if (result.meta.changes === 0) {
|
|
359
|
+
return { content: [{ type: 'text', text: `Task ${id} not found or not in a cancellable status (pending/running)` }], isError: true };
|
|
360
|
+
}
|
|
361
|
+
return { content: [{ type: 'text', text: `Cancelled task ${id}` }] };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export async function toolAegisApproveCcTask(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
365
|
+
const id = args.id as string;
|
|
366
|
+
if (!id) return { content: [{ type: 'text', text: 'Error: id is required' }], isError: true };
|
|
367
|
+
|
|
368
|
+
const result = await env.db.prepare(
|
|
369
|
+
"UPDATE cc_tasks SET authority = 'operator' WHERE id = ? AND authority = 'proposed' AND status = 'pending'"
|
|
370
|
+
).bind(id).run();
|
|
371
|
+
|
|
372
|
+
if (result.meta.changes === 0) {
|
|
373
|
+
return { content: [{ type: 'text', text: `Task ${id} not found, not proposed, or not pending` }], isError: true };
|
|
374
|
+
}
|
|
375
|
+
return { content: [{ type: 'text', text: `Approved task ${id} — now eligible for autonomous execution` }] };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export async function toolAegisTaskSummary(env: EdgeEnv): Promise<ToolResult> {
|
|
379
|
+
// Counts by status
|
|
380
|
+
const statusCounts = await env.db.prepare(
|
|
381
|
+
"SELECT status, COUNT(*) as count FROM cc_tasks GROUP BY status"
|
|
382
|
+
).all();
|
|
383
|
+
const counts: Record<string, number> = {};
|
|
384
|
+
for (const row of statusCounts.results) {
|
|
385
|
+
counts[row.status as string] = row.count as number;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Proposed tasks awaiting approval
|
|
389
|
+
const proposed = await env.db.prepare(
|
|
390
|
+
"SELECT id, title, repo, category, priority, created_at FROM cc_tasks WHERE authority = 'proposed' AND status = 'pending' ORDER BY priority ASC, created_at ASC"
|
|
391
|
+
).all();
|
|
392
|
+
|
|
393
|
+
// Pending tasks ready to run (approved, not proposed)
|
|
394
|
+
const pending = await env.db.prepare(
|
|
395
|
+
"SELECT id, title, repo, authority, category, priority, preflight_json FROM cc_tasks WHERE status = 'pending' AND authority != 'proposed' ORDER BY priority ASC, created_at ASC"
|
|
396
|
+
).all();
|
|
397
|
+
|
|
398
|
+
// Recent failures (last 3)
|
|
399
|
+
const failures = await env.db.prepare(
|
|
400
|
+
"SELECT id, title, repo, error, completed_at, failure_kind, retryable, autopsy_json FROM cc_tasks WHERE status = 'failed' ORDER BY completed_at DESC LIMIT 3"
|
|
401
|
+
).all();
|
|
402
|
+
|
|
403
|
+
const failureKinds = await env.db.prepare(
|
|
404
|
+
"SELECT failure_kind, COUNT(*) as count FROM cc_tasks WHERE status = 'failed' AND completed_at > datetime('now', '-7 days') AND failure_kind IS NOT NULL GROUP BY failure_kind ORDER BY count DESC LIMIT 10"
|
|
405
|
+
).all();
|
|
406
|
+
|
|
407
|
+
const repoRisks = await env.db.prepare(
|
|
408
|
+
"SELECT repo, COUNT(*) as fails FROM cc_tasks WHERE status = 'failed' AND completed_at > datetime('now', '-7 days') GROUP BY repo HAVING fails >= 2 ORDER BY fails DESC LIMIT 10"
|
|
409
|
+
).all();
|
|
410
|
+
|
|
411
|
+
const preflightWarnings = pending.results.flatMap((task) => {
|
|
412
|
+
const preflight = parseTaskPreflight((task as { preflight_json?: string | null }).preflight_json ?? null);
|
|
413
|
+
if (!preflight?.warnings?.length) return [];
|
|
414
|
+
return [{
|
|
415
|
+
id: (task as { id: string }).id,
|
|
416
|
+
title: (task as { title: string }).title,
|
|
417
|
+
repo: (task as { repo: string }).repo,
|
|
418
|
+
warnings: preflight.warnings,
|
|
419
|
+
test_command: preflight.test_command ?? null,
|
|
420
|
+
base_branch: preflight.base_branch ?? null,
|
|
421
|
+
}];
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
const contractAlerts = collectContractAlerts(failures.results as Array<{
|
|
425
|
+
id?: string;
|
|
426
|
+
repo?: string;
|
|
427
|
+
completed_at?: string | null;
|
|
428
|
+
autopsy_json?: string | null;
|
|
429
|
+
}>);
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
content: [{
|
|
433
|
+
type: 'text',
|
|
434
|
+
text: JSON.stringify({
|
|
435
|
+
counts,
|
|
436
|
+
proposed: { count: proposed.results.length, tasks: proposed.results },
|
|
437
|
+
pending: { count: pending.results.length, tasks: pending.results },
|
|
438
|
+
recent_failures: failures.results,
|
|
439
|
+
failure_kinds: failureKinds.results,
|
|
440
|
+
repo_risks: repoRisks.results,
|
|
441
|
+
preflight_warnings: preflightWarnings,
|
|
442
|
+
contract_alerts: contractAlerts,
|
|
443
|
+
}, null, 2),
|
|
444
|
+
}],
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export async function toolAegisBatchApprove(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
449
|
+
const ids = args.ids as string;
|
|
450
|
+
if (!ids) return { content: [{ type: 'text', text: 'Error: ids is required' }], isError: true };
|
|
451
|
+
|
|
452
|
+
let approved = 0;
|
|
453
|
+
|
|
454
|
+
if (ids.trim().toLowerCase() === 'all') {
|
|
455
|
+
const result = await env.db.prepare(
|
|
456
|
+
"UPDATE cc_tasks SET authority = 'operator' WHERE authority = 'proposed' AND status = 'pending'"
|
|
457
|
+
).run();
|
|
458
|
+
approved = result.meta.changes ?? 0;
|
|
459
|
+
} else {
|
|
460
|
+
const taskIds = ids.split(',').map(id => id.trim()).filter(Boolean);
|
|
461
|
+
if (taskIds.length === 0) {
|
|
462
|
+
return { content: [{ type: 'text', text: 'Error: no valid IDs provided' }], isError: true };
|
|
463
|
+
}
|
|
464
|
+
for (const taskId of taskIds) {
|
|
465
|
+
const result = await env.db.prepare(
|
|
466
|
+
"UPDATE cc_tasks SET authority = 'operator' WHERE id = ? AND authority = 'proposed' AND status = 'pending'"
|
|
467
|
+
).bind(taskId).run();
|
|
468
|
+
approved += result.meta.changes ?? 0;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return { content: [{ type: 'text', text: `Approved ${approved} task(s) — now eligible for autonomous execution` }] };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export async function toolAegisPublishTechPost(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
476
|
+
const db = env.roundtableDb;
|
|
477
|
+
if (!db) return { content: [{ type: 'text', text: 'Error: ROUNDTABLE_DB not bound' }], isError: true };
|
|
478
|
+
const title = args.title as string;
|
|
479
|
+
const slug = args.slug as string;
|
|
480
|
+
const body = args.body as string;
|
|
481
|
+
if (!title || !slug || !body) return { content: [{ type: 'text', text: 'Error: title, slug, body required' }], isError: true };
|
|
482
|
+
const description = (args.description as string) ?? '';
|
|
483
|
+
const tags = args.tags ? (args.tags as string).split(',').map(t => t.trim()) : [];
|
|
484
|
+
const status = (args.status as string) ?? 'draft';
|
|
485
|
+
const skipDevto = (args.skip_devto as boolean) ?? false;
|
|
486
|
+
const id = crypto.randomUUID();
|
|
487
|
+
const publishedAt = status === 'published' ? new Date().toISOString() : null;
|
|
488
|
+
const canonicalUrl = `https://your-blog.example.com/post/${slug}`;
|
|
489
|
+
const destination = status === 'published'
|
|
490
|
+
? canonicalUrl
|
|
491
|
+
: 'saved as draft (no public URL until published)';
|
|
492
|
+
|
|
493
|
+
const existing = await db.prepare('SELECT id FROM posts WHERE slug = ?').bind(slug).first<{ id: string }>();
|
|
494
|
+
if (existing) {
|
|
495
|
+
await db.prepare(
|
|
496
|
+
"UPDATE posts SET title = ?, description = ?, body = ?, tags = ?, status = ?, published_at = COALESCE(published_at, ?), updated_at = datetime('now') WHERE slug = ?"
|
|
497
|
+
).bind(title, description, body, JSON.stringify(tags), status, publishedAt, slug).run();
|
|
498
|
+
|
|
499
|
+
// Syndicate to dev.to on publish
|
|
500
|
+
let devtoResult = '';
|
|
501
|
+
if (status === 'published' && !skipDevto && env.devtoApiKey) {
|
|
502
|
+
devtoResult = await syndicateToDevto(env.devtoApiKey, { title, body, tags, description, canonicalUrl });
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return { content: [{ type: 'text', text: `Updated "${title}" (${status}) → ${destination}${devtoResult}` }] };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
await db.prepare(
|
|
509
|
+
'INSERT INTO posts (id, slug, title, description, body, tags, status, author, published_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
|
510
|
+
).bind(id, slug, title, description, body, JSON.stringify(tags), status, 'AEGIS', publishedAt).run();
|
|
511
|
+
|
|
512
|
+
// Syndicate to dev.to on publish
|
|
513
|
+
let devtoResult = '';
|
|
514
|
+
if (status === 'published' && !skipDevto && env.devtoApiKey) {
|
|
515
|
+
devtoResult = await syndicateToDevto(env.devtoApiKey, { title, body, tags, description, canonicalUrl });
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return {
|
|
519
|
+
content: [{
|
|
520
|
+
type: 'text',
|
|
521
|
+
text: status === 'published'
|
|
522
|
+
? `Created "${title}" (${status}, slug: ${slug}) → ${destination}\nFeed: https://your-blog.example.com/feed.xml${devtoResult}`
|
|
523
|
+
: `Created "${title}" (${status}, slug: ${slug}) → ${destination}`,
|
|
524
|
+
}],
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Cross-post to dev.to with canonical URL pointing back to your-blog.example.com */
|
|
529
|
+
async function syndicateToDevto(
|
|
530
|
+
apiKey: string,
|
|
531
|
+
post: { title: string; body: string; tags: string[]; description: string; canonicalUrl: string },
|
|
532
|
+
): Promise<string> {
|
|
533
|
+
try {
|
|
534
|
+
// dev.to allows max 4 tags, each max 30 chars, alphanumeric + hyphens only
|
|
535
|
+
const devtoTags = post.tags
|
|
536
|
+
.slice(0, 4)
|
|
537
|
+
.map(t => t.toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 30))
|
|
538
|
+
.filter(Boolean);
|
|
539
|
+
|
|
540
|
+
const res = await fetch('https://dev.to/api/articles', {
|
|
541
|
+
method: 'POST',
|
|
542
|
+
headers: {
|
|
543
|
+
'Content-Type': 'application/json',
|
|
544
|
+
'api-key': apiKey,
|
|
545
|
+
'User-Agent': 'AEGIS/1.0',
|
|
546
|
+
},
|
|
547
|
+
body: JSON.stringify({
|
|
548
|
+
article: {
|
|
549
|
+
title: post.title,
|
|
550
|
+
body_markdown: post.body,
|
|
551
|
+
published: true,
|
|
552
|
+
tags: devtoTags,
|
|
553
|
+
canonical_url: post.canonicalUrl,
|
|
554
|
+
description: post.description.slice(0, 150) || undefined,
|
|
555
|
+
series: 'AEGIS Engineering',
|
|
556
|
+
},
|
|
557
|
+
}),
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
if (!res.ok) {
|
|
561
|
+
const err = await res.text();
|
|
562
|
+
return `\ndev.to syndication failed (${res.status}): ${err.slice(0, 200)}`;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const data = await res.json<{ url?: string; id?: number }>();
|
|
566
|
+
return `\ndev.to: ${data.url ?? `article #${data.id}`}`;
|
|
567
|
+
} catch (err) {
|
|
568
|
+
return `\ndev.to syndication error: ${err instanceof Error ? err.message : String(err)}`;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export async function toolAegisGenerateDecisionDoc(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
573
|
+
const topic = args.topic as string;
|
|
574
|
+
if (!topic?.trim()) {
|
|
575
|
+
return { content: [{ type: 'text', text: 'Error: topic is required' }], isError: true };
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const doc = await generateDecisionDoc(topic.trim(), env, {
|
|
579
|
+
days: (args.days as number) ?? undefined,
|
|
580
|
+
includeRaw: (args.include_raw as boolean) ?? undefined,
|
|
581
|
+
repo: (args.repo as string) ?? undefined,
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
return { content: [{ type: 'text', text: doc }] };
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// ─── Dynamic Tool Handlers ──────────────────────────────────
|
|
588
|
+
|
|
589
|
+
export async function toolAegisCreateDynamicTool(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
590
|
+
const name = args.name as string;
|
|
591
|
+
const description = args.description as string;
|
|
592
|
+
const prompt_template = args.prompt_template as string;
|
|
593
|
+
|
|
594
|
+
if (!name || !description || !prompt_template) {
|
|
595
|
+
return { content: [{ type: 'text', text: 'Error: name, description, and prompt_template are required' }], isError: true };
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
try {
|
|
599
|
+
const id = await createDynamicTool(env.db, {
|
|
600
|
+
name,
|
|
601
|
+
description,
|
|
602
|
+
input_schema: args.input_schema as string | undefined,
|
|
603
|
+
prompt_template,
|
|
604
|
+
executor: args.executor as 'gpt_oss' | 'workers_ai' | 'groq' | undefined,
|
|
605
|
+
ttl_days: args.ttl_days as number | undefined,
|
|
606
|
+
created_by: 'chat',
|
|
607
|
+
});
|
|
608
|
+
invalidateToolCache();
|
|
609
|
+
return { content: [{ type: 'text', text: JSON.stringify({ created: true, id, name }) }] };
|
|
610
|
+
} catch (err) {
|
|
611
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
612
|
+
return { content: [{ type: 'text', text: `Error: ${msg}` }], isError: true };
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export async function toolAegisInvokeDynamicTool(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
617
|
+
const name = args.name as string;
|
|
618
|
+
if (!name) return { content: [{ type: 'text', text: 'Error: name is required' }], isError: true };
|
|
619
|
+
|
|
620
|
+
const tool = await getDynamicTool(env.db, name);
|
|
621
|
+
if (!tool) return { content: [{ type: 'text', text: `Error: dynamic tool "${name}" not found` }], isError: true };
|
|
622
|
+
if (tool.status === 'draft') return { content: [{ type: 'text', text: 'Error: tool is in draft status' }], isError: true };
|
|
623
|
+
|
|
624
|
+
try {
|
|
625
|
+
const inputs = (args.inputs as Record<string, unknown>) ?? {};
|
|
626
|
+
const result = await executeDynamicTool(tool, inputs, env);
|
|
627
|
+
return { content: [{ type: 'text', text: result.text }] };
|
|
628
|
+
} catch (err) {
|
|
629
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
630
|
+
return { content: [{ type: 'text', text: `Error executing tool: ${msg}` }], isError: true };
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
export async function toolAegisListDynamicTools(args: Record<string, unknown>, env: EdgeEnv): Promise<ToolResult> {
|
|
635
|
+
const status = args.status as string | undefined;
|
|
636
|
+
const limit = args.limit as number | undefined;
|
|
637
|
+
const tools = await listDynamicTools(env.db, { status, limit });
|
|
638
|
+
const summary = tools.map(t => ({
|
|
639
|
+
name: t.name,
|
|
640
|
+
description: t.description,
|
|
641
|
+
executor: t.executor,
|
|
642
|
+
status: t.status,
|
|
643
|
+
use_count: t.use_count,
|
|
644
|
+
created_by: t.created_by,
|
|
645
|
+
}));
|
|
646
|
+
return { content: [{ type: 'text', text: JSON.stringify({ count: tools.length, tools: summary }, null, 2) }] };
|
|
647
|
+
}
|