anorion 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 (53) hide show
  1. package/README.md +87 -0
  2. package/agents/001.yaml +32 -0
  3. package/agents/example.yaml +6 -0
  4. package/bin/anorion.js +8093 -0
  5. package/package.json +72 -0
  6. package/scripts/cli.ts +182 -0
  7. package/scripts/postinstall.js +6 -0
  8. package/scripts/setup.ts +255 -0
  9. package/src/agents/pipeline.ts +231 -0
  10. package/src/agents/registry.ts +153 -0
  11. package/src/agents/runtime.ts +593 -0
  12. package/src/agents/session.ts +338 -0
  13. package/src/agents/subagent.ts +185 -0
  14. package/src/bridge/client.ts +221 -0
  15. package/src/bridge/federator.ts +221 -0
  16. package/src/bridge/protocol.ts +88 -0
  17. package/src/bridge/server.ts +221 -0
  18. package/src/channels/base.ts +43 -0
  19. package/src/channels/router.ts +122 -0
  20. package/src/channels/telegram.ts +592 -0
  21. package/src/channels/webhook.ts +143 -0
  22. package/src/cli/index.ts +1036 -0
  23. package/src/cli/interactive.ts +26 -0
  24. package/src/gateway/routes-v2.ts +165 -0
  25. package/src/gateway/server.ts +512 -0
  26. package/src/gateway/ws.ts +75 -0
  27. package/src/index.ts +182 -0
  28. package/src/llm/provider.ts +243 -0
  29. package/src/llm/providers.ts +381 -0
  30. package/src/memory/context.ts +125 -0
  31. package/src/memory/store.ts +214 -0
  32. package/src/scheduler/cron.ts +239 -0
  33. package/src/shared/audit.ts +231 -0
  34. package/src/shared/config.ts +129 -0
  35. package/src/shared/db/index.ts +165 -0
  36. package/src/shared/db/prepared.ts +111 -0
  37. package/src/shared/db/schema.ts +84 -0
  38. package/src/shared/events.ts +79 -0
  39. package/src/shared/logger.ts +10 -0
  40. package/src/shared/metrics.ts +190 -0
  41. package/src/shared/rbac.ts +151 -0
  42. package/src/shared/token-budget.ts +157 -0
  43. package/src/shared/types.ts +166 -0
  44. package/src/tools/builtin/echo.ts +19 -0
  45. package/src/tools/builtin/file-read.ts +78 -0
  46. package/src/tools/builtin/file-write.ts +64 -0
  47. package/src/tools/builtin/http-request.ts +63 -0
  48. package/src/tools/builtin/memory.ts +71 -0
  49. package/src/tools/builtin/shell.ts +94 -0
  50. package/src/tools/builtin/web-search.ts +22 -0
  51. package/src/tools/executor.ts +126 -0
  52. package/src/tools/registry.ts +56 -0
  53. package/src/tools/skill-manager.ts +252 -0
@@ -0,0 +1,221 @@
1
+ // Bridge Client — outbound connection to a peer gateway
2
+
3
+ import type {
4
+ BridgeMessage,
5
+ HelloPayload,
6
+ MessageForwardPayload,
7
+ MessageResponsePayload,
8
+ } from './protocol';
9
+ import {
10
+ createBridgeMessage,
11
+ parseBridgeMessage,
12
+ BRIDGE_VERSION,
13
+ } from './protocol';
14
+ import { logger } from '../shared/logger';
15
+ import { agentRegistry } from '../agents/registry';
16
+
17
+ export type ClientState = 'disconnected' | 'connecting' | 'connected';
18
+
19
+ interface PendingRequest {
20
+ resolve: (msg: BridgeMessage) => void;
21
+ reject: (err: Error) => void;
22
+ timer: ReturnType<typeof setTimeout>;
23
+ }
24
+
25
+ export class BridgeClient {
26
+ readonly peerUrl: string;
27
+ readonly secret: string;
28
+ state: ClientState = 'disconnected';
29
+ private ws: WebSocket | null = null;
30
+ private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
31
+ private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
32
+ private backoffMs = 1000;
33
+ private queue: BridgeMessage[] = [];
34
+ private pendingRequests = new Map<string, PendingRequest>();
35
+ private peerGatewayId: string | null = null;
36
+ private onMessage: ((msg: BridgeMessage) => void) | null = null;
37
+ private onClose: (() => void) | null = null;
38
+ private stopped = false;
39
+
40
+ constructor(peerUrl: string, secret: string) {
41
+ this.peerUrl = peerUrl;
42
+ this.secret = secret;
43
+ }
44
+
45
+ get connected(): boolean {
46
+ return this.state === 'connected' && this.ws !== null && this.ws.readyState === WebSocket.OPEN;
47
+ }
48
+
49
+ setHandlers(onMessage: (msg: BridgeMessage) => void, onClose: () => void) {
50
+ this.onMessage = onMessage;
51
+ this.onClose = onClose;
52
+ }
53
+
54
+ async start(ownGatewayId: string): Promise<void> {
55
+ this.stopped = false;
56
+ this.connect(ownGatewayId);
57
+ }
58
+
59
+ stop(): void {
60
+ this.stopped = true;
61
+ this.cleanup();
62
+ // Reject pending
63
+ for (const [id, req] of this.pendingRequests) {
64
+ clearTimeout(req.timer);
65
+ req.reject(new Error('Client stopped'));
66
+ this.pendingRequests.delete(id);
67
+ }
68
+ }
69
+
70
+ send(msg: BridgeMessage): void {
71
+ if (this.connected) {
72
+ this.ws!.send(JSON.stringify(msg));
73
+ } else {
74
+ this.queue.push(msg);
75
+ }
76
+ }
77
+
78
+ async request(msg: BridgeMessage, timeoutMs = 30000): Promise<BridgeMessage> {
79
+ return new Promise((resolve, reject) => {
80
+ const timer = setTimeout(() => {
81
+ this.pendingRequests.delete(msg.messageId);
82
+ reject(new Error('Bridge request timeout'));
83
+ }, timeoutMs);
84
+ this.pendingRequests.set(msg.messageId, { resolve, reject, timer });
85
+ this.send(msg);
86
+ });
87
+ }
88
+
89
+ private connect(ownGatewayId: string): void {
90
+ if (this.stopped) return;
91
+ this.state = 'connecting';
92
+ logger.info({ url: this.peerUrl, backoff: this.backoffMs }, 'Bridge client connecting');
93
+
94
+ try {
95
+ const url = new URL(this.peerUrl);
96
+ url.searchParams.set('secret', this.secret);
97
+
98
+ this.ws = new WebSocket(url.toString());
99
+ this.ws.onopen = () => {
100
+ this.state = 'connected';
101
+ this.backoffMs = 1000;
102
+ logger.info({ url: this.peerUrl }, 'Bridge client connected');
103
+
104
+ // Send hello
105
+ const hello = createBridgeMessage('hello', ownGatewayId, {
106
+ secret: this.secret,
107
+ gatewayId: ownGatewayId,
108
+ agents: agentRegistry.list().map((a) => ({ id: a.id, name: a.name, status: a.state })),
109
+ } as HelloPayload);
110
+ this.ws!.send(JSON.stringify(hello));
111
+
112
+ // Flush queue
113
+ for (const msg of this.queue) {
114
+ this.ws!.send(JSON.stringify(msg));
115
+ }
116
+ this.queue = [];
117
+
118
+ // Start heartbeat
119
+ this.heartbeatTimer = setInterval(() => {
120
+ if (this.connected) {
121
+ this.send(createBridgeMessage('health-ping', ownGatewayId, {}));
122
+ }
123
+ }, 30_000);
124
+ };
125
+
126
+ this.ws.onmessage = (event) => {
127
+ const msg = parseBridgeMessage(event.data as string);
128
+ if (!msg) return;
129
+
130
+ // Handle pending requests
131
+ const pending = this.pendingRequests.get(msg.messageId);
132
+ if (pending) {
133
+ clearTimeout(pending.timer);
134
+ this.pendingRequests.delete(msg.messageId);
135
+ pending.resolve(msg);
136
+ return;
137
+ }
138
+
139
+ if (msg.type === 'hello-ack') {
140
+ this.peerGatewayId = msg.gatewayId;
141
+ logger.info({ peerGatewayId: this.peerGatewayId }, 'Bridge peer acknowledged');
142
+ }
143
+
144
+ if (msg.type === 'message-forward') {
145
+ // Handle incoming message-forward from peer (we are the target)
146
+ void this.handleMessageForward(msg, ownGatewayId);
147
+ return;
148
+ }
149
+
150
+ this.onMessage?.(msg);
151
+ };
152
+
153
+ this.ws.onclose = () => {
154
+ logger.info({ url: this.peerUrl }, 'Bridge client disconnected');
155
+ this.state = 'disconnected';
156
+ this.onClose?.();
157
+ this.scheduleReconnect(ownGatewayId);
158
+ };
159
+
160
+ this.ws.onerror = (err) => {
161
+ logger.error({ url: this.peerUrl, error: String(err) }, 'Bridge client error');
162
+ };
163
+ } catch (err) {
164
+ logger.error({ url: this.peerUrl, error: (err as Error).message }, 'Bridge client connect failed');
165
+ this.scheduleReconnect(ownGatewayId);
166
+ }
167
+ }
168
+
169
+ private async handleMessageForward(msg: BridgeMessage, ownGatewayId: string): Promise<void> {
170
+ const payload = msg.payload as MessageForwardPayload;
171
+ const { sendMessage } = await import('../agents/runtime');
172
+ try {
173
+ const result = await sendMessage({
174
+ agentId: payload.targetAgentId,
175
+ sessionId: payload.sessionId,
176
+ text: payload.text,
177
+ channelId: payload.channelId,
178
+ });
179
+ const response = createBridgeMessage('message-response', ownGatewayId, {
180
+ requestMessageId: msg.messageId,
181
+ content: result.content,
182
+ } as MessageResponsePayload);
183
+ this.send(response);
184
+ } catch (err) {
185
+ const response = createBridgeMessage('message-response', ownGatewayId, {
186
+ requestMessageId: msg.messageId,
187
+ content: '',
188
+ error: (err as Error).message,
189
+ } as MessageResponsePayload);
190
+ this.send(response);
191
+ }
192
+ }
193
+
194
+ private scheduleReconnect(ownGatewayId: string): void {
195
+ if (this.stopped) return;
196
+ this.cleanup();
197
+ this.reconnectTimer = setTimeout(() => {
198
+ this.connect(ownGatewayId);
199
+ }, this.backoffMs);
200
+ this.backoffMs = Math.min(this.backoffMs * 2, 30_000);
201
+ }
202
+
203
+ private cleanup(): void {
204
+ if (this.reconnectTimer) {
205
+ clearTimeout(this.reconnectTimer);
206
+ this.reconnectTimer = null;
207
+ }
208
+ if (this.heartbeatTimer) {
209
+ clearInterval(this.heartbeatTimer);
210
+ this.heartbeatTimer = null;
211
+ }
212
+ if (this.ws) {
213
+ this.ws.onclose = null;
214
+ this.ws.onerror = null;
215
+ if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
216
+ this.ws.close();
217
+ }
218
+ this.ws = null;
219
+ }
220
+ }
221
+ }
@@ -0,0 +1,221 @@
1
+ // Federator — peer table management, agent discovery, message routing
2
+
3
+ import { createBridgeMessage, type MessageForwardPayload, type MessageResponsePayload } from './protocol';
4
+ import { BridgeClient } from './client';
5
+ import { BridgeServer } from './server';
6
+ import { agentRegistry } from '../agents/registry';
7
+ import { logger } from '../shared/logger';
8
+
9
+ export interface PeerInfo {
10
+ id: string;
11
+ url: string;
12
+ status: 'connecting' | 'connected' | 'disconnected';
13
+ agentCount: number;
14
+ connectedAt?: number;
15
+ lastPing: number;
16
+ }
17
+
18
+ interface RemoteAgent {
19
+ id: string;
20
+ name: string;
21
+ status: string;
22
+ gatewayId: string;
23
+ }
24
+
25
+ const MAX_QUEUE_SIZE = 100;
26
+
27
+ export class Federator {
28
+ private ownGatewayId: string;
29
+ private secret: string;
30
+ private clients = new Map<string, BridgeClient>(); // url -> client
31
+ private bridgeServer: BridgeServer;
32
+ private messageQueue: Array<{ targetAgentId: string; payload: MessageForwardPayload; resolve: (v: unknown) => void; reject: (e: Error) => void }> = [];
33
+ private stats = { messagesForwarded: 0, messagesReceived: 0, queueDropped: 0, startTime: Date.now() };
34
+
35
+ constructor(ownGatewayId: string, secret: string, bridgeServer: BridgeServer) {
36
+ this.ownGatewayId = ownGatewayId;
37
+ this.secret = secret;
38
+ this.bridgeServer = bridgeServer;
39
+ }
40
+
41
+ async connectPeer(url: string, secret: string): Promise<void> {
42
+ if (this.clients.has(url)) {
43
+ logger.warn({ url }, 'Peer already configured');
44
+ return;
45
+ }
46
+
47
+ const client = new BridgeClient(url, secret);
48
+
49
+ client.setHandlers(
50
+ (msg) => this.handleClientMessage(url, msg),
51
+ () => logger.info({ url }, 'Bridge client closed'),
52
+ );
53
+
54
+ this.clients.set(url, client);
55
+ await client.start(this.ownGatewayId);
56
+ }
57
+
58
+ disconnectPeer(url: string): void {
59
+ const client = this.clients.get(url);
60
+ if (client) {
61
+ client.stop();
62
+ this.clients.delete(url);
63
+ }
64
+ }
65
+
66
+ private handleClientMessage(_url: string, _msg: unknown): void {
67
+ // Client-level messages handled internally (hello-ack etc.)
68
+ }
69
+
70
+ // Get all agents: local + remote
71
+ getAllAgents(): { local: ReturnType<typeof agentRegistry.list>; remote: RemoteAgent[] } {
72
+ const local = agentRegistry.list();
73
+ const remote: RemoteAgent[] = [];
74
+
75
+ // From server-side connections
76
+ for (const [, conn] of this.bridgeServer.getConnections()) {
77
+ for (const agent of conn.agents) {
78
+ remote.push({ ...agent, gatewayId: conn.gatewayId });
79
+ }
80
+ }
81
+
82
+ return { local, remote };
83
+ }
84
+
85
+ // Check if an agent is remote (hosted on a peer)
86
+ findRemoteAgent(agentId: string): { gatewayId: string; gatewayUrl?: string } | null {
87
+ // Check server-side connections (inbound)
88
+ for (const [gwId, conn] of this.bridgeServer.getConnections()) {
89
+ if (conn.agents.some((a) => a.id === agentId)) {
90
+ return { gatewayId: gwId };
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+
96
+ // Route a message to an agent (local or remote)
97
+ async routeMessage(targetAgentId: string, text: string, sessionId?: string, channelId?: string): Promise<{ content: string; error?: string }> {
98
+ // Check local first
99
+ const local = agentRegistry.get(targetAgentId) || agentRegistry.getByName(targetAgentId);
100
+ if (local) {
101
+ const { sendMessage } = await import('../agents/runtime');
102
+ const result = await sendMessage({ agentId: local.id, sessionId, text, channelId });
103
+ return { content: result.content };
104
+ }
105
+
106
+ // Check remote
107
+ const remote = this.findRemoteAgent(targetAgentId);
108
+ if (remote) {
109
+ return this.sendToRemote(remote.gatewayId, targetAgentId, text, sessionId, channelId);
110
+ }
111
+
112
+ // Check remote by name too
113
+ const { remote: allRemote } = this.getAllAgents();
114
+ const namedRemote = allRemote.find((a) => a.name === targetAgentId);
115
+ if (namedRemote) {
116
+ return this.sendToRemote(namedRemote.gatewayId, namedRemote.id, text, sessionId, channelId);
117
+ }
118
+
119
+ // Queue if not found (maybe agent will appear)
120
+ if (this.messageQueue.length >= MAX_QUEUE_SIZE) {
121
+ // Drop the oldest queued message to cap memory
122
+ const oldest = this.messageQueue.shift();
123
+ if (oldest) {
124
+ oldest.reject(new Error('Message dropped: queue full'));
125
+ this.stats.queueDropped++;
126
+ }
127
+ }
128
+
129
+ return new Promise((resolve, reject) => {
130
+ this.messageQueue.push({ targetAgentId, payload: { targetAgentId, text, sessionId, channelId }, resolve, reject });
131
+ // Auto-reject after 30s
132
+ setTimeout(() => {
133
+ const idx = this.messageQueue.findIndex((m) => m.resolve === resolve);
134
+ if (idx !== -1) {
135
+ this.messageQueue.splice(idx, 1);
136
+ reject(new Error(`Agent not found: ${targetAgentId}`));
137
+ }
138
+ }, 30_000);
139
+ });
140
+ }
141
+
142
+ private async sendToRemote(gatewayId: string, targetAgentId: string, text: string, sessionId?: string, channelId?: string): Promise<{ content: string; error?: string }> {
143
+ const msg = createBridgeMessage('message-forward', this.ownGatewayId, {
144
+ targetAgentId,
145
+ sessionId,
146
+ text,
147
+ channelId,
148
+ } as MessageForwardPayload);
149
+
150
+ this.stats.messagesForwarded++;
151
+
152
+ try {
153
+ const response = await this.bridgeServer.sendToPeer(gatewayId, msg);
154
+ const payload = response.payload as MessageResponsePayload;
155
+ this.stats.messagesReceived++;
156
+ return { content: payload.content, error: payload.error };
157
+ } catch (err) {
158
+ return { content: '', error: (err as Error).message };
159
+ }
160
+ }
161
+
162
+ // Process queued messages if agents become available
163
+ flushQueue(): void {
164
+ const remaining = [];
165
+ for (const item of this.messageQueue) {
166
+ const local = agentRegistry.get(item.targetAgentId);
167
+ const remote = this.findRemoteAgent(item.targetAgentId);
168
+ if (local || remote) {
169
+ this.routeMessage(item.targetAgentId, item.payload.text, item.payload.sessionId, item.payload.channelId)
170
+ .then(item.resolve)
171
+ .catch(item.reject);
172
+ } else {
173
+ remaining.push(item);
174
+ }
175
+ }
176
+ this.messageQueue = remaining;
177
+ }
178
+
179
+ // Peer status for API
180
+ getPeers(): PeerInfo[] {
181
+ const peers: PeerInfo[] = [];
182
+
183
+ // Outbound clients
184
+ for (const [url, client] of this.clients) {
185
+ const gwId = 'outbound-' + url.replace(/[^a-zA-Z0-9]/g, '-');
186
+ peers.push({
187
+ id: gwId,
188
+ url,
189
+ status: client.state,
190
+ agentCount: 0,
191
+ lastPing: Date.now(),
192
+ });
193
+ }
194
+
195
+ // Inbound connections
196
+ for (const [gwId, conn] of this.bridgeServer.getConnections()) {
197
+ peers.push({
198
+ id: gwId,
199
+ url: 'inbound',
200
+ status: 'connected',
201
+ agentCount: conn.agents.length,
202
+ connectedAt: conn.connectedAt,
203
+ lastPing: conn.lastPong,
204
+ });
205
+ }
206
+
207
+ return peers;
208
+ }
209
+
210
+ getStatus() {
211
+ return {
212
+ enabled: true,
213
+ peerCount: this.clients.size + this.bridgeServer.getConnections().size,
214
+ queueSize: this.messageQueue.length,
215
+ messagesForwarded: this.stats.messagesForwarded,
216
+ messagesReceived: this.stats.messagesReceived,
217
+ queueDropped: this.stats.queueDropped,
218
+ uptime: Date.now() - this.stats.startTime,
219
+ };
220
+ }
221
+ }
@@ -0,0 +1,88 @@
1
+ // Bridge wire protocol for gateway-to-gateway communication
2
+
3
+ export const BRIDGE_VERSION = '1.0.0';
4
+
5
+ export type BridgeMessageType =
6
+ | 'hello'
7
+ | 'agent-register'
8
+ | 'agent-update'
9
+ | 'message-forward'
10
+ | 'message-response'
11
+ | 'health-ping'
12
+ | 'health-pong'
13
+ | 'unsubscribe'
14
+ | 'hello-ack';
15
+
16
+ export interface BridgeMessage {
17
+ version: string;
18
+ type: BridgeMessageType;
19
+ messageId: string;
20
+ gatewayId: string;
21
+ timestamp: number;
22
+ payload: unknown;
23
+ }
24
+
25
+ // Payloads
26
+
27
+ export interface HelloPayload {
28
+ secret: string;
29
+ gatewayId: string;
30
+ agents: { id: string; name: string; status: string }[];
31
+ capabilities?: string[];
32
+ }
33
+
34
+ export interface HelloAckPayload {
35
+ gatewayId: string;
36
+ agents: { id: string; name: string; status: string }[];
37
+ }
38
+
39
+ export interface AgentRegisterPayload {
40
+ agents: { id: string; name: string; status: string }[];
41
+ }
42
+
43
+ export interface AgentUpdatePayload {
44
+ agentId: string;
45
+ status: string;
46
+ }
47
+
48
+ export interface MessageForwardPayload {
49
+ targetAgentId: string;
50
+ messageId: string;
51
+ sessionId?: string;
52
+ text: string;
53
+ channelId?: string;
54
+ }
55
+
56
+ export interface MessageResponsePayload {
57
+ requestMessageId: string;
58
+ content: string;
59
+ error?: string;
60
+ }
61
+
62
+ export function createBridgeMessage(
63
+ type: BridgeMessageType,
64
+ gatewayId: string,
65
+ payload: unknown,
66
+ messageId?: string,
67
+ ): BridgeMessage {
68
+ return {
69
+ version: BRIDGE_VERSION,
70
+ type,
71
+ messageId: messageId || crypto.randomUUID(),
72
+ gatewayId,
73
+ timestamp: Date.now(),
74
+ payload,
75
+ };
76
+ }
77
+
78
+ export function parseBridgeMessage(data: string): BridgeMessage | null {
79
+ try {
80
+ const msg = JSON.parse(data);
81
+ if (msg.version && msg.type && msg.messageId && msg.gatewayId && msg.timestamp !== undefined) {
82
+ return msg as BridgeMessage;
83
+ }
84
+ return null;
85
+ } catch {
86
+ return null;
87
+ }
88
+ }