homarus 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +114 -0
- package/dist/agent-manager.d.ts +35 -0
- package/dist/agent-manager.js +127 -0
- package/dist/agent-worker.d.ts +2 -0
- package/dist/agent-worker.js +141 -0
- package/dist/agent.d.ts +33 -0
- package/dist/agent.js +197 -0
- package/dist/browser-manager.d.ts +33 -0
- package/dist/browser-manager.js +170 -0
- package/dist/channel-adapter.d.ts +29 -0
- package/dist/channel-adapter.js +94 -0
- package/dist/channel-manager.d.ts +17 -0
- package/dist/channel-manager.js +84 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +212 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.js +185 -0
- package/dist/embedding-provider.d.ts +35 -0
- package/dist/embedding-provider.js +103 -0
- package/dist/event-bus.d.ts +18 -0
- package/dist/event-bus.js +46 -0
- package/dist/event-queue.d.ts +18 -0
- package/dist/event-queue.js +77 -0
- package/dist/execution-strategy.d.ts +26 -0
- package/dist/execution-strategy.js +20 -0
- package/dist/homarus.d.ts +36 -0
- package/dist/homarus.js +308 -0
- package/dist/http-api.d.ts +16 -0
- package/dist/http-api.js +82 -0
- package/dist/identity-manager.d.ts +28 -0
- package/dist/identity-manager.js +123 -0
- package/dist/memory-index.d.ts +52 -0
- package/dist/memory-index.js +286 -0
- package/dist/model-provider.d.ts +33 -0
- package/dist/model-provider.js +255 -0
- package/dist/model-router.d.ts +32 -0
- package/dist/model-router.js +148 -0
- package/dist/setup-wizard.d.ts +28 -0
- package/dist/setup-wizard.js +240 -0
- package/dist/skill-manager.d.ts +26 -0
- package/dist/skill-manager.js +171 -0
- package/dist/skill-transport.d.ts +51 -0
- package/dist/skill-transport.js +116 -0
- package/dist/skill.d.ts +22 -0
- package/dist/skill.js +118 -0
- package/dist/subprocess-strategy.d.ts +54 -0
- package/dist/subprocess-strategy.js +106 -0
- package/dist/telegram-adapter.d.ts +34 -0
- package/dist/telegram-adapter.js +165 -0
- package/dist/timer-service.d.ts +30 -0
- package/dist/timer-service.js +142 -0
- package/dist/tool-registry.d.ts +29 -0
- package/dist/tool-registry.js +100 -0
- package/dist/tools/bash.d.ts +3 -0
- package/dist/tools/bash.js +48 -0
- package/dist/tools/browser.d.ts +4 -0
- package/dist/tools/browser.js +47 -0
- package/dist/tools/edit.d.ts +3 -0
- package/dist/tools/edit.js +48 -0
- package/dist/tools/git.d.ts +3 -0
- package/dist/tools/git.js +109 -0
- package/dist/tools/glob.d.ts +3 -0
- package/dist/tools/glob.js +86 -0
- package/dist/tools/grep.d.ts +3 -0
- package/dist/tools/grep.js +169 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.js +46 -0
- package/dist/tools/lsp.d.ts +3 -0
- package/dist/tools/lsp.js +216 -0
- package/dist/tools/memory.d.ts +4 -0
- package/dist/tools/memory.js +64 -0
- package/dist/tools/read.d.ts +3 -0
- package/dist/tools/read.js +49 -0
- package/dist/tools/web-fetch.d.ts +3 -0
- package/dist/tools/web-fetch.js +51 -0
- package/dist/tools/web-search.d.ts +3 -0
- package/dist/tools/web-search.js +73 -0
- package/dist/tools/write.d.ts +3 -0
- package/dist/tools/write.js +31 -0
- package/dist/types.d.ts +240 -0
- package/dist/types.js +14 -0
- package/package.json +69 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// CRC: crc-Config.md | Seq: seq-startup.md
|
|
2
|
+
import { readFileSync, existsSync, watchFile, unwatchFile } from "node:fs";
|
|
3
|
+
import { resolve, dirname } from "node:path";
|
|
4
|
+
import { config as loadDotenv } from "dotenv";
|
|
5
|
+
const DEFAULT_CONFIG = {
|
|
6
|
+
models: { default: "anthropic/claude-sonnet-4-5" },
|
|
7
|
+
channels: {},
|
|
8
|
+
agents: { maxConcurrent: 5, defaultTimeout: 300_000, defaultMaxTurns: 20 },
|
|
9
|
+
memory: { search: { vectorWeight: 0.7, ftsWeight: 0.3 } },
|
|
10
|
+
skills: { paths: [] },
|
|
11
|
+
server: { port: 18800 },
|
|
12
|
+
timers: { enabled: true },
|
|
13
|
+
identity: {},
|
|
14
|
+
browser: { enabled: false, headless: true },
|
|
15
|
+
};
|
|
16
|
+
// Keys that can be hot-reloaded without restart
|
|
17
|
+
const SAFE_KEYS = new Set([
|
|
18
|
+
"models.aliases",
|
|
19
|
+
"models.fallback",
|
|
20
|
+
"agents.maxConcurrent",
|
|
21
|
+
"agents.defaultTimeout",
|
|
22
|
+
"agents.defaultMaxTurns",
|
|
23
|
+
"memory.search",
|
|
24
|
+
"skills.paths",
|
|
25
|
+
"timers.enabled",
|
|
26
|
+
]);
|
|
27
|
+
export class Config {
|
|
28
|
+
data = structuredClone(DEFAULT_CONFIG);
|
|
29
|
+
configPath;
|
|
30
|
+
watching = false;
|
|
31
|
+
logger;
|
|
32
|
+
constructor(logger, configPath) {
|
|
33
|
+
this.logger = logger;
|
|
34
|
+
this.configPath = configPath ?? this.resolveConfigPath();
|
|
35
|
+
}
|
|
36
|
+
// CRC: crc-Config.md — load()
|
|
37
|
+
load(path) {
|
|
38
|
+
if (path)
|
|
39
|
+
this.configPath = path;
|
|
40
|
+
this.loadEnvFile();
|
|
41
|
+
if (!existsSync(this.configPath)) {
|
|
42
|
+
this.logger.info("No config file found, using defaults", { path: this.configPath });
|
|
43
|
+
return this.data;
|
|
44
|
+
}
|
|
45
|
+
const raw = readFileSync(this.configPath, "utf-8");
|
|
46
|
+
const parsed = JSON.parse(raw);
|
|
47
|
+
const resolved = this.resolveEnvVars(parsed);
|
|
48
|
+
this.data = this.merge(DEFAULT_CONFIG, resolved);
|
|
49
|
+
this.logger.info("Config loaded", { path: this.configPath });
|
|
50
|
+
return this.data;
|
|
51
|
+
}
|
|
52
|
+
// CRC: crc-Config.md — get()
|
|
53
|
+
get(key) {
|
|
54
|
+
const parts = key.split(".");
|
|
55
|
+
let current = this.data;
|
|
56
|
+
for (const part of parts) {
|
|
57
|
+
if (current == null || typeof current !== "object")
|
|
58
|
+
return undefined;
|
|
59
|
+
current = current[part];
|
|
60
|
+
}
|
|
61
|
+
return current;
|
|
62
|
+
}
|
|
63
|
+
// CRC: crc-Config.md — getSection()
|
|
64
|
+
getSection(section) {
|
|
65
|
+
return this.data[section];
|
|
66
|
+
}
|
|
67
|
+
getAll() {
|
|
68
|
+
return this.data;
|
|
69
|
+
}
|
|
70
|
+
// CRC: crc-Config.md — resolveEnvVars()
|
|
71
|
+
resolveEnvVars(obj) {
|
|
72
|
+
if (typeof obj === "string") {
|
|
73
|
+
return obj.replace(/\$\{([^}]+)\}/g, (_, varName) => {
|
|
74
|
+
return process.env[varName] ?? "";
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (Array.isArray(obj)) {
|
|
78
|
+
return obj.map((item) => this.resolveEnvVars(item));
|
|
79
|
+
}
|
|
80
|
+
if (obj !== null && typeof obj === "object") {
|
|
81
|
+
const result = {};
|
|
82
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
83
|
+
result[key] = this.resolveEnvVars(value);
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
return obj;
|
|
88
|
+
}
|
|
89
|
+
// CRC: crc-Config.md — loadEnvFile()
|
|
90
|
+
loadEnvFile() {
|
|
91
|
+
const envPath = resolve(dirname(this.configPath), ".env");
|
|
92
|
+
if (existsSync(envPath)) {
|
|
93
|
+
loadDotenv({ path: envPath });
|
|
94
|
+
this.logger.debug("Loaded .env file", { path: envPath });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// CRC: crc-Config.md — startWatching()
|
|
98
|
+
startWatching(onChange) {
|
|
99
|
+
if (this.watching)
|
|
100
|
+
return;
|
|
101
|
+
this.watching = true;
|
|
102
|
+
watchFile(this.configPath, { interval: 2000 }, () => {
|
|
103
|
+
try {
|
|
104
|
+
const oldData = structuredClone(this.data);
|
|
105
|
+
this.load();
|
|
106
|
+
const safe = this.isSafeChange(oldData, this.data);
|
|
107
|
+
if (!safe) {
|
|
108
|
+
this.logger.warn("Config change requires restart for full effect");
|
|
109
|
+
}
|
|
110
|
+
onChange(safe);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
this.logger.error("Failed to reload config", { error: String(err) });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
// CRC: crc-Config.md — stopWatching()
|
|
118
|
+
stopWatching() {
|
|
119
|
+
if (!this.watching)
|
|
120
|
+
return;
|
|
121
|
+
unwatchFile(this.configPath);
|
|
122
|
+
this.watching = false;
|
|
123
|
+
}
|
|
124
|
+
// CRC: crc-Config.md — isSafeChange()
|
|
125
|
+
isSafeChange(oldConfig, newConfig) {
|
|
126
|
+
const oldJson = JSON.stringify(oldConfig);
|
|
127
|
+
const newJson = JSON.stringify(newConfig);
|
|
128
|
+
if (oldJson === newJson)
|
|
129
|
+
return true;
|
|
130
|
+
// Check if only safe keys changed
|
|
131
|
+
const oldFlat = this.flatten(oldConfig);
|
|
132
|
+
const newFlat = this.flatten(newConfig);
|
|
133
|
+
const allKeys = new Set([...Object.keys(oldFlat), ...Object.keys(newFlat)]);
|
|
134
|
+
for (const key of allKeys) {
|
|
135
|
+
if (JSON.stringify(oldFlat[key]) !== JSON.stringify(newFlat[key])) {
|
|
136
|
+
const isSafe = [...SAFE_KEYS].some((sk) => key.startsWith(sk));
|
|
137
|
+
if (!isSafe)
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
resolveConfigPath() {
|
|
144
|
+
// Check project-level first, then user-level
|
|
145
|
+
const projectPath = resolve(process.cwd(), "homarus.json");
|
|
146
|
+
if (existsSync(projectPath))
|
|
147
|
+
return projectPath;
|
|
148
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
|
|
149
|
+
return resolve(home, ".homarus", "config.json");
|
|
150
|
+
}
|
|
151
|
+
merge(defaults, overrides) {
|
|
152
|
+
const result = structuredClone(defaults);
|
|
153
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
154
|
+
if (value !== undefined && value !== null) {
|
|
155
|
+
const resultObj = result;
|
|
156
|
+
if (typeof value === "object" && !Array.isArray(value) && typeof resultObj[key] === "object") {
|
|
157
|
+
resultObj[key] = {
|
|
158
|
+
...resultObj[key],
|
|
159
|
+
...value,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
resultObj[key] = value;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
flatten(obj, prefix = "") {
|
|
170
|
+
const result = {};
|
|
171
|
+
if (obj !== null && typeof obj === "object" && !Array.isArray(obj)) {
|
|
172
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
173
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
174
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
175
|
+
Object.assign(result, this.flatten(value, fullKey));
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
result[fullKey] = value;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Logger } from "./types.js";
|
|
2
|
+
import type { EmbeddingProvider } from "./memory-index.js";
|
|
3
|
+
/**
|
|
4
|
+
* OpenAI-compatible embedding provider.
|
|
5
|
+
* Works with: OpenAI, Ollama (/v1/), LiteLLM, vLLM, LocalAI, etc.
|
|
6
|
+
*/
|
|
7
|
+
export declare class OpenAIEmbeddingProvider implements EmbeddingProvider {
|
|
8
|
+
private baseUrl;
|
|
9
|
+
private apiKey;
|
|
10
|
+
private model;
|
|
11
|
+
private dims;
|
|
12
|
+
private logger;
|
|
13
|
+
constructor(options: {
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
apiKey?: string;
|
|
16
|
+
model: string;
|
|
17
|
+
dimensions: number;
|
|
18
|
+
logger: Logger;
|
|
19
|
+
});
|
|
20
|
+
dimensions(): number;
|
|
21
|
+
embed(text: string): Promise<number[]>;
|
|
22
|
+
embedBatch(texts: string[]): Promise<number[][]>;
|
|
23
|
+
private requestEmbeddings;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create an embedding provider from HomarUS config.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createEmbeddingProvider(config: {
|
|
29
|
+
provider: string;
|
|
30
|
+
model: string;
|
|
31
|
+
baseUrl?: string;
|
|
32
|
+
apiKey?: string;
|
|
33
|
+
dimensions?: number;
|
|
34
|
+
}, logger: Logger): EmbeddingProvider;
|
|
35
|
+
//# sourceMappingURL=embedding-provider.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-compatible embedding provider.
|
|
3
|
+
* Works with: OpenAI, Ollama (/v1/), LiteLLM, vLLM, LocalAI, etc.
|
|
4
|
+
*/
|
|
5
|
+
export class OpenAIEmbeddingProvider {
|
|
6
|
+
baseUrl;
|
|
7
|
+
apiKey;
|
|
8
|
+
model;
|
|
9
|
+
dims;
|
|
10
|
+
logger;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
13
|
+
this.apiKey = options.apiKey ?? "";
|
|
14
|
+
this.model = options.model;
|
|
15
|
+
this.dims = options.dimensions;
|
|
16
|
+
this.logger = options.logger;
|
|
17
|
+
}
|
|
18
|
+
dimensions() {
|
|
19
|
+
return this.dims;
|
|
20
|
+
}
|
|
21
|
+
async embed(text) {
|
|
22
|
+
const results = await this.embedBatch([text]);
|
|
23
|
+
return results[0];
|
|
24
|
+
}
|
|
25
|
+
async embedBatch(texts) {
|
|
26
|
+
// Ollama doesn't support batch API, so we fall back to sequential calls
|
|
27
|
+
// if batch fails. Most OpenAI-compatible APIs support array input though.
|
|
28
|
+
try {
|
|
29
|
+
return await this.requestEmbeddings(texts);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
// Fallback: sequential single requests
|
|
33
|
+
this.logger.debug("Batch embedding failed, falling back to sequential", { error: String(err) });
|
|
34
|
+
const results = [];
|
|
35
|
+
for (const text of texts) {
|
|
36
|
+
const [embedding] = await this.requestEmbeddings([text]);
|
|
37
|
+
results.push(embedding);
|
|
38
|
+
}
|
|
39
|
+
return results;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async requestEmbeddings(input) {
|
|
43
|
+
const body = {
|
|
44
|
+
model: this.model,
|
|
45
|
+
input: input.length === 1 ? input[0] : input,
|
|
46
|
+
};
|
|
47
|
+
const headers = {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
};
|
|
50
|
+
if (this.apiKey) {
|
|
51
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
52
|
+
}
|
|
53
|
+
const response = await fetch(`${this.baseUrl}/embeddings`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers,
|
|
56
|
+
body: JSON.stringify(body),
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const errorText = await response.text();
|
|
60
|
+
throw new Error(`Embedding API error ${response.status}: ${errorText}`);
|
|
61
|
+
}
|
|
62
|
+
const data = (await response.json());
|
|
63
|
+
// Sort by index to ensure order matches input
|
|
64
|
+
const sorted = data.data.sort((a, b) => a.index - b.index);
|
|
65
|
+
return sorted.map((d) => d.embedding);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Create an embedding provider from HomarUS config.
|
|
70
|
+
*/
|
|
71
|
+
export function createEmbeddingProvider(config, logger) {
|
|
72
|
+
const baseUrl = config.baseUrl ?? getDefaultBaseUrl(config.provider);
|
|
73
|
+
const dimensions = config.dimensions ?? getDefaultDimensions(config.model);
|
|
74
|
+
return new OpenAIEmbeddingProvider({
|
|
75
|
+
baseUrl,
|
|
76
|
+
apiKey: config.apiKey,
|
|
77
|
+
model: config.model,
|
|
78
|
+
dimensions,
|
|
79
|
+
logger,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function getDefaultBaseUrl(provider) {
|
|
83
|
+
switch (provider) {
|
|
84
|
+
case "ollama": return "http://127.0.0.1:11434/v1";
|
|
85
|
+
case "openai": return "https://api.openai.com/v1";
|
|
86
|
+
default: return `https://api.${provider}.com/v1`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function getDefaultDimensions(model) {
|
|
90
|
+
// Known model dimensions
|
|
91
|
+
if (model.includes("nomic-embed-text"))
|
|
92
|
+
return 768;
|
|
93
|
+
if (model.includes("all-minilm"))
|
|
94
|
+
return 384;
|
|
95
|
+
if (model.includes("text-embedding-3-small"))
|
|
96
|
+
return 1536;
|
|
97
|
+
if (model.includes("text-embedding-3-large"))
|
|
98
|
+
return 3072;
|
|
99
|
+
if (model.includes("text-embedding-ada"))
|
|
100
|
+
return 1536;
|
|
101
|
+
return 768; // sensible default
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=embedding-provider.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DirectHandler, AgentHandlerConfig, Logger } from "./types.js";
|
|
2
|
+
export interface HandlerSet {
|
|
3
|
+
direct: DirectHandler[];
|
|
4
|
+
agent: AgentHandlerConfig[];
|
|
5
|
+
}
|
|
6
|
+
export declare class EventBus {
|
|
7
|
+
private directHandlers;
|
|
8
|
+
private agentHandlers;
|
|
9
|
+
private logger;
|
|
10
|
+
constructor(logger: Logger);
|
|
11
|
+
registerDirect(eventType: string, handler: DirectHandler): void;
|
|
12
|
+
registerAgent(eventType: string, config: AgentHandlerConfig): void;
|
|
13
|
+
unregister(eventType: string, handlerId: string): void;
|
|
14
|
+
getHandlers(eventType: string): HandlerSet;
|
|
15
|
+
hasHandlers(eventType: string): boolean;
|
|
16
|
+
clear(): void;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=event-bus.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export class EventBus {
|
|
2
|
+
directHandlers = new Map();
|
|
3
|
+
agentHandlers = new Map();
|
|
4
|
+
logger;
|
|
5
|
+
constructor(logger) {
|
|
6
|
+
this.logger = logger;
|
|
7
|
+
}
|
|
8
|
+
// CRC: crc-EventBus.md — registerDirect()
|
|
9
|
+
registerDirect(eventType, handler) {
|
|
10
|
+
const handlers = this.directHandlers.get(eventType) ?? [];
|
|
11
|
+
handlers.push(handler);
|
|
12
|
+
this.directHandlers.set(eventType, handlers);
|
|
13
|
+
this.logger.debug("Registered direct handler", { eventType });
|
|
14
|
+
}
|
|
15
|
+
// CRC: crc-EventBus.md — registerAgent()
|
|
16
|
+
registerAgent(eventType, config) {
|
|
17
|
+
const handlers = this.agentHandlers.get(eventType) ?? [];
|
|
18
|
+
handlers.push(config);
|
|
19
|
+
this.agentHandlers.set(eventType, handlers);
|
|
20
|
+
this.logger.debug("Registered agent handler", { eventType, handlerId: config.id });
|
|
21
|
+
}
|
|
22
|
+
// CRC: crc-EventBus.md — unregister()
|
|
23
|
+
unregister(eventType, handlerId) {
|
|
24
|
+
const agents = this.agentHandlers.get(eventType);
|
|
25
|
+
if (agents) {
|
|
26
|
+
this.agentHandlers.set(eventType, agents.filter((a) => a.id !== handlerId));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// CRC: crc-EventBus.md — getHandlers()
|
|
30
|
+
getHandlers(eventType) {
|
|
31
|
+
return {
|
|
32
|
+
direct: this.directHandlers.get(eventType) ?? [],
|
|
33
|
+
agent: this.agentHandlers.get(eventType) ?? [],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// CRC: crc-EventBus.md — hasHandlers()
|
|
37
|
+
hasHandlers(eventType) {
|
|
38
|
+
const { direct, agent } = this.getHandlers(eventType);
|
|
39
|
+
return direct.length > 0 || agent.length > 0;
|
|
40
|
+
}
|
|
41
|
+
clear() {
|
|
42
|
+
this.directHandlers.clear();
|
|
43
|
+
this.agentHandlers.clear();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=event-bus.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Event, Logger } from "./types.js";
|
|
2
|
+
export type OverflowStrategy = "drop_lowest" | "delay" | "reject";
|
|
3
|
+
export declare class EventQueue {
|
|
4
|
+
private queue;
|
|
5
|
+
private maxSize;
|
|
6
|
+
private overflowStrategy;
|
|
7
|
+
private logger;
|
|
8
|
+
constructor(logger: Logger, maxSize?: number, overflowStrategy?: OverflowStrategy);
|
|
9
|
+
enqueue(event: Event): boolean;
|
|
10
|
+
dequeue(): Event | undefined;
|
|
11
|
+
peek(): Event | undefined;
|
|
12
|
+
size(): number;
|
|
13
|
+
isFull(): boolean;
|
|
14
|
+
clear(): Event[];
|
|
15
|
+
private insertByPriority;
|
|
16
|
+
private handleOverflow;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=event-queue.d.ts.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export class EventQueue {
|
|
2
|
+
queue = [];
|
|
3
|
+
maxSize;
|
|
4
|
+
overflowStrategy;
|
|
5
|
+
logger;
|
|
6
|
+
constructor(logger, maxSize = 1000, overflowStrategy = "drop_lowest") {
|
|
7
|
+
this.logger = logger;
|
|
8
|
+
this.maxSize = maxSize;
|
|
9
|
+
this.overflowStrategy = overflowStrategy;
|
|
10
|
+
}
|
|
11
|
+
// CRC: crc-EventQueue.md — enqueue()
|
|
12
|
+
enqueue(event) {
|
|
13
|
+
if (this.isFull()) {
|
|
14
|
+
return this.handleOverflow(event);
|
|
15
|
+
}
|
|
16
|
+
this.insertByPriority(event);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
// CRC: crc-EventQueue.md — dequeue()
|
|
20
|
+
dequeue() {
|
|
21
|
+
return this.queue.shift();
|
|
22
|
+
}
|
|
23
|
+
// CRC: crc-EventQueue.md — peek()
|
|
24
|
+
peek() {
|
|
25
|
+
return this.queue[0];
|
|
26
|
+
}
|
|
27
|
+
// CRC: crc-EventQueue.md — size()
|
|
28
|
+
size() {
|
|
29
|
+
return this.queue.length;
|
|
30
|
+
}
|
|
31
|
+
// CRC: crc-EventQueue.md — isFull()
|
|
32
|
+
isFull() {
|
|
33
|
+
return this.queue.length >= this.maxSize;
|
|
34
|
+
}
|
|
35
|
+
// CRC: crc-EventQueue.md — clear()
|
|
36
|
+
clear() {
|
|
37
|
+
const remaining = [...this.queue];
|
|
38
|
+
this.queue = [];
|
|
39
|
+
return remaining;
|
|
40
|
+
}
|
|
41
|
+
insertByPriority(event) {
|
|
42
|
+
const priority = event.priority ?? 0;
|
|
43
|
+
// Higher priority = earlier in queue. Same priority = FIFO.
|
|
44
|
+
let i = this.queue.length;
|
|
45
|
+
while (i > 0 && (this.queue[i - 1].priority ?? 0) < priority) {
|
|
46
|
+
i--;
|
|
47
|
+
}
|
|
48
|
+
this.queue.splice(i, 0, event);
|
|
49
|
+
}
|
|
50
|
+
handleOverflow(event) {
|
|
51
|
+
switch (this.overflowStrategy) {
|
|
52
|
+
case "drop_lowest": {
|
|
53
|
+
// Drop the lowest priority event (last in queue)
|
|
54
|
+
const dropped = this.queue.pop();
|
|
55
|
+
if (dropped) {
|
|
56
|
+
this.logger.warn("Queue full, dropped lowest priority event", {
|
|
57
|
+
droppedId: dropped.id,
|
|
58
|
+
droppedType: dropped.type,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
this.insertByPriority(event);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
case "reject": {
|
|
65
|
+
this.logger.warn("Queue full, rejecting event", { eventId: event.id, eventType: event.type });
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
case "delay": {
|
|
69
|
+
// In delay mode, we still accept but log a warning
|
|
70
|
+
this.logger.warn("Queue at capacity, event delayed", { eventId: event.id, size: this.queue.length });
|
|
71
|
+
this.insertByPriority(event);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=event-queue.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AgentConfig, AgentResult, Event, Logger } from "./types.js";
|
|
2
|
+
import { Agent } from "./agent.js";
|
|
3
|
+
import type { ModelRouter } from "./model-router.js";
|
|
4
|
+
import type { ToolRegistry } from "./tool-registry.js";
|
|
5
|
+
import type { IdentityManager } from "./identity-manager.js";
|
|
6
|
+
export interface ExecutionHandle {
|
|
7
|
+
readonly agentId: string;
|
|
8
|
+
cancel(): void;
|
|
9
|
+
readonly result: Promise<AgentResult>;
|
|
10
|
+
onEvent(fn: (event: Event) => void): void;
|
|
11
|
+
/** The Agent instance, available only for embedded strategy */
|
|
12
|
+
readonly agent?: Agent;
|
|
13
|
+
}
|
|
14
|
+
export interface ExecutionDeps {
|
|
15
|
+
modelRouter: ModelRouter;
|
|
16
|
+
toolRegistry: ToolRegistry;
|
|
17
|
+
identityManager: IdentityManager;
|
|
18
|
+
logger: Logger;
|
|
19
|
+
}
|
|
20
|
+
export interface ExecutionStrategy {
|
|
21
|
+
execute(config: AgentConfig, deps: ExecutionDeps): ExecutionHandle;
|
|
22
|
+
}
|
|
23
|
+
export declare class EmbeddedStrategy implements ExecutionStrategy {
|
|
24
|
+
execute(config: AgentConfig, deps: ExecutionDeps): ExecutionHandle;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=execution-strategy.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Agent } from "./agent.js";
|
|
2
|
+
// Wraps the existing Agent class — same behavior as the original AgentManager.spawn()
|
|
3
|
+
export class EmbeddedStrategy {
|
|
4
|
+
execute(config, deps) {
|
|
5
|
+
const agent = new Agent(config, deps.modelRouter, deps.toolRegistry, deps.identityManager, deps.logger);
|
|
6
|
+
let eventListener = null;
|
|
7
|
+
agent.setEmitter((event) => {
|
|
8
|
+
eventListener?.(event);
|
|
9
|
+
});
|
|
10
|
+
const resultPromise = agent.run();
|
|
11
|
+
return {
|
|
12
|
+
agentId: agent.id,
|
|
13
|
+
cancel: () => agent.cancel(),
|
|
14
|
+
result: resultPromise,
|
|
15
|
+
onEvent: (fn) => { eventListener = fn; },
|
|
16
|
+
agent,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=execution-strategy.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Event, AgentConfig, Logger } from "./types.js";
|
|
2
|
+
export type LoopState = "starting" | "running" | "stopping" | "stopped";
|
|
3
|
+
export declare class Homarus {
|
|
4
|
+
private state;
|
|
5
|
+
private config;
|
|
6
|
+
private eventBus;
|
|
7
|
+
private eventQueue;
|
|
8
|
+
private agentManager;
|
|
9
|
+
private skillManager;
|
|
10
|
+
private channelManager;
|
|
11
|
+
private toolRegistry;
|
|
12
|
+
private modelRouter;
|
|
13
|
+
private memoryIndex;
|
|
14
|
+
private identityManager;
|
|
15
|
+
private timerService;
|
|
16
|
+
private httpApi;
|
|
17
|
+
private browserManager;
|
|
18
|
+
private logger;
|
|
19
|
+
private processing;
|
|
20
|
+
private processInterval;
|
|
21
|
+
constructor(logger: Logger, configPath?: string);
|
|
22
|
+
getState(): LoopState;
|
|
23
|
+
emit(event: Event): void;
|
|
24
|
+
registerHandler(eventType: string, handler: (event: Event) => void | Promise<void>): void;
|
|
25
|
+
registerAgentHandler(eventType: string, agentConfig: Partial<AgentConfig>, buildPrompt?: (event: Event) => string): void;
|
|
26
|
+
start(): Promise<void>;
|
|
27
|
+
stop(): Promise<void>;
|
|
28
|
+
private processEvent;
|
|
29
|
+
private startProcessing;
|
|
30
|
+
private stopProcessing;
|
|
31
|
+
private processNext;
|
|
32
|
+
private registerDefaultHandlers;
|
|
33
|
+
private initProviders;
|
|
34
|
+
private getStatus;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=homarus.d.ts.map
|