aegiscode 3.1.7 → 3.1.10
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/README.md +23 -15
- package/dist/main.js +555 -556
- package/package.json +4 -2
- package/src/agent/Agent.ts +903 -0
- package/src/agent/SimpleAgent.ts +48 -0
- package/src/agent/index.ts +54 -0
- package/src/agent/orchestrator/AppBuilder.ts +443 -0
- package/src/agent/orchestrator/CouncilAgent.ts +310 -0
- package/src/agent/orchestrator/DiscussionRoom.ts +462 -0
- package/src/agent/orchestrator/OrchestratorAgent.ts +637 -0
- package/src/agent/orchestrator/index.ts +38 -0
- package/src/agent/orchestrator/utils.ts +397 -0
- package/src/agent/pricing.ts +115 -0
- package/src/agent/router.ts +74 -0
- package/src/agent/routerStats.ts +121 -0
- package/src/agent/types.ts +318 -0
- package/src/auth/login.ts +383 -0
- package/src/cli/config.ts +189 -0
- package/src/cli/index.ts +17 -0
- package/src/cli/middleware.ts +119 -0
- package/src/cli/types.ts +75 -0
- package/src/config/ConfigManager.ts +587 -0
- package/src/config/index.ts +7 -0
- package/src/config/types.ts +584 -0
- package/src/context/CompactionService.ts +300 -0
- package/src/context/ContextManager.ts +450 -0
- package/src/context/FileAnalyzer.ts +267 -0
- package/src/context/TokenCounter.ts +265 -0
- package/src/context/index.ts +27 -0
- package/src/context/storage/CacheStore.ts +176 -0
- package/src/context/storage/JSONLStore.ts +201 -0
- package/src/context/storage/MemoryStore.ts +205 -0
- package/src/context/storage/PersistentStore.ts +327 -0
- package/src/context/storage/index.ts +9 -0
- package/src/context/storage/pathUtils.ts +114 -0
- package/src/context/test.ts +309 -0
- package/src/context/types.ts +268 -0
- package/src/hooks/HookExecutor.ts +434 -0
- package/src/hooks/HookManager.ts +596 -0
- package/src/hooks/HookService.ts +269 -0
- package/src/hooks/Matcher.ts +157 -0
- package/src/hooks/index.ts +63 -0
- package/src/hooks/types.ts +424 -0
- package/src/main.tsx +596 -0
- package/src/mcp/HealthMonitor.ts +150 -0
- package/src/mcp/McpClient.ts +491 -0
- package/src/mcp/McpRegistry.ts +321 -0
- package/src/mcp/createMcpTool.ts +251 -0
- package/src/mcp/index.ts +15 -0
- package/src/mcp/server.ts +334 -0
- package/src/mcp/test-server.ts +88 -0
- package/src/mcp/test.ts +372 -0
- package/src/mcp/types.ts +247 -0
- package/src/memory/AgentMemoryBus.ts +432 -0
- package/src/memory/CloudSync.ts +99 -0
- package/src/memory/DriveSync.ts +106 -0
- package/src/memory/SharedMemory.ts +951 -0
- package/src/memory/index.ts +14 -0
- package/src/memory/machineFingerprint.ts +40 -0
- package/src/orchestrator/SubAgentMetadata.ts +136 -0
- package/src/prompts/builder.ts +213 -0
- package/src/prompts/default.ts +144 -0
- package/src/prompts/index.ts +16 -0
- package/src/prompts/plan.ts +64 -0
- package/src/prompts/test.ts +78 -0
- package/src/services/AnthropicChatService.ts +341 -0
- package/src/services/ChatService.ts +347 -0
- package/src/services/ClaudeCliChatService.ts +256 -0
- package/src/services/CloudSync.ts +168 -0
- package/src/services/CostLedger.ts +211 -0
- package/src/services/Heartbeat.ts +135 -0
- package/src/services/LearningCollector.ts +291 -0
- package/src/services/OllamaInstaller.ts +342 -0
- package/src/services/VersionChecker.ts +445 -0
- package/src/services/index.ts +57 -0
- package/src/services/streaming/OpenAIEventAdapter.ts +126 -0
- package/src/services/streaming/RenderingProfile.ts +90 -0
- package/src/services/streaming/StreamEventParser.ts +181 -0
- package/src/services/streaming/ThrottledRenderer.ts +139 -0
- package/src/services/streaming/TranscriptBuffer.ts +574 -0
- package/src/services/streaming/eventStatusMap.ts +52 -0
- package/src/services/streaming/index.ts +46 -0
- package/src/services/streaming/renderFormatting.ts +79 -0
- package/src/services/streaming/types.ts +234 -0
- package/src/skills/SkillLoader.ts +126 -0
- package/src/skills/SkillRegistry.ts +366 -0
- package/src/skills/index.ts +48 -0
- package/src/skills/types.ts +146 -0
- package/src/slash-commands/billing.ts +70 -0
- package/src/slash-commands/build.ts +413 -0
- package/src/slash-commands/builtinCommands.ts +2733 -0
- package/src/slash-commands/clone.ts +242 -0
- package/src/slash-commands/council.ts +125 -0
- package/src/slash-commands/custom/CustomCommandExecutor.ts +143 -0
- package/src/slash-commands/custom/CustomCommandLoader.ts +251 -0
- package/src/slash-commands/custom/CustomCommandRegistry.ts +144 -0
- package/src/slash-commands/custom/index.ts +7 -0
- package/src/slash-commands/debate.ts +254 -0
- package/src/slash-commands/gmail.ts +105 -0
- package/src/slash-commands/index.ts +388 -0
- package/src/slash-commands/mcpCommand.ts +205 -0
- package/src/slash-commands/types.ts +201 -0
- package/src/store/index.ts +76 -0
- package/src/store/selectors.ts +246 -0
- package/src/store/slices/appSlice.ts +205 -0
- package/src/store/slices/commandSlice.ts +115 -0
- package/src/store/slices/configSlice.ts +46 -0
- package/src/store/slices/focusSlice.ts +64 -0
- package/src/store/slices/index.ts +9 -0
- package/src/store/slices/sessionSlice.ts +424 -0
- package/src/store/streaming-buffer.ts +425 -0
- package/src/store/test.ts +296 -0
- package/src/store/types.ts +274 -0
- package/src/store/vanilla.ts +186 -0
- package/src/tools/builtin/bash.ts +236 -0
- package/src/tools/builtin/council.ts +105 -0
- package/src/tools/builtin/edit.ts +213 -0
- package/src/tools/builtin/glob.ts +136 -0
- package/src/tools/builtin/grep.ts +263 -0
- package/src/tools/builtin/index.ts +61 -0
- package/src/tools/builtin/memory.ts +66 -0
- package/src/tools/builtin/read.ts +168 -0
- package/src/tools/builtin/skill.ts +97 -0
- package/src/tools/builtin/snapshot.ts +40 -0
- package/src/tools/builtin/task.ts +106 -0
- package/src/tools/builtin/write.ts +134 -0
- package/src/tools/createTool.ts +221 -0
- package/src/tools/execution/ExecutionPipeline.ts +263 -0
- package/src/tools/execution/index.ts +40 -0
- package/src/tools/execution/stages/CacheStage.ts +131 -0
- package/src/tools/execution/stages/ConfirmationStage.ts +203 -0
- package/src/tools/execution/stages/DiscoveryStage.ts +46 -0
- package/src/tools/execution/stages/ExecutionStage.ts +48 -0
- package/src/tools/execution/stages/FormattingStage.ts +44 -0
- package/src/tools/execution/stages/HookStage.ts +72 -0
- package/src/tools/execution/stages/PermissionStage.ts +287 -0
- package/src/tools/execution/stages/PostHookStage.ts +71 -0
- package/src/tools/execution/stages/index.ts +12 -0
- package/src/tools/execution/test.ts +266 -0
- package/src/tools/execution/types.ts +273 -0
- package/src/tools/index.ts +81 -0
- package/src/tools/registry.ts +304 -0
- package/src/tools/schemas.ts +109 -0
- package/src/tools/test.ts +220 -0
- package/src/tools/types.ts +175 -0
- package/src/tools/validation/PermissionChecker.ts +242 -0
- package/src/tools/validation/SensitiveFileDetector.ts +210 -0
- package/src/tools/validation/index.ts +11 -0
- package/src/ui/App.tsx +166 -0
- package/src/ui/components/AegisInterface.tsx +484 -0
- package/src/ui/components/common/ChatSearch.tsx +150 -0
- package/src/ui/components/common/ErrorBoundary.tsx +82 -0
- package/src/ui/components/common/ExitMessage.tsx +120 -0
- package/src/ui/components/common/LoadingIndicator.tsx +49 -0
- package/src/ui/components/common/index.ts +6 -0
- package/src/ui/components/dialog/ConfirmationPrompt.tsx +208 -0
- package/src/ui/components/dialog/InteractiveSelector.tsx +149 -0
- package/src/ui/components/dialog/SetupWizard.tsx +297 -0
- package/src/ui/components/dialog/UpdatePrompt.tsx +155 -0
- package/src/ui/components/dialog/index.ts +8 -0
- package/src/ui/components/index.ts +28 -0
- package/src/ui/components/input/CommandSuggestions.tsx +139 -0
- package/src/ui/components/input/CustomTextInput.tsx +220 -0
- package/src/ui/components/input/InputArea.tsx +361 -0
- package/src/ui/components/input/PromptSuggestions.tsx +66 -0
- package/src/ui/components/input/index.ts +6 -0
- package/src/ui/components/layout/ChatStatusBar.tsx +90 -0
- package/src/ui/components/layout/ContextBar.tsx +79 -0
- package/src/ui/components/layout/MessageArea.tsx +96 -0
- package/src/ui/components/layout/MessageList.tsx +647 -0
- package/src/ui/components/layout/MessageSeparator.tsx +26 -0
- package/src/ui/components/layout/WelcomeMessage.tsx +93 -0
- package/src/ui/components/layout/index.ts +7 -0
- package/src/ui/components/markdown/CodeHighlighter.tsx +292 -0
- package/src/ui/components/markdown/MessageRenderer.tsx +1211 -0
- package/src/ui/components/markdown/index.ts +8 -0
- package/src/ui/components/markdown/parser.ts +336 -0
- package/src/ui/components/markdown/types.ts +66 -0
- package/src/ui/focus/FocusManager.ts +137 -0
- package/src/ui/focus/index.ts +13 -0
- package/src/ui/focus/types.ts +54 -0
- package/src/ui/focus/useFocus.ts +75 -0
- package/src/ui/hooks/index.ts +11 -0
- package/src/ui/hooks/useAgent.ts +284 -0
- package/src/ui/hooks/useCommandHistory.ts +87 -0
- package/src/ui/hooks/useCommandProcessor.ts +443 -0
- package/src/ui/hooks/useConfirmation.ts +99 -0
- package/src/ui/hooks/useCtrlCHandler.ts +100 -0
- package/src/ui/hooks/useInputBuffer.ts +122 -0
- package/src/ui/hooks/useTerminalSize.ts +68 -0
- package/src/ui/hooks/useTerminalWidth.ts +5 -0
- package/src/ui/hooks/useWindowedList.ts +118 -0
- package/src/ui/render-debugger.ts +621 -0
- package/src/ui/test.ts +189 -0
- package/src/ui/themes/ThemeManager.ts +332 -0
- package/src/ui/themes/aegisTheme.ts +87 -0
- package/src/ui/themes/darkTheme.ts +87 -0
- package/src/ui/themes/defaultTheme.ts +85 -0
- package/src/ui/themes/index.ts +10 -0
- package/src/ui/themes/lightTheme.ts +89 -0
- package/src/ui/themes/popularThemes.ts +187 -0
- package/src/ui/themes/types.ts +130 -0
- package/src/utils/clipboard.ts +48 -0
- package/src/utils/debug.ts +43 -0
- package/src/utils/environment.ts +68 -0
- package/src/utils/index.ts +10 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentMemoryBus — Shared memory bus for multi-agent communication
|
|
3
|
+
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* Allows multiple agents (Orchestrator sub-agents, Council members)
|
|
6
|
+
* to read/write shared contextual memory in real-time.
|
|
7
|
+
*
|
|
8
|
+
* Design:
|
|
9
|
+
* - In-memory fast cache for active session + SQLite persistence
|
|
10
|
+
* - Channels: each agent publishes to typed channels
|
|
11
|
+
* - Sub-agents automatically get relevant context from prior agents
|
|
12
|
+
* - Supports TTL-based expiration, importance scoring, cross-referencing
|
|
13
|
+
*
|
|
14
|
+
* Integration with existing SharedMemory:
|
|
15
|
+
* AgentMemoryBus uses SharedMemory as its persistent backing store,
|
|
16
|
+
* adding a lightweight pub/sub layer on top for multi-agent coordination.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { sharedMemory, type MemoryEntry } from './SharedMemory.js';
|
|
20
|
+
import { v4 as uuid } from 'uuid';
|
|
21
|
+
import { agentDebug } from '../utils/debug.js';
|
|
22
|
+
|
|
23
|
+
// ── Types ────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export type AgentChannel =
|
|
26
|
+
| 'decision' // Agent decisions / conclusions
|
|
27
|
+
| 'fact' // Facts discovered during work
|
|
28
|
+
| 'context' // Contextual information (files, code, env)
|
|
29
|
+
| 'intermediate' // Intermediate results
|
|
30
|
+
| 'error' // Errors encountered
|
|
31
|
+
| 'question' // Questions to other agents
|
|
32
|
+
| 'suggestion'; // Suggestions for next steps
|
|
33
|
+
|
|
34
|
+
export interface AgentMemoryMessage {
|
|
35
|
+
id: string;
|
|
36
|
+
channel: AgentChannel;
|
|
37
|
+
sourceAgent: string;
|
|
38
|
+
targetAgent?: string; // If specified, only this agent should consume
|
|
39
|
+
sessionId: string;
|
|
40
|
+
content: string;
|
|
41
|
+
timestamp: string;
|
|
42
|
+
importance: number;
|
|
43
|
+
references?: string[]; // IDs of related messages
|
|
44
|
+
ttl?: number; // Seconds until auto-expire (0 = forever)
|
|
45
|
+
tags?: string[];
|
|
46
|
+
metadata?: Record<string, unknown>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface AgentMemoryQuery {
|
|
50
|
+
channels?: AgentChannel[];
|
|
51
|
+
sourceAgent?: string;
|
|
52
|
+
targetAgent?: string;
|
|
53
|
+
sessionId?: string;
|
|
54
|
+
query?: string; // Free-text search
|
|
55
|
+
limit?: number;
|
|
56
|
+
minImportance?: number;
|
|
57
|
+
maxAgeSeconds?: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── In-memory fast cache ─────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
interface CacheEntry {
|
|
63
|
+
msg: AgentMemoryMessage;
|
|
64
|
+
expiresAt: number; // 0 = never
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class MemoryCache {
|
|
68
|
+
private cache = new Map<string, CacheEntry>();
|
|
69
|
+
private byChannel = new Map<AgentChannel, Set<string>>();
|
|
70
|
+
private bySession = new Map<string, Set<string>>();
|
|
71
|
+
private bySource = new Map<string, Set<string>>();
|
|
72
|
+
|
|
73
|
+
put(msg: AgentMemoryMessage): void {
|
|
74
|
+
const expiresAt = msg.ttl && msg.ttl > 0 ? Date.now() + msg.ttl * 1000 : 0;
|
|
75
|
+
this.cache.set(msg.id, { msg, expiresAt });
|
|
76
|
+
|
|
77
|
+
if (!this.byChannel.has(msg.channel)) this.byChannel.set(msg.channel, new Set());
|
|
78
|
+
this.byChannel.get(msg.channel)!.add(msg.id);
|
|
79
|
+
|
|
80
|
+
if (!this.bySession.has(msg.sessionId)) this.bySession.set(msg.sessionId, new Set());
|
|
81
|
+
this.bySession.get(msg.sessionId)!.add(msg.id);
|
|
82
|
+
|
|
83
|
+
if (!this.bySource.has(msg.sourceAgent)) this.bySource.set(msg.sourceAgent, new Set());
|
|
84
|
+
this.bySource.get(msg.sourceAgent)!.add(msg.id);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get(id: string): AgentMemoryMessage | null {
|
|
88
|
+
const entry = this.cache.get(id);
|
|
89
|
+
if (!entry) return null;
|
|
90
|
+
if (entry.expiresAt > 0 && Date.now() > entry.expiresAt) {
|
|
91
|
+
this.cache.delete(id);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return entry.msg;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
query(q: AgentMemoryQuery): AgentMemoryMessage[] {
|
|
98
|
+
this.evictExpired();
|
|
99
|
+
|
|
100
|
+
let candidateIds = new Set(this.cache.keys());
|
|
101
|
+
|
|
102
|
+
if (q.channels && q.channels.length > 0) {
|
|
103
|
+
const channelIds = new Set<string>();
|
|
104
|
+
for (const ch of q.channels) {
|
|
105
|
+
const ids = this.byChannel.get(ch);
|
|
106
|
+
if (ids) for (const id of ids) channelIds.add(id);
|
|
107
|
+
}
|
|
108
|
+
candidateIds = intersect(candidateIds, channelIds);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (q.sessionId) {
|
|
112
|
+
const sessionIds = this.bySession.get(q.sessionId);
|
|
113
|
+
candidateIds = sessionIds
|
|
114
|
+
? intersect(candidateIds, sessionIds)
|
|
115
|
+
: new Set();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (q.sourceAgent) {
|
|
119
|
+
const sourceIds = this.bySource.get(q.sourceAgent);
|
|
120
|
+
candidateIds = sourceIds
|
|
121
|
+
? intersect(candidateIds, sourceIds)
|
|
122
|
+
: new Set();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (q.targetAgent) {
|
|
126
|
+
const filtered = new Set<string>();
|
|
127
|
+
for (const id of candidateIds) {
|
|
128
|
+
const msg = this.cache.get(id)?.msg;
|
|
129
|
+
if (msg && (!msg.targetAgent || msg.targetAgent === q.targetAgent)) {
|
|
130
|
+
filtered.add(id);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
candidateIds = filtered;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let results = Array.from(candidateIds)
|
|
137
|
+
.map(id => this.cache.get(id)!.msg)
|
|
138
|
+
.filter(msg => {
|
|
139
|
+
if (q.minImportance !== undefined && msg.importance < q.minImportance) return false;
|
|
140
|
+
if (q.maxAgeSeconds !== undefined) {
|
|
141
|
+
const age = (Date.now() - new Date(msg.timestamp).getTime()) / 1000;
|
|
142
|
+
if (age > q.maxAgeSeconds) return false;
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Free-text filter
|
|
148
|
+
if (q.query) {
|
|
149
|
+
const lower = q.query.toLowerCase();
|
|
150
|
+
results = results.filter(m =>
|
|
151
|
+
m.content.toLowerCase().includes(lower) ||
|
|
152
|
+
(m.tags || []).some(t => t.toLowerCase().includes(lower))
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Sort by importance desc, then timestamp desc
|
|
157
|
+
results.sort((a, b) => {
|
|
158
|
+
const imp = b.importance - a.importance;
|
|
159
|
+
if (imp !== 0) return imp;
|
|
160
|
+
return b.timestamp.localeCompare(a.timestamp);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return results.slice(0, q.limit || 20);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
clearSession(sessionId: string): void {
|
|
167
|
+
const ids = this.bySession.get(sessionId);
|
|
168
|
+
if (!ids) return;
|
|
169
|
+
for (const id of ids) {
|
|
170
|
+
const msg = this.cache.get(id)?.msg;
|
|
171
|
+
if (msg) {
|
|
172
|
+
this.byChannel.get(msg.channel)?.delete(id);
|
|
173
|
+
this.bySource.get(msg.sourceAgent)?.delete(id);
|
|
174
|
+
this.cache.delete(id);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
this.bySession.delete(sessionId);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
size(): number {
|
|
181
|
+
this.evictExpired();
|
|
182
|
+
return this.cache.size;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private evictExpired(): void {
|
|
186
|
+
const now = Date.now();
|
|
187
|
+
for (const [id, entry] of this.cache) {
|
|
188
|
+
if (entry.expiresAt > 0 && now > entry.expiresAt) {
|
|
189
|
+
const msg = entry.msg;
|
|
190
|
+
this.byChannel.get(msg.channel)?.delete(id);
|
|
191
|
+
this.bySession.get(msg.sessionId)?.delete(id);
|
|
192
|
+
this.bySource.get(msg.sourceAgent)?.delete(id);
|
|
193
|
+
this.cache.delete(id);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function intersect(a: Set<string>, b: Set<string>): Set<string> {
|
|
200
|
+
const result = new Set<string>();
|
|
201
|
+
for (const item of a) {
|
|
202
|
+
if (b.has(item)) result.add(item);
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ── AgentMemoryBus ───────────────────────────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
export class AgentMemoryBus {
|
|
210
|
+
private cache: MemoryCache;
|
|
211
|
+
private subscribers: Map<AgentChannel, Set<(msg: AgentMemoryMessage) => void>> = new Map();
|
|
212
|
+
private globalSubscribers: Set<(msg: AgentMemoryMessage) => void> = new Set();
|
|
213
|
+
private persistenceEnabled: boolean;
|
|
214
|
+
|
|
215
|
+
constructor(persistToSharedMemory = true) {
|
|
216
|
+
this.cache = new MemoryCache();
|
|
217
|
+
this.persistenceEnabled = persistToSharedMemory;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Publish a message to the shared memory bus.
|
|
222
|
+
* Stored in fast cache + optionally persisted to SharedMemory SQLite.
|
|
223
|
+
*/
|
|
224
|
+
async publish(msg: Omit<AgentMemoryMessage, 'id' | 'timestamp'>): Promise<AgentMemoryMessage> {
|
|
225
|
+
const full: AgentMemoryMessage = {
|
|
226
|
+
...msg,
|
|
227
|
+
id: uuid(),
|
|
228
|
+
timestamp: new Date().toISOString(),
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// 1. Fast in-memory cache
|
|
232
|
+
this.cache.put(full);
|
|
233
|
+
|
|
234
|
+
// 2. Notify subscribers
|
|
235
|
+
this.notifySubscribers(full);
|
|
236
|
+
|
|
237
|
+
// 3. Persist to SharedMemory SQLite (if enabled)
|
|
238
|
+
if (this.persistenceEnabled) {
|
|
239
|
+
const channelTag = `agent:${msg.channel}`;
|
|
240
|
+
const sourceTag = `agent:${msg.sourceAgent}`;
|
|
241
|
+
const tags = [...(msg.tags || []), channelTag, sourceTag];
|
|
242
|
+
if (msg.targetAgent) tags.push(`target:${msg.targetAgent}`);
|
|
243
|
+
|
|
244
|
+
const importance = msg.importance;
|
|
245
|
+
|
|
246
|
+
// Store as a memory entry so it appears in semantic search
|
|
247
|
+
sharedMemory.add(
|
|
248
|
+
`[AgentBus:${msg.channel}] [${msg.sourceAgent}]${msg.targetAgent ? ` → ${msg.targetAgent}` : ''}: ${msg.content}`,
|
|
249
|
+
`agent-bus-${msg.channel}`,
|
|
250
|
+
msg.sessionId,
|
|
251
|
+
tags,
|
|
252
|
+
'assistant',
|
|
253
|
+
false, // defer commit for batching
|
|
254
|
+
).catch(() => {});
|
|
255
|
+
|
|
256
|
+
// High-importance items get stored with extra metadata
|
|
257
|
+
if (importance >= 0.7) {
|
|
258
|
+
sharedMemory.add(
|
|
259
|
+
`[AgentBus:${msg.channel}] [DECISION] ${msg.sourceAgent}: ${msg.content}`,
|
|
260
|
+
'agent-bus-decision',
|
|
261
|
+
msg.sessionId,
|
|
262
|
+
[...tags, 'high-importance', 'decision'],
|
|
263
|
+
'assistant',
|
|
264
|
+
true,
|
|
265
|
+
).catch(() => {});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return full;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Query messages from the shared memory bus (cache + persistent)
|
|
274
|
+
*/
|
|
275
|
+
async query(q: AgentMemoryQuery): Promise<AgentMemoryMessage[]> {
|
|
276
|
+
// 1. Get from fast cache
|
|
277
|
+
const cached = this.cache.query(q);
|
|
278
|
+
|
|
279
|
+
// 2. If we need more, search SharedMemory
|
|
280
|
+
if (cached.length < (q.limit || 20) && this.persistenceEnabled) {
|
|
281
|
+
const searchQuery = q.query || q.channels?.join(' ') || '';
|
|
282
|
+
const persisted = await sharedMemory.search(
|
|
283
|
+
`AgentBus ${searchQuery}`,
|
|
284
|
+
(q.limit || 20) - cached.length
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
// Convert MemoryEntry → AgentMemoryMessage (best-effort)
|
|
288
|
+
const fromPersisted: AgentMemoryMessage[] = persisted
|
|
289
|
+
.filter(e => e.content.startsWith('[AgentBus'))
|
|
290
|
+
.map(e => this.entryToMessage(e))
|
|
291
|
+
.filter((m): m is AgentMemoryMessage => m !== null);
|
|
292
|
+
|
|
293
|
+
// Merge, deduplicate by ID
|
|
294
|
+
const seen = new Set(cached.map(m => m.id));
|
|
295
|
+
for (const m of fromPersisted) {
|
|
296
|
+
if (!seen.has(m.id)) {
|
|
297
|
+
cached.push(m);
|
|
298
|
+
seen.add(m.id);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return cached;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Subscribe to messages on a specific channel
|
|
308
|
+
*/
|
|
309
|
+
subscribe(channel: AgentChannel, callback: (msg: AgentMemoryMessage) => void): () => void {
|
|
310
|
+
if (!this.subscribers.has(channel)) {
|
|
311
|
+
this.subscribers.set(channel, new Set());
|
|
312
|
+
}
|
|
313
|
+
this.subscribers.get(channel)!.add(callback);
|
|
314
|
+
return () => this.subscribers.get(channel)?.delete(callback);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Subscribe to all channels
|
|
319
|
+
*/
|
|
320
|
+
subscribeAll(callback: (msg: AgentMemoryMessage) => void): () => void {
|
|
321
|
+
this.globalSubscribers.add(callback);
|
|
322
|
+
return () => this.globalSubscribers.delete(callback);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Get context for an agent: relevant messages from other agents in the same session
|
|
327
|
+
*/
|
|
328
|
+
async getContextForAgent(
|
|
329
|
+
agentName: string,
|
|
330
|
+
sessionId: string,
|
|
331
|
+
limit = 10,
|
|
332
|
+
maxAgeSeconds = 300, // last 5 minutes by default
|
|
333
|
+
): Promise<string> {
|
|
334
|
+
const relevant = await this.query({
|
|
335
|
+
sessionId,
|
|
336
|
+
limit,
|
|
337
|
+
maxAgeSeconds,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Filter out messages from the requesting agent
|
|
341
|
+
const others = relevant.filter(m => m.sourceAgent !== agentName);
|
|
342
|
+
|
|
343
|
+
if (others.length === 0) return '';
|
|
344
|
+
|
|
345
|
+
const lines: string[] = [
|
|
346
|
+
'--- AGENT MEMORY (shared context from other agents) ---',
|
|
347
|
+
];
|
|
348
|
+
|
|
349
|
+
for (const m of others) {
|
|
350
|
+
const target = m.targetAgent ? ` → ${m.targetAgent}` : '';
|
|
351
|
+
lines.push(
|
|
352
|
+
`[${m.channel}] ${m.sourceAgent}${target} (${m.timestamp.slice(11, 19)}): ${m.content.slice(0, 300)}`
|
|
353
|
+
);
|
|
354
|
+
if (m.importance >= 0.8) lines.push(` ⭐ Important: ${m.content.slice(0, 200)}`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
lines.push('--- END AGENT MEMORY ---');
|
|
358
|
+
return lines.join('\n');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Clear all messages for a session
|
|
363
|
+
*/
|
|
364
|
+
clearSession(sessionId: string): void {
|
|
365
|
+
this.cache.clearSession(sessionId);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Get stats
|
|
370
|
+
*/
|
|
371
|
+
stats(): { cacheSize: number; subscribers: number; channels: number } {
|
|
372
|
+
let subCount = this.globalSubscribers.size;
|
|
373
|
+
for (const subs of this.subscribers.values()) {
|
|
374
|
+
subCount += subs.size;
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
cacheSize: this.cache.size(),
|
|
378
|
+
subscribers: subCount,
|
|
379
|
+
channels: this.subscribers.size,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ── Private ──────────────────────────────────────────────────────────────
|
|
384
|
+
|
|
385
|
+
private notifySubscribers(msg: AgentMemoryMessage): void {
|
|
386
|
+
// Channel-specific
|
|
387
|
+
const channelSubs = this.subscribers.get(msg.channel);
|
|
388
|
+
if (channelSubs) {
|
|
389
|
+
for (const cb of channelSubs) {
|
|
390
|
+
try { cb(msg); } catch { /* subscriber error */ }
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// Global
|
|
394
|
+
for (const cb of this.globalSubscribers) {
|
|
395
|
+
try { cb(msg); } catch { /* subscriber error */ }
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
private entryToMessage(entry: MemoryEntry): AgentMemoryMessage | null {
|
|
400
|
+
try {
|
|
401
|
+
const content = entry.content;
|
|
402
|
+
// Two stored formats:
|
|
403
|
+
// normal: [AgentBus:channel] [sourceAgent] → targetAgent?: body
|
|
404
|
+
// decision: [AgentBus:channel] [DECISION] sourceAgent: body
|
|
405
|
+
const channelMatch = content.match(/^\[AgentBus:(\w+)\]/);
|
|
406
|
+
const decisionMatch = content.match(/\[DECISION\] (\w+):/);
|
|
407
|
+
const sourceMatch = decisionMatch ?? content.match(/\] \[(\w+)\]/);
|
|
408
|
+
const targetMatch = content.match(/\] → (\w+):/);
|
|
409
|
+
const contentStart = content.indexOf(': ', content.lastIndexOf(']'));
|
|
410
|
+
|
|
411
|
+
if (!channelMatch || !sourceMatch) return null;
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
id: entry.id,
|
|
415
|
+
channel: channelMatch[1] as AgentChannel,
|
|
416
|
+
sourceAgent: sourceMatch[1],
|
|
417
|
+
targetAgent: targetMatch ? targetMatch[1] : undefined,
|
|
418
|
+
sessionId: entry.session,
|
|
419
|
+
content: contentStart > 0 ? content.slice(contentStart + 2) : content,
|
|
420
|
+
timestamp: entry.timestamp,
|
|
421
|
+
importance: entry.importance || 0.5,
|
|
422
|
+
tags: entry.tags,
|
|
423
|
+
};
|
|
424
|
+
} catch {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ── Singleton ────────────────────────────────────────────────────────────────
|
|
431
|
+
|
|
432
|
+
export const agentMemoryBus = new AgentMemoryBus(true);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AEGIS Memory Cloud Sync
|
|
3
|
+
*
|
|
4
|
+
* Pushes/pulls SharedMemory entries through aegiscloud.org so memory is
|
|
5
|
+
* shared across devices (CLI + mobile) for subscribed users. Best-effort:
|
|
6
|
+
* failures are swallowed, matching DriveSync's "silent fail — optional"
|
|
7
|
+
* pattern. Never blocks the main session — callers fire-and-forget.
|
|
8
|
+
*/
|
|
9
|
+
import type { MemoryEntry } from './SharedMemory.js';
|
|
10
|
+
|
|
11
|
+
const MEMORY_API_BASE = process.env.AEGIS_MEMORY_API_BASE || 'https://aegiscloud.org/api/memory';
|
|
12
|
+
|
|
13
|
+
// Embeddings are 384 floats — as JSON that's ~7KB per entry. The server only
|
|
14
|
+
// ever does keyword (LIKE) search/dashboard listing on memory_entries, never
|
|
15
|
+
// vector similarity, so transmitting them bloats every push for zero server-side
|
|
16
|
+
// benefit. A bulk upload of a few thousand entries turned tens of MB of pure
|
|
17
|
+
// embedding JSON across many sequential requests, which is what was actually
|
|
18
|
+
// timing out — not a network problem. Dropping it: a device that pulls this
|
|
19
|
+
// entry down just lacks a precomputed vector locally (falls back to keyword
|
|
20
|
+
// search for it, same as any entry whose embedder failed at add()-time).
|
|
21
|
+
function stripEmbedding(entries: MemoryEntry[]): Omit<MemoryEntry, 'embedding'>[] {
|
|
22
|
+
return entries.map(({ embedding, ...rest }) => rest);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function pushEntries(entries: MemoryEntry[], apiKey: string): Promise<void> {
|
|
26
|
+
if (entries.length === 0) return;
|
|
27
|
+
try {
|
|
28
|
+
await fetch(`${MEMORY_API_BASE}/save`, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
|
|
31
|
+
body: JSON.stringify({ entries: stripEmbedding(entries) }),
|
|
32
|
+
});
|
|
33
|
+
} catch {
|
|
34
|
+
// silent fail — sync is optional, local memory already has the entry
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Like pushEntries, but reports how many entries the server actually saved (for bulk uploads). */
|
|
39
|
+
export async function pushBatch(entries: MemoryEntry[], apiKey: string): Promise<number> {
|
|
40
|
+
if (entries.length === 0) return 0;
|
|
41
|
+
try {
|
|
42
|
+
// A bulk upload can be many sequential batches — one slow/hung request
|
|
43
|
+
// without its own timeout would stall the whole pushAll() loop indefinitely,
|
|
44
|
+
// regardless of any timeout the caller process is given from outside.
|
|
45
|
+
const controller = new AbortController();
|
|
46
|
+
const timer = setTimeout(() => controller.abort(), 20_000);
|
|
47
|
+
let res: Response;
|
|
48
|
+
try {
|
|
49
|
+
res = await fetch(`${MEMORY_API_BASE}/save`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
|
|
52
|
+
body: JSON.stringify({ entries: stripEmbedding(entries) }),
|
|
53
|
+
signal: controller.signal,
|
|
54
|
+
});
|
|
55
|
+
} finally {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
}
|
|
58
|
+
if (!res.ok) return 0;
|
|
59
|
+
const data = await res.json() as { saved?: number };
|
|
60
|
+
return data.saved ?? 0;
|
|
61
|
+
} catch {
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function pullSince(since: string | null, apiKey: string): Promise<MemoryEntry[]> {
|
|
67
|
+
try {
|
|
68
|
+
const res = await fetch(`${MEMORY_API_BASE}/pull`, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
|
|
71
|
+
body: JSON.stringify(since ? { since } : {}),
|
|
72
|
+
});
|
|
73
|
+
if (!res.ok) return [];
|
|
74
|
+
const data = await res.json() as { entries?: MemoryEntry[] };
|
|
75
|
+
return data.entries ?? [];
|
|
76
|
+
} catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Keyword search against the caller's synced entries (catches entries written on
|
|
83
|
+
* another device since the last `pullSince`). Caller is responsible for merging
|
|
84
|
+
* with local results — this never throws, just returns [] on any failure.
|
|
85
|
+
*/
|
|
86
|
+
export async function searchCloud(query: string, limit: number, apiKey: string): Promise<MemoryEntry[]> {
|
|
87
|
+
try {
|
|
88
|
+
const res = await fetch(`${MEMORY_API_BASE}/search`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
|
|
91
|
+
body: JSON.stringify({ query, limit }),
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) return [];
|
|
94
|
+
const data = await res.json() as { entries?: MemoryEntry[] };
|
|
95
|
+
return data.entries ?? [];
|
|
96
|
+
} catch {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AEGIS Drive Sync
|
|
3
|
+
* Uploads conversation sessions to user's Google Drive
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as os from 'os';
|
|
8
|
+
import * as https from 'https';
|
|
9
|
+
|
|
10
|
+
const CONFIG_PATH = path.join(os.homedir(), '.aegiscode', 'config.json');
|
|
11
|
+
const SESSIONS_DIR = path.join(os.homedir(), '.aegis', 'projects');
|
|
12
|
+
|
|
13
|
+
function getConfig() {
|
|
14
|
+
try { return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); }
|
|
15
|
+
catch { return {}; }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function getDriveToken(): Promise<string | null> {
|
|
19
|
+
const cfg = getConfig();
|
|
20
|
+
const apiKey = cfg?.aegiscloud?.api_key || process.env.AEGISCLOUD_API_KEY;
|
|
21
|
+
if (!apiKey) return null;
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
const req = https.request({
|
|
24
|
+
hostname: 'aegiscloud.org',
|
|
25
|
+
path: '/api/drive/token',
|
|
26
|
+
method: 'GET',
|
|
27
|
+
headers: { 'X-API-Key': apiKey }
|
|
28
|
+
}, res => {
|
|
29
|
+
let data = '';
|
|
30
|
+
res.on('data', c => data += c);
|
|
31
|
+
res.on('end', () => {
|
|
32
|
+
try {
|
|
33
|
+
const d = JSON.parse(data);
|
|
34
|
+
resolve(d.connected ? d.access_token : null);
|
|
35
|
+
} catch { resolve(null); }
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
req.on('error', () => resolve(null));
|
|
39
|
+
req.end();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async function uploadToDrive(token: string, filename: string, content: string): Promise<void> {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const boundary = '-------aegis_boundary';
|
|
47
|
+
const metadata = JSON.stringify({ name: filename, parents: ['root'] });
|
|
48
|
+
const body = [
|
|
49
|
+
`--${boundary}`,
|
|
50
|
+
'Content-Type: application/json; charset=UTF-8',
|
|
51
|
+
'',
|
|
52
|
+
metadata,
|
|
53
|
+
`--${boundary}`,
|
|
54
|
+
'Content-Type: text/plain',
|
|
55
|
+
'',
|
|
56
|
+
content,
|
|
57
|
+
`--${boundary}--`,
|
|
58
|
+
].join('\r\n');
|
|
59
|
+
|
|
60
|
+
const req = https.request({
|
|
61
|
+
hostname: 'www.googleapis.com',
|
|
62
|
+
path: '/upload/drive/v3/files?uploadType=multipart',
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: {
|
|
65
|
+
'Authorization': `Bearer ${token}`,
|
|
66
|
+
'Content-Type': `multipart/related; boundary=${boundary}`,
|
|
67
|
+
'Content-Length': Buffer.byteLength(body),
|
|
68
|
+
}
|
|
69
|
+
}, res => {
|
|
70
|
+
let data = '';
|
|
71
|
+
res.on('data', c => data += c);
|
|
72
|
+
res.on('end', () => {
|
|
73
|
+
if (res.statusCode === 200 || res.statusCode === 201) resolve();
|
|
74
|
+
else reject(new Error(`Drive upload failed: ${res.statusCode}`));
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
req.on('error', reject);
|
|
78
|
+
req.write(body);
|
|
79
|
+
req.end();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function syncSessionToDrive(sessionId: string): Promise<void> {
|
|
84
|
+
const token = await getDriveToken();
|
|
85
|
+
if (!token) return;
|
|
86
|
+
|
|
87
|
+
// Find session file
|
|
88
|
+
const projects = fs.readdirSync(SESSIONS_DIR).filter(d =>
|
|
89
|
+
fs.statSync(path.join(SESSIONS_DIR, d)).isDirectory()
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
for (const project of projects) {
|
|
93
|
+
const sessionFile = path.join(SESSIONS_DIR, project, `${sessionId}.jsonl`);
|
|
94
|
+
if (fs.existsSync(sessionFile)) {
|
|
95
|
+
const content = fs.readFileSync(sessionFile, 'utf8');
|
|
96
|
+
const filename = `AEGIS/${project}/${sessionId}.jsonl`;
|
|
97
|
+
try {
|
|
98
|
+
await uploadToDrive(token, filename, content);
|
|
99
|
+
console.log(`\x1b[38;2;68;64;90m[Drive] Synced ${sessionId}\x1b[0m`);
|
|
100
|
+
} catch(e) {
|
|
101
|
+
// Silent fail — Drive sync is optional
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|