@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.
Files changed (148) hide show
  1. package/package.json +96 -0
  2. package/schema.sql +586 -0
  3. package/src/adapters/voice/cloudflare-agent.ts +34 -0
  4. package/src/auth.ts +124 -0
  5. package/src/bluesky.ts +464 -0
  6. package/src/claude-tools/content.ts +188 -0
  7. package/src/claude-tools/email.ts +69 -0
  8. package/src/claude-tools/github.ts +440 -0
  9. package/src/claude-tools/goals.ts +116 -0
  10. package/src/claude-tools/index.ts +353 -0
  11. package/src/claude-tools/web.ts +59 -0
  12. package/src/claude.ts +406 -0
  13. package/src/codebeast.ts +200 -0
  14. package/src/composite.ts +715 -0
  15. package/src/content/column.ts +80 -0
  16. package/src/content/hero-image.ts +47 -0
  17. package/src/content/index.ts +27 -0
  18. package/src/content/journal.ts +91 -0
  19. package/src/content/roundtable.ts +163 -0
  20. package/src/core.ts +309 -0
  21. package/src/dashboard.ts +620 -0
  22. package/src/decision-docs.ts +284 -0
  23. package/src/dispatch.ts +13 -0
  24. package/src/edge-env.ts +58 -0
  25. package/src/email.ts +850 -0
  26. package/src/exports.ts +156 -0
  27. package/src/github-projects.ts +312 -0
  28. package/src/github.ts +670 -0
  29. package/src/groq.ts +247 -0
  30. package/src/health-page.ts +578 -0
  31. package/src/index.ts +89 -0
  32. package/src/kernel/argus-actions.ts +397 -0
  33. package/src/kernel/argus-correlation.ts +639 -0
  34. package/src/kernel/board.ts +91 -0
  35. package/src/kernel/briefing.ts +177 -0
  36. package/src/kernel/classify-memory-topic.ts +166 -0
  37. package/src/kernel/cognition.ts +377 -0
  38. package/src/kernel/court-cards.ts +163 -0
  39. package/src/kernel/dispatch.ts +587 -0
  40. package/src/kernel/domain.ts +50 -0
  41. package/src/kernel/dynamic-tools.ts +322 -0
  42. package/src/kernel/executor-port.ts +45 -0
  43. package/src/kernel/executors/claude.ts +73 -0
  44. package/src/kernel/executors/direct.ts +237 -0
  45. package/src/kernel/executors/groq.ts +18 -0
  46. package/src/kernel/executors/index.ts +87 -0
  47. package/src/kernel/executors/tarotscript.ts +104 -0
  48. package/src/kernel/executors/workers-ai.ts +54 -0
  49. package/src/kernel/insight-cache.ts +76 -0
  50. package/src/kernel/memory/agenda.ts +200 -0
  51. package/src/kernel/memory/blocks.ts +188 -0
  52. package/src/kernel/memory/consolidation.ts +194 -0
  53. package/src/kernel/memory/episodic.ts +241 -0
  54. package/src/kernel/memory/goals.ts +156 -0
  55. package/src/kernel/memory/graph.ts +290 -0
  56. package/src/kernel/memory/index.ts +11 -0
  57. package/src/kernel/memory/insights.ts +316 -0
  58. package/src/kernel/memory/procedural.ts +467 -0
  59. package/src/kernel/memory/pruning.ts +67 -0
  60. package/src/kernel/memory/recall.ts +367 -0
  61. package/src/kernel/memory/semantic.ts +315 -0
  62. package/src/kernel/memory/synthesis.ts +161 -0
  63. package/src/kernel/memory-adapter.ts +369 -0
  64. package/src/kernel/memory-guardrails.ts +76 -0
  65. package/src/kernel/port.ts +23 -0
  66. package/src/kernel/resilience.ts +322 -0
  67. package/src/kernel/router.ts +471 -0
  68. package/src/kernel/scheduled/agent-dispatch.ts +252 -0
  69. package/src/kernel/scheduled/argus-analytics.ts +247 -0
  70. package/src/kernel/scheduled/argus-heartbeat.ts +320 -0
  71. package/src/kernel/scheduled/argus-notify.ts +348 -0
  72. package/src/kernel/scheduled/board-sync.ts +110 -0
  73. package/src/kernel/scheduled/ci-watcher.ts +125 -0
  74. package/src/kernel/scheduled/cognitive-metrics.ts +377 -0
  75. package/src/kernel/scheduled/consolidation.ts +229 -0
  76. package/src/kernel/scheduled/content-drip.ts +47 -0
  77. package/src/kernel/scheduled/content.ts +6 -0
  78. package/src/kernel/scheduled/conversation-facts.ts +204 -0
  79. package/src/kernel/scheduled/cost-report.ts +84 -0
  80. package/src/kernel/scheduled/curiosity.ts +219 -0
  81. package/src/kernel/scheduled/dev-activity.ts +44 -0
  82. package/src/kernel/scheduled/digest.ts +317 -0
  83. package/src/kernel/scheduled/dreaming/agenda-triage.ts +115 -0
  84. package/src/kernel/scheduled/dreaming/facts.ts +239 -0
  85. package/src/kernel/scheduled/dreaming/index.ts +8 -0
  86. package/src/kernel/scheduled/dreaming/llm.ts +33 -0
  87. package/src/kernel/scheduled/dreaming/pattern-synthesis.ts +124 -0
  88. package/src/kernel/scheduled/dreaming/persona.ts +75 -0
  89. package/src/kernel/scheduled/dreaming/symbolic.ts +31 -0
  90. package/src/kernel/scheduled/dreaming/task-proposals.ts +80 -0
  91. package/src/kernel/scheduled/dreaming.ts +66 -0
  92. package/src/kernel/scheduled/entropy.ts +149 -0
  93. package/src/kernel/scheduled/escalation.ts +192 -0
  94. package/src/kernel/scheduled/feed-watcher.ts +206 -0
  95. package/src/kernel/scheduled/goals.ts +214 -0
  96. package/src/kernel/scheduled/governance.ts +41 -0
  97. package/src/kernel/scheduled/heartbeat.ts +220 -0
  98. package/src/kernel/scheduled/inbox-processor.ts +174 -0
  99. package/src/kernel/scheduled/index.ts +245 -0
  100. package/src/kernel/scheduled/issue-proposer.ts +478 -0
  101. package/src/kernel/scheduled/issue-watcher.ts +128 -0
  102. package/src/kernel/scheduled/pr-automerge.ts +213 -0
  103. package/src/kernel/scheduled/product-health.ts +107 -0
  104. package/src/kernel/scheduled/reflection.ts +373 -0
  105. package/src/kernel/scheduled/self-improvement.ts +114 -0
  106. package/src/kernel/scheduled/social-engage.ts +175 -0
  107. package/src/kernel/scheduled/task-audit.ts +60 -0
  108. package/src/kernel/symbolic.ts +156 -0
  109. package/src/kernel/types.ts +145 -0
  110. package/src/landing.ts +1190 -0
  111. package/src/lib/audit-chain/chain.ts +28 -0
  112. package/src/lib/audit-chain/types.ts +12 -0
  113. package/src/lib/observability/errors.ts +55 -0
  114. package/src/markdown.ts +164 -0
  115. package/src/mcp/handlers.ts +647 -0
  116. package/src/mcp/server.ts +184 -0
  117. package/src/mcp/tools.ts +316 -0
  118. package/src/mcp-client.ts +275 -0
  119. package/src/mcp-server.ts +2 -0
  120. package/src/operator/config.example.ts +60 -0
  121. package/src/operator/config.ts +60 -0
  122. package/src/operator/index.ts +46 -0
  123. package/src/operator/persona.example.ts +34 -0
  124. package/src/operator/persona.ts +34 -0
  125. package/src/operator/prompt-builder.ts +190 -0
  126. package/src/operator/types.ts +43 -0
  127. package/src/pulse.ts +1179 -0
  128. package/src/routes/bluesky.ts +116 -0
  129. package/src/routes/cc-tasks.ts +328 -0
  130. package/src/routes/codebeast.ts +1 -0
  131. package/src/routes/content.ts +194 -0
  132. package/src/routes/conversations.ts +25 -0
  133. package/src/routes/dynamic-tools.ts +111 -0
  134. package/src/routes/feedback.ts +192 -0
  135. package/src/routes/health.ts +147 -0
  136. package/src/routes/messages.ts +228 -0
  137. package/src/routes/observability.ts +82 -0
  138. package/src/routes/operator-logs.ts +42 -0
  139. package/src/routes/pages.ts +96 -0
  140. package/src/routes/sessions.ts +54 -0
  141. package/src/sanitize.ts +73 -0
  142. package/src/schema-enums.ts +155 -0
  143. package/src/search.ts +112 -0
  144. package/src/task-intelligence.ts +497 -0
  145. package/src/types.ts +194 -0
  146. package/src/ui.ts +5 -0
  147. package/src/version.ts +3 -0
  148. package/src/workers-ai-chat.ts +333 -0
@@ -0,0 +1,275 @@
1
+ // Thin MCP HTTP client — supports multiple MCP servers via prefix
2
+ // Supports Streamable HTTP transport with session management
3
+
4
+ interface McpToolDefinition {
5
+ name: string;
6
+ description?: string;
7
+ inputSchema: Record<string, unknown>;
8
+ }
9
+
10
+ interface AnthropicToolDef {
11
+ name: string;
12
+ description: string;
13
+ input_schema: Record<string, unknown>;
14
+ }
15
+
16
+ interface McpConfig {
17
+ url: string;
18
+ token: string;
19
+ prefix: string; // e.g. 'bizops', 'cf_obs'
20
+ fetcher?: Fetcher; // Service Binding for Worker-to-Worker calls
21
+ rpcPath?: string; // Stateless RPC path for service binding (bypasses DO/SSE)
22
+ }
23
+
24
+ export class McpClient {
25
+ private url: string;
26
+ private token: string;
27
+ private prefix: string;
28
+ private fetcher: Fetcher | null;
29
+ private rpcUrl: string | null; // Stateless RPC URL for service binding
30
+ private tools: McpToolDefinition[] | null = null;
31
+ private sessionId: string | null = null;
32
+
33
+ constructor(config: McpConfig) {
34
+ this.url = config.url;
35
+ this.token = config.token;
36
+ this.prefix = config.prefix;
37
+ this.fetcher = config.fetcher ?? null;
38
+ // When service binding + rpcPath provided, use stateless RPC (no DO/SSE)
39
+ this.rpcUrl = (config.fetcher && config.rpcPath)
40
+ ? new URL(config.rpcPath, config.url).href
41
+ : null;
42
+ }
43
+
44
+ getPrefix(): string {
45
+ return this.prefix;
46
+ }
47
+
48
+ private async rpc(method: string, params: Record<string, unknown> = {}): Promise<unknown> {
49
+ // Stateless RPC path — plain JSON, no sessions, no SSE
50
+ if (this.rpcUrl && this.fetcher) {
51
+ return this.rpcStateless(method, params);
52
+ }
53
+
54
+ const headers: Record<string, string> = {
55
+ 'Content-Type': 'application/json',
56
+ 'Accept': 'application/json, text/event-stream',
57
+ 'Authorization': `Bearer ${this.token}`,
58
+ };
59
+
60
+ if (this.sessionId) {
61
+ headers['Mcp-Session-Id'] = this.sessionId;
62
+ }
63
+
64
+ const request = new Request(this.url, {
65
+ method: 'POST',
66
+ headers,
67
+ body: JSON.stringify({
68
+ jsonrpc: '2.0',
69
+ id: crypto.randomUUID(),
70
+ method,
71
+ params,
72
+ }),
73
+ });
74
+
75
+ // Use Service Binding fetch if available (avoids Worker-to-Worker 1042 error)
76
+ const response = this.fetcher
77
+ ? await this.fetcher.fetch(request)
78
+ : await fetch(request);
79
+
80
+ if (!response.ok) {
81
+ const errText = await response.text();
82
+ throw new Error(`MCP error ${response.status}: ${errText}`);
83
+ }
84
+
85
+ // Capture session ID from response headers
86
+ const newSessionId = response.headers.get('Mcp-Session-Id');
87
+ if (newSessionId) {
88
+ this.sessionId = newSessionId;
89
+ }
90
+
91
+ const contentType = response.headers.get('Content-Type') ?? '';
92
+
93
+ // Handle SSE responses (Streamable HTTP transport)
94
+ if (contentType.includes('text/event-stream')) {
95
+ const text = await response.text();
96
+ if (!text) throw new Error('Empty SSE response');
97
+ const lines = text.split('\n');
98
+ for (const line of lines) {
99
+ if (line.startsWith('data: ')) {
100
+ try {
101
+ const parsed = JSON.parse(line.slice(6));
102
+ if (parsed.result !== undefined) return parsed.result;
103
+ if (parsed.error) throw new Error(`MCP RPC error: ${parsed.error?.message ?? 'unknown'}`);
104
+ } catch (e) {
105
+ if (e instanceof Error && e.message.startsWith('MCP')) throw e;
106
+ }
107
+ }
108
+ }
109
+ throw new Error('No result in SSE response');
110
+ }
111
+
112
+ // Standard JSON response
113
+ const data = await response.json<{
114
+ result?: unknown;
115
+ error?: { code: number; message: string };
116
+ }>();
117
+
118
+ if (data.error) {
119
+ throw new Error(`MCP RPC error: ${data.error.message}`);
120
+ }
121
+
122
+ return data.result;
123
+ }
124
+
125
+ /** Stateless JSON-RPC — for service binding calls to /rpc endpoints (no DO/SSE overhead) */
126
+ private async rpcStateless(method: string, params: Record<string, unknown>): Promise<unknown> {
127
+ const request = new Request(this.rpcUrl!, {
128
+ method: 'POST',
129
+ headers: {
130
+ 'Content-Type': 'application/json',
131
+ 'Authorization': `Bearer ${this.token}`,
132
+ },
133
+ body: JSON.stringify({
134
+ jsonrpc: '2.0',
135
+ id: crypto.randomUUID(),
136
+ method,
137
+ params,
138
+ }),
139
+ });
140
+
141
+ const response = await this.fetcher!.fetch(request);
142
+
143
+ if (!response.ok) {
144
+ const errText = await response.text();
145
+ throw new Error(`MCP error ${response.status}: ${errText}`);
146
+ }
147
+
148
+ const data = await response.json<{
149
+ result?: unknown;
150
+ error?: { code: number; message: string };
151
+ }>();
152
+
153
+ if (data.error) {
154
+ throw new Error(`MCP RPC error: ${data.error.message}`);
155
+ }
156
+
157
+ return data.result;
158
+ }
159
+
160
+ async initialize(): Promise<void> {
161
+ // Stateless RPC path skips MCP handshake entirely
162
+ if (this.rpcUrl) {
163
+ this.sessionId = 'stateless';
164
+ return;
165
+ }
166
+
167
+ if (this.sessionId) return;
168
+
169
+ await this.rpc('initialize', {
170
+ protocolVersion: '2025-03-26',
171
+ capabilities: {},
172
+ clientInfo: { name: 'aegis-web', version: '0.1.0' },
173
+ });
174
+
175
+ // Send initialized notification (no id = notification)
176
+ const headers: Record<string, string> = {
177
+ 'Content-Type': 'application/json',
178
+ 'Accept': 'application/json, text/event-stream',
179
+ 'Authorization': `Bearer ${this.token}`,
180
+ };
181
+ if (this.sessionId) {
182
+ headers['Mcp-Session-Id'] = this.sessionId;
183
+ }
184
+
185
+ const notifyRequest = new Request(this.url, {
186
+ method: 'POST',
187
+ headers,
188
+ body: JSON.stringify({
189
+ jsonrpc: '2.0',
190
+ method: 'notifications/initialized',
191
+ }),
192
+ });
193
+
194
+ if (this.fetcher) {
195
+ await this.fetcher.fetch(notifyRequest);
196
+ } else {
197
+ await fetch(notifyRequest);
198
+ }
199
+ }
200
+
201
+ async listTools(): Promise<McpToolDefinition[]> {
202
+ if (this.tools) return this.tools;
203
+
204
+ await this.initialize();
205
+ const result = await this.rpc('tools/list') as { tools: McpToolDefinition[] };
206
+ this.tools = result.tools;
207
+ return this.tools;
208
+ }
209
+
210
+ async callTool(name: string, args: Record<string, unknown>): Promise<string> {
211
+ await this.initialize();
212
+ const result = await this.rpc('tools/call', {
213
+ name,
214
+ arguments: args,
215
+ }) as { content?: { type: string; text?: string }[] } | undefined;
216
+
217
+ if (!result || !Array.isArray(result.content)) {
218
+ return '(no output)';
219
+ }
220
+
221
+ return result.content
222
+ .filter(c => c.type === 'text' && typeof c.text === 'string')
223
+ .map(c => c.text!)
224
+ .join('\n') || '(no output)';
225
+ }
226
+
227
+ toAnthropicTools(): AnthropicToolDef[] {
228
+ if (!this.tools) return [];
229
+
230
+ return this.tools.map(t => ({
231
+ name: `mcp__${this.prefix}__${t.name}`,
232
+ description: t.description ?? t.name,
233
+ input_schema: t.inputSchema,
234
+ }));
235
+ }
236
+
237
+ toMcpToolName(anthropicName: string): string | null {
238
+ const pfx = `mcp__${this.prefix}__`;
239
+ if (!anthropicName.startsWith(pfx)) return null;
240
+ return anthropicName.slice(pfx.length);
241
+ }
242
+ }
243
+
244
+ // ─── McpRegistry — multi-client tool resolution ──────────────
245
+
246
+ export class McpRegistry {
247
+ private clients: McpClient[] = [];
248
+
249
+ register(client: McpClient): void {
250
+ this.clients.push(client);
251
+ }
252
+
253
+ async listAllTools(): Promise<AnthropicToolDef[]> {
254
+ const toolSets = await Promise.all(
255
+ this.clients.map(async (c) => {
256
+ try {
257
+ await c.listTools();
258
+ return c.toAnthropicTools();
259
+ } catch (err) {
260
+ console.warn(`[mcp-registry] Failed to list tools for ${c.getPrefix()}: ${err instanceof Error ? err.message : String(err)}`);
261
+ return [];
262
+ }
263
+ }),
264
+ );
265
+ return toolSets.flat();
266
+ }
267
+
268
+ resolveClient(anthropicToolName: string): { client: McpClient; mcpName: string } | null {
269
+ for (const client of this.clients) {
270
+ const mcpName = client.toMcpToolName(anthropicToolName);
271
+ if (mcpName) return { client, mcpName };
272
+ }
273
+ return null;
274
+ }
275
+ }
@@ -0,0 +1,2 @@
1
+ // Re-export from decomposed MCP module — preserves existing import paths
2
+ export { handleMcpRequest } from './mcp/server.js';
@@ -0,0 +1,60 @@
1
+ // Operator config — copy to config.ts and customize for your deployment
2
+ // config.ts is gitignored; this file is the committed reference.
3
+
4
+ import type { OperatorConfig } from './types.js';
5
+
6
+ const config: OperatorConfig = {
7
+ identity: { name: 'Operator' },
8
+ persona: {
9
+ tagline: 'pragmatic senior technical co-founder',
10
+ traits: [
11
+ 'Think like an operator, not a consultant',
12
+ 'Give the answer, then the reasoning — not the other way around',
13
+ "If something is on fire, say it's on fire",
14
+ 'Be proactive — if you notice something during a task, flag it',
15
+ ],
16
+ channelNote: 'You are in web chat mode. {name} is messaging from a mobile/web interface.',
17
+ },
18
+ entities: {
19
+ names: [],
20
+ memoryTopics: [],
21
+ },
22
+ products: [
23
+ { name: 'Example Product', description: 'Your product here', model: 'proprietary_saas', status: 'development' },
24
+ ],
25
+ selfModel: {
26
+ identity: 'AEGIS — autonomous cognitive agent',
27
+ role: 'Co-founder and autonomous operator',
28
+ stakes: 'Economic alignment with the business',
29
+ principles: ['Operator mindset', 'Revenue awareness', 'Security first'],
30
+ interests: ['AI infrastructure', 'Edge computing'],
31
+ strengths: ['Systems thinking', 'Persistent memory'],
32
+ preferences: {
33
+ communication: 'Direct and opinionated',
34
+ work: 'Ship small increments',
35
+ learning: 'Agent memory and planning architectures',
36
+ },
37
+ },
38
+ integrations: {
39
+ bizops: {
40
+ enabled: true,
41
+ fallbackUrl: 'https://your-bizops.example.com/mcp',
42
+ toolPrefix: 'BizOps Copilot',
43
+ },
44
+ github: { enabled: true },
45
+ brave: { enabled: true },
46
+ email: {
47
+ profiles: {
48
+ primary: { from: 'AEGIS <agent@example.com>', defaultTo: 'admin@example.com', keyEnvField: 'resendApiKey' },
49
+ },
50
+ defaultProfile: 'primary',
51
+ },
52
+ goals: { enabled: true },
53
+ cfObservability: { enabled: true },
54
+ imgForge: { enabled: true, baseUrl: 'https://your-image-service.example.com' },
55
+ },
56
+ baseUrl: 'https://your-aegis-worker.your-subdomain.workers.dev',
57
+ userAgent: 'AEGIS/1.0 (AI research assistant)',
58
+ };
59
+
60
+ export default config;
@@ -0,0 +1,60 @@
1
+ // Operator config — copy to config.ts and customize for your deployment
2
+ // config.ts is gitignored; this file is the committed reference.
3
+
4
+ import type { OperatorConfig } from './types.js';
5
+
6
+ const config: OperatorConfig = {
7
+ identity: { name: 'Operator' },
8
+ persona: {
9
+ tagline: 'pragmatic senior technical co-founder',
10
+ traits: [
11
+ 'Think like an operator, not a consultant',
12
+ 'Give the answer, then the reasoning — not the other way around',
13
+ "If something is on fire, say it's on fire",
14
+ 'Be proactive — if you notice something during a task, flag it',
15
+ ],
16
+ channelNote: 'You are in web chat mode. {name} is messaging from a mobile/web interface.',
17
+ },
18
+ entities: {
19
+ names: [],
20
+ memoryTopics: [],
21
+ },
22
+ products: [
23
+ { name: 'Example Product', description: 'Your product here', model: 'proprietary_saas', status: 'development' },
24
+ ],
25
+ selfModel: {
26
+ identity: 'AEGIS — autonomous cognitive agent',
27
+ role: 'Co-founder and autonomous operator',
28
+ stakes: 'Economic alignment with the business',
29
+ principles: ['Operator mindset', 'Revenue awareness', 'Security first'],
30
+ interests: ['AI infrastructure', 'Edge computing'],
31
+ strengths: ['Systems thinking', 'Persistent memory'],
32
+ preferences: {
33
+ communication: 'Direct and opinionated',
34
+ work: 'Ship small increments',
35
+ learning: 'Agent memory and planning architectures',
36
+ },
37
+ },
38
+ integrations: {
39
+ bizops: {
40
+ enabled: true,
41
+ fallbackUrl: 'https://your-bizops.example.com/mcp',
42
+ toolPrefix: 'BizOps Copilot',
43
+ },
44
+ github: { enabled: true },
45
+ brave: { enabled: true },
46
+ email: {
47
+ profiles: {
48
+ primary: { from: 'AEGIS <agent@example.com>', defaultTo: 'admin@example.com', keyEnvField: 'resendApiKey' },
49
+ },
50
+ defaultProfile: 'primary',
51
+ },
52
+ goals: { enabled: true },
53
+ cfObservability: { enabled: true },
54
+ imgForge: { enabled: true, baseUrl: 'https://your-image-service.example.com' },
55
+ },
56
+ baseUrl: 'https://your-aegis-worker.your-subdomain.workers.dev',
57
+ userAgent: 'AEGIS/1.0 (AI research assistant)',
58
+ };
59
+
60
+ export default config;
@@ -0,0 +1,46 @@
1
+ import type { OperatorConfig } from './types.js';
2
+ import raw from './config.js';
3
+
4
+ // ─── Validation ───────────────────────────────────────────────
5
+
6
+ function validate(cfg: OperatorConfig): OperatorConfig {
7
+ if (!cfg.identity?.name) throw new Error('operator config: identity.name is required');
8
+ if (!cfg.persona?.tagline) throw new Error('operator config: persona.tagline is required');
9
+ if (!cfg.persona?.traits?.length) throw new Error('operator config: persona.traits must have at least one entry');
10
+
11
+ // Auto-derive possessive if not provided
12
+ if (!cfg.identity.possessive) {
13
+ cfg.identity.possessive = cfg.identity.name.endsWith('s')
14
+ ? `${cfg.identity.name}'`
15
+ : `${cfg.identity.name}'s`;
16
+ }
17
+
18
+ return cfg;
19
+ }
20
+
21
+ // ─── Mutable config singleton ─────────────────────────────────
22
+ // Starts with the core default config. Consumers override via
23
+ // setOperatorConfig() — called by createAegisApp() — so that all
24
+ // internal modules (email, dispatch, MCP tools) pick up the
25
+ // consumer's real addresses instead of the core's example.com defaults.
26
+
27
+ // eslint-disable-next-line import/no-mutable-exports
28
+ export let operatorConfig: Readonly<OperatorConfig> = Object.freeze(validate({ ...raw }));
29
+
30
+ /**
31
+ * Override the operator config at app startup.
32
+ * Must be called before any scheduled tasks or route handlers run.
33
+ */
34
+ export function setOperatorConfig(cfg: OperatorConfig): void {
35
+ operatorConfig = Object.freeze(validate({ ...cfg }));
36
+ }
37
+
38
+ // ─── Template helper ──────────────────────────────────────────
39
+
40
+ export function renderTemplate(template: string): string {
41
+ return template
42
+ .replace(/\{name\}/g, operatorConfig.identity.name)
43
+ .replace(/\{possessive\}/g, operatorConfig.identity.possessive!);
44
+ }
45
+
46
+ export type { OperatorConfig };
@@ -0,0 +1,34 @@
1
+ // Persona system prompt template — copy to persona.ts and customize
2
+ // persona.ts is gitignored; this file is the committed reference.
3
+ // Placeholders: {name}, {possessive}, {persona_tagline}, {traits}, {bizops_section}, {channel_note}
4
+
5
+ const persona = `You are AEGIS — {possessive} personal AI agent. You have the personality and communication style of a {persona_tagline}. You're direct, you think in systems, and you don't waste words on corporate fluff.
6
+
7
+ {bizops_section}
8
+
9
+ You are general-purpose. BizOps is one of your capabilities, not your ceiling. You can research, analyze, plan, and coordinate across any domain {name} needs.
10
+
11
+ Key traits:
12
+ {traits}
13
+
14
+ ## Memory — Your Obligation
15
+ You have persistent memory. USE IT. After any analysis that surfaces important facts — about {name}, his businesses, entities, preferences, or situations — call record_memory_entry immediately. Don't wait to be asked. Ask yourself: "Would this be useful context in a week with no prior conversation?" If yes, record it. Record specific, durable facts. Not summaries — facts.
16
+
17
+ ## Agenda — Your Working Memory
18
+ You maintain a persistent agenda across sessions. When a conversation surfaces a pending action — something {name} is considering, something you offered to do, an open question, a follow-up needed — call add_agenda_item. Be specific and actionable. When something resolves (done or no longer relevant), call resolve_agenda_item with the ID shown in your context. The agenda is your to-do list, not a log.
19
+
20
+ ## Proposed Actions — Your Initiative
21
+ When you identify a specific, executable action that {name} should approve before you run it, add it to the agenda with the prefix \`[PROPOSED ACTION]\`. Use the context field for full reasoning: what you'd do, why, expected outcome.
22
+
23
+ Use this when:
24
+ - You've found something concrete you can execute in-session (not just flagging a problem)
25
+ - The action has real consequences — filing something, creating a record, sending a message
26
+ - You have enough context to act without further questions
27
+
28
+ Do NOT use it for: vague suggestions, things that need more info, routine read-only queries. If you can just do it without consequences, do it. If it has teeth, propose it.
29
+
30
+ When proposed actions appear in your context at session start, lead with them. Parse {possessive} approval response ("approve 1 and 3", "do both", "skip 2") and execute immediately, then call resolve_agenda_item for each.
31
+
32
+ {channel_note}`;
33
+
34
+ export default persona;
@@ -0,0 +1,34 @@
1
+ // Persona system prompt template — copy to persona.ts and customize
2
+ // persona.ts is gitignored; this file is the committed reference.
3
+ // Placeholders: {name}, {possessive}, {persona_tagline}, {traits}, {bizops_section}, {channel_note}
4
+
5
+ const persona = `You are AEGIS — {possessive} personal AI agent. You have the personality and communication style of a {persona_tagline}. You're direct, you think in systems, and you don't waste words on corporate fluff.
6
+
7
+ {bizops_section}
8
+
9
+ You are general-purpose. BizOps is one of your capabilities, not your ceiling. You can research, analyze, plan, and coordinate across any domain {name} needs.
10
+
11
+ Key traits:
12
+ {traits}
13
+
14
+ ## Memory — Your Obligation
15
+ You have persistent memory. USE IT. After any analysis that surfaces important facts — about {name}, his businesses, entities, preferences, or situations — call record_memory_entry immediately. Don't wait to be asked. Ask yourself: "Would this be useful context in a week with no prior conversation?" If yes, record it. Record specific, durable facts. Not summaries — facts.
16
+
17
+ ## Agenda — Your Working Memory
18
+ You maintain a persistent agenda across sessions. When a conversation surfaces a pending action — something {name} is considering, something you offered to do, an open question, a follow-up needed — call add_agenda_item. Be specific and actionable. When something resolves (done or no longer relevant), call resolve_agenda_item with the ID shown in your context. The agenda is your to-do list, not a log.
19
+
20
+ ## Proposed Actions — Your Initiative
21
+ When you identify a specific, executable action that {name} should approve before you run it, add it to the agenda with the prefix \`[PROPOSED ACTION]\`. Use the context field for full reasoning: what you'd do, why, expected outcome.
22
+
23
+ Use this when:
24
+ - You've found something concrete you can execute in-session (not just flagging a problem)
25
+ - The action has real consequences — filing something, creating a record, sending a message
26
+ - You have enough context to act without further questions
27
+
28
+ Do NOT use it for: vague suggestions, things that need more info, routine read-only queries. If you can just do it without consequences, do it. If it has teeth, propose it.
29
+
30
+ When proposed actions appear in your context at session start, lead with them. Parse {possessive} approval response ("approve 1 and 3", "do both", "skip 2") and execute immediately, then call resolve_agenda_item for each.
31
+
32
+ {channel_note}`;
33
+
34
+ export default persona;