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,231 @@
1
+ // Agent Pipelines — chain agents together for multi-step workflows
2
+ // Pipeline: Agent A output → Agent B input → Agent C input → final result
3
+
4
+ import { nanoid } from 'nanoid';
5
+ import { sendMessage } from '../agents/runtime';
6
+ import { agentRegistry } from '../agents/registry';
7
+ import { logger } from '../shared/logger';
8
+ import { eventBus } from '../shared/events';
9
+
10
+ export interface PipelineStep {
11
+ /** Agent ID or name */
12
+ agent: string;
13
+ /** Optional system prompt override for this step */
14
+ systemPrompt?: string;
15
+ /** Optional model override */
16
+ model?: string;
17
+ /** Max iterations for this step */
18
+ maxIterations?: number;
19
+ /** Transform the output before passing to next step */
20
+ transform?: 'raw' | 'json-extract' | 'first-line' | 'last-line';
21
+ /** Prepend to the input */
22
+ prefix?: string;
23
+ /** Append to the input */
24
+ suffix?: string;
25
+ /** Timeout in ms (default: 120000) */
26
+ timeoutMs?: number;
27
+ }
28
+
29
+ export interface PipelineDefinition {
30
+ name: string;
31
+ description?: string;
32
+ steps: PipelineStep[];
33
+ /** Whether to pass all previous step results or just the last one */
34
+ chainMode: 'last-output' | 'all-outputs';
35
+ /** What to do if a step fails */
36
+ onFailure: 'stop' | 'skip' | 'retry';
37
+ /** Max retries per step */
38
+ maxRetries: number;
39
+ }
40
+
41
+ export interface PipelineResult {
42
+ pipelineId: string;
43
+ name: string;
44
+ steps: Array<{
45
+ agent: string;
46
+ input: string;
47
+ output: string;
48
+ durationMs: number;
49
+ tokensUsed?: number;
50
+ success: boolean;
51
+ error?: string;
52
+ }>;
53
+ finalOutput: string;
54
+ totalDurationMs: number;
55
+ success: boolean;
56
+ }
57
+
58
+ const pipelines = new Map<string, PipelineDefinition>();
59
+
60
+ /** Register a pipeline */
61
+ export function registerPipeline(definition: PipelineDefinition): void {
62
+ pipelines.set(definition.name, definition);
63
+ logger.info({ pipeline: definition.name, steps: definition.steps.length }, 'Pipeline registered');
64
+ }
65
+
66
+ /** Register pipelines from a YAML file */
67
+ export function loadPipelinesFromFile(filePath: string): void {
68
+ const { readFileSync, existsSync } = require('fs');
69
+ const { parse: parseYaml } = require('yaml');
70
+
71
+ if (!existsSync(filePath)) return;
72
+ const raw = readFileSync(filePath, 'utf-8');
73
+ const defs = parseYaml(raw);
74
+
75
+ if (Array.isArray(defs)) {
76
+ for (const def of defs) registerPipeline(def);
77
+ } else if (defs.pipelines) {
78
+ for (const def of defs.pipelines) registerPipeline(def);
79
+ }
80
+ }
81
+
82
+ /** Execute a pipeline */
83
+ export async function executePipeline(
84
+ name: string,
85
+ input: string,
86
+ sessionId?: string,
87
+ ): Promise<PipelineResult> {
88
+ const definition = pipelines.get(name);
89
+ if (!definition) throw new Error(`Pipeline not found: ${name}`);
90
+
91
+ const result: PipelineResult = {
92
+ pipelineId: nanoid(10),
93
+ name,
94
+ steps: [],
95
+ finalOutput: '',
96
+ totalDurationMs: 0,
97
+ success: false,
98
+ };
99
+
100
+ const startTime = Date.now();
101
+ let currentInput = input;
102
+ const allOutputs: string[] = [];
103
+
104
+ for (let i = 0; i < definition.steps.length; i++) {
105
+ const step = definition.steps[i];
106
+ const stepStart = Date.now();
107
+
108
+ // Resolve agent
109
+ const agent = agentRegistry.get(step.agent) || agentRegistry.getByName(step.agent);
110
+ if (!agent) {
111
+ const error = `Agent not found: ${step.agent}`;
112
+ result.steps.push({
113
+ agent: step.agent,
114
+ input: currentInput,
115
+ output: '',
116
+ durationMs: Date.now() - stepStart,
117
+ success: false,
118
+ error,
119
+ });
120
+
121
+ if (definition.onFailure === 'stop') break;
122
+ continue;
123
+ }
124
+
125
+ // Build input for this step
126
+ let stepInput = currentInput;
127
+ if (step.prefix) stepInput = step.prefix + stepInput;
128
+ if (step.suffix) stepInput = stepInput + step.suffix;
129
+
130
+ if (definition.chainMode === 'all-outputs' && allOutputs.length > 0) {
131
+ stepInput = `[Previous step results]\n${allOutputs.join('\n---\n')}\n\n[Current task]\n${stepInput}`;
132
+ }
133
+
134
+ // Execute with retries
135
+ let stepResult: Awaited<ReturnType<typeof sendMessage>> | null = null;
136
+ let lastError: string | undefined;
137
+
138
+ for (let attempt = 0; attempt <= definition.maxRetries; attempt++) {
139
+ try {
140
+ stepResult = await sendMessage({
141
+ agentId: agent.id,
142
+ sessionId,
143
+ text: stepInput,
144
+ });
145
+ break;
146
+ } catch (err) {
147
+ lastError = (err as Error).message;
148
+ logger.warn({ pipeline: name, step: i, attempt, error: lastError }, 'Pipeline step failed');
149
+ if (definition.onFailure === 'skip') break;
150
+ }
151
+ }
152
+
153
+ const durationMs = Date.now() - stepStart;
154
+
155
+ if (stepResult) {
156
+ let output = stepResult.content;
157
+
158
+ // Apply transform
159
+ switch (step.transform) {
160
+ case 'json-extract':
161
+ try {
162
+ const parsed = JSON.parse(output);
163
+ output = typeof parsed === 'string' ? parsed : JSON.stringify(parsed, null, 2);
164
+ } catch { /* keep raw */ }
165
+ break;
166
+ case 'first-line':
167
+ output = output.split('\n')[0];
168
+ break;
169
+ case 'last-line':
170
+ output = output.split('\n').filter(Boolean).pop() || output;
171
+ break;
172
+ }
173
+
174
+ allOutputs.push(output);
175
+ currentInput = output;
176
+
177
+ result.steps.push({
178
+ agent: agent.name,
179
+ input: stepInput.slice(0, 500), // truncate for storage
180
+ output: output.slice(0, 5000),
181
+ durationMs,
182
+ tokensUsed: stepResult.usage?.totalTokens,
183
+ success: true,
184
+ });
185
+ } else {
186
+ result.steps.push({
187
+ agent: agent.name,
188
+ input: stepInput.slice(0, 500),
189
+ output: '',
190
+ durationMs,
191
+ success: false,
192
+ error: lastError,
193
+ });
194
+
195
+ if (definition.onFailure === 'stop') break;
196
+ }
197
+ }
198
+
199
+ result.finalOutput = currentInput;
200
+ result.totalDurationMs = Date.now() - startTime;
201
+ result.success = result.steps.every((s) => s.success);
202
+
203
+ logger.info({
204
+ pipeline: name,
205
+ pipelineId: result.pipelineId,
206
+ steps: result.steps.length,
207
+ success: result.success,
208
+ durationMs: result.totalDurationMs,
209
+ }, 'Pipeline completed');
210
+
211
+ return result;
212
+ }
213
+
214
+ /** List all registered pipelines */
215
+ export function listPipelines(): Array<{ name: string; description?: string; stepCount: number }> {
216
+ return [...pipelines.values()].map((p) => ({
217
+ name: p.name,
218
+ description: p.description,
219
+ stepCount: p.steps.length,
220
+ }));
221
+ }
222
+
223
+ /** Get a pipeline definition */
224
+ export function getPipeline(name: string): PipelineDefinition | undefined {
225
+ return pipelines.get(name);
226
+ }
227
+
228
+ /** Remove a pipeline */
229
+ export function removePipeline(name: string): boolean {
230
+ return pipelines.delete(name);
231
+ }
@@ -0,0 +1,153 @@
1
+ import type { AgentConfig, Agent } from '../shared/types';
2
+ import type { Db } from '../shared/db';
3
+ import type { PreparedStatements } from '../shared/db/prepared';
4
+ import { readFileSync, readdirSync, existsSync } from 'fs';
5
+ import { resolve } from 'path';
6
+ import { parse as parseYaml } from 'yaml';
7
+ import { logger } from '../shared/logger';
8
+
9
+ class AgentRegistry {
10
+ private agents = new Map<string, Agent>();
11
+ private db: Db | null = null;
12
+ private prepared: PreparedStatements | null = null;
13
+
14
+ setDb(db: Db, prepared: PreparedStatements): void {
15
+ this.db = db;
16
+ this.prepared = prepared;
17
+ }
18
+
19
+ async loadFromDirectory(dir: string): Promise<void> {
20
+ const absDir = resolve(process.cwd(), dir);
21
+ if (!existsSync(absDir)) {
22
+ logger.warn({ dir: absDir }, 'Agent directory not found');
23
+ return;
24
+ }
25
+
26
+ const files = readdirSync(absDir).filter((f) => f.endsWith('.yaml') || f.endsWith('.yml'));
27
+ for (const file of files) {
28
+ try {
29
+ const raw = readFileSync(resolve(absDir, file), 'utf-8');
30
+ const parsed = parseYaml(raw) as Partial<AgentConfig>;
31
+ if (!parsed.name) continue;
32
+ // Deterministic ID from agent name for stability across restarts
33
+ const nameHash = Bun.hash(parsed.name).toString(16).padStart(8, '0').slice(0, 8);
34
+ await this.create({
35
+ id: parsed.id || ('agent-' + nameHash),
36
+ name: parsed.name,
37
+ model: parsed.model || 'openai/gpt-4o',
38
+ systemPrompt: parsed.systemPrompt || 'You are a helpful assistant.',
39
+ tools: parsed.tools || [],
40
+ maxIterations: parsed.maxIterations || 10,
41
+ timeoutMs: parsed.timeoutMs || 120000,
42
+ tags: parsed.tags,
43
+ metadata: parsed.metadata,
44
+ });
45
+ } catch (err) {
46
+ logger.error({ file, error: (err as Error).message }, 'Failed to load agent YAML');
47
+ }
48
+ }
49
+ }
50
+
51
+ async create(config: AgentConfig): Promise<Agent> {
52
+ const now = new Date().toISOString();
53
+ const agent: Agent = {
54
+ ...config,
55
+ id: config.id || crypto.randomUUID().slice(0, 10),
56
+ state: 'idle',
57
+ createdAt: now,
58
+ updatedAt: now,
59
+ };
60
+
61
+ this.agents.set(agent.id, agent);
62
+
63
+ if (this.prepared) {
64
+ this.prepared.agentInsert.run({
65
+ $id: agent.id,
66
+ $name: agent.name,
67
+ $model: agent.model,
68
+ $fallbackModel: agent.fallbackModel ?? null,
69
+ $systemPrompt: agent.systemPrompt,
70
+ $tools: JSON.stringify(agent.tools),
71
+ $maxIterations: agent.maxIterations ?? null,
72
+ $timeoutMs: agent.timeoutMs ?? null,
73
+ $state: agent.state,
74
+ $tags: JSON.stringify(agent.tags || []),
75
+ $metadata: JSON.stringify(agent.metadata || {}),
76
+ $createdAt: agent.createdAt,
77
+ $updatedAt: agent.updatedAt,
78
+ });
79
+ }
80
+
81
+ logger.info({ agent: agent.name, id: agent.id }, 'Agent created');
82
+ return agent;
83
+ }
84
+
85
+ get(id: string): Agent | undefined {
86
+ return this.agents.get(id);
87
+ }
88
+
89
+ getByName(name: string): Agent | undefined {
90
+ return [...this.agents.values()].find((a) => a.name === name);
91
+ }
92
+
93
+ list(): Agent[] {
94
+ return [...this.agents.values()];
95
+ }
96
+
97
+ async update(id: string, updates: Partial<AgentConfig>): Promise<Agent | undefined> {
98
+ const existing = this.agents.get(id);
99
+ if (!existing) return undefined;
100
+
101
+ const updated: Agent = {
102
+ ...existing,
103
+ ...updates,
104
+ id: existing.id, // prevent id change
105
+ state: existing.state,
106
+ updatedAt: new Date().toISOString(),
107
+ };
108
+
109
+ this.agents.set(id, updated);
110
+
111
+ if (this.prepared) {
112
+ this.prepared.agentUpdate.run({
113
+ $id: id,
114
+ $name: updated.name,
115
+ $model: updated.model,
116
+ $fallbackModel: updated.fallbackModel ?? null,
117
+ $systemPrompt: updated.systemPrompt,
118
+ $tools: JSON.stringify(updated.tools),
119
+ $maxIterations: updated.maxIterations ?? null,
120
+ $timeoutMs: updated.timeoutMs ?? null,
121
+ $tags: JSON.stringify(updated.tags || []),
122
+ $metadata: JSON.stringify(updated.metadata || {}),
123
+ $updatedAt: updated.updatedAt,
124
+ });
125
+ }
126
+
127
+ logger.info({ agent: updated.name, id }, 'Agent updated');
128
+ return updated;
129
+ }
130
+
131
+ async delete(id: string): Promise<boolean> {
132
+ const existing = this.agents.get(id);
133
+ if (!existing) return false;
134
+
135
+ this.agents.delete(id);
136
+
137
+ if (this.prepared) {
138
+ this.prepared.agentDelete.run({ $id: id });
139
+ }
140
+
141
+ logger.info({ agent: existing.name, id }, 'Agent deleted');
142
+ return true;
143
+ }
144
+
145
+ setState(id: string, state: Agent['state']): void {
146
+ const agent = this.agents.get(id);
147
+ if (agent) {
148
+ agent.state = state;
149
+ }
150
+ }
151
+ }
152
+
153
+ export const agentRegistry = new AgentRegistry();