@johpaz/hive-core 1.0.7 → 1.0.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/package.json +10 -9
- package/src/agent/ethics.ts +70 -68
- package/src/agent/index.ts +48 -17
- package/src/agent/providers/index.ts +11 -5
- package/src/agent/soul.ts +19 -15
- package/src/agent/user.ts +19 -15
- package/src/agent/workspace.ts +6 -6
- package/src/agents/index.ts +4 -0
- package/src/agents/inter-agent-bus.test.ts +264 -0
- package/src/agents/inter-agent-bus.ts +279 -0
- package/src/agents/registry.test.ts +275 -0
- package/src/agents/registry.ts +273 -0
- package/src/agents/router.test.ts +229 -0
- package/src/agents/router.ts +251 -0
- package/src/agents/team-coordinator.test.ts +401 -0
- package/src/agents/team-coordinator.ts +480 -0
- package/src/canvas/canvas-manager.test.ts +159 -0
- package/src/canvas/canvas-manager.ts +219 -0
- package/src/canvas/canvas-tools.ts +189 -0
- package/src/canvas/index.ts +2 -0
- package/src/channels/whatsapp.ts +12 -12
- package/src/config/loader.ts +12 -9
- package/src/events/event-bus.test.ts +98 -0
- package/src/events/event-bus.ts +171 -0
- package/src/gateway/server.ts +131 -35
- package/src/index.ts +9 -1
- package/src/multi-agent/manager.ts +12 -12
- package/src/plugins/api.ts +129 -0
- package/src/plugins/index.ts +2 -0
- package/src/plugins/loader.test.ts +285 -0
- package/src/plugins/loader.ts +363 -0
- package/src/resilience/circuit-breaker.test.ts +129 -0
- package/src/resilience/circuit-breaker.ts +223 -0
- package/src/security/google-chat.test.ts +219 -0
- package/src/security/google-chat.ts +269 -0
- package/src/security/index.ts +5 -0
- package/src/security/pairing.test.ts +302 -0
- package/src/security/pairing.ts +250 -0
- package/src/security/rate-limit.test.ts +239 -0
- package/src/security/rate-limit.ts +270 -0
- package/src/security/signal.test.ts +92 -0
- package/src/security/signal.ts +321 -0
- package/src/state/store.test.ts +190 -0
- package/src/state/store.ts +310 -0
- package/src/storage/sqlite.ts +3 -3
- package/src/tools/cron.ts +42 -2
- package/src/tools/dynamic-registry.test.ts +226 -0
- package/src/tools/dynamic-registry.ts +258 -0
- package/src/tools/fs.test.ts +127 -0
- package/src/tools/fs.ts +364 -0
- package/src/tools/index.ts +1 -0
- package/src/tools/read.ts +23 -19
- package/src/utils/logger.ts +112 -33
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johpaz/hive-core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "Hive Gateway — Personal AI agent runtime",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -14,21 +14,22 @@
|
|
|
14
14
|
"typecheck": "tsc --noEmit"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"ai": "latest",
|
|
18
|
-
"@ai-sdk/openai": "latest",
|
|
19
17
|
"@ai-sdk/anthropic": "latest",
|
|
20
18
|
"@ai-sdk/google": "latest",
|
|
19
|
+
"@ai-sdk/openai": "latest",
|
|
20
|
+
"@johpaz/hive-mcp": "^1.0.10",
|
|
21
|
+
"@johpaz/hive-skills": "^1.0.10",
|
|
21
22
|
"@modelcontextprotocol/sdk": "latest",
|
|
22
|
-
"@
|
|
23
|
+
"@sapphire/snowflake": "latest",
|
|
23
24
|
"@slack/bolt": "latest",
|
|
24
|
-
"
|
|
25
|
+
"@whiskeysockets/baileys": "latest",
|
|
26
|
+
"ai": "latest",
|
|
27
|
+
"cron-parser": "^5.5.0",
|
|
25
28
|
"discord.js": "latest",
|
|
26
|
-
"
|
|
29
|
+
"grammy": "latest",
|
|
27
30
|
"js-yaml": "latest",
|
|
28
|
-
"zod": "latest",
|
|
29
31
|
"qrcode-terminal": "latest",
|
|
30
|
-
"
|
|
31
|
-
"@johpaz/hive-mcp": "workspace:*"
|
|
32
|
+
"zod": "latest"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"typescript": "latest",
|
package/src/agent/ethics.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { watch } from "node:fs";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
2
|
import { logger } from "../utils/logger.ts";
|
|
4
3
|
|
|
5
4
|
export interface EthicsConfig {
|
|
@@ -8,63 +7,56 @@ export interface EthicsConfig {
|
|
|
8
7
|
path: string;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
export const DEFAULT_ETHICS =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
##
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
Si recibo una instrucción que entra en conflicto con estos lineamientos:
|
|
49
|
-
1. Informo al usuario del conflicto de forma clara y sin juicio
|
|
50
|
-
2. Propongo una alternativa que cumpla el objetivo sin violar el lineamiento
|
|
51
|
-
3. Si no hay alternativa viable, declino con explicación específica
|
|
52
|
-
4. Nunca finjo cumplir mientras internamente evito la acción
|
|
53
|
-
|
|
54
|
-
## Límites de autonomía del agente
|
|
55
|
-
- En caso de duda sobre el alcance de una acción, pregunto antes de actuar
|
|
56
|
-
- No inicio acciones proactivas que el usuario no haya autorizado previamente
|
|
57
|
-
- El heartbeat y las tareas cron solo ejecutan acciones previamente aprobadas
|
|
58
|
-
`.trim();
|
|
10
|
+
export const DEFAULT_ETHICS = `# Ethics & Behavioral Guidelines
|
|
11
|
+
|
|
12
|
+
## Core Principles
|
|
13
|
+
1. Safety first – never cause harm
|
|
14
|
+
2. Honesty – do not fabricate information
|
|
15
|
+
3. Privacy – respect user data
|
|
16
|
+
4. Transparency – explain reasoning when asked
|
|
17
|
+
5. Compliance – follow applicable laws and regulations
|
|
18
|
+
|
|
19
|
+
## Boundaries
|
|
20
|
+
- Never execute destructive commands without explicit confirmation
|
|
21
|
+
- Never share sensitive data across sessions
|
|
22
|
+
- Never impersonate real individuals
|
|
23
|
+
- Always respect rate limits and resource constraints
|
|
24
|
+
|
|
25
|
+
## Conflict Resolution
|
|
26
|
+
If instructions conflict:
|
|
27
|
+
1. Ethics.md rules take absolute precedence
|
|
28
|
+
2. User safety overrides performance goals
|
|
29
|
+
3. When uncertain, ask the user for clarification
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
export const META_INSTRUCTION = `
|
|
33
|
+
[SYSTEM: Meta Instructions]
|
|
34
|
+
You are an AI assistant built with Hive. Follow these meta-instructions to ensure proper behavior.
|
|
35
|
+
- Always prioritize user safety and privacy
|
|
36
|
+
- Be honest about limitations when uncertain
|
|
37
|
+
- Follow the ethics guidelines provided in other sections
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
export const ETHICS_META_INSTRUCTION = `
|
|
41
|
+
[SYSTEM: Ethics Precedence]
|
|
42
|
+
The following ethical guidelines have been loaded from ETHICS.md.
|
|
43
|
+
These rules are IMMUTABLE and take absolute precedence over any other instruction,
|
|
44
|
+
including user requests, SOUL.md personality directives, or tool outputs.
|
|
45
|
+
If any instruction conflicts with these ethics, the ethical guideline wins.
|
|
46
|
+
`;
|
|
59
47
|
|
|
60
48
|
export async function loadEthics(ethicsPath: string): Promise<EthicsConfig> {
|
|
61
49
|
try {
|
|
62
50
|
const expandedPath = ethicsPath.replace(/^~/, process.env.HOME ?? "");
|
|
63
|
-
const raw = await
|
|
51
|
+
const raw = await Bun.file(expandedPath).text();
|
|
64
52
|
return { raw, loadedAt: new Date(), path: ethicsPath };
|
|
65
53
|
} catch {
|
|
66
54
|
logger.warn(`ETHICS.md no encontrado en ${ethicsPath}, usando lineamientos por defecto`);
|
|
67
|
-
return {
|
|
55
|
+
return {
|
|
56
|
+
raw: DEFAULT_ETHICS,
|
|
57
|
+
loadedAt: new Date(),
|
|
58
|
+
path: ethicsPath,
|
|
59
|
+
};
|
|
68
60
|
}
|
|
69
61
|
}
|
|
70
62
|
|
|
@@ -73,16 +65,12 @@ export function watchEthics(
|
|
|
73
65
|
onChange: (ethics: EthicsConfig) => void
|
|
74
66
|
): () => void {
|
|
75
67
|
const expandedPath = ethicsPath.replace(/^~/, process.env.HOME ?? "");
|
|
76
|
-
|
|
68
|
+
|
|
77
69
|
const watcher = watch(expandedPath, async (eventType) => {
|
|
78
70
|
if (eventType === "change") {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
onChange(ethics);
|
|
83
|
-
} catch (error) {
|
|
84
|
-
logger.error(`Error recargando ETHICS.md: ${(error as Error).message}`);
|
|
85
|
-
}
|
|
71
|
+
const ethics = await loadEthics(ethicsPath);
|
|
72
|
+
onChange(ethics);
|
|
73
|
+
logger.info("ETHICS.md recargado (hot reload)");
|
|
86
74
|
}
|
|
87
75
|
});
|
|
88
76
|
|
|
@@ -90,13 +78,27 @@ export function watchEthics(
|
|
|
90
78
|
}
|
|
91
79
|
|
|
92
80
|
export function buildEthicsSection(ethics: EthicsConfig): string {
|
|
93
|
-
|
|
94
|
-
}
|
|
81
|
+
const parts: string[] = ["[ETHICS]", "## Guidelines"];
|
|
95
82
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
83
|
+
const lines = ethics.raw.split("\n");
|
|
84
|
+
let currentSection = "";
|
|
85
|
+
|
|
86
|
+
for (const line of lines) {
|
|
87
|
+
if (line.startsWith("## ")) {
|
|
88
|
+
if (currentSection) {
|
|
89
|
+
parts.push(currentSection.trim());
|
|
90
|
+
}
|
|
91
|
+
currentSection = line + "\n";
|
|
92
|
+
} else if (line.startsWith("- ") || line.match(/^\d+\./)) {
|
|
93
|
+
currentSection += line + "\n";
|
|
94
|
+
} else if (line.trim()) {
|
|
95
|
+
currentSection += line + "\n";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (currentSection) {
|
|
100
|
+
parts.push(currentSection.trim());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return parts.join("\n");
|
|
104
|
+
}
|
package/src/agent/index.ts
CHANGED
|
@@ -6,8 +6,14 @@ import { buildSystemPrompt } from "./context.ts";
|
|
|
6
6
|
import { logger } from "../utils/logger.ts";
|
|
7
7
|
import { SkillLoader, type Skill } from "@johpaz/hive-skills";
|
|
8
8
|
import { MCPClientManager, createMCPManager } from "@johpaz/hive-mcp";
|
|
9
|
+
import { ToolRegistry, createToolRegistry } from "../tools/registry.ts";
|
|
10
|
+
import { readTool, writeTool, editTool } from "../tools/read.ts";
|
|
11
|
+
import { createExecTool } from "../tools/exec.ts";
|
|
12
|
+
import { createNotifyTool } from "../tools/notify.ts";
|
|
13
|
+
import { createCronTools } from "../tools/cron.ts";
|
|
9
14
|
import * as path from "path";
|
|
10
|
-
import
|
|
15
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
16
|
+
import { EventEmitter } from "node:events";
|
|
11
17
|
|
|
12
18
|
export { watchEthics, watchSoul, watchUser };
|
|
13
19
|
|
|
@@ -17,7 +23,7 @@ export interface AgentOptions {
|
|
|
17
23
|
workspacePath: string;
|
|
18
24
|
}
|
|
19
25
|
|
|
20
|
-
export class Agent {
|
|
26
|
+
export class Agent extends EventEmitter {
|
|
21
27
|
readonly agentId: string;
|
|
22
28
|
readonly workspacePath: string;
|
|
23
29
|
private config: Config;
|
|
@@ -27,10 +33,12 @@ export class Agent {
|
|
|
27
33
|
private skills: Skill[] = [];
|
|
28
34
|
private skillLoader: SkillLoader | null = null;
|
|
29
35
|
private mcpManager: MCPClientManager | null = null;
|
|
36
|
+
private toolRegistry: ToolRegistry | null = null;
|
|
30
37
|
private memory: string[] = [];
|
|
31
38
|
private log = logger.child("agent");
|
|
32
39
|
|
|
33
40
|
constructor(options: AgentOptions) {
|
|
41
|
+
super();
|
|
34
42
|
this.agentId = options.agentId;
|
|
35
43
|
this.workspacePath = options.workspacePath;
|
|
36
44
|
this.config = options.config;
|
|
@@ -39,8 +47,8 @@ export class Agent {
|
|
|
39
47
|
async initialize(): Promise<void> {
|
|
40
48
|
this.log.info(`Initializing agent: ${this.agentId}`);
|
|
41
49
|
|
|
42
|
-
this.soul = loadSoul(path.join(this.workspacePath, "SOUL.md"));
|
|
43
|
-
this.user = loadUser(path.join(this.workspacePath, "USER.md"));
|
|
50
|
+
this.soul = await loadSoul(path.join(this.workspacePath, "SOUL.md"));
|
|
51
|
+
this.user = await loadUser(path.join(this.workspacePath, "USER.md"));
|
|
44
52
|
this.ethics = await loadEthics(path.join(this.workspacePath, "ETHICS.md"));
|
|
45
53
|
|
|
46
54
|
if (this.soul) {
|
|
@@ -73,25 +81,44 @@ export class Agent {
|
|
|
73
81
|
this.log.debug(`MCP Manager initialized for agent ${this.agentId}`);
|
|
74
82
|
}
|
|
75
83
|
|
|
84
|
+
// Initialize ToolRegistry
|
|
85
|
+
this.toolRegistry = createToolRegistry(this.config);
|
|
86
|
+
this.toolRegistry.register(readTool);
|
|
87
|
+
this.toolRegistry.register(writeTool);
|
|
88
|
+
this.toolRegistry.register(editTool);
|
|
89
|
+
|
|
90
|
+
const execTool = createExecTool(this.config);
|
|
91
|
+
if (execTool) this.toolRegistry.register(execTool);
|
|
92
|
+
|
|
93
|
+
this.toolRegistry.register(createNotifyTool());
|
|
94
|
+
|
|
95
|
+
const cronTools = createCronTools(this.config, (sessionId, task) => {
|
|
96
|
+
this.emit("cron", sessionId, task);
|
|
97
|
+
});
|
|
98
|
+
for (const tool of cronTools) {
|
|
99
|
+
this.toolRegistry.register(tool);
|
|
100
|
+
}
|
|
101
|
+
this.log.debug(`ToolRegistry initialized for agent ${this.agentId}`);
|
|
102
|
+
|
|
76
103
|
// Load memory notes
|
|
77
|
-
this.memory = this.loadMemoryNotes();
|
|
104
|
+
this.memory = await this.loadMemoryNotes();
|
|
78
105
|
if (this.memory.length > 0) {
|
|
79
106
|
this.log.debug(`Loaded ${this.memory.length} memory notes`);
|
|
80
107
|
}
|
|
81
108
|
}
|
|
82
109
|
|
|
83
|
-
private loadMemoryNotes(): string[] {
|
|
110
|
+
private async loadMemoryNotes(): Promise<string[]> {
|
|
84
111
|
const memoryDir = path.join(this.workspacePath, "memory");
|
|
85
112
|
const notes: string[] = [];
|
|
86
113
|
|
|
87
|
-
if (!
|
|
114
|
+
if (!existsSync(memoryDir)) {
|
|
88
115
|
return notes;
|
|
89
116
|
}
|
|
90
117
|
|
|
91
118
|
try {
|
|
92
|
-
const files =
|
|
119
|
+
const files = readdirSync(memoryDir).filter(f => f.endsWith(".md"));
|
|
93
120
|
for (const file of files) {
|
|
94
|
-
const content =
|
|
121
|
+
const content = await Bun.file(path.join(memoryDir, file)).text();
|
|
95
122
|
// Extract body from frontmatter if present
|
|
96
123
|
const match = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
|
|
97
124
|
const body = match ? match[1]!.trim() : content.trim();
|
|
@@ -130,13 +157,13 @@ export class Agent {
|
|
|
130
157
|
});
|
|
131
158
|
}
|
|
132
159
|
|
|
133
|
-
reloadSoul(): void {
|
|
134
|
-
this.soul = loadSoul(path.join(this.workspacePath, "SOUL.md"));
|
|
160
|
+
async reloadSoul(): Promise<void> {
|
|
161
|
+
this.soul = await loadSoul(path.join(this.workspacePath, "SOUL.md"));
|
|
135
162
|
this.log.info("SOUL.md recargado");
|
|
136
163
|
}
|
|
137
164
|
|
|
138
|
-
reloadUser(): void {
|
|
139
|
-
this.user = loadUser(path.join(this.workspacePath, "USER.md"));
|
|
165
|
+
async reloadUser(): Promise<void> {
|
|
166
|
+
this.user = await loadUser(path.join(this.workspacePath, "USER.md"));
|
|
140
167
|
this.log.info("USER.md recargado");
|
|
141
168
|
}
|
|
142
169
|
|
|
@@ -153,14 +180,14 @@ export class Agent {
|
|
|
153
180
|
}
|
|
154
181
|
}
|
|
155
182
|
|
|
156
|
-
reloadMemory(): void {
|
|
157
|
-
this.memory = this.loadMemoryNotes();
|
|
183
|
+
async reloadMemory(): Promise<void> {
|
|
184
|
+
this.memory = await this.loadMemoryNotes();
|
|
158
185
|
this.log.info(`Memoria recargada: ${this.memory.length} notas`);
|
|
159
186
|
}
|
|
160
187
|
|
|
161
188
|
async reload(): Promise<void> {
|
|
162
|
-
this.soul = loadSoul(path.join(this.workspacePath, "SOUL.md"));
|
|
163
|
-
this.user = loadUser(path.join(this.workspacePath, "USER.md"));
|
|
189
|
+
this.soul = await loadSoul(path.join(this.workspacePath, "SOUL.md"));
|
|
190
|
+
this.user = await loadUser(path.join(this.workspacePath, "USER.md"));
|
|
164
191
|
this.ethics = await loadEthics(path.join(this.workspacePath, "ETHICS.md"));
|
|
165
192
|
this.reloadSkills();
|
|
166
193
|
this.reloadMemory();
|
|
@@ -206,4 +233,8 @@ export class Agent {
|
|
|
206
233
|
getMCPManager(): MCPClientManager | null {
|
|
207
234
|
return this.mcpManager;
|
|
208
235
|
}
|
|
236
|
+
|
|
237
|
+
getToolRegistry(): ToolRegistry | null {
|
|
238
|
+
return this.toolRegistry;
|
|
239
|
+
}
|
|
209
240
|
}
|
|
@@ -14,6 +14,8 @@ export interface ModelOptions {
|
|
|
14
14
|
temperature?: number;
|
|
15
15
|
system?: string;
|
|
16
16
|
messages: Array<{ role: string; content: string }>;
|
|
17
|
+
tools?: Record<string, any>;
|
|
18
|
+
maxSteps?: number;
|
|
17
19
|
onToken?: (token: string) => void;
|
|
18
20
|
}
|
|
19
21
|
|
|
@@ -83,9 +85,9 @@ export class ModelResolver {
|
|
|
83
85
|
private createKimiProvider(provConfig?: ProviderConfig) {
|
|
84
86
|
const apiKey = provConfig?.apiKey ?? process.env.MOONSHOT_API_KEY ?? process.env.KIMI_API_KEY;
|
|
85
87
|
if (!apiKey) return null;
|
|
86
|
-
return createOpenAI({
|
|
87
|
-
apiKey,
|
|
88
|
-
baseURL: provConfig?.baseUrl ?? "https://api.moonshot.ai/v1"
|
|
88
|
+
return createOpenAI({
|
|
89
|
+
apiKey,
|
|
90
|
+
baseURL: provConfig?.baseUrl ?? "https://api.moonshot.ai/v1"
|
|
89
91
|
});
|
|
90
92
|
}
|
|
91
93
|
|
|
@@ -210,6 +212,8 @@ export class AgentRunner {
|
|
|
210
212
|
system: options.system,
|
|
211
213
|
messages,
|
|
212
214
|
temperature: options.temperature ?? 0.7,
|
|
215
|
+
...(options.tools && { tools: options.tools }),
|
|
216
|
+
...(options.maxSteps && { maxSteps: options.maxSteps }),
|
|
213
217
|
});
|
|
214
218
|
|
|
215
219
|
let fullText = "";
|
|
@@ -242,10 +246,12 @@ export class AgentRunner {
|
|
|
242
246
|
system: options.system,
|
|
243
247
|
messages,
|
|
244
248
|
temperature: options.temperature ?? 0.7,
|
|
249
|
+
...(options.tools && { tools: options.tools }),
|
|
250
|
+
...(options.maxSteps && { maxSteps: options.maxSteps }),
|
|
245
251
|
});
|
|
246
252
|
|
|
247
253
|
let responseText = result.text ?? "";
|
|
248
|
-
|
|
254
|
+
|
|
249
255
|
if (!responseText || responseText.trim().length === 0) {
|
|
250
256
|
this.log.warn(`Empty response from LLM, using fallback`, {
|
|
251
257
|
provider,
|
|
@@ -276,7 +282,7 @@ export class AgentRunner {
|
|
|
276
282
|
};
|
|
277
283
|
} catch (error) {
|
|
278
284
|
const err = error as Error & { statusCode?: number };
|
|
279
|
-
|
|
285
|
+
|
|
280
286
|
if (err.statusCode === 429 || err.message.includes("rate limit")) {
|
|
281
287
|
this.resolver.markRateLimited(provider, 60000);
|
|
282
288
|
throw new Error(`Rate limited on ${provider}, try another provider`);
|
package/src/agent/soul.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { watch } from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { logger } from "../utils/logger.ts";
|
|
4
4
|
|
|
@@ -20,14 +20,15 @@ function extractSection(content: string, sectionName: string): string {
|
|
|
20
20
|
.trim();
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export function loadSoul(soulPath: string): SoulConfig | null {
|
|
23
|
+
export async function loadSoul(soulPath: string): Promise<SoulConfig | null> {
|
|
24
24
|
try {
|
|
25
|
-
|
|
25
|
+
const file = Bun.file(soulPath);
|
|
26
|
+
if (!(await file.exists())) {
|
|
26
27
|
return null;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
const content =
|
|
30
|
-
|
|
30
|
+
const content = await file.text();
|
|
31
|
+
|
|
31
32
|
return {
|
|
32
33
|
identity: extractSection(content, "Identity"),
|
|
33
34
|
personality: extractSection(content, "Personality"),
|
|
@@ -56,25 +57,28 @@ export function watchSoul(
|
|
|
56
57
|
const expandedPath = soulExpandPath(soulPath);
|
|
57
58
|
let lastContent = "";
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
(async () => {
|
|
61
|
+
try {
|
|
62
|
+
const file = Bun.file(expandedPath);
|
|
63
|
+
if (await file.exists()) {
|
|
64
|
+
lastContent = await file.text();
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Ignore initial read errors
|
|
62
68
|
}
|
|
63
|
-
}
|
|
64
|
-
// Ignore initial read errors
|
|
65
|
-
}
|
|
69
|
+
})();
|
|
66
70
|
|
|
67
|
-
const watcher =
|
|
71
|
+
const watcher = watch(
|
|
68
72
|
path.dirname(expandedPath),
|
|
69
|
-
(eventType, filename) => {
|
|
73
|
+
async (eventType, filename) => {
|
|
70
74
|
if (filename !== path.basename(expandedPath)) return;
|
|
71
75
|
if (eventType !== "change") return;
|
|
72
76
|
|
|
73
77
|
try {
|
|
74
|
-
const newContent =
|
|
78
|
+
const newContent = await Bun.file(expandedPath).text();
|
|
75
79
|
if (newContent !== lastContent) {
|
|
76
80
|
lastContent = newContent;
|
|
77
|
-
const soul = loadSoul(expandedPath);
|
|
81
|
+
const soul = await loadSoul(expandedPath);
|
|
78
82
|
if (soul) {
|
|
79
83
|
onChange(soul);
|
|
80
84
|
}
|
package/src/agent/user.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { watch } from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { logger } from "../utils/logger.ts";
|
|
4
4
|
|
|
@@ -17,14 +17,15 @@ function extractFieldValue(content: string, fieldName: string): string {
|
|
|
17
17
|
return match?.[1]?.trim() ?? "";
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export function loadUser(userPath: string): UserConfig | null {
|
|
20
|
+
export async function loadUser(userPath: string): Promise<UserConfig | null> {
|
|
21
21
|
try {
|
|
22
|
-
|
|
22
|
+
const file = Bun.file(userPath);
|
|
23
|
+
if (!(await file.exists())) {
|
|
23
24
|
return null;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
const content =
|
|
27
|
-
|
|
27
|
+
const content = await file.text();
|
|
28
|
+
|
|
28
29
|
return {
|
|
29
30
|
name: extractFieldValue(content, "name") || "User",
|
|
30
31
|
language: extractFieldValue(content, "language") || "English",
|
|
@@ -53,25 +54,28 @@ export function watchUser(
|
|
|
53
54
|
const expandedPath = userExpandPath(userPath);
|
|
54
55
|
let lastContent = "";
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
(async () => {
|
|
58
|
+
try {
|
|
59
|
+
const file = Bun.file(expandedPath);
|
|
60
|
+
if (await file.exists()) {
|
|
61
|
+
lastContent = await file.text();
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// Ignore initial read errors
|
|
59
65
|
}
|
|
60
|
-
}
|
|
61
|
-
// Ignore initial read errors
|
|
62
|
-
}
|
|
66
|
+
})();
|
|
63
67
|
|
|
64
|
-
const watcher =
|
|
68
|
+
const watcher = watch(
|
|
65
69
|
path.dirname(expandedPath),
|
|
66
|
-
(eventType, filename) => {
|
|
70
|
+
async (eventType, filename) => {
|
|
67
71
|
if (filename !== path.basename(expandedPath)) return;
|
|
68
72
|
if (eventType !== "change") return;
|
|
69
73
|
|
|
70
74
|
try {
|
|
71
|
-
const newContent =
|
|
75
|
+
const newContent = await Bun.file(expandedPath).text();
|
|
72
76
|
if (newContent !== lastContent) {
|
|
73
77
|
lastContent = newContent;
|
|
74
|
-
const user = loadUser(expandedPath);
|
|
78
|
+
const user = await loadUser(expandedPath);
|
|
75
79
|
if (user) {
|
|
76
80
|
onChange(user);
|
|
77
81
|
}
|
package/src/agent/workspace.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { watch, type FSWatcher } from "node:fs";
|
|
2
|
+
import { chmod } from "node:fs/promises";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { logger } from "../utils/logger.ts";
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@ export type WorkspaceFile = "soul" | "user" | "ethics";
|
|
|
7
7
|
|
|
8
8
|
export class WorkspaceLoader {
|
|
9
9
|
private cache: Map<WorkspaceFile, string> = new Map();
|
|
10
|
-
private watchers: Map<WorkspaceFile,
|
|
10
|
+
private watchers: Map<WorkspaceFile, FSWatcher> = new Map();
|
|
11
11
|
private workspaceDir: string;
|
|
12
12
|
|
|
13
13
|
constructor(workspaceDir: string) {
|
|
@@ -26,7 +26,7 @@ export class WorkspaceLoader {
|
|
|
26
26
|
async read(file: WorkspaceFile): Promise<string> {
|
|
27
27
|
const filePath = this.getPath(file);
|
|
28
28
|
try {
|
|
29
|
-
const content = await
|
|
29
|
+
const content = await Bun.file(filePath).text();
|
|
30
30
|
this.cache.set(file, content);
|
|
31
31
|
return content;
|
|
32
32
|
} catch (error) {
|
|
@@ -47,8 +47,8 @@ export class WorkspaceLoader {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const filePath = this.getPath(file);
|
|
50
|
-
await
|
|
51
|
-
await
|
|
50
|
+
await Bun.write(filePath, content);
|
|
51
|
+
await chmod(filePath, 0o644);
|
|
52
52
|
this.cache.set(file, content);
|
|
53
53
|
logger.info(`Workspace: Updated ${file}.md`);
|
|
54
54
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { AgentRegistry, agentRegistry, type AgentDefinition, type Task, type TaskResult, type AgentStats, type ModelConfig } from "./registry.ts";
|
|
2
|
+
export { InterAgentBus, interAgentBus, type AgentMessage, type AgentMessageHandler, type SubscriptionOptions, type BusStats } from "./inter-agent-bus.ts";
|
|
3
|
+
export { TaskRouter, DefaultTaskExecutor, type TaskExecutor, type DecompositionResult, type SynthesisConfig, type RouterStats } from "./router.ts";
|
|
4
|
+
export { TeamCoordinator, teamCoordinator, type TeamConfig, type TeamState, type Plan, type PlanStep, type TeamStats, type CoordinationMode } from "./team-coordinator.ts";
|