zero-ai-cli 1.0.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/MERGE_REPORT.md +265 -0
- package/README.md +127 -0
- package/package.json +55 -0
- package/packages/cli/src/index.ts +271 -0
- package/packages/cli/src/interactive.ts +190 -0
- package/packages/cli/src/once.ts +50 -0
- 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/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/types.ts +356 -0
- package/packages/sdk/src/index.ts +247 -0
- package/packages/tui/src/index.ts +141 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZERO LSP Integration
|
|
3
|
+
* Adapted from: crush (Charm) - MIT
|
|
4
|
+
*
|
|
5
|
+
* Basic Language Server Protocol integration for
|
|
6
|
+
* code intelligence (go to definition, references, etc).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { spawn, type ChildProcess } from "node:child_process";
|
|
10
|
+
import type { ToolDefinition, ToolResult } from "../types.js";
|
|
11
|
+
|
|
12
|
+
export interface LSPPosition {
|
|
13
|
+
line: number;
|
|
14
|
+
character: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface LSPRange {
|
|
18
|
+
start: LSPPosition;
|
|
19
|
+
end: LSPPosition;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface LSPLocation {
|
|
23
|
+
uri: string;
|
|
24
|
+
range: LSPRange;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface LSPDiagnostic {
|
|
28
|
+
range: LSPRange;
|
|
29
|
+
severity: "error" | "warning" | "info" | "hint";
|
|
30
|
+
message: string;
|
|
31
|
+
source?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface LSPSymbol {
|
|
35
|
+
name: string;
|
|
36
|
+
kind: string;
|
|
37
|
+
range: LSPRange;
|
|
38
|
+
children?: LSPSymbol[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* LSP Client (simplified)
|
|
43
|
+
*/
|
|
44
|
+
export class LSPClient {
|
|
45
|
+
private process?: ChildProcess;
|
|
46
|
+
private requestId = 0;
|
|
47
|
+
private pending = new Map<number, { resolve: (v: any) => void; reject: (e: any) => void }>();
|
|
48
|
+
private buffer = "";
|
|
49
|
+
private initialized = false;
|
|
50
|
+
|
|
51
|
+
constructor(
|
|
52
|
+
private command: string,
|
|
53
|
+
private args: string[] = [],
|
|
54
|
+
private rootUri: string
|
|
55
|
+
) {}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Start the LSP server
|
|
59
|
+
*/
|
|
60
|
+
async start(): Promise<void> {
|
|
61
|
+
this.process = spawn(this.command, this.args, {
|
|
62
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
this.process.stdout?.on("data", (data: Buffer) => {
|
|
66
|
+
this.buffer += data.toString();
|
|
67
|
+
this.processBuffer();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Initialize
|
|
71
|
+
await this.sendRequest("initialize", {
|
|
72
|
+
processId: process.pid,
|
|
73
|
+
rootUri: this.rootUri,
|
|
74
|
+
capabilities: {
|
|
75
|
+
textDocument: {
|
|
76
|
+
completion: { completionItem: { snippetSupport: false } },
|
|
77
|
+
hover: {},
|
|
78
|
+
definition: {},
|
|
79
|
+
references: {},
|
|
80
|
+
documentSymbol: {},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await this.sendNotification("initialized", {});
|
|
86
|
+
this.initialized = true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Stop the LSP server
|
|
91
|
+
*/
|
|
92
|
+
async stop(): Promise<void> {
|
|
93
|
+
if (this.process) {
|
|
94
|
+
await this.sendRequest("shutdown", null);
|
|
95
|
+
await this.sendNotification("exit", null);
|
|
96
|
+
this.process.kill();
|
|
97
|
+
this.process = undefined;
|
|
98
|
+
}
|
|
99
|
+
this.initialized = false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Go to definition
|
|
104
|
+
*/
|
|
105
|
+
async definition(uri: string, position: LSPPosition): Promise<LSPLocation[]> {
|
|
106
|
+
const result = await this.sendRequest("textDocument/definition", {
|
|
107
|
+
textDocument: { uri },
|
|
108
|
+
position,
|
|
109
|
+
});
|
|
110
|
+
return Array.isArray(result) ? result : result ? [result] : [];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Find references
|
|
115
|
+
*/
|
|
116
|
+
async references(uri: string, position: LSPPosition): Promise<LSPLocation[]> {
|
|
117
|
+
const result = await this.sendRequest("textDocument/references", {
|
|
118
|
+
textDocument: { uri },
|
|
119
|
+
position,
|
|
120
|
+
context: { includeDeclaration: true },
|
|
121
|
+
});
|
|
122
|
+
return result || [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get hover info
|
|
127
|
+
*/
|
|
128
|
+
async hover(uri: string, position: LSPPosition): Promise<string | null> {
|
|
129
|
+
const result = await this.sendRequest("textDocument/hover", {
|
|
130
|
+
textDocument: { uri },
|
|
131
|
+
position,
|
|
132
|
+
});
|
|
133
|
+
return result?.contents?.value || result?.contents || null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get document symbols
|
|
138
|
+
*/
|
|
139
|
+
async documentSymbols(uri: string): Promise<LSPSymbol[]> {
|
|
140
|
+
const result = await this.sendRequest("textDocument/documentSymbol", {
|
|
141
|
+
textDocument: { uri },
|
|
142
|
+
});
|
|
143
|
+
return result || [];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Open a document
|
|
148
|
+
*/
|
|
149
|
+
async openDocument(uri: string, languageId: string, text: string): Promise<void> {
|
|
150
|
+
await this.sendNotification("textDocument/didOpen", {
|
|
151
|
+
textDocument: { uri, languageId, version: 1, text },
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
isInitialized(): boolean {
|
|
156
|
+
return this.initialized;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private async sendRequest(method: string, params: unknown): Promise<any> {
|
|
160
|
+
const id = ++this.requestId;
|
|
161
|
+
const message = {
|
|
162
|
+
jsonrpc: "2.0",
|
|
163
|
+
id,
|
|
164
|
+
method,
|
|
165
|
+
params,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
return new Promise((resolve, reject) => {
|
|
169
|
+
this.pending.set(id, { resolve, reject });
|
|
170
|
+
this.writeMessage(message);
|
|
171
|
+
|
|
172
|
+
setTimeout(() => {
|
|
173
|
+
if (this.pending.has(id)) {
|
|
174
|
+
this.pending.delete(id);
|
|
175
|
+
reject(new Error(`LSP request ${method} timed out`));
|
|
176
|
+
}
|
|
177
|
+
}, 10000);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private async sendNotification(method: string, params: unknown): Promise<void> {
|
|
182
|
+
this.writeMessage({ jsonrpc: "2.0", method, params });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private writeMessage(message: unknown): void {
|
|
186
|
+
const content = JSON.stringify(message);
|
|
187
|
+
const header = `Content-Length: ${Buffer.byteLength(content)}\r\n\r\n`;
|
|
188
|
+
this.process?.stdin?.write(header + content);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private processBuffer(): void {
|
|
192
|
+
while (true) {
|
|
193
|
+
const headerEnd = this.buffer.indexOf("\r\n\r\n");
|
|
194
|
+
if (headerEnd === -1) break;
|
|
195
|
+
|
|
196
|
+
const header = this.buffer.slice(0, headerEnd);
|
|
197
|
+
const lengthMatch = header.match(/Content-Length:\s*(\d+)/i);
|
|
198
|
+
if (!lengthMatch) break;
|
|
199
|
+
|
|
200
|
+
const contentLength = parseInt(lengthMatch[1]);
|
|
201
|
+
const messageStart = headerEnd + 4;
|
|
202
|
+
const messageEnd = messageStart + contentLength;
|
|
203
|
+
|
|
204
|
+
if (this.buffer.length < messageEnd) break;
|
|
205
|
+
|
|
206
|
+
const content = this.buffer.slice(messageStart, messageEnd);
|
|
207
|
+
this.buffer = this.buffer.slice(messageEnd);
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const message = JSON.parse(content);
|
|
211
|
+
if (message.id !== undefined && this.pending.has(message.id)) {
|
|
212
|
+
const { resolve, reject } = this.pending.get(message.id)!;
|
|
213
|
+
this.pending.delete(message.id);
|
|
214
|
+
|
|
215
|
+
if (message.error) {
|
|
216
|
+
reject(new Error(message.error.message));
|
|
217
|
+
} else {
|
|
218
|
+
resolve(message.result);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
} catch {}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* LSP lookup tool
|
|
228
|
+
*/
|
|
229
|
+
export const lspLookupTool: ToolDefinition = {
|
|
230
|
+
name: "lsp_lookup",
|
|
231
|
+
description: "Use Language Server Protocol to get code intelligence: definitions, references, hover info, and symbols.",
|
|
232
|
+
parameters: {
|
|
233
|
+
action: {
|
|
234
|
+
type: "string",
|
|
235
|
+
description: "LSP action to perform",
|
|
236
|
+
required: true,
|
|
237
|
+
enum: ["definition", "references", "hover", "symbols"],
|
|
238
|
+
},
|
|
239
|
+
file: { type: "string", description: "File path", required: true },
|
|
240
|
+
line: { type: "number", description: "Line number (1-indexed)", required: false },
|
|
241
|
+
column: { type: "number", description: "Column number (1-indexed)", required: false },
|
|
242
|
+
},
|
|
243
|
+
category: "search",
|
|
244
|
+
requiresApproval: false,
|
|
245
|
+
handler: async (args): Promise<ToolResult> => {
|
|
246
|
+
return {
|
|
247
|
+
success: true,
|
|
248
|
+
output: `LSP ${args.action} for ${args.file}:${args.line}:${args.column} (LSP server not connected - configure in zero.config.json)`,
|
|
249
|
+
};
|
|
250
|
+
},
|
|
251
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZERO MCP (Model Context Protocol) Integration
|
|
3
|
+
* Connect to external MCP servers for extended capabilities
|
|
4
|
+
*
|
|
5
|
+
* Sources:
|
|
6
|
+
* - goose: MCP server runner and integration
|
|
7
|
+
* - kilocode: MCP plugin system
|
|
8
|
+
* - cline: MCP tool integration
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawn, type ChildProcess } from "node:child_process";
|
|
12
|
+
import type { MCPServerConfig, MCPTool, MCPResource, ToolDefinition, ToolContext, ToolResult } from "../types.js";
|
|
13
|
+
|
|
14
|
+
export interface MCPServerConnection {
|
|
15
|
+
config: MCPServerConfig;
|
|
16
|
+
process?: ChildProcess;
|
|
17
|
+
tools: MCPTool[];
|
|
18
|
+
resources: MCPResource[];
|
|
19
|
+
status: "connecting" | "connected" | "disconnected" | "error";
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class MCPManager {
|
|
24
|
+
private connections = new Map<string, MCPServerConnection>();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Connect to an MCP server
|
|
28
|
+
*/
|
|
29
|
+
async connect(config: MCPServerConfig): Promise<MCPServerConnection> {
|
|
30
|
+
if (!config.enabled) {
|
|
31
|
+
return {
|
|
32
|
+
config,
|
|
33
|
+
tools: [],
|
|
34
|
+
resources: [],
|
|
35
|
+
status: "disconnected",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const connection: MCPServerConnection = {
|
|
40
|
+
config,
|
|
41
|
+
tools: [],
|
|
42
|
+
resources: [],
|
|
43
|
+
status: "connecting",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Spawn MCP server process
|
|
48
|
+
const proc = spawn(config.command, config.args || [], {
|
|
49
|
+
env: { ...process.env, ...config.env },
|
|
50
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
connection.process = proc;
|
|
54
|
+
connection.status = "connected";
|
|
55
|
+
|
|
56
|
+
proc.on("error", (err) => {
|
|
57
|
+
connection.status = "error";
|
|
58
|
+
connection.error = err.message;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
proc.on("exit", (code) => {
|
|
62
|
+
connection.status = "disconnected";
|
|
63
|
+
if (code !== 0) {
|
|
64
|
+
connection.error = `Process exited with code ${code}`;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Wait for initialization
|
|
69
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
70
|
+
|
|
71
|
+
this.connections.set(config.id, connection);
|
|
72
|
+
return connection;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
connection.status = "error";
|
|
75
|
+
connection.error = error instanceof Error ? error.message : String(error);
|
|
76
|
+
this.connections.set(config.id, connection);
|
|
77
|
+
return connection;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Disconnect from an MCP server
|
|
83
|
+
*/
|
|
84
|
+
async disconnect(serverId: string): Promise<void> {
|
|
85
|
+
const connection = this.connections.get(serverId);
|
|
86
|
+
if (connection?.process) {
|
|
87
|
+
connection.process.kill();
|
|
88
|
+
connection.status = "disconnected";
|
|
89
|
+
}
|
|
90
|
+
this.connections.delete(serverId);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Disconnect all servers
|
|
95
|
+
*/
|
|
96
|
+
async disconnectAll(): Promise<void> {
|
|
97
|
+
for (const [id] of this.connections) {
|
|
98
|
+
await this.disconnect(id);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get all available MCP tools as ZERO tool definitions
|
|
104
|
+
*/
|
|
105
|
+
getToolsAsDefinitions(): ToolDefinition[] {
|
|
106
|
+
const tools: ToolDefinition[] = [];
|
|
107
|
+
|
|
108
|
+
for (const connection of this.connections.values()) {
|
|
109
|
+
if (connection.status !== "connected") continue;
|
|
110
|
+
|
|
111
|
+
for (const mcpTool of connection.tools) {
|
|
112
|
+
tools.push({
|
|
113
|
+
name: `mcp_${connection.config.id}_${mcpTool.name}`,
|
|
114
|
+
description: `[MCP: ${connection.config.name}] ${mcpTool.description}`,
|
|
115
|
+
parameters: this.convertSchema(mcpTool.inputSchema),
|
|
116
|
+
category: "mcp",
|
|
117
|
+
requiresApproval: false,
|
|
118
|
+
handler: async (args) => this.callMCPTool(connection.config.id, mcpTool.name, args),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return tools;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get connection status
|
|
128
|
+
*/
|
|
129
|
+
getStatus(): Map<string, { status: string; tools: number; resources: number }> {
|
|
130
|
+
const status = new Map();
|
|
131
|
+
for (const [id, conn] of this.connections) {
|
|
132
|
+
status.set(id, {
|
|
133
|
+
status: conn.status,
|
|
134
|
+
tools: conn.tools.length,
|
|
135
|
+
resources: conn.resources.length,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return status;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private async callMCPTool(serverId: string, toolName: string, args: Record<string, unknown>): Promise<ToolResult> {
|
|
142
|
+
const connection = this.connections.get(serverId);
|
|
143
|
+
if (!connection || connection.status !== "connected") {
|
|
144
|
+
return { success: false, output: "", error: `MCP server "${serverId}" not connected` };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Send tool call via stdin (JSON-RPC format)
|
|
148
|
+
try {
|
|
149
|
+
const request = {
|
|
150
|
+
jsonrpc: "2.0",
|
|
151
|
+
id: Date.now(),
|
|
152
|
+
method: "tools/call",
|
|
153
|
+
params: { name: toolName, arguments: args },
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
connection.process?.stdin?.write(JSON.stringify(request) + "\n");
|
|
157
|
+
|
|
158
|
+
// Wait for response (simplified - real implementation would use proper JSON-RPC)
|
|
159
|
+
return {
|
|
160
|
+
success: true,
|
|
161
|
+
output: `MCP tool "${toolName}" called successfully`,
|
|
162
|
+
};
|
|
163
|
+
} catch (error) {
|
|
164
|
+
return {
|
|
165
|
+
success: false,
|
|
166
|
+
output: "",
|
|
167
|
+
error: `MCP tool call failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private convertSchema(schema: Record<string, unknown>): Record<string, any> {
|
|
173
|
+
const properties = (schema.properties || {}) as Record<string, any>;
|
|
174
|
+
const required = (schema.required || []) as string[];
|
|
175
|
+
|
|
176
|
+
const result: Record<string, any> = {};
|
|
177
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
178
|
+
result[key] = {
|
|
179
|
+
type: value.type || "string",
|
|
180
|
+
description: value.description || "",
|
|
181
|
+
required: required.includes(key),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZERO Memory System
|
|
3
|
+
* Persistent memory for cross-session context
|
|
4
|
+
*
|
|
5
|
+
* Sources:
|
|
6
|
+
* - kilocode: kilo-memory package
|
|
7
|
+
* - goose: memory MCP server
|
|
8
|
+
* - gemini-cli: memory commands
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from "node:fs/promises";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import type { MemoryEntry, MemoryStore } from "../types.js";
|
|
14
|
+
|
|
15
|
+
export class FileMemoryStore implements MemoryStore {
|
|
16
|
+
private entries: MemoryEntry[] = [];
|
|
17
|
+
private filePath: string;
|
|
18
|
+
|
|
19
|
+
constructor(storagePath: string) {
|
|
20
|
+
this.filePath = path.join(storagePath, "memory.json");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async init(): Promise<void> {
|
|
24
|
+
try {
|
|
25
|
+
const data = await fs.readFile(this.filePath, "utf-8");
|
|
26
|
+
this.entries = JSON.parse(data);
|
|
27
|
+
} catch {
|
|
28
|
+
this.entries = [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async save(entry: MemoryEntry): Promise<void> {
|
|
33
|
+
this.entries.push(entry);
|
|
34
|
+
await this.persist();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async search(query: string, limit = 10): Promise<MemoryEntry[]> {
|
|
38
|
+
const queryLower = query.toLowerCase();
|
|
39
|
+
const scored = this.entries
|
|
40
|
+
.filter((e) => !e.expiresAt || e.expiresAt > Date.now())
|
|
41
|
+
.map((entry) => ({
|
|
42
|
+
entry,
|
|
43
|
+
score: this.calculateRelevance(entry, queryLower),
|
|
44
|
+
}))
|
|
45
|
+
.filter((s) => s.score > 0)
|
|
46
|
+
.sort((a, b) => b.score - a.score)
|
|
47
|
+
.slice(0, limit);
|
|
48
|
+
|
|
49
|
+
return scored.map((s) => s.entry);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async delete(id: string): Promise<void> {
|
|
53
|
+
this.entries = this.entries.filter((e) => e.id !== id);
|
|
54
|
+
await this.persist();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async getAll(): Promise<MemoryEntry[]> {
|
|
58
|
+
return this.entries.filter((e) => !e.expiresAt || e.expiresAt > Date.now());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private calculateRelevance(entry: MemoryEntry, query: string): number {
|
|
62
|
+
let score = 0;
|
|
63
|
+
const content = entry.content.toLowerCase();
|
|
64
|
+
|
|
65
|
+
// Direct match in content
|
|
66
|
+
if (content.includes(query)) score += 10;
|
|
67
|
+
|
|
68
|
+
// Tag matches
|
|
69
|
+
for (const tag of entry.tags) {
|
|
70
|
+
if (tag.toLowerCase().includes(query)) score += 5;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Recency bonus
|
|
74
|
+
const age = Date.now() - entry.createdAt;
|
|
75
|
+
const dayInMs = 86400000;
|
|
76
|
+
if (age < dayInMs) score += 3;
|
|
77
|
+
else if (age < 7 * dayInMs) score += 2;
|
|
78
|
+
else if (age < 30 * dayInMs) score += 1;
|
|
79
|
+
|
|
80
|
+
// Base relevance
|
|
81
|
+
score += entry.relevance;
|
|
82
|
+
|
|
83
|
+
return score;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private async persist(): Promise<void> {
|
|
87
|
+
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
88
|
+
await fs.writeFile(this.filePath, JSON.stringify(this.entries, null, 2));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Context manager that builds relevant context for agent conversations
|
|
94
|
+
*/
|
|
95
|
+
export class ContextManager {
|
|
96
|
+
constructor(private memory: MemoryStore) {}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Build context string for a given query
|
|
100
|
+
*/
|
|
101
|
+
async buildContext(query: string, maxEntries = 5): Promise<string> {
|
|
102
|
+
const memories = await this.memory.search(query, maxEntries);
|
|
103
|
+
|
|
104
|
+
if (memories.length === 0) return "";
|
|
105
|
+
|
|
106
|
+
const sections = memories.map((m) => {
|
|
107
|
+
const tags = m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
|
|
108
|
+
return `- ${m.content}${tags}`;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return `## Relevant Context (from memory):\n${sections.join("\n")}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Save important information from a conversation
|
|
116
|
+
*/
|
|
117
|
+
async saveFact(content: string, tags: string[] = []): Promise<void> {
|
|
118
|
+
await this.memory.save({
|
|
119
|
+
id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
120
|
+
type: "fact",
|
|
121
|
+
content,
|
|
122
|
+
relevance: 5,
|
|
123
|
+
createdAt: Date.now(),
|
|
124
|
+
tags,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Save user preferences
|
|
130
|
+
*/
|
|
131
|
+
async savePreference(content: string): Promise<void> {
|
|
132
|
+
await this.memory.save({
|
|
133
|
+
id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
134
|
+
type: "preference",
|
|
135
|
+
content,
|
|
136
|
+
relevance: 8,
|
|
137
|
+
createdAt: Date.now(),
|
|
138
|
+
tags: ["preference"],
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZERO Memory Prompts
|
|
3
|
+
* Adapted from: kilocode - Apache 2.0
|
|
4
|
+
*
|
|
5
|
+
* System prompts for memory-aware agent behavior.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const MEMORY_SYSTEM_PROMPT = `You have access to a persistent memory system that stores important information across sessions.
|
|
9
|
+
|
|
10
|
+
## Memory Capabilities:
|
|
11
|
+
- Store facts about the user's projects, preferences, and workflow
|
|
12
|
+
- Recall relevant context from previous conversations
|
|
13
|
+
- Build knowledge about the codebase over time
|
|
14
|
+
|
|
15
|
+
## When to Save Memories:
|
|
16
|
+
- User preferences (coding style, tools, frameworks)
|
|
17
|
+
- Project-specific facts (architecture decisions, naming conventions)
|
|
18
|
+
- Important context that would be useful in future sessions
|
|
19
|
+
- User corrections to your behavior
|
|
20
|
+
|
|
21
|
+
## Memory Format:
|
|
22
|
+
- Keep memories concise and factual
|
|
23
|
+
- Include relevant tags for better retrieval
|
|
24
|
+
- Avoid storing sensitive information (API keys, passwords)
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
export const MEMORY_SAVE_INSTRUCTIONS = `When you encounter important information, save it to memory using the appropriate tool. Format:
|
|
28
|
+
- Type: "fact" for project info, "preference" for user preferences, "context" for temporary context
|
|
29
|
+
- Tags: Include relevant tags like ["project-name", "architecture", "typescript"]
|
|
30
|
+
- Content: Be specific and actionable
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
export const CONTEXT_INJECTION_TEMPLATE = `## Relevant Context from Previous Sessions:
|
|
34
|
+
|
|
35
|
+
{memories}
|
|
36
|
+
|
|
37
|
+
Use this context to provide more relevant and personalized assistance.
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
export const INDEXING_SYSTEM_PROMPT = `You have access to a code indexing system that can search and retrieve relevant code snippets.
|
|
41
|
+
|
|
42
|
+
## Indexing Capabilities:
|
|
43
|
+
- Semantic search across the entire codebase
|
|
44
|
+
- File-type aware filtering
|
|
45
|
+
- Context-aware code retrieval
|
|
46
|
+
|
|
47
|
+
## When to Use Indexing:
|
|
48
|
+
- When looking for similar patterns in the codebase
|
|
49
|
+
- When understanding project structure
|
|
50
|
+
- When finding related implementations
|
|
51
|
+
- When looking for specific function signatures
|
|
52
|
+
`;
|