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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -0
  3. package/dist/agent-manager.d.ts +35 -0
  4. package/dist/agent-manager.js +127 -0
  5. package/dist/agent-worker.d.ts +2 -0
  6. package/dist/agent-worker.js +141 -0
  7. package/dist/agent.d.ts +33 -0
  8. package/dist/agent.js +197 -0
  9. package/dist/browser-manager.d.ts +33 -0
  10. package/dist/browser-manager.js +170 -0
  11. package/dist/channel-adapter.d.ts +29 -0
  12. package/dist/channel-adapter.js +94 -0
  13. package/dist/channel-manager.d.ts +17 -0
  14. package/dist/channel-manager.js +84 -0
  15. package/dist/cli.d.ts +3 -0
  16. package/dist/cli.js +212 -0
  17. package/dist/config.d.ts +21 -0
  18. package/dist/config.js +185 -0
  19. package/dist/embedding-provider.d.ts +35 -0
  20. package/dist/embedding-provider.js +103 -0
  21. package/dist/event-bus.d.ts +18 -0
  22. package/dist/event-bus.js +46 -0
  23. package/dist/event-queue.d.ts +18 -0
  24. package/dist/event-queue.js +77 -0
  25. package/dist/execution-strategy.d.ts +26 -0
  26. package/dist/execution-strategy.js +20 -0
  27. package/dist/homarus.d.ts +36 -0
  28. package/dist/homarus.js +308 -0
  29. package/dist/http-api.d.ts +16 -0
  30. package/dist/http-api.js +82 -0
  31. package/dist/identity-manager.d.ts +28 -0
  32. package/dist/identity-manager.js +123 -0
  33. package/dist/memory-index.d.ts +52 -0
  34. package/dist/memory-index.js +286 -0
  35. package/dist/model-provider.d.ts +33 -0
  36. package/dist/model-provider.js +255 -0
  37. package/dist/model-router.d.ts +32 -0
  38. package/dist/model-router.js +148 -0
  39. package/dist/setup-wizard.d.ts +28 -0
  40. package/dist/setup-wizard.js +240 -0
  41. package/dist/skill-manager.d.ts +26 -0
  42. package/dist/skill-manager.js +171 -0
  43. package/dist/skill-transport.d.ts +51 -0
  44. package/dist/skill-transport.js +116 -0
  45. package/dist/skill.d.ts +22 -0
  46. package/dist/skill.js +118 -0
  47. package/dist/subprocess-strategy.d.ts +54 -0
  48. package/dist/subprocess-strategy.js +106 -0
  49. package/dist/telegram-adapter.d.ts +34 -0
  50. package/dist/telegram-adapter.js +165 -0
  51. package/dist/timer-service.d.ts +30 -0
  52. package/dist/timer-service.js +142 -0
  53. package/dist/tool-registry.d.ts +29 -0
  54. package/dist/tool-registry.js +100 -0
  55. package/dist/tools/bash.d.ts +3 -0
  56. package/dist/tools/bash.js +48 -0
  57. package/dist/tools/browser.d.ts +4 -0
  58. package/dist/tools/browser.js +47 -0
  59. package/dist/tools/edit.d.ts +3 -0
  60. package/dist/tools/edit.js +48 -0
  61. package/dist/tools/git.d.ts +3 -0
  62. package/dist/tools/git.js +109 -0
  63. package/dist/tools/glob.d.ts +3 -0
  64. package/dist/tools/glob.js +86 -0
  65. package/dist/tools/grep.d.ts +3 -0
  66. package/dist/tools/grep.js +169 -0
  67. package/dist/tools/index.d.ts +6 -0
  68. package/dist/tools/index.js +46 -0
  69. package/dist/tools/lsp.d.ts +3 -0
  70. package/dist/tools/lsp.js +216 -0
  71. package/dist/tools/memory.d.ts +4 -0
  72. package/dist/tools/memory.js +64 -0
  73. package/dist/tools/read.d.ts +3 -0
  74. package/dist/tools/read.js +49 -0
  75. package/dist/tools/web-fetch.d.ts +3 -0
  76. package/dist/tools/web-fetch.js +51 -0
  77. package/dist/tools/web-search.d.ts +3 -0
  78. package/dist/tools/web-search.js +73 -0
  79. package/dist/tools/write.d.ts +3 -0
  80. package/dist/tools/write.js +31 -0
  81. package/dist/types.d.ts +240 -0
  82. package/dist/types.js +14 -0
  83. package/package.json +69 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Max Ross
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # HomarUS
2
+
3
+ > **Why "HomarUS"?** *Homarus* is the genus of true clawed lobsters — the evolved successor to the claw. The **US** isn't just taxonomy. It's the whole point: human and AI, working together. There is no artificial intelligence without *us*.
4
+
5
+ Event-driven AI agent coordinator. Receives events from channels, skills, timers, and webhooks — spawns parallel agents to handle the work.
6
+
7
+ Model-agnostic. Skill-based. Built for people who want to run their own AI infrastructure.
8
+
9
+ ## What it does
10
+
11
+ ```
12
+ Channels (Telegram, Discord, CLI, ...)
13
+ Skills (web UIs, scripts, services) → Event Loop → Agents (parallel)
14
+ Timers (cron, intervals, one-shots) ↓
15
+ Webhooks (HTTP callbacks) Tools, Memory, Models
16
+ ```
17
+
18
+ The loop is a **scheduler and router**, not an AI agent. It receives events, decides what work needs to happen, spawns agents to do it, and handles the results. Agents run in parallel with configurable concurrency, backpressure, and failover.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install -g homarus
24
+ ```
25
+
26
+ Or use without installing:
27
+
28
+ ```bash
29
+ npx homarus init
30
+ npx homarus start
31
+ ```
32
+
33
+ ## Quick start
34
+
35
+ ```bash
36
+ homarus init # Interactive wizard: pick provider, enter API key, configure Telegram
37
+ homarus start # Start the event loop
38
+ ```
39
+
40
+ The setup wizard walks you through provider selection (Anthropic, OpenAI, OpenRouter, or Ollama), API key entry, default model choice, and optional Telegram bot setup. Your config is ready to use immediately — no manual JSON editing required.
41
+
42
+ Use `--no-wizard` to skip the wizard and generate a default config for manual editing.
43
+
44
+ See the **[Setup & Usage Guide](docs/guide.md)** for detailed configuration and more.
45
+
46
+ ## Architecture
47
+
48
+ - **Homarus** — central event loop, single-threaded coordinator
49
+ - **EventBus / EventQueue** — typed pub/sub with priority ordering and backpressure
50
+ - **AgentManager / Agent** — spawns and tracks parallel AI agents
51
+ - **ModelRouter / ModelProvider** — model-agnostic with failover chains (Anthropic, OpenAI, OpenRouter, Ollama, any OpenAI-compatible endpoint)
52
+ - **SkillManager / Skill** — open plugin system via HTTP, stdio, or in-process transports
53
+ - **ChannelManager / ChannelAdapter** — normalized message ingestion from any platform
54
+ - **MemoryIndex** — vector + full-text hybrid search for long-term memory
55
+ - **IdentityManager** — layered soul/user/overlay identity system
56
+ - **TimerService** — cron expressions, intervals, one-shots via croner
57
+ - **HttpApi** — REST API for status, skill callbacks, and external integrations
58
+ - **BrowserManager** — optional Playwright-based headless browser (lazy-loaded, requires `npm install playwright`)
59
+ - **Config** — JSON config with JSON Schema validation and hot reload
60
+
61
+ ## CLI
62
+
63
+ ```
64
+ homarus start [config] Start the event loop (foreground)
65
+ homarus init [--no-wizard] Interactive setup wizard (or defaults)
66
+ homarus status [port] Show status of running instance
67
+ homarus config [config] Validate config file
68
+ homarus skills List loaded skills
69
+ homarus install-daemon Install systemd or launchd service
70
+ ```
71
+
72
+ ## Config
73
+
74
+ Primary: `~/.homarus/config.json`
75
+ Per-project override: `./homarus.json`
76
+
77
+ Skills go in `~/.homarus/skills/` or `./skills/`.
78
+ Identity files in `~/.homarus/identity/`.
79
+ Memory index at `~/.homarus/memory/`.
80
+
81
+ ## Built-in tools
82
+
83
+ Agents get access to: `bash`, `read`, `write`, `edit`, `glob`, `grep`, `git`, `web_fetch`, `web_search`, `lsp`, `memory_search`, `memory_get`, `memory_store`, and optionally `browser`.
84
+
85
+ Browser support requires Playwright as an optional dependency:
86
+
87
+ ```bash
88
+ npm install playwright # only needed if browser.enabled is set in config
89
+ ```
90
+
91
+ ## Requirements
92
+
93
+ - Node.js >= 22
94
+ - Unix-like system (Linux, macOS)
95
+
96
+ ## Built with mini-spec
97
+
98
+ HomarUS was designed and implemented using [mini-spec](https://github.com/zot/mini-spec), an 8-phase methodology for AI-assisted software development. Every source file traces back through the full chain: reference materials → natural language specs → requirements → CRC cards → sequence diagrams → code with traceability comments.
99
+
100
+ The `refs/`, `specs/`, and `design/` directories are the living design artifacts, not just documentation.
101
+
102
+ ## Status
103
+
104
+ Core architecture implemented (19 source files, 70 requirements, 20 CRC cards, 7 sequence diagrams, 37 tests passing across 5 test suites). Built-in tool suite complete. Currently pre-release.
105
+
106
+ ## Roadmap
107
+
108
+ - [ ] npm publish + SEA binaries + Homebrew tap
109
+ - [ ] OAuth support for Google/Gemini (the only major provider with third-party OAuth)
110
+ - [x] ~~`homarus auth` onboarding command~~ (shipped as interactive `homarus init` wizard)
111
+
112
+ ## License
113
+
114
+ [MIT](LICENSE)
@@ -0,0 +1,35 @@
1
+ import type { AgentConfig, Event, ExecutionStrategyType, 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 declare class AgentManager {
7
+ private agents;
8
+ private maxConcurrent;
9
+ private defaultTimeout;
10
+ private defaultMaxTurns;
11
+ private defaultExecutionStrategy;
12
+ private modelRouter;
13
+ private toolRegistry;
14
+ private identityManager;
15
+ private logger;
16
+ private emitFn;
17
+ private strategies;
18
+ constructor(logger: Logger, modelRouter: ModelRouter, toolRegistry: ToolRegistry, identityManager: IdentityManager, options?: {
19
+ maxConcurrent?: number;
20
+ defaultTimeout?: number;
21
+ defaultMaxTurns?: number;
22
+ defaultExecutionStrategy?: ExecutionStrategyType;
23
+ });
24
+ setEmitter(fn: (event: Event) => void): void;
25
+ spawn(config: AgentConfig): Promise<string>;
26
+ cancel(agentId: string): void;
27
+ cancelAll(): void;
28
+ getAgent(agentId: string): Agent | undefined;
29
+ getActive(): Agent[];
30
+ activeCount(): number;
31
+ canSpawn(): boolean;
32
+ waitForAll(timeout?: number): Promise<void>;
33
+ private onAgentComplete;
34
+ }
35
+ //# sourceMappingURL=agent-manager.d.ts.map
@@ -0,0 +1,127 @@
1
+ import { EmbeddedStrategy } from "./execution-strategy.js";
2
+ import { SubprocessStrategy } from "./subprocess-strategy.js";
3
+ export class AgentManager {
4
+ agents = new Map();
5
+ maxConcurrent;
6
+ defaultTimeout;
7
+ defaultMaxTurns;
8
+ defaultExecutionStrategy;
9
+ modelRouter;
10
+ toolRegistry;
11
+ identityManager;
12
+ logger;
13
+ emitFn = null;
14
+ strategies = {
15
+ embedded: new EmbeddedStrategy(),
16
+ subprocess: new SubprocessStrategy(),
17
+ };
18
+ constructor(logger, modelRouter, toolRegistry, identityManager, options) {
19
+ this.logger = logger;
20
+ this.modelRouter = modelRouter;
21
+ this.toolRegistry = toolRegistry;
22
+ this.identityManager = identityManager;
23
+ this.maxConcurrent = options?.maxConcurrent ?? 5;
24
+ this.defaultTimeout = options?.defaultTimeout ?? 300_000;
25
+ this.defaultMaxTurns = options?.defaultMaxTurns ?? 20;
26
+ this.defaultExecutionStrategy = options?.defaultExecutionStrategy ?? "embedded";
27
+ }
28
+ setEmitter(fn) {
29
+ this.emitFn = fn;
30
+ }
31
+ // CRC: crc-AgentManager.md — spawn()
32
+ async spawn(config) {
33
+ if (!this.canSpawn()) {
34
+ throw new Error(`Max concurrent agents reached (${this.maxConcurrent})`);
35
+ }
36
+ const agentConfig = {
37
+ ...config,
38
+ timeout: config.timeout ?? this.defaultTimeout,
39
+ maxTurns: config.maxTurns ?? this.defaultMaxTurns,
40
+ };
41
+ const strategyType = config.executionStrategy ?? this.defaultExecutionStrategy;
42
+ const strategy = this.strategies[strategyType];
43
+ const handle = strategy.execute(agentConfig, {
44
+ modelRouter: this.modelRouter,
45
+ toolRegistry: this.toolRegistry,
46
+ identityManager: this.identityManager,
47
+ logger: this.logger,
48
+ });
49
+ if (this.emitFn) {
50
+ handle.onEvent(this.emitFn);
51
+ }
52
+ const activeAgent = {
53
+ handle,
54
+ cancel: () => handle.cancel(),
55
+ agent: handle.agent,
56
+ };
57
+ this.agents.set(handle.agentId, activeAgent);
58
+ this.logger.info("Spawning agent", {
59
+ id: handle.agentId,
60
+ model: config.model,
61
+ strategy: strategyType,
62
+ });
63
+ // Run async — don't await, let it complete in background
64
+ handle.result
65
+ .then((result) => this.onAgentComplete(handle.agentId, result))
66
+ .catch((err) => {
67
+ this.logger.error("Agent run failed", { id: handle.agentId, error: String(err) });
68
+ this.agents.delete(handle.agentId);
69
+ });
70
+ return handle.agentId;
71
+ }
72
+ // CRC: crc-AgentManager.md — cancel()
73
+ cancel(agentId) {
74
+ const active = this.agents.get(agentId);
75
+ if (!active)
76
+ return;
77
+ active.cancel();
78
+ this.logger.info("Agent cancel requested", { id: agentId });
79
+ }
80
+ // CRC: crc-AgentManager.md — cancelAll()
81
+ cancelAll() {
82
+ for (const active of this.agents.values()) {
83
+ active.cancel();
84
+ }
85
+ this.logger.info("All agents cancel requested", { count: this.agents.size });
86
+ }
87
+ // CRC: crc-AgentManager.md — getAgent() — returns undefined for subprocess agents
88
+ getAgent(agentId) {
89
+ return this.agents.get(agentId)?.agent;
90
+ }
91
+ // CRC: crc-AgentManager.md — getActive()
92
+ getActive() {
93
+ return [...this.agents.values()]
94
+ .filter((a) => a.agent?.getState() === "running")
95
+ .map((a) => a.agent);
96
+ }
97
+ // CRC: crc-AgentManager.md — activeCount()
98
+ activeCount() {
99
+ return this.agents.size;
100
+ }
101
+ // CRC: crc-AgentManager.md — canSpawn()
102
+ canSpawn() {
103
+ return this.activeCount() < this.maxConcurrent;
104
+ }
105
+ // CRC: crc-AgentManager.md — waitForAll()
106
+ async waitForAll(timeout = 30_000) {
107
+ const start = Date.now();
108
+ while (this.agents.size > 0) {
109
+ if (Date.now() - start > timeout) {
110
+ this.logger.warn("Wait for agents timed out, force cancelling", { remaining: this.agents.size });
111
+ this.cancelAll();
112
+ return;
113
+ }
114
+ await new Promise((resolve) => setTimeout(resolve, 100));
115
+ }
116
+ }
117
+ // CRC: crc-AgentManager.md — onAgentComplete()
118
+ onAgentComplete(agentId, result) {
119
+ this.agents.delete(agentId);
120
+ this.logger.info("Agent complete", {
121
+ id: agentId,
122
+ toolCalls: result.toolCalls.length,
123
+ tokens: result.usage.inputTokens + result.usage.outputTokens,
124
+ });
125
+ }
126
+ }
127
+ //# sourceMappingURL=agent-manager.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=agent-worker.d.ts.map
@@ -0,0 +1,141 @@
1
+ // CRC: design.md O4 — Agent subprocess worker entry point
2
+ // This file runs as a child process forked by SubprocessStrategy.
3
+ // It receives an init message, reconstructs deps, runs an Agent, and reports back via IPC.
4
+ import { v4 as uuid } from "uuid";
5
+ import { Agent } from "./agent.js";
6
+ import { ModelRouter } from "./model-router.js";
7
+ import { OpenAICompatibleProvider } from "./model-provider.js";
8
+ // Minimal logger that writes to stderr (stdout is reserved for IPC)
9
+ const logger = {
10
+ debug: (msg, meta) => console.error(`[worker:debug] ${msg}`, meta ?? ""),
11
+ info: (msg, meta) => console.error(`[worker:info] ${msg}`, meta ?? ""),
12
+ warn: (msg, meta) => console.error(`[worker:warn] ${msg}`, meta ?? ""),
13
+ error: (msg, meta) => console.error(`[worker:error] ${msg}`, meta ?? ""),
14
+ };
15
+ // Pending tool requests awaiting parent response
16
+ const pendingToolCalls = new Map();
17
+ let agent = null;
18
+ function send(msg) {
19
+ process.send(msg);
20
+ }
21
+ // Build a proxy ToolRegistry that delegates execution to the parent via IPC
22
+ function buildProxyToolRegistry(toolSchemas) {
23
+ const proxyTools = new Map();
24
+ for (const schema of toolSchemas) {
25
+ proxyTools.set(schema.name, {
26
+ name: schema.name,
27
+ description: schema.description,
28
+ parameters: schema.parameters,
29
+ source: "subprocess-proxy",
30
+ execute: async (params, context) => {
31
+ const callId = uuid();
32
+ return new Promise((resolve, reject) => {
33
+ pendingToolCalls.set(callId, { resolve, reject });
34
+ send({
35
+ type: "tool_request",
36
+ callId,
37
+ toolName: schema.name,
38
+ params,
39
+ agentId: context.agentId,
40
+ sandbox: context.sandbox,
41
+ });
42
+ });
43
+ },
44
+ });
45
+ }
46
+ // Return an object that satisfies the ToolRegistry interface used by Agent
47
+ return {
48
+ getForAgent(_allowedTools) {
49
+ return [...proxyTools.values()];
50
+ },
51
+ execute: async (name, params, context) => {
52
+ const tool = proxyTools.get(name);
53
+ if (!tool)
54
+ return { output: "", error: `Unknown tool: ${name}` };
55
+ return tool.execute(params, context);
56
+ },
57
+ // Stubs for interface compliance — not used by Agent.run()
58
+ register() { },
59
+ unregister() { },
60
+ get(name) { return proxyTools.get(name); },
61
+ getAll() { return [...proxyTools.values()]; },
62
+ registerGroup() { },
63
+ resolveGroup() { return []; },
64
+ addPolicy() { },
65
+ checkPolicy() { return true; },
66
+ toSchemas() {
67
+ return [...proxyTools.values()].map((t) => ({
68
+ name: t.name, description: t.description, parameters: t.parameters,
69
+ }));
70
+ },
71
+ };
72
+ }
73
+ // Build a minimal IdentityManager that returns the pre-built system prompt
74
+ function buildProxyIdentityManager(systemPrompt) {
75
+ return {
76
+ buildSystemPrompt: () => systemPrompt,
77
+ load() { },
78
+ reload() { },
79
+ getSoul() { return ""; },
80
+ getUser() { return ""; },
81
+ getOverlay() { return undefined; },
82
+ getWorkspaceFile() { return undefined; },
83
+ listOverlays() { return []; },
84
+ };
85
+ }
86
+ // Build a ModelRouter from serialized provider configs
87
+ function buildModelRouter(providers, defaultModel, aliases, fallbackChain) {
88
+ const router = new ModelRouter(logger, defaultModel);
89
+ for (const pc of providers) {
90
+ const provider = new OpenAICompatibleProvider(pc.id, pc.baseUrl, { apiKey: pc.apiKey }, logger);
91
+ router.registerProvider(provider);
92
+ }
93
+ router.setAliases(aliases);
94
+ router.setFallbackChain(fallbackChain);
95
+ return router;
96
+ }
97
+ async function handleInit(msg) {
98
+ const modelRouter = buildModelRouter(msg.providerConfigs, msg.defaultModel, msg.aliases, msg.fallbackChain);
99
+ const toolRegistry = buildProxyToolRegistry(msg.toolSchemas);
100
+ const identityManager = buildProxyIdentityManager(msg.systemPrompt);
101
+ agent = new Agent(msg.agentConfig, modelRouter, toolRegistry, identityManager, logger);
102
+ agent.setEmitter((event) => {
103
+ send({ type: "event", event });
104
+ });
105
+ try {
106
+ const result = await agent.run();
107
+ send({ type: "result", result });
108
+ process.exit(0);
109
+ }
110
+ catch (err) {
111
+ send({ type: "error", error: String(err.message ?? err) });
112
+ process.exit(1);
113
+ }
114
+ }
115
+ process.on("message", (msg) => {
116
+ switch (msg.type) {
117
+ case "init":
118
+ handleInit(msg).catch((err) => {
119
+ send({ type: "error", error: String(err) });
120
+ process.exit(1);
121
+ });
122
+ break;
123
+ case "tool_result": {
124
+ const pending = pendingToolCalls.get(msg.callId);
125
+ if (pending) {
126
+ pendingToolCalls.delete(msg.callId);
127
+ pending.resolve(msg.result);
128
+ }
129
+ break;
130
+ }
131
+ case "cancel":
132
+ agent?.cancel();
133
+ break;
134
+ }
135
+ });
136
+ // Clean exit on disconnect
137
+ process.on("disconnect", () => {
138
+ agent?.cancel();
139
+ process.exit(0);
140
+ });
141
+ //# sourceMappingURL=agent-worker.js.map
@@ -0,0 +1,33 @@
1
+ import type { AgentConfig, AgentState, AgentResult, Event, Logger } from "./types.js";
2
+ import type { ModelRouter } from "./model-router.js";
3
+ import type { ToolRegistry } from "./tool-registry.js";
4
+ import type { IdentityManager } from "./identity-manager.js";
5
+ export declare class Agent {
6
+ readonly id: string;
7
+ private state;
8
+ private config;
9
+ private turns;
10
+ private startTime;
11
+ private result;
12
+ private cancelled;
13
+ private totalUsage;
14
+ private toolCallLog;
15
+ private modelRouter;
16
+ private toolRegistry;
17
+ private identityManager;
18
+ private logger;
19
+ private emitFn;
20
+ constructor(config: AgentConfig, modelRouter: ModelRouter, toolRegistry: ToolRegistry, identityManager: IdentityManager, logger: Logger);
21
+ setEmitter(fn: (event: Event) => void): void;
22
+ getState(): AgentState;
23
+ getResult(): AgentResult | null;
24
+ run(): Promise<AgentResult>;
25
+ cancel(): void;
26
+ isTimedOut(): boolean;
27
+ isOverTurns(): boolean;
28
+ private buildResult;
29
+ private emitProgress;
30
+ private emitResult;
31
+ private emitError;
32
+ }
33
+ //# sourceMappingURL=agent.d.ts.map
package/dist/agent.js ADDED
@@ -0,0 +1,197 @@
1
+ // CRC: crc-Agent.md | Seq: seq-agent-execution.md
2
+ import { v4 as uuid } from "uuid";
3
+ export class Agent {
4
+ id;
5
+ state = "pending";
6
+ config;
7
+ turns = 0;
8
+ startTime = 0;
9
+ result = null;
10
+ cancelled = false;
11
+ totalUsage = { inputTokens: 0, outputTokens: 0 };
12
+ toolCallLog = [];
13
+ modelRouter;
14
+ toolRegistry;
15
+ identityManager;
16
+ logger;
17
+ emitFn = null;
18
+ constructor(config, modelRouter, toolRegistry, identityManager, logger) {
19
+ this.id = uuid();
20
+ this.config = config;
21
+ this.modelRouter = modelRouter;
22
+ this.toolRegistry = toolRegistry;
23
+ this.identityManager = identityManager;
24
+ this.logger = logger;
25
+ }
26
+ setEmitter(fn) {
27
+ this.emitFn = fn;
28
+ }
29
+ getState() {
30
+ return this.state;
31
+ }
32
+ getResult() {
33
+ return this.result;
34
+ }
35
+ // CRC: crc-Agent.md — run()
36
+ async run() {
37
+ this.state = "running";
38
+ this.startTime = Date.now();
39
+ this.emitProgress("started");
40
+ const maxTurns = this.config.maxTurns ?? 20;
41
+ const systemPrompt = this.identityManager.buildSystemPrompt({
42
+ channel: this.config.channel,
43
+ taskOverlay: this.config.taskOverlay,
44
+ taskPrompt: this.config.systemPrompt,
45
+ });
46
+ const messages = [
47
+ { role: "system", content: systemPrompt },
48
+ { role: "user", content: this.config.prompt },
49
+ ];
50
+ const tools = this.toolRegistry.getForAgent(this.config.tools);
51
+ const toolSchemas = tools.length > 0
52
+ ? tools.map((t) => ({ name: t.name, description: t.description, parameters: t.parameters }))
53
+ : undefined;
54
+ try {
55
+ while (this.turns < maxTurns && !this.cancelled) {
56
+ if (this.isTimedOut()) {
57
+ this.state = "failed";
58
+ this.emitError("Agent timed out");
59
+ return this.buildResult("Agent timed out after " + (this.config.timeout ?? 300_000) + "ms");
60
+ }
61
+ this.turns++;
62
+ let fullContent = "";
63
+ const pendingToolCalls = [];
64
+ // Stream inference
65
+ for await (const chunk of this.modelRouter.chat({
66
+ model: this.modelRouter.resolve(this.config.model),
67
+ messages,
68
+ tools: toolSchemas,
69
+ maxTokens: 4096,
70
+ })) {
71
+ if (chunk.content)
72
+ fullContent += chunk.content;
73
+ if (chunk.toolCalls)
74
+ pendingToolCalls.push(...chunk.toolCalls);
75
+ if (chunk.usage) {
76
+ this.totalUsage.inputTokens += chunk.usage.inputTokens;
77
+ this.totalUsage.outputTokens += chunk.usage.outputTokens;
78
+ }
79
+ }
80
+ // No tool calls — agent is done
81
+ if (pendingToolCalls.length === 0) {
82
+ this.state = "complete";
83
+ this.result = this.buildResult(fullContent);
84
+ this.emitResult();
85
+ return this.result;
86
+ }
87
+ // Record assistant message with tool calls
88
+ messages.push({
89
+ role: "assistant",
90
+ content: fullContent,
91
+ toolCalls: pendingToolCalls,
92
+ });
93
+ // Execute tool calls
94
+ for (const tc of pendingToolCalls) {
95
+ if (this.cancelled)
96
+ break;
97
+ const start = Date.now();
98
+ let params;
99
+ try {
100
+ params = JSON.parse(tc.arguments);
101
+ }
102
+ catch {
103
+ params = tc.arguments;
104
+ }
105
+ const result = await this.toolRegistry.execute(tc.name, params, {
106
+ agentId: this.id,
107
+ sandbox: this.config.sandbox ?? false,
108
+ workingDir: process.cwd(),
109
+ });
110
+ this.toolCallLog.push({
111
+ tool: tc.name,
112
+ params,
113
+ result: result.error ?? result.output,
114
+ durationMs: Date.now() - start,
115
+ });
116
+ messages.push({
117
+ role: "tool",
118
+ content: result.error ?? result.output,
119
+ toolCallId: tc.id,
120
+ });
121
+ }
122
+ this.emitProgress(`turn ${this.turns}/${maxTurns}`);
123
+ }
124
+ // Exhausted turns
125
+ if (this.cancelled) {
126
+ this.state = "cancelled";
127
+ this.result = this.buildResult("Agent cancelled");
128
+ }
129
+ else {
130
+ this.state = "complete";
131
+ this.result = this.buildResult("Agent reached max turns");
132
+ }
133
+ this.emitResult();
134
+ return this.result;
135
+ }
136
+ catch (err) {
137
+ this.state = "failed";
138
+ const errorMsg = String(err.message ?? err);
139
+ this.emitError(errorMsg);
140
+ return this.buildResult(errorMsg);
141
+ }
142
+ }
143
+ // CRC: crc-Agent.md — cancel()
144
+ cancel() {
145
+ this.cancelled = true;
146
+ this.logger.info("Agent cancellation requested", { id: this.id });
147
+ }
148
+ // CRC: crc-Agent.md — isTimedOut()
149
+ isTimedOut() {
150
+ if (!this.config.timeout)
151
+ return false;
152
+ return Date.now() - this.startTime > this.config.timeout;
153
+ }
154
+ // CRC: crc-Agent.md — isOverTurns()
155
+ isOverTurns() {
156
+ return this.turns >= (this.config.maxTurns ?? 20);
157
+ }
158
+ buildResult(output) {
159
+ return {
160
+ output,
161
+ toolCalls: [...this.toolCallLog],
162
+ usage: { ...this.totalUsage },
163
+ };
164
+ }
165
+ // CRC: crc-Agent.md — emitProgress()
166
+ emitProgress(progress) {
167
+ this.emitFn?.({
168
+ id: uuid(),
169
+ type: "agent_progress",
170
+ source: `agent:${this.id}`,
171
+ timestamp: Date.now(),
172
+ payload: { agentId: this.id, progress, turns: this.turns },
173
+ });
174
+ }
175
+ // CRC: crc-Agent.md — emitResult()
176
+ emitResult() {
177
+ this.emitFn?.({
178
+ id: uuid(),
179
+ type: "agent_complete",
180
+ source: `agent:${this.id}`,
181
+ timestamp: Date.now(),
182
+ replyTo: this.config.replyTo,
183
+ payload: { agentId: this.id, result: this.result, state: this.state },
184
+ });
185
+ }
186
+ // CRC: crc-Agent.md — emitError()
187
+ emitError(error) {
188
+ this.emitFn?.({
189
+ id: uuid(),
190
+ type: "agent_error",
191
+ source: `agent:${this.id}`,
192
+ timestamp: Date.now(),
193
+ payload: { agentId: this.id, error },
194
+ });
195
+ }
196
+ }
197
+ //# sourceMappingURL=agent.js.map