@c4ccz/zero 1.0.0 → 1.0.1
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 -7
- package/packages/core/src/agent/config-loader.ts +174 -0
- package/packages/core/src/agent/engine.ts +266 -0
- package/packages/core/src/agent/registry-tools.ts +58 -0
- package/packages/core/src/agent/registry.ts +213 -0
- package/packages/core/src/commands/index.ts +116 -0
- package/packages/core/src/config/index.ts +139 -0
- package/packages/core/src/confirmation/message-bus.ts +206 -0
- package/packages/core/src/diff/index.ts +232 -0
- package/packages/core/src/diff-detect/index.ts +207 -0
- package/packages/core/src/edit-coder/index.ts +194 -0
- package/packages/core/src/events/index.ts +151 -0
- package/packages/core/src/file-tracker/index.ts +183 -0
- package/packages/core/src/git/index.ts +305 -0
- package/packages/core/src/history/index.ts +236 -0
- package/packages/core/src/image/index.ts +131 -0
- package/packages/core/src/index.ts +131 -0
- package/packages/core/src/lsp/index.ts +251 -0
- package/packages/core/src/mcp/index.ts +186 -0
- package/packages/core/src/memory/index.ts +141 -0
- package/packages/core/src/memory/prompts.ts +52 -0
- package/packages/core/src/orchestrator/protocol.ts +140 -0
- package/packages/core/src/orchestrator/server.ts +319 -0
- package/packages/core/src/output/index.ts +164 -0
- package/packages/core/src/permissions/index.ts +126 -0
- package/packages/core/src/prompts/engine.ts +188 -0
- package/packages/core/src/providers/extended.ts +8 -0
- package/packages/core/src/providers/registry.ts +687 -0
- package/packages/core/src/routing/model-router.ts +160 -0
- package/packages/core/src/server/index.ts +232 -0
- package/packages/core/src/session/index.ts +174 -0
- package/packages/core/src/session/service.ts +322 -0
- package/packages/core/src/skills/index.ts +205 -0
- package/packages/core/src/streaming/index.ts +166 -0
- package/packages/core/src/sub-agent/index.ts +179 -0
- package/packages/core/src/tools/builtin.ts +568 -0
- package/packages/core/src/tools/linter.ts +26 -0
- package/packages/core/src/tools/repo-map.ts +11 -0
- package/packages/core/src/types.ts +356 -0
- package/packages/sdk/src/index.ts +247 -0
- package/packages/tui/src/index.ts +141 -0
- package/bun.lock +0 -35
- package/bunfig.toml +0 -6
- package/packages/cli/package.json +0 -23
- package/packages/core/package.json +0 -41
- package/packages/sdk/package.json +0 -16
- package/packages/tui/package.json +0 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c4ccz/zero",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "ZERO - Unified AI Coding Assistant. Merges 9 open-source AI coding projects into one powerful tool.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./packages/core/src/index.ts",
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
"./sdk": "./packages/sdk/src/index.ts",
|
|
16
16
|
"./tui": "./packages/tui/src/index.ts"
|
|
17
17
|
},
|
|
18
|
+
"files": [
|
|
19
|
+
"packages/",
|
|
20
|
+
"README.md",
|
|
21
|
+
"MERGE_REPORT.md",
|
|
22
|
+
"tsconfig.json"
|
|
23
|
+
],
|
|
18
24
|
"scripts": {
|
|
19
25
|
"build": "echo 'ZERO uses Bun runtime - no build step required'",
|
|
20
26
|
"dev": "bun run packages/cli/src/index.ts",
|
|
@@ -27,19 +33,16 @@
|
|
|
27
33
|
"gemini", "openai", "anthropic", "claude", "aider",
|
|
28
34
|
"code-generation", "developer-tools", "zero"
|
|
29
35
|
],
|
|
30
|
-
"author": "
|
|
36
|
+
"author": "c4ccz",
|
|
31
37
|
"license": "MIT",
|
|
32
38
|
"engines": {
|
|
33
39
|
"node": ">=20.0.0"
|
|
34
40
|
},
|
|
35
41
|
"repository": {
|
|
36
42
|
"type": "git",
|
|
37
|
-
"url": "https://github.com/
|
|
38
|
-
},
|
|
39
|
-
"bugs": {
|
|
40
|
-
"url": "https://github.com/zero-ai/zero/issues"
|
|
43
|
+
"url": "git+https://github.com/c4ccz/zero.git"
|
|
41
44
|
},
|
|
42
|
-
"homepage": "https://github.com/
|
|
45
|
+
"homepage": "https://github.com/c4ccz/zero#readme",
|
|
43
46
|
"dependencies": {
|
|
44
47
|
"zod": "^3.23.0",
|
|
45
48
|
"chalk": "^5.3.0",
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZERO Agent Config Loader
|
|
3
|
+
* Adapted from: cline - Apache 2.0
|
|
4
|
+
*
|
|
5
|
+
* Loads agent configurations from YAML/Markdown frontmatter files.
|
|
6
|
+
* Supports file watching for dynamic agent reloading.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from "node:fs/promises";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import type { AgentConfig, ToolCategory } from "../types.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Built-in tool names available for agents
|
|
15
|
+
*/
|
|
16
|
+
export const BUILTIN_TOOLS = [
|
|
17
|
+
"read_file",
|
|
18
|
+
"write_file",
|
|
19
|
+
"edit_file",
|
|
20
|
+
"list_directory",
|
|
21
|
+
"search_files",
|
|
22
|
+
"search_code",
|
|
23
|
+
"execute_shell",
|
|
24
|
+
"git_status",
|
|
25
|
+
"git_diff",
|
|
26
|
+
"git_commit",
|
|
27
|
+
"web_fetch",
|
|
28
|
+
"web_search",
|
|
29
|
+
"delegate_to_agent",
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
export type BuiltinTool = (typeof BUILTIN_TOOLS)[number];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Schema for agent configuration in YAML frontmatter
|
|
36
|
+
*/
|
|
37
|
+
export interface AgentFrontmatterConfig {
|
|
38
|
+
name: string;
|
|
39
|
+
description: string;
|
|
40
|
+
modelId?: string;
|
|
41
|
+
tools?: string | string[];
|
|
42
|
+
skills?: string | string[];
|
|
43
|
+
maxTurns?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Parse YAML frontmatter from a markdown file
|
|
48
|
+
*/
|
|
49
|
+
export function parseFrontmatter(content: string): { frontmatter: Record<string, unknown>; body: string } {
|
|
50
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
51
|
+
if (!match) {
|
|
52
|
+
return { frontmatter: {}, body: content };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const yamlStr = match[1];
|
|
56
|
+
const body = match[2];
|
|
57
|
+
|
|
58
|
+
// Simple YAML parser for key: value pairs
|
|
59
|
+
const frontmatter: Record<string, unknown> = {};
|
|
60
|
+
const lines = yamlStr.split("\n");
|
|
61
|
+
let currentKey = "";
|
|
62
|
+
let currentArray: string[] = [];
|
|
63
|
+
let inArray = false;
|
|
64
|
+
|
|
65
|
+
for (const line of lines) {
|
|
66
|
+
const trimmed = line.trim();
|
|
67
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
68
|
+
|
|
69
|
+
if (trimmed.startsWith("- ") && inArray) {
|
|
70
|
+
currentArray.push(trimmed.slice(2).trim());
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (inArray && currentKey) {
|
|
75
|
+
frontmatter[currentKey] = currentArray;
|
|
76
|
+
inArray = false;
|
|
77
|
+
currentArray = [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const colonIdx = trimmed.indexOf(":");
|
|
81
|
+
if (colonIdx > 0) {
|
|
82
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
83
|
+
const value = trimmed.slice(colonIdx + 1).trim();
|
|
84
|
+
|
|
85
|
+
if (value === "" || value === "[]") {
|
|
86
|
+
currentKey = key;
|
|
87
|
+
inArray = true;
|
|
88
|
+
currentArray = [];
|
|
89
|
+
} else {
|
|
90
|
+
frontmatter[key] = value.replace(/^["']|["']$/g, "");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (inArray && currentKey) {
|
|
96
|
+
frontmatter[currentKey] = currentArray;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { frontmatter, body };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parse tools from config (string or array)
|
|
104
|
+
*/
|
|
105
|
+
function parseTools(tools: string | string[] | undefined): string[] {
|
|
106
|
+
if (!tools) return [...BUILTIN_TOOLS].filter((t) => t !== "delegate_to_agent");
|
|
107
|
+
|
|
108
|
+
const rawTools = Array.isArray(tools) ? tools : tools.split(",").map((t) => t.trim());
|
|
109
|
+
return Array.from(new Set(rawTools.filter((t) => BUILTIN_TOOLS.includes(t as BuiltinTool))));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Parse skills from config
|
|
114
|
+
*/
|
|
115
|
+
function parseSkills(skills: string | string[] | undefined): string[] {
|
|
116
|
+
if (!skills) return [];
|
|
117
|
+
return Array.isArray(skills) ? skills : skills.split(",").map((s) => s.trim());
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Load an agent configuration from a markdown file
|
|
122
|
+
*/
|
|
123
|
+
export async function loadAgentFromFile(filePath: string): Promise<AgentConfig> {
|
|
124
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
125
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
126
|
+
|
|
127
|
+
const config = frontmatter as unknown as AgentFrontmatterConfig;
|
|
128
|
+
|
|
129
|
+
if (!config.name) {
|
|
130
|
+
throw new Error(`Agent config in ${filePath} missing required field: name`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!config.description) {
|
|
134
|
+
throw new Error(`Agent config in ${filePath} missing required field: description`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const id = path.basename(filePath, path.extname(filePath)).toLowerCase().replace(/[^a-z0-9]/g, "-");
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
id,
|
|
141
|
+
name: config.name,
|
|
142
|
+
description: config.description,
|
|
143
|
+
systemPrompt: body.trim() || `You are ${config.name}, a specialized agent.`,
|
|
144
|
+
tools: parseTools(config.tools),
|
|
145
|
+
maxTurns: config.maxTurns || 50,
|
|
146
|
+
model: config.modelId || "default",
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Load all agent configurations from a directory
|
|
152
|
+
*/
|
|
153
|
+
export async function loadAgentsFromDirectory(dirPath: string): Promise<AgentConfig[]> {
|
|
154
|
+
const agents: AgentConfig[] = [];
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
158
|
+
|
|
159
|
+
for (const entry of entries) {
|
|
160
|
+
if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".yaml"))) {
|
|
161
|
+
try {
|
|
162
|
+
const agent = await loadAgentFromFile(path.join(dirPath, entry.name));
|
|
163
|
+
agents.push(agent);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
// Skip invalid files
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
// Directory doesn't exist
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return agents;
|
|
174
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZERO Agent Engine
|
|
3
|
+
* Core agent loop implementation combining best patterns from:
|
|
4
|
+
* - goose: sub-agent orchestration
|
|
5
|
+
* - gemini-cli: ReAct loop with tool execution
|
|
6
|
+
* - pi: multi-agent orchestrator
|
|
7
|
+
* - cline: tool approval flow
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
AgentConfig,
|
|
12
|
+
AgentResult,
|
|
13
|
+
ChatOptions,
|
|
14
|
+
LLMClient,
|
|
15
|
+
Message,
|
|
16
|
+
MessageContent,
|
|
17
|
+
ToolCallContent,
|
|
18
|
+
ToolDefinition,
|
|
19
|
+
ToolResult,
|
|
20
|
+
TokenUsage,
|
|
21
|
+
PermissionHandler,
|
|
22
|
+
SessionState,
|
|
23
|
+
} from "../types.js";
|
|
24
|
+
|
|
25
|
+
export interface AgentEngineOptions {
|
|
26
|
+
config: AgentConfig;
|
|
27
|
+
client: LLMClient;
|
|
28
|
+
tools: Map<string, ToolDefinition>;
|
|
29
|
+
permissionHandler: PermissionHandler;
|
|
30
|
+
workingDirectory: string;
|
|
31
|
+
sessionId: string;
|
|
32
|
+
onStateChange?: (state: SessionState) => void;
|
|
33
|
+
onMessage?: (message: Message) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class AgentEngine {
|
|
37
|
+
private config: AgentConfig;
|
|
38
|
+
private client: LLMClient;
|
|
39
|
+
private tools: Map<string, ToolDefinition>;
|
|
40
|
+
private permissionHandler: PermissionHandler;
|
|
41
|
+
private workingDirectory: string;
|
|
42
|
+
private sessionId: string;
|
|
43
|
+
private messages: Message[] = [];
|
|
44
|
+
private totalUsage: TokenUsage = { inputTokens: 0, outputTokens: 0 };
|
|
45
|
+
private turnCount = 0;
|
|
46
|
+
private aborted = false;
|
|
47
|
+
private abortController = new AbortController();
|
|
48
|
+
private onStateChange?: (state: SessionState) => void;
|
|
49
|
+
private onMessage?: (message: Message) => void;
|
|
50
|
+
|
|
51
|
+
constructor(options: AgentEngineOptions) {
|
|
52
|
+
this.config = options.config;
|
|
53
|
+
this.client = options.client;
|
|
54
|
+
this.tools = options.tools;
|
|
55
|
+
this.permissionHandler = options.permissionHandler;
|
|
56
|
+
this.workingDirectory = options.workingDirectory;
|
|
57
|
+
this.sessionId = options.sessionId;
|
|
58
|
+
this.onStateChange = options.onStateChange;
|
|
59
|
+
this.onMessage = options.onMessage;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Run the agent loop with a user message
|
|
64
|
+
*/
|
|
65
|
+
async run(userMessage: string): Promise<AgentResult> {
|
|
66
|
+
const startTime = Date.now();
|
|
67
|
+
this.aborted = false;
|
|
68
|
+
this.turnCount = 0;
|
|
69
|
+
|
|
70
|
+
// Add user message
|
|
71
|
+
const userMsg: Message = {
|
|
72
|
+
id: this.generateId(),
|
|
73
|
+
role: "user",
|
|
74
|
+
content: [{ type: "text", text: userMessage }],
|
|
75
|
+
timestamp: Date.now(),
|
|
76
|
+
};
|
|
77
|
+
this.messages.push(userMsg);
|
|
78
|
+
this.onMessage?.(userMsg);
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
// Agent loop (ReAct pattern)
|
|
82
|
+
while (this.turnCount < this.config.maxTurns && !this.aborted) {
|
|
83
|
+
this.turnCount++;
|
|
84
|
+
this.updateState({ status: "running", currentAgent: this.config.name, tokenUsage: this.totalUsage });
|
|
85
|
+
|
|
86
|
+
// Get available tools for this agent
|
|
87
|
+
const availableTools = this.getAvailableTools();
|
|
88
|
+
|
|
89
|
+
// Call LLM
|
|
90
|
+
const chatOptions: ChatOptions = {
|
|
91
|
+
tools: availableTools.length > 0 ? availableTools : undefined,
|
|
92
|
+
systemPrompt: this.config.systemPrompt,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const response = await this.client.chat(this.messages, chatOptions);
|
|
96
|
+
|
|
97
|
+
// Track usage
|
|
98
|
+
this.totalUsage.inputTokens += response.usage.inputTokens;
|
|
99
|
+
this.totalUsage.outputTokens += response.usage.outputTokens;
|
|
100
|
+
|
|
101
|
+
// Add assistant message
|
|
102
|
+
const assistantMsg: Message = {
|
|
103
|
+
id: this.generateId(),
|
|
104
|
+
role: "assistant",
|
|
105
|
+
content: response.content,
|
|
106
|
+
timestamp: Date.now(),
|
|
107
|
+
metadata: { model: response.model, usage: response.usage },
|
|
108
|
+
};
|
|
109
|
+
this.messages.push(assistantMsg);
|
|
110
|
+
this.onMessage?.(assistantMsg);
|
|
111
|
+
|
|
112
|
+
// Check if we should stop
|
|
113
|
+
if (response.stopReason === "end_turn" || response.stopReason === "max_tokens") {
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Process tool calls
|
|
118
|
+
if (response.stopReason === "tool_use") {
|
|
119
|
+
const toolCalls = response.content.filter(
|
|
120
|
+
(c): c is ToolCallContent => c.type === "tool_call"
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
for (const toolCall of toolCalls) {
|
|
124
|
+
const result = await this.executeTool(toolCall);
|
|
125
|
+
const toolResultMsg: Message = {
|
|
126
|
+
id: this.generateId(),
|
|
127
|
+
role: "tool",
|
|
128
|
+
content: [{
|
|
129
|
+
type: "tool_result",
|
|
130
|
+
id: toolCall.id,
|
|
131
|
+
name: toolCall.name,
|
|
132
|
+
result: result.output,
|
|
133
|
+
error: result.error,
|
|
134
|
+
}],
|
|
135
|
+
timestamp: Date.now(),
|
|
136
|
+
};
|
|
137
|
+
this.messages.push(toolResultMsg);
|
|
138
|
+
this.onMessage?.(toolResultMsg);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const duration = Date.now() - startTime;
|
|
144
|
+
const result: AgentResult = {
|
|
145
|
+
success: !this.aborted,
|
|
146
|
+
messages: this.messages,
|
|
147
|
+
tokenUsage: this.totalUsage,
|
|
148
|
+
duration,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
this.updateState({ status: "completed", tokenUsage: this.totalUsage });
|
|
152
|
+
return result;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
const duration = Date.now() - startTime;
|
|
155
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
156
|
+
this.updateState({ status: "error", tokenUsage: this.totalUsage, error: errorMessage });
|
|
157
|
+
return {
|
|
158
|
+
success: false,
|
|
159
|
+
messages: this.messages,
|
|
160
|
+
tokenUsage: this.totalUsage,
|
|
161
|
+
duration,
|
|
162
|
+
error: errorMessage,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Execute a single tool call with permission checking
|
|
169
|
+
*/
|
|
170
|
+
private async executeTool(toolCall: ToolCallContent): Promise<ToolResult> {
|
|
171
|
+
const tool = this.tools.get(toolCall.name);
|
|
172
|
+
|
|
173
|
+
if (!tool) {
|
|
174
|
+
return {
|
|
175
|
+
success: false,
|
|
176
|
+
output: "",
|
|
177
|
+
error: `Tool "${toolCall.name}" not found`,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Check permissions if tool requires approval
|
|
182
|
+
if (tool.requiresApproval) {
|
|
183
|
+
this.updateState({ status: "waiting_approval", tokenUsage: this.totalUsage });
|
|
184
|
+
|
|
185
|
+
const decision = await this.permissionHandler({
|
|
186
|
+
type: this.getPermissionType(tool.category),
|
|
187
|
+
resource: toolCall.name,
|
|
188
|
+
details: JSON.stringify(toolCall.arguments),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (decision === "deny") {
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
output: "",
|
|
195
|
+
error: "Permission denied by user",
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.updateState({ status: "running", tokenUsage: this.totalUsage });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Execute the tool
|
|
203
|
+
try {
|
|
204
|
+
const result = await tool.handler(toolCall.arguments, {
|
|
205
|
+
sessionId: this.sessionId,
|
|
206
|
+
workingDirectory: this.workingDirectory,
|
|
207
|
+
permissions: {
|
|
208
|
+
fileRead: "allow",
|
|
209
|
+
fileWrite: tool.requiresApproval ? "allow" : "ask",
|
|
210
|
+
shellExecution: tool.requiresApproval ? "allow" : "ask",
|
|
211
|
+
networkAccess: "allow",
|
|
212
|
+
mcpAccess: "allow",
|
|
213
|
+
},
|
|
214
|
+
abortSignal: this.abortController.signal,
|
|
215
|
+
});
|
|
216
|
+
return result;
|
|
217
|
+
} catch (error) {
|
|
218
|
+
return {
|
|
219
|
+
success: false,
|
|
220
|
+
output: "",
|
|
221
|
+
error: error instanceof Error ? error.message : String(error),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get tools available to this agent
|
|
228
|
+
*/
|
|
229
|
+
private getAvailableTools(): ToolDefinition[] {
|
|
230
|
+
return this.config.tools
|
|
231
|
+
.map((name) => this.tools.get(name))
|
|
232
|
+
.filter((t): t is ToolDefinition => t !== undefined);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private getPermissionType(category: string): "file_read" | "file_write" | "shell" | "network" | "mcp" {
|
|
236
|
+
switch (category) {
|
|
237
|
+
case "file": return "file_write";
|
|
238
|
+
case "shell": return "shell";
|
|
239
|
+
case "web": return "network";
|
|
240
|
+
case "mcp": return "mcp";
|
|
241
|
+
default: return "file_read";
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
abort(): void {
|
|
246
|
+
this.aborted = true;
|
|
247
|
+
this.abortController.abort();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
getMessages(): Message[] {
|
|
251
|
+
return [...this.messages];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private updateState(partial: Partial<SessionState>): void {
|
|
255
|
+
this.onStateChange?.({
|
|
256
|
+
status: partial.status ?? "idle",
|
|
257
|
+
currentAgent: partial.currentAgent ?? this.config.name,
|
|
258
|
+
tokenUsage: partial.tokenUsage ?? this.totalUsage,
|
|
259
|
+
error: partial.error,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private generateId(): string {
|
|
264
|
+
return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZERO Tool Registry
|
|
3
|
+
* Manages all available tools for agents
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ToolDefinition, ToolCategory } from "../types.js";
|
|
7
|
+
|
|
8
|
+
export class ToolRegistry {
|
|
9
|
+
private tools = new Map<string, ToolDefinition>();
|
|
10
|
+
|
|
11
|
+
register(tool: ToolDefinition): void {
|
|
12
|
+
this.tools.set(tool.name, tool);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
unregister(name: string): void {
|
|
16
|
+
this.tools.delete(name);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get(name: string): ToolDefinition | undefined {
|
|
20
|
+
return this.tools.get(name);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getAll(): ToolDefinition[] {
|
|
24
|
+
return Array.from(this.tools.values());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getByCategory(category: ToolCategory): ToolDefinition[] {
|
|
28
|
+
return this.getAll().filter((t) => t.category === category);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getMap(): Map<string, ToolDefinition> {
|
|
32
|
+
return new Map(this.tools);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
has(name: string): boolean {
|
|
36
|
+
return this.tools.has(name);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
names(): string[] {
|
|
40
|
+
return Array.from(this.tools.keys());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load all built-in tools
|
|
45
|
+
*/
|
|
46
|
+
loadBuiltins(): void {
|
|
47
|
+
const builtins = getBuiltinTools();
|
|
48
|
+
for (const tool of builtins) {
|
|
49
|
+
this.register(tool);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Lazy import to avoid circular deps - tools are loaded at runtime
|
|
55
|
+
function getBuiltinTools(): ToolDefinition[] {
|
|
56
|
+
// Will be populated by the tools/index.ts
|
|
57
|
+
return [];
|
|
58
|
+
}
|