0agent 1.0.17 → 1.0.18
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/bin/0agent.js +315 -78
- package/dist/daemon.mjs +492 -36
- package/package.json +1 -1
package/bin/0agent.js
CHANGED
|
@@ -98,26 +98,63 @@ switch (cmd) {
|
|
|
98
98
|
await runWatch();
|
|
99
99
|
break;
|
|
100
100
|
|
|
101
|
+
case 'memory':
|
|
102
|
+
await runMemoryCommand(args.slice(1));
|
|
103
|
+
break;
|
|
104
|
+
|
|
101
105
|
default:
|
|
102
106
|
showHelp();
|
|
103
107
|
break;
|
|
104
108
|
}
|
|
105
109
|
|
|
106
|
-
// ─── Init wizard
|
|
110
|
+
// ─── Init wizard — arrow key selection, GitHub memory built in ───────────────
|
|
111
|
+
|
|
112
|
+
// Arrow-key select using enquirer (falls back to number input if not available)
|
|
113
|
+
async function arrowSelect(message, choices, initial = 0) {
|
|
114
|
+
try {
|
|
115
|
+
const { Select } = await import('enquirer');
|
|
116
|
+
const prompt = new Select({ message, choices: choices.map((c, i) => ({ name: c, value: i })), initial });
|
|
117
|
+
const answer = await prompt.run();
|
|
118
|
+
return choices.indexOf(answer);
|
|
119
|
+
} catch {
|
|
120
|
+
// Fallback: number-based selection (no enquirer)
|
|
121
|
+
return choose(message, choices, initial);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function arrowInput(message, initial = '') {
|
|
126
|
+
try {
|
|
127
|
+
const { Input } = await import('enquirer');
|
|
128
|
+
const prompt = new Input({ message, initial });
|
|
129
|
+
return await prompt.run();
|
|
130
|
+
} catch {
|
|
131
|
+
return ask(` ${message}: `);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function arrowPassword(message) {
|
|
136
|
+
try {
|
|
137
|
+
const { Password } = await import('enquirer');
|
|
138
|
+
const prompt = new Password({ message });
|
|
139
|
+
return await prompt.run();
|
|
140
|
+
} catch {
|
|
141
|
+
return ask(` ${message}: `);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
107
144
|
|
|
108
145
|
async function runInit() {
|
|
109
|
-
console.log('\n
|
|
110
|
-
console.log(' │
|
|
111
|
-
console.log(' │ 0agent — An agent that learns.
|
|
112
|
-
console.log(' │
|
|
113
|
-
console.log(' │ v1.0
|
|
114
|
-
console.log(' └─────────────────────────────────────────┘\n');
|
|
115
|
-
|
|
116
|
-
// Check if already initialised
|
|
146
|
+
console.log('\n \x1b[1m┌─────────────────────────────────────────┐\x1b[0m');
|
|
147
|
+
console.log(' \x1b[1m│ │\x1b[0m');
|
|
148
|
+
console.log(' \x1b[1m│ 0agent — An agent that learns. │\x1b[0m');
|
|
149
|
+
console.log(' \x1b[1m│ │\x1b[0m');
|
|
150
|
+
console.log(' \x1b[1m│ v1.0 · Apache 2.0 │\x1b[0m');
|
|
151
|
+
console.log(' \x1b[1m└─────────────────────────────────────────┘\x1b[0m\n');
|
|
152
|
+
|
|
117
153
|
if (existsSync(CONFIG_PATH)) {
|
|
118
|
-
const answer = await ask('Config already exists. Reinitialise? [y/N] ');
|
|
154
|
+
const answer = await ask(' Config already exists. Reinitialise? [y/N] ');
|
|
119
155
|
if (answer.toLowerCase() !== 'y') {
|
|
120
|
-
console.log('\n
|
|
156
|
+
console.log('\n Running: 0agent start\n');
|
|
157
|
+
await startDaemon();
|
|
121
158
|
return;
|
|
122
159
|
}
|
|
123
160
|
}
|
|
@@ -127,96 +164,110 @@ async function runInit() {
|
|
|
127
164
|
mkdirSync(resolve(AGENT_DIR, 'skills', 'builtin'), { recursive: true });
|
|
128
165
|
mkdirSync(resolve(AGENT_DIR, 'skills', 'custom'), { recursive: true });
|
|
129
166
|
|
|
130
|
-
|
|
131
|
-
const
|
|
167
|
+
// ── Step 1: LLM Provider ────────────────────────────────────────────────
|
|
168
|
+
const providerIdx = await arrowSelect('LLM Provider', [
|
|
132
169
|
'Anthropic (Claude) ← recommended',
|
|
133
170
|
'OpenAI (GPT-4o)',
|
|
134
171
|
'xAI (Grok)',
|
|
135
172
|
'Google (Gemini)',
|
|
136
|
-
'Ollama (local
|
|
173
|
+
'Ollama (local — no API key)',
|
|
137
174
|
], 0);
|
|
138
|
-
const providerKey = ['anthropic', 'openai', 'xai', 'gemini', 'ollama'][
|
|
175
|
+
const providerKey = ['anthropic', 'openai', 'xai', 'gemini', 'ollama'][providerIdx];
|
|
139
176
|
|
|
140
|
-
// Model selection per provider
|
|
141
177
|
const MODELS = {
|
|
142
|
-
anthropic: [
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
],
|
|
147
|
-
openai: [
|
|
148
|
-
'gpt-4o ← recommended',
|
|
149
|
-
'gpt-4o-mini (faster, cheaper)',
|
|
150
|
-
'o3-mini (reasoning)',
|
|
151
|
-
],
|
|
152
|
-
xai: [
|
|
153
|
-
'grok-3 ← recommended',
|
|
154
|
-
'grok-3-mini',
|
|
155
|
-
],
|
|
156
|
-
gemini: [
|
|
157
|
-
'gemini-2.0-flash ← recommended',
|
|
158
|
-
'gemini-2.0-pro',
|
|
159
|
-
],
|
|
160
|
-
ollama: [
|
|
161
|
-
'llama3.1 ← recommended',
|
|
162
|
-
'mistral',
|
|
163
|
-
'codellama',
|
|
164
|
-
],
|
|
178
|
+
anthropic: ['claude-sonnet-4-6 ← recommended', 'claude-opus-4-6 (most capable)', 'claude-haiku-4-5 (fastest)'],
|
|
179
|
+
openai: ['gpt-4o ← recommended', 'gpt-4o-mini', 'o3-mini'],
|
|
180
|
+
xai: ['grok-3 ← recommended', 'grok-3-mini'],
|
|
181
|
+
gemini: ['gemini-2.0-flash ← recommended', 'gemini-2.0-pro'],
|
|
182
|
+
ollama: ['llama3.1 ← recommended', 'mistral', 'codellama'],
|
|
165
183
|
};
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (MODELS[providerKey]) {
|
|
169
|
-
console.log();
|
|
170
|
-
const modelIdx = await choose(' Which model?', MODELS[providerKey], 0);
|
|
171
|
-
model = MODELS[providerKey][modelIdx].split(/\s+/)[0];
|
|
172
|
-
}
|
|
184
|
+
const modelIdx = await arrowSelect('Which model?', MODELS[providerKey], 0);
|
|
185
|
+
const model = MODELS[providerKey][modelIdx].split(/\s+/)[0];
|
|
173
186
|
|
|
174
187
|
let apiKey = '';
|
|
175
188
|
if (providerKey !== 'ollama') {
|
|
176
|
-
apiKey = await
|
|
177
|
-
|
|
178
|
-
|
|
189
|
+
apiKey = await arrowPassword(`${providerKey} API key`);
|
|
190
|
+
apiKey = apiKey.trim();
|
|
191
|
+
if (!apiKey) {
|
|
192
|
+
console.log(' \x1b[33m⚠\x1b[0m No key — add it later in ~/.0agent/config.yaml');
|
|
179
193
|
} else {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (expectedPrefix && !apiKey.startsWith(expectedPrefix)) {
|
|
184
|
-
console.log(` ⚠️ Key doesn't look like a ${providerKey} key (expected: ${expectedPrefix}...)`);
|
|
194
|
+
const pfx = { anthropic: 'sk-ant-', openai: 'sk-', xai: 'xai-', gemini: 'AI' }[providerKey];
|
|
195
|
+
if (pfx && !apiKey.startsWith(pfx)) {
|
|
196
|
+
console.log(` \x1b[33m⚠\x1b[0m Unexpected key format (expected ${pfx}...)`);
|
|
185
197
|
} else {
|
|
186
|
-
console.log('
|
|
198
|
+
console.log(' \x1b[32m✓\x1b[0m Key format valid');
|
|
187
199
|
}
|
|
188
200
|
}
|
|
189
201
|
}
|
|
190
202
|
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
'
|
|
194
|
-
'
|
|
195
|
-
'Skip (text-only mode)',
|
|
203
|
+
// ── Step 2: GitHub Memory ───────────────────────────────────────────────
|
|
204
|
+
const memChoice = await arrowSelect('Back up memory to GitHub?', [
|
|
205
|
+
'Yes — private repo, free, cross-device sync ← recommended',
|
|
206
|
+
'No — local only',
|
|
196
207
|
], 0);
|
|
197
|
-
const embeddingProvider = ['nomic-ollama', 'openai', 'none'][embedding];
|
|
198
208
|
|
|
199
|
-
|
|
209
|
+
let ghToken = '', ghOwner = '', ghRepo = '0agent-memory';
|
|
210
|
+
if (memChoice === 0) {
|
|
211
|
+
// Try gh CLI first
|
|
212
|
+
try {
|
|
213
|
+
const { execSync: ex } = await import('node:child_process');
|
|
214
|
+
ghToken = ex('gh auth token 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
215
|
+
ghOwner = ex('gh api user --jq .login 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
216
|
+
if (ghToken && ghOwner) {
|
|
217
|
+
console.log(` \x1b[32m✓\x1b[0m gh CLI — authenticated as \x1b[1m${ghOwner}\x1b[0m`);
|
|
218
|
+
}
|
|
219
|
+
} catch {}
|
|
220
|
+
|
|
221
|
+
if (!ghToken) {
|
|
222
|
+
console.log('\n Create a GitHub token: \x1b[4mhttps://github.com/settings/tokens/new\x1b[0m');
|
|
223
|
+
console.log(' Required scope: \x1b[1mrepo\x1b[0m\n');
|
|
224
|
+
ghToken = await arrowPassword('GitHub token (ghp_...)');
|
|
225
|
+
ghToken = ghToken.trim();
|
|
226
|
+
if (ghToken) {
|
|
227
|
+
ghOwner = await verifyGitHubToken(ghToken) ?? '';
|
|
228
|
+
if (!ghOwner) {
|
|
229
|
+
console.log(' \x1b[31m✗\x1b[0m Invalid token — skipping GitHub memory');
|
|
230
|
+
ghToken = '';
|
|
231
|
+
} else {
|
|
232
|
+
console.log(` \x1b[32m✓\x1b[0m Authenticated as \x1b[1m${ghOwner}\x1b[0m`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (ghToken && ghOwner) {
|
|
238
|
+
process.stdout.write(` Creating private repo \x1b[1m${ghOwner}/0agent-memory\x1b[0m...`);
|
|
239
|
+
const ok = await createGitHubRepo(ghToken, '0agent-memory');
|
|
240
|
+
console.log(ok ? ' \x1b[32m✓\x1b[0m' : ' \x1b[33m(exists)\x1b[0m');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ── Step 3: Embedding ────────────────────────────────────────────────────
|
|
245
|
+
const embIdx = await arrowSelect('Embeddings (for semantic memory search)?', [
|
|
246
|
+
'Local via Ollama (nomic-embed-text) — free, private',
|
|
247
|
+
'OpenAI text-embedding-3-small — cloud',
|
|
248
|
+
'Skip — text-only mode',
|
|
249
|
+
], 0);
|
|
250
|
+
const embeddingProvider = ['nomic-ollama', 'openai', 'none'][embIdx];
|
|
251
|
+
|
|
252
|
+
// ── Step 4: Sandbox ──────────────────────────────────────────────────────
|
|
200
253
|
const sandboxes = detectSandboxes();
|
|
201
|
-
console.log(` Detected: ${sandboxes.join(', ') || 'process (fallback)'}`);
|
|
202
254
|
const sandboxChoice = sandboxes[0] ?? 'process';
|
|
203
|
-
console.log(
|
|
255
|
+
console.log(`\n Sandbox: \x1b[32m${sandboxChoice}\x1b[0m detected`);
|
|
204
256
|
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
'software-engineering
|
|
208
|
-
'
|
|
257
|
+
// ── Step 5: Seed graph ───────────────────────────────────────────────────
|
|
258
|
+
const seedIdx = await arrowSelect('Starting knowledge?', [
|
|
259
|
+
'software-engineering — sprint workflow + 15 skills ← recommended',
|
|
260
|
+
'Start from scratch',
|
|
209
261
|
], 0);
|
|
210
|
-
const seedName =
|
|
211
|
-
|
|
212
|
-
//
|
|
213
|
-
console.log('\n
|
|
214
|
-
console.log(`
|
|
215
|
-
console.log(`
|
|
216
|
-
console.log(`
|
|
217
|
-
console.log(` Sandbox:
|
|
218
|
-
console.log(` Seed:
|
|
219
|
-
console.log();
|
|
262
|
+
const seedName = seedIdx === 0 ? 'software-engineering' : null;
|
|
263
|
+
|
|
264
|
+
// ── Summary ───────────────────────────────────────────────────────────────
|
|
265
|
+
console.log('\n \x1b[1mReady to launch\x1b[0m\n');
|
|
266
|
+
console.log(` LLM: \x1b[36m${providerKey}/${model}\x1b[0m`);
|
|
267
|
+
console.log(` API Key: ${apiKey ? '\x1b[32m✓ set\x1b[0m (' + apiKey.slice(0, 8) + '••••)' : '\x1b[33mnot set\x1b[0m'}`);
|
|
268
|
+
console.log(` Memory: ${ghToken ? `\x1b[32mgithub.com/${ghOwner}/0agent-memory\x1b[0m` : '\x1b[2mlocal only\x1b[0m'}`);
|
|
269
|
+
console.log(` Sandbox: \x1b[36m${sandboxChoice}\x1b[0m`);
|
|
270
|
+
console.log(` Seed: \x1b[36m${seedName ?? 'scratch'}\x1b[0m\n`);
|
|
220
271
|
|
|
221
272
|
// Write config
|
|
222
273
|
const dbPath = resolve(AGENT_DIR, 'graph.db');
|
|
@@ -252,6 +303,7 @@ graph:
|
|
|
252
303
|
hnsw_path: "${hnswPath}"
|
|
253
304
|
object_store_path: "${objPath}"
|
|
254
305
|
${seedName ? `\nseed: "${seedName}"` : ''}
|
|
306
|
+
${ghToken && ghOwner ? `\ngithub_memory:\n enabled: true\n token: "${ghToken}"\n owner: "${ghOwner}"\n repo: "${ghRepo}"` : ''}
|
|
255
307
|
`;
|
|
256
308
|
|
|
257
309
|
writeFileSync(CONFIG_PATH, config, 'utf8');
|
|
@@ -809,6 +861,191 @@ async function waitForTunnelUrl(proc, pattern, timeout) {
|
|
|
809
861
|
});
|
|
810
862
|
}
|
|
811
863
|
|
|
864
|
+
// ─── Memory sync (GitHub backend) ─────────────────────────────────────────
|
|
865
|
+
|
|
866
|
+
async function runMemoryCommand(memArgs) {
|
|
867
|
+
const sub = memArgs[0] ?? 'status';
|
|
868
|
+
|
|
869
|
+
switch (sub) {
|
|
870
|
+
// ── 0agent memory connect github ──────────────────────────────────────
|
|
871
|
+
case 'connect': {
|
|
872
|
+
const provider = memArgs[1] ?? 'github';
|
|
873
|
+
if (provider !== 'github') { console.log(' Only GitHub is supported: 0agent memory connect github'); break; }
|
|
874
|
+
|
|
875
|
+
console.log('\n \x1b[1m0agent Memory — GitHub Sync\x1b[0m\n');
|
|
876
|
+
console.log(' Your knowledge graph will be backed up to a private GitHub repository.');
|
|
877
|
+
console.log(' Free, versioned, cross-device. No server needed.\n');
|
|
878
|
+
|
|
879
|
+
// ── Authentication ──
|
|
880
|
+
let token = '';
|
|
881
|
+
|
|
882
|
+
// Try gh CLI first (already logged in for most devs)
|
|
883
|
+
try {
|
|
884
|
+
const { execSync: ex } = await import('node:child_process');
|
|
885
|
+
token = ex('gh auth token 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
886
|
+
if (token) {
|
|
887
|
+
const { execSync: ex2 } = await import('node:child_process');
|
|
888
|
+
const user = ex2('gh api user --jq .login 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
889
|
+
console.log(` \x1b[32m✓\x1b[0m Detected gh CLI — authenticated as \x1b[1m${user}\x1b[0m`);
|
|
890
|
+
}
|
|
891
|
+
} catch {}
|
|
892
|
+
|
|
893
|
+
// Fallback: GitHub Device Flow
|
|
894
|
+
if (!token) {
|
|
895
|
+
console.log(' \x1b[2mgh CLI not found — using GitHub token auth\x1b[0m\n');
|
|
896
|
+
console.log(' Create a token at: \x1b[4mhttps://github.com/settings/tokens/new\x1b[0m');
|
|
897
|
+
console.log(' Required scope: \x1b[1mrepo\x1b[0m\n');
|
|
898
|
+
token = await ask(' Paste token (starts with ghp_): ');
|
|
899
|
+
token = token.trim();
|
|
900
|
+
if (!token) { console.log(' No token provided.'); break; }
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// Verify token
|
|
904
|
+
const owner = await verifyGitHubToken(token);
|
|
905
|
+
if (!owner) { console.log(' \x1b[31m✗\x1b[0m Invalid token or no access.'); break; }
|
|
906
|
+
console.log(` \x1b[32m✓\x1b[0m Authenticated as \x1b[1m${owner}\x1b[0m`);
|
|
907
|
+
|
|
908
|
+
// ── Create repo ──
|
|
909
|
+
const repoName = memArgs[2] ?? '0agent-memory';
|
|
910
|
+
process.stdout.write(` Creating private repo \x1b[1m${owner}/${repoName}\x1b[0m...`);
|
|
911
|
+
const created = await createGitHubRepo(token, repoName);
|
|
912
|
+
console.log(created ? ' \x1b[32m✓\x1b[0m' : ' \x1b[33m(already exists)\x1b[0m');
|
|
913
|
+
|
|
914
|
+
// ── Save to config ──
|
|
915
|
+
const YAML = await import('yaml');
|
|
916
|
+
const { readFileSync: rf, writeFileSync: wf, existsSync: ef } = await import('node:fs');
|
|
917
|
+
if (ef(CONFIG_PATH)) {
|
|
918
|
+
let cfg = rf(CONFIG_PATH, 'utf8');
|
|
919
|
+
// Remove old github_memory block if present
|
|
920
|
+
cfg = cfg.replace(/\ngithub_memory:[\s\S]*?(?=\n\w|\n$|$)/, '');
|
|
921
|
+
cfg += `\ngithub_memory:\n enabled: true\n token: "${token}"\n owner: "${owner}"\n repo: "${repoName}"\n`;
|
|
922
|
+
wf(CONFIG_PATH, cfg, 'utf8');
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// ── Initial push ──
|
|
926
|
+
console.log('\n Performing initial sync...');
|
|
927
|
+
await requireDaemon();
|
|
928
|
+
const result = await daemonMemorySync('push');
|
|
929
|
+
if (result?.pushed) {
|
|
930
|
+
console.log(` \x1b[32m✓\x1b[0m Synced — ${result.nodes_synced} nodes, ${result.edges_synced} edges`);
|
|
931
|
+
console.log(`\n Memory repo: \x1b[4mhttps://github.com/${owner}/${repoName}\x1b[0m`);
|
|
932
|
+
console.log('\n From any machine, run:');
|
|
933
|
+
console.log(` \x1b[36m0agent memory connect github --repo ${owner}/${repoName}\x1b[0m\n`);
|
|
934
|
+
} else {
|
|
935
|
+
console.log(' \x1b[33m⚠\x1b[0m Initial sync skipped — run `0agent memory sync` after daemon starts.');
|
|
936
|
+
console.log(`\n Memory repo: \x1b[4mhttps://github.com/${owner}/${repoName}\x1b[0m\n`);
|
|
937
|
+
}
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// ── 0agent memory sync ────────────────────────────────────────────────
|
|
942
|
+
case 'sync': {
|
|
943
|
+
await requireDaemon();
|
|
944
|
+
process.stdout.write(' Syncing memory to GitHub...');
|
|
945
|
+
const result = await daemonMemorySync('push');
|
|
946
|
+
if (result?.pushed) {
|
|
947
|
+
console.log(` \x1b[32m✓\x1b[0m ${result.nodes_synced} nodes, ${result.edges_synced} edges`);
|
|
948
|
+
} else {
|
|
949
|
+
console.log(` \x1b[31m✗\x1b[0m ${result?.error ?? 'No GitHub memory configured'}`);
|
|
950
|
+
if (!result?.error) console.log(' Run: 0agent memory connect github');
|
|
951
|
+
}
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// ── 0agent memory pull ───────────────────────────────────────────────
|
|
956
|
+
case 'pull': {
|
|
957
|
+
await requireDaemon();
|
|
958
|
+
process.stdout.write(' Pulling memory from GitHub...');
|
|
959
|
+
const result = await daemonMemorySync('pull');
|
|
960
|
+
if (result?.pulled) {
|
|
961
|
+
console.log(` \x1b[32m✓\x1b[0m +${result.nodes_synced} nodes, +${result.edges_synced} edges merged`);
|
|
962
|
+
} else {
|
|
963
|
+
console.log(` \x1b[31m✗\x1b[0m ${result?.error ?? 'No GitHub memory configured'}`);
|
|
964
|
+
}
|
|
965
|
+
break;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// ── 0agent memory status ──────────────────────────────────────────────
|
|
969
|
+
case 'status': {
|
|
970
|
+
const YAML = await import('yaml');
|
|
971
|
+
const { readFileSync: rf, existsSync: ef } = await import('node:fs');
|
|
972
|
+
if (!ef(CONFIG_PATH)) { console.log('\n Not initialised. Run: 0agent init\n'); break; }
|
|
973
|
+
|
|
974
|
+
const cfg = YAML.parse(rf(CONFIG_PATH, 'utf8'));
|
|
975
|
+
const ghMem = cfg.github_memory;
|
|
976
|
+
|
|
977
|
+
if (!ghMem?.enabled) {
|
|
978
|
+
console.log('\n Memory sync: \x1b[33mnot connected\x1b[0m');
|
|
979
|
+
console.log(' Run: 0agent memory connect github\n');
|
|
980
|
+
} else {
|
|
981
|
+
console.log(`\n Memory sync: \x1b[32m✓ connected\x1b[0m`);
|
|
982
|
+
console.log(` Repo: https://github.com/${ghMem.owner}/${ghMem.repo}`);
|
|
983
|
+
// Get last sync from daemon
|
|
984
|
+
try {
|
|
985
|
+
const res = await fetch(`${BASE_URL}/api/memory/status`).catch(() => null);
|
|
986
|
+
const data = res?.ok ? await res.json() : null;
|
|
987
|
+
if (data) {
|
|
988
|
+
console.log(` Last push: ${data.pushed_at ? new Date(data.pushed_at).toLocaleString() : 'never'}`);
|
|
989
|
+
console.log(` Last pull: ${data.pulled_at ? new Date(data.pulled_at).toLocaleString() : 'never'}`);
|
|
990
|
+
}
|
|
991
|
+
} catch {}
|
|
992
|
+
console.log();
|
|
993
|
+
}
|
|
994
|
+
break;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// ── 0agent memory disconnect ──────────────────────────────────────────
|
|
998
|
+
case 'disconnect': {
|
|
999
|
+
const { readFileSync: rf, writeFileSync: wf, existsSync: ef } = await import('node:fs');
|
|
1000
|
+
if (ef(CONFIG_PATH)) {
|
|
1001
|
+
let cfg = rf(CONFIG_PATH, 'utf8');
|
|
1002
|
+
cfg = cfg.replace(/\ngithub_memory:[\s\S]*?(?=\n\w|\n$|$)/, '');
|
|
1003
|
+
wf(CONFIG_PATH, cfg, 'utf8');
|
|
1004
|
+
}
|
|
1005
|
+
console.log(' \x1b[32m✓\x1b[0m GitHub memory sync disabled. Local graph unchanged.');
|
|
1006
|
+
break;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
default:
|
|
1010
|
+
console.log(' Usage: 0agent memory connect github | sync | pull | status | disconnect');
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
async function verifyGitHubToken(token) {
|
|
1015
|
+
try {
|
|
1016
|
+
const res = await fetch('https://api.github.com/user', {
|
|
1017
|
+
headers: { Authorization: `Bearer ${token}`, 'User-Agent': '0agent/1.0' },
|
|
1018
|
+
signal: AbortSignal.timeout(8000),
|
|
1019
|
+
});
|
|
1020
|
+
if (!res.ok) return null;
|
|
1021
|
+
const user = await res.json();
|
|
1022
|
+
return user.login;
|
|
1023
|
+
} catch { return null; }
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
async function createGitHubRepo(token, repoName) {
|
|
1027
|
+
const res = await fetch('https://api.github.com/user/repos', {
|
|
1028
|
+
method: 'POST',
|
|
1029
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', 'User-Agent': '0agent/1.0' },
|
|
1030
|
+
body: JSON.stringify({
|
|
1031
|
+
name: repoName,
|
|
1032
|
+
description: '0agent memory — knowledge graph backed up automatically',
|
|
1033
|
+
private: true,
|
|
1034
|
+
auto_init: true,
|
|
1035
|
+
}),
|
|
1036
|
+
signal: AbortSignal.timeout(10000),
|
|
1037
|
+
});
|
|
1038
|
+
return res.ok || res.status === 422; // 422 = already exists
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
async function daemonMemorySync(direction) {
|
|
1042
|
+
try {
|
|
1043
|
+
const res = await fetch(`${BASE_URL}/api/memory/${direction}`, { method: 'POST' });
|
|
1044
|
+
if (!res.ok) return null;
|
|
1045
|
+
return await res.json();
|
|
1046
|
+
} catch { return null; }
|
|
1047
|
+
}
|
|
1048
|
+
|
|
812
1049
|
// ─── Result preview — confirms the agent's work actually ran ────────────────
|
|
813
1050
|
|
|
814
1051
|
async function showResultPreview(result) {
|
package/dist/daemon.mjs
CHANGED
|
@@ -1490,7 +1490,7 @@ var init_EdgeWeightUpdater = __esm({
|
|
|
1490
1490
|
this.weightLog.append(event);
|
|
1491
1491
|
}
|
|
1492
1492
|
sleep(ms) {
|
|
1493
|
-
return new Promise((
|
|
1493
|
+
return new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
1494
1494
|
}
|
|
1495
1495
|
};
|
|
1496
1496
|
}
|
|
@@ -2613,7 +2613,7 @@ var init_AgentExecutor = __esm({
|
|
|
2613
2613
|
}
|
|
2614
2614
|
}
|
|
2615
2615
|
shellExec(command, timeoutMs) {
|
|
2616
|
-
return new Promise((
|
|
2616
|
+
return new Promise((resolve12) => {
|
|
2617
2617
|
const chunks = [];
|
|
2618
2618
|
const proc = spawn2("bash", ["-c", command], {
|
|
2619
2619
|
cwd: this.cwd,
|
|
@@ -2624,10 +2624,10 @@ var init_AgentExecutor = __esm({
|
|
|
2624
2624
|
proc.stderr.on("data", (d) => chunks.push(d.toString()));
|
|
2625
2625
|
proc.on("close", (code) => {
|
|
2626
2626
|
const output = chunks.join("").trim();
|
|
2627
|
-
|
|
2627
|
+
resolve12(output || (code === 0 ? "(command completed, no output)" : `exit code ${code}`));
|
|
2628
2628
|
});
|
|
2629
2629
|
proc.on("error", (err) => {
|
|
2630
|
-
|
|
2630
|
+
resolve12(`Error: ${err.message}`);
|
|
2631
2631
|
});
|
|
2632
2632
|
});
|
|
2633
2633
|
}
|
|
@@ -2933,11 +2933,11 @@ __export(ProactiveSurface_exports, {
|
|
|
2933
2933
|
ProactiveSurface: () => ProactiveSurface
|
|
2934
2934
|
});
|
|
2935
2935
|
import { execSync as execSync4 } from "node:child_process";
|
|
2936
|
-
import { existsSync as
|
|
2937
|
-
import { resolve as
|
|
2936
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10, statSync, readdirSync as readdirSync5 } from "node:fs";
|
|
2937
|
+
import { resolve as resolve9, join as join3 } from "node:path";
|
|
2938
2938
|
function readdirSafe(dir) {
|
|
2939
2939
|
try {
|
|
2940
|
-
return
|
|
2940
|
+
return readdirSync5(dir);
|
|
2941
2941
|
} catch {
|
|
2942
2942
|
return [];
|
|
2943
2943
|
}
|
|
@@ -2983,7 +2983,7 @@ var init_ProactiveSurface = __esm({
|
|
|
2983
2983
|
return [...this.insights];
|
|
2984
2984
|
}
|
|
2985
2985
|
async poll() {
|
|
2986
|
-
if (!
|
|
2986
|
+
if (!existsSync10(resolve9(this.cwd, ".git"))) return;
|
|
2987
2987
|
const newInsights = [];
|
|
2988
2988
|
const gitInsight = this.checkGitActivity();
|
|
2989
2989
|
if (gitInsight) newInsights.push(gitInsight);
|
|
@@ -3027,13 +3027,13 @@ var init_ProactiveSurface = __esm({
|
|
|
3027
3027
|
];
|
|
3028
3028
|
for (const dir of outputPaths) {
|
|
3029
3029
|
try {
|
|
3030
|
-
if (!
|
|
3030
|
+
if (!existsSync10(dir)) continue;
|
|
3031
3031
|
const xmlFiles = readdirSafe(dir).filter((f) => f.endsWith(".xml"));
|
|
3032
3032
|
for (const xml of xmlFiles) {
|
|
3033
3033
|
const path = join3(dir, xml);
|
|
3034
3034
|
const stat = statSync(path);
|
|
3035
3035
|
if (stat.mtimeMs < this.lastPollAt) continue;
|
|
3036
|
-
const content =
|
|
3036
|
+
const content = readFileSync10(path, "utf8");
|
|
3037
3037
|
const failures = [...content.matchAll(/<failure[^>]*message="([^"]+)"/g)].length;
|
|
3038
3038
|
if (failures > 0) {
|
|
3039
3039
|
return this.makeInsight(
|
|
@@ -3088,9 +3088,9 @@ var init_ProactiveSurface = __esm({
|
|
|
3088
3088
|
|
|
3089
3089
|
// packages/daemon/src/ZeroAgentDaemon.ts
|
|
3090
3090
|
init_src();
|
|
3091
|
-
import { writeFileSync as
|
|
3092
|
-
import { resolve as
|
|
3093
|
-
import { homedir as
|
|
3091
|
+
import { writeFileSync as writeFileSync7, unlinkSync as unlinkSync2, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "node:fs";
|
|
3092
|
+
import { resolve as resolve10 } from "node:path";
|
|
3093
|
+
import { homedir as homedir6 } from "node:os";
|
|
3094
3094
|
|
|
3095
3095
|
// packages/daemon/src/config/DaemonConfig.ts
|
|
3096
3096
|
import { readFileSync, existsSync } from "node:fs";
|
|
@@ -3199,7 +3199,13 @@ var DaemonConfigSchema = z.object({
|
|
|
3199
3199
|
decay: DecayConfigSchema.default({}),
|
|
3200
3200
|
seed: z.string().optional(),
|
|
3201
3201
|
self_improvement: SelfImprovementConfigSchema.default({}),
|
|
3202
|
-
entity_nesting: EntityNestingConfigSchema.default({})
|
|
3202
|
+
entity_nesting: EntityNestingConfigSchema.default({}),
|
|
3203
|
+
github_memory: z.object({
|
|
3204
|
+
enabled: z.boolean().default(false),
|
|
3205
|
+
token: z.string().default(""),
|
|
3206
|
+
owner: z.string().default(""),
|
|
3207
|
+
repo: z.string().default("0agent-memory")
|
|
3208
|
+
}).default({})
|
|
3203
3209
|
});
|
|
3204
3210
|
|
|
3205
3211
|
// packages/daemon/src/config/DaemonConfig.ts
|
|
@@ -3536,19 +3542,19 @@ var ProjectScanner = class {
|
|
|
3536
3542
|
async getRunningPorts() {
|
|
3537
3543
|
const open = [];
|
|
3538
3544
|
await Promise.all(PORTS_TO_CHECK.map(
|
|
3539
|
-
(port) => new Promise((
|
|
3545
|
+
(port) => new Promise((resolve12) => {
|
|
3540
3546
|
const s = createServer();
|
|
3541
3547
|
s.listen(port, "127.0.0.1", () => {
|
|
3542
3548
|
s.close();
|
|
3543
|
-
|
|
3549
|
+
resolve12();
|
|
3544
3550
|
});
|
|
3545
3551
|
s.on("error", () => {
|
|
3546
3552
|
open.push(port);
|
|
3547
|
-
|
|
3553
|
+
resolve12();
|
|
3548
3554
|
});
|
|
3549
3555
|
setTimeout(() => {
|
|
3550
3556
|
s.close();
|
|
3551
|
-
|
|
3557
|
+
resolve12();
|
|
3552
3558
|
}, 200);
|
|
3553
3559
|
})
|
|
3554
3560
|
));
|
|
@@ -4291,7 +4297,7 @@ var SkillRegistry = class {
|
|
|
4291
4297
|
};
|
|
4292
4298
|
|
|
4293
4299
|
// packages/daemon/src/HTTPServer.ts
|
|
4294
|
-
import { Hono as
|
|
4300
|
+
import { Hono as Hono10 } from "hono";
|
|
4295
4301
|
import { serve } from "@hono/node-server";
|
|
4296
4302
|
import { readFileSync as readFileSync6 } from "node:fs";
|
|
4297
4303
|
import { resolve as resolve5, dirname as dirname3 } from "node:path";
|
|
@@ -4546,6 +4552,48 @@ function skillRoutes(deps) {
|
|
|
4546
4552
|
return app;
|
|
4547
4553
|
}
|
|
4548
4554
|
|
|
4555
|
+
// packages/daemon/src/routes/insights.ts
|
|
4556
|
+
import { Hono as Hono8 } from "hono";
|
|
4557
|
+
function insightsRoutes(deps) {
|
|
4558
|
+
const app = new Hono8();
|
|
4559
|
+
app.get("/", (c) => {
|
|
4560
|
+
if (!deps.proactiveSurface) return c.json([]);
|
|
4561
|
+
const seen = c.req.query("seen");
|
|
4562
|
+
const insights = seen === "false" ? deps.proactiveSurface.getUnseen() : deps.proactiveSurface.getAll();
|
|
4563
|
+
return c.json(insights);
|
|
4564
|
+
});
|
|
4565
|
+
app.post("/:id/seen", (c) => {
|
|
4566
|
+
if (!deps.proactiveSurface) return c.json({ ok: false, error: "not available" }, 404);
|
|
4567
|
+
deps.proactiveSurface.markSeen(c.req.param("id"));
|
|
4568
|
+
return c.json({ ok: true });
|
|
4569
|
+
});
|
|
4570
|
+
return app;
|
|
4571
|
+
}
|
|
4572
|
+
|
|
4573
|
+
// packages/daemon/src/routes/memory.ts
|
|
4574
|
+
import { Hono as Hono9 } from "hono";
|
|
4575
|
+
function memoryRoutes(deps) {
|
|
4576
|
+
const app = new Hono9();
|
|
4577
|
+
app.post("/push", async (c) => {
|
|
4578
|
+
const sync = deps.getSync();
|
|
4579
|
+
if (!sync) return c.json({ error: "GitHub memory not configured. Run: 0agent memory connect github" }, 404);
|
|
4580
|
+
const result = await sync.push();
|
|
4581
|
+
return c.json(result);
|
|
4582
|
+
});
|
|
4583
|
+
app.post("/pull", async (c) => {
|
|
4584
|
+
const sync = deps.getSync();
|
|
4585
|
+
if (!sync) return c.json({ error: "GitHub memory not configured." }, 404);
|
|
4586
|
+
const result = await sync.pull();
|
|
4587
|
+
return c.json(result);
|
|
4588
|
+
});
|
|
4589
|
+
app.get("/status", (c) => {
|
|
4590
|
+
const sync = deps.getSync();
|
|
4591
|
+
if (!sync) return c.json({ configured: false });
|
|
4592
|
+
return c.json({ configured: true, ...sync.getLastSyncTimes() });
|
|
4593
|
+
});
|
|
4594
|
+
return app;
|
|
4595
|
+
}
|
|
4596
|
+
|
|
4549
4597
|
// packages/daemon/src/HTTPServer.ts
|
|
4550
4598
|
function findGraphHtml() {
|
|
4551
4599
|
const candidates = [
|
|
@@ -4571,7 +4619,7 @@ var HTTPServer = class {
|
|
|
4571
4619
|
deps;
|
|
4572
4620
|
constructor(deps) {
|
|
4573
4621
|
this.deps = deps;
|
|
4574
|
-
this.app = new
|
|
4622
|
+
this.app = new Hono10();
|
|
4575
4623
|
this.app.route("/api/health", healthRoutes({ getStatus: deps.getStatus }));
|
|
4576
4624
|
this.app.route("/api/sessions", sessionRoutes({ sessions: deps.sessions }));
|
|
4577
4625
|
this.app.route("/api/graph", graphRoutes({ graph: deps.graph }));
|
|
@@ -4579,6 +4627,8 @@ var HTTPServer = class {
|
|
|
4579
4627
|
this.app.route("/api/traces", traceRoutes({ traceStore: deps.traceStore }));
|
|
4580
4628
|
this.app.route("/api/subagents", subagentRoutes());
|
|
4581
4629
|
this.app.route("/api/skills", skillRoutes({ skillRegistry: deps.skillRegistry }));
|
|
4630
|
+
this.app.route("/api/insights", insightsRoutes({ proactiveSurface: deps.proactiveSurface ?? null }));
|
|
4631
|
+
this.app.route("/api/memory", memoryRoutes({ getSync: deps.getMemorySync ?? (() => null) }));
|
|
4582
4632
|
const serveGraph = (c) => {
|
|
4583
4633
|
try {
|
|
4584
4634
|
const html = readFileSync6(GRAPH_HTML_PATH, "utf8");
|
|
@@ -4591,7 +4641,7 @@ var HTTPServer = class {
|
|
|
4591
4641
|
this.app.get("/graph", serveGraph);
|
|
4592
4642
|
}
|
|
4593
4643
|
start() {
|
|
4594
|
-
return new Promise((
|
|
4644
|
+
return new Promise((resolve12) => {
|
|
4595
4645
|
this.server = serve(
|
|
4596
4646
|
{
|
|
4597
4647
|
fetch: this.app.fetch,
|
|
@@ -4599,20 +4649,20 @@ var HTTPServer = class {
|
|
|
4599
4649
|
hostname: this.deps.host
|
|
4600
4650
|
},
|
|
4601
4651
|
() => {
|
|
4602
|
-
|
|
4652
|
+
resolve12();
|
|
4603
4653
|
}
|
|
4604
4654
|
);
|
|
4605
4655
|
});
|
|
4606
4656
|
}
|
|
4607
4657
|
stop() {
|
|
4608
|
-
return new Promise((
|
|
4658
|
+
return new Promise((resolve12, reject) => {
|
|
4609
4659
|
if (!this.server) {
|
|
4610
|
-
|
|
4660
|
+
resolve12();
|
|
4611
4661
|
return;
|
|
4612
4662
|
}
|
|
4613
4663
|
this.server.close((err) => {
|
|
4614
4664
|
if (err) reject(err);
|
|
4615
|
-
else
|
|
4665
|
+
else resolve12();
|
|
4616
4666
|
});
|
|
4617
4667
|
});
|
|
4618
4668
|
}
|
|
@@ -5148,6 +5198,372 @@ var TeamSync = class {
|
|
|
5148
5198
|
}
|
|
5149
5199
|
};
|
|
5150
5200
|
|
|
5201
|
+
// packages/daemon/src/GitHubMemorySync.ts
|
|
5202
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync9, readdirSync as readdirSync4 } from "node:fs";
|
|
5203
|
+
import { resolve as resolve8 } from "node:path";
|
|
5204
|
+
import { homedir as homedir5 } from "node:os";
|
|
5205
|
+
var GITHUB_API = "https://api.github.com";
|
|
5206
|
+
async function ghFetch(path, token, opts) {
|
|
5207
|
+
return fetch(`${GITHUB_API}${path}`, {
|
|
5208
|
+
...opts,
|
|
5209
|
+
headers: {
|
|
5210
|
+
Authorization: `Bearer ${token}`,
|
|
5211
|
+
Accept: "application/vnd.github.v3+json",
|
|
5212
|
+
"Content-Type": "application/json",
|
|
5213
|
+
"User-Agent": "0agent/1.0",
|
|
5214
|
+
...opts?.headers ?? {}
|
|
5215
|
+
},
|
|
5216
|
+
signal: AbortSignal.timeout(15e3)
|
|
5217
|
+
});
|
|
5218
|
+
}
|
|
5219
|
+
async function getFileSha(token, owner, repo, path) {
|
|
5220
|
+
try {
|
|
5221
|
+
const res = await ghFetch(`/repos/${owner}/${repo}/contents/${path}`, token);
|
|
5222
|
+
if (!res.ok) return null;
|
|
5223
|
+
const data = await res.json();
|
|
5224
|
+
return data.sha;
|
|
5225
|
+
} catch {
|
|
5226
|
+
return null;
|
|
5227
|
+
}
|
|
5228
|
+
}
|
|
5229
|
+
async function putFile(token, owner, repo, path, content, message) {
|
|
5230
|
+
const sha = await getFileSha(token, owner, repo, path);
|
|
5231
|
+
const body = {
|
|
5232
|
+
message,
|
|
5233
|
+
content: Buffer.from(content).toString("base64")
|
|
5234
|
+
};
|
|
5235
|
+
if (sha) body.sha = sha;
|
|
5236
|
+
const res = await ghFetch(`/repos/${owner}/${repo}/contents/${path}`, token, {
|
|
5237
|
+
method: "PUT",
|
|
5238
|
+
body: JSON.stringify(body)
|
|
5239
|
+
});
|
|
5240
|
+
return res.ok;
|
|
5241
|
+
}
|
|
5242
|
+
async function getFile(token, owner, repo, path) {
|
|
5243
|
+
try {
|
|
5244
|
+
const res = await ghFetch(`/repos/${owner}/${repo}/contents/${path}`, token);
|
|
5245
|
+
if (!res.ok) return null;
|
|
5246
|
+
const data = await res.json();
|
|
5247
|
+
return Buffer.from(data.content, "base64").toString("utf8");
|
|
5248
|
+
} catch {
|
|
5249
|
+
return null;
|
|
5250
|
+
}
|
|
5251
|
+
}
|
|
5252
|
+
var GitHubMemorySync = class {
|
|
5253
|
+
constructor(config, adapter, graph) {
|
|
5254
|
+
this.config = config;
|
|
5255
|
+
this.adapter = adapter;
|
|
5256
|
+
this.graph = graph;
|
|
5257
|
+
}
|
|
5258
|
+
lastPushAt = 0;
|
|
5259
|
+
lastPullAt = 0;
|
|
5260
|
+
pendingChanges = false;
|
|
5261
|
+
/**
|
|
5262
|
+
* Push current graph state to GitHub.
|
|
5263
|
+
* Called automatically every 30 minutes and on `0agent memory sync`.
|
|
5264
|
+
*/
|
|
5265
|
+
async push(message) {
|
|
5266
|
+
const now = Date.now();
|
|
5267
|
+
const commitMsg = message ?? `sync: ${new Date(now).toISOString()}`;
|
|
5268
|
+
try {
|
|
5269
|
+
const nodes = this.adapter.queryNodes({});
|
|
5270
|
+
const nodesJson = JSON.stringify(nodes.map((n) => ({
|
|
5271
|
+
id: n.id,
|
|
5272
|
+
graph_id: n.graph_id,
|
|
5273
|
+
label: n.label,
|
|
5274
|
+
type: n.type,
|
|
5275
|
+
visit_count: n.visit_count,
|
|
5276
|
+
metadata: n.metadata,
|
|
5277
|
+
subgraph_id: n.subgraph_id,
|
|
5278
|
+
created_at: n.created_at,
|
|
5279
|
+
last_seen: n.last_seen
|
|
5280
|
+
// Omit embeddings — too large, recomputed locally
|
|
5281
|
+
})), null, 2);
|
|
5282
|
+
const edges = this.adapter.getAllEdges();
|
|
5283
|
+
const edgesJson = JSON.stringify(edges.map((e) => ({
|
|
5284
|
+
id: e.id,
|
|
5285
|
+
graph_id: e.graph_id,
|
|
5286
|
+
from_node: e.from_node,
|
|
5287
|
+
to_node: e.to_node,
|
|
5288
|
+
type: e.type,
|
|
5289
|
+
weight: e.weight,
|
|
5290
|
+
locked: e.locked,
|
|
5291
|
+
decay_rate: e.decay_rate,
|
|
5292
|
+
traversal_count: e.traversal_count,
|
|
5293
|
+
created_at: e.created_at
|
|
5294
|
+
})), null, 2);
|
|
5295
|
+
const weightEvents = this.getRecentWeightEvents(500);
|
|
5296
|
+
const weightEventsJsonl = weightEvents.map((e) => JSON.stringify(e)).join("\n");
|
|
5297
|
+
const personalityMap = this.exportPersonalityProfiles();
|
|
5298
|
+
const conversations = this.getConversations(1e3);
|
|
5299
|
+
const convsJsonl = conversations.map((c) => JSON.stringify(c)).join("\n");
|
|
5300
|
+
const { token, owner, repo } = this.config;
|
|
5301
|
+
const pushes = [
|
|
5302
|
+
putFile(token, owner, repo, "graph/nodes.json", nodesJson, commitMsg),
|
|
5303
|
+
putFile(token, owner, repo, "graph/edges.json", edgesJson, commitMsg)
|
|
5304
|
+
];
|
|
5305
|
+
if (weightEventsJsonl) {
|
|
5306
|
+
pushes.push(putFile(token, owner, repo, "graph/weight_events.jsonl", weightEventsJsonl, commitMsg));
|
|
5307
|
+
}
|
|
5308
|
+
if (convsJsonl) {
|
|
5309
|
+
pushes.push(putFile(token, owner, repo, "memory/conversations.jsonl", convsJsonl, commitMsg));
|
|
5310
|
+
}
|
|
5311
|
+
for (const [entityId, profile] of Object.entries(personalityMap)) {
|
|
5312
|
+
pushes.push(
|
|
5313
|
+
putFile(
|
|
5314
|
+
token,
|
|
5315
|
+
owner,
|
|
5316
|
+
repo,
|
|
5317
|
+
`memory/personalities/${entityId}.json`,
|
|
5318
|
+
JSON.stringify(profile, null, 2),
|
|
5319
|
+
commitMsg
|
|
5320
|
+
)
|
|
5321
|
+
);
|
|
5322
|
+
}
|
|
5323
|
+
const customSkillsDir = resolve8(homedir5(), ".0agent", "skills", "custom");
|
|
5324
|
+
if (existsSync9(customSkillsDir)) {
|
|
5325
|
+
for (const file of readdirSync4(customSkillsDir).filter((f) => f.endsWith(".yaml"))) {
|
|
5326
|
+
const content = readFileSync9(resolve8(customSkillsDir, file), "utf8");
|
|
5327
|
+
pushes.push(putFile(token, owner, repo, `skills/custom/${file}`, content, commitMsg));
|
|
5328
|
+
}
|
|
5329
|
+
}
|
|
5330
|
+
const readme = this.generateReadme(nodes.length, edges.length);
|
|
5331
|
+
pushes.push(putFile(token, owner, repo, "README.md", readme, commitMsg));
|
|
5332
|
+
await Promise.all(pushes);
|
|
5333
|
+
this.lastPushAt = now;
|
|
5334
|
+
this.pendingChanges = false;
|
|
5335
|
+
return { pushed: true, pulled: false, nodes_synced: nodes.length, edges_synced: edges.length, timestamp: now };
|
|
5336
|
+
} catch (err) {
|
|
5337
|
+
return {
|
|
5338
|
+
pushed: false,
|
|
5339
|
+
pulled: false,
|
|
5340
|
+
nodes_synced: 0,
|
|
5341
|
+
edges_synced: 0,
|
|
5342
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5343
|
+
timestamp: now
|
|
5344
|
+
};
|
|
5345
|
+
}
|
|
5346
|
+
}
|
|
5347
|
+
/**
|
|
5348
|
+
* Pull graph state from GitHub and merge into local SQLite.
|
|
5349
|
+
* Safe: never overwrites locked edges or newer local data.
|
|
5350
|
+
*/
|
|
5351
|
+
async pull() {
|
|
5352
|
+
const now = Date.now();
|
|
5353
|
+
try {
|
|
5354
|
+
const { token, owner, repo } = this.config;
|
|
5355
|
+
const [nodesJson, edgesJson] = await Promise.all([
|
|
5356
|
+
getFile(token, owner, repo, "graph/nodes.json"),
|
|
5357
|
+
getFile(token, owner, repo, "graph/edges.json")
|
|
5358
|
+
]);
|
|
5359
|
+
let nodeCount = 0, edgeCount = 0;
|
|
5360
|
+
if (nodesJson) {
|
|
5361
|
+
const nodes = JSON.parse(nodesJson);
|
|
5362
|
+
for (const n of nodes) {
|
|
5363
|
+
const existing = this.graph.getNode(String(n.id));
|
|
5364
|
+
if (!existing) {
|
|
5365
|
+
this.graph.addNode({
|
|
5366
|
+
id: String(n.id),
|
|
5367
|
+
graph_id: String(n.graph_id ?? "root"),
|
|
5368
|
+
label: String(n.label),
|
|
5369
|
+
type: n.type,
|
|
5370
|
+
created_at: Number(n.created_at ?? now),
|
|
5371
|
+
last_seen: Number(n.last_seen ?? now),
|
|
5372
|
+
visit_count: Number(n.visit_count ?? 1),
|
|
5373
|
+
metadata: n.metadata ?? {},
|
|
5374
|
+
subgraph_id: n.subgraph_id ? String(n.subgraph_id) : null,
|
|
5375
|
+
embedding: null,
|
|
5376
|
+
embedding_model: null,
|
|
5377
|
+
embedding_at: null,
|
|
5378
|
+
content: []
|
|
5379
|
+
});
|
|
5380
|
+
nodeCount++;
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5383
|
+
}
|
|
5384
|
+
if (edgesJson) {
|
|
5385
|
+
const edges = JSON.parse(edgesJson);
|
|
5386
|
+
for (const e of edges) {
|
|
5387
|
+
const existing = this.graph.getEdge(String(e.id));
|
|
5388
|
+
if (!existing) {
|
|
5389
|
+
this.graph.addEdge({
|
|
5390
|
+
id: String(e.id),
|
|
5391
|
+
graph_id: String(e.graph_id ?? "root"),
|
|
5392
|
+
from_node: String(e.from_node),
|
|
5393
|
+
to_node: String(e.to_node),
|
|
5394
|
+
type: e.type,
|
|
5395
|
+
weight: Number(e.weight ?? 0.5),
|
|
5396
|
+
locked: Boolean(e.locked),
|
|
5397
|
+
decay_rate: Number(e.decay_rate ?? 1e-3),
|
|
5398
|
+
created_at: Number(e.created_at ?? now),
|
|
5399
|
+
last_traversed: null,
|
|
5400
|
+
traversal_count: Number(e.traversal_count ?? 0),
|
|
5401
|
+
metadata: {}
|
|
5402
|
+
});
|
|
5403
|
+
edgeCount++;
|
|
5404
|
+
} else if (!existing.locked) {
|
|
5405
|
+
const remoteWeight = Number(e.weight ?? 0.5);
|
|
5406
|
+
const blended = existing.weight * 0.6 + remoteWeight * 0.4;
|
|
5407
|
+
if (Math.abs(blended - existing.weight) > 0.01) {
|
|
5408
|
+
this.adapter.forceUpdateEdgeWeight(existing.id, blended);
|
|
5409
|
+
edgeCount++;
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
await this.pullCustomSkills();
|
|
5415
|
+
this.lastPullAt = now;
|
|
5416
|
+
return { pushed: false, pulled: true, nodes_synced: nodeCount, edges_synced: edgeCount, timestamp: now };
|
|
5417
|
+
} catch (err) {
|
|
5418
|
+
return {
|
|
5419
|
+
pushed: false,
|
|
5420
|
+
pulled: false,
|
|
5421
|
+
nodes_synced: 0,
|
|
5422
|
+
edges_synced: 0,
|
|
5423
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5424
|
+
timestamp: now
|
|
5425
|
+
};
|
|
5426
|
+
}
|
|
5427
|
+
}
|
|
5428
|
+
/** Mark that changes exist — scheduler will push on next interval. */
|
|
5429
|
+
markDirty() {
|
|
5430
|
+
this.pendingChanges = true;
|
|
5431
|
+
}
|
|
5432
|
+
hasPendingChanges() {
|
|
5433
|
+
return this.pendingChanges;
|
|
5434
|
+
}
|
|
5435
|
+
getLastSyncTimes() {
|
|
5436
|
+
return { pushed_at: this.lastPushAt, pulled_at: this.lastPullAt };
|
|
5437
|
+
}
|
|
5438
|
+
// ─── Repo creation ─────────────────────────────────────────────────────────
|
|
5439
|
+
/**
|
|
5440
|
+
* Create the private GitHub repo for memory storage.
|
|
5441
|
+
* Returns the repo URL on success.
|
|
5442
|
+
*/
|
|
5443
|
+
static async createRepo(token, owner, repoName) {
|
|
5444
|
+
const res = await ghFetch("/user/repos", token, {
|
|
5445
|
+
method: "POST",
|
|
5446
|
+
body: JSON.stringify({
|
|
5447
|
+
name: repoName,
|
|
5448
|
+
description: "0agent memory \u2014 knowledge graph, personality profiles, conversation history",
|
|
5449
|
+
private: true,
|
|
5450
|
+
auto_init: true
|
|
5451
|
+
// creates main branch with README
|
|
5452
|
+
})
|
|
5453
|
+
});
|
|
5454
|
+
if (!res.ok) {
|
|
5455
|
+
const err = await res.json();
|
|
5456
|
+
if (res.status !== 422) throw new Error(`Failed to create repo: ${err.message ?? res.status}`);
|
|
5457
|
+
}
|
|
5458
|
+
return `https://github.com/${owner}/${repoName}`;
|
|
5459
|
+
}
|
|
5460
|
+
/**
|
|
5461
|
+
* Verify token works and return the authenticated username.
|
|
5462
|
+
*/
|
|
5463
|
+
static async verifyToken(token) {
|
|
5464
|
+
try {
|
|
5465
|
+
const res = await ghFetch("/user", token);
|
|
5466
|
+
if (!res.ok) return null;
|
|
5467
|
+
const user = await res.json();
|
|
5468
|
+
return user.login;
|
|
5469
|
+
} catch {
|
|
5470
|
+
return null;
|
|
5471
|
+
}
|
|
5472
|
+
}
|
|
5473
|
+
// ─── Private helpers ───────────────────────────────────────────────────────
|
|
5474
|
+
getRecentWeightEvents(limit) {
|
|
5475
|
+
try {
|
|
5476
|
+
const db = this.adapter.db;
|
|
5477
|
+
return db.prepare(`SELECT * FROM weight_events ORDER BY created_at DESC LIMIT ?`).all(limit);
|
|
5478
|
+
} catch {
|
|
5479
|
+
return [];
|
|
5480
|
+
}
|
|
5481
|
+
}
|
|
5482
|
+
getConversations(limit) {
|
|
5483
|
+
try {
|
|
5484
|
+
const db = this.adapter.db;
|
|
5485
|
+
return db.prepare(`SELECT * FROM conversations ORDER BY created_at DESC LIMIT ?`).all(limit);
|
|
5486
|
+
} catch {
|
|
5487
|
+
return [];
|
|
5488
|
+
}
|
|
5489
|
+
}
|
|
5490
|
+
exportPersonalityProfiles() {
|
|
5491
|
+
const profiles = {};
|
|
5492
|
+
try {
|
|
5493
|
+
const profileNodes = this.adapter.queryNodes({ type: "signal" }).filter(
|
|
5494
|
+
(n) => n.metadata?.is_personality_profile
|
|
5495
|
+
);
|
|
5496
|
+
for (const node of profileNodes) {
|
|
5497
|
+
const entityId = node.metadata?.entity_id;
|
|
5498
|
+
if (entityId && node.content?.[0]?.data) {
|
|
5499
|
+
try {
|
|
5500
|
+
profiles[entityId] = JSON.parse(node.content[0].data);
|
|
5501
|
+
} catch {
|
|
5502
|
+
}
|
|
5503
|
+
}
|
|
5504
|
+
}
|
|
5505
|
+
} catch {
|
|
5506
|
+
}
|
|
5507
|
+
return profiles;
|
|
5508
|
+
}
|
|
5509
|
+
async pullCustomSkills() {
|
|
5510
|
+
const { token, owner, repo } = this.config;
|
|
5511
|
+
const dir = resolve8(homedir5(), ".0agent", "skills", "custom");
|
|
5512
|
+
try {
|
|
5513
|
+
const res = await ghFetch(`/repos/${owner}/${repo}/contents/skills/custom`, token);
|
|
5514
|
+
if (!res.ok) return;
|
|
5515
|
+
const files = await res.json();
|
|
5516
|
+
for (const file of files.filter((f) => f.name.endsWith(".yaml"))) {
|
|
5517
|
+
const content = await getFile(token, owner, repo, `skills/custom/${file.name}`);
|
|
5518
|
+
if (content) {
|
|
5519
|
+
const { mkdirSync: mkdirSync7 } = await import("node:fs");
|
|
5520
|
+
mkdirSync7(dir, { recursive: true });
|
|
5521
|
+
writeFileSync6(resolve8(dir, file.name), content, "utf8");
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
} catch {
|
|
5525
|
+
}
|
|
5526
|
+
}
|
|
5527
|
+
generateReadme(nodeCount, edgeCount) {
|
|
5528
|
+
return `# 0agent Memory
|
|
5529
|
+
|
|
5530
|
+
> Private knowledge graph \u2014 backed up automatically by [0agent](https://github.com/cadetmaze/0agentv1)
|
|
5531
|
+
|
|
5532
|
+
**Last synced:** ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
5533
|
+
|
|
5534
|
+
## Contents
|
|
5535
|
+
|
|
5536
|
+
| File | Description |
|
|
5537
|
+
|------|-------------|
|
|
5538
|
+
| \`graph/nodes.json\` | ${nodeCount} entities, strategies, plans, context nodes |
|
|
5539
|
+
| \`graph/edges.json\` | ${edgeCount} relationships with learned weights |
|
|
5540
|
+
| \`graph/weight_events.jsonl\` | Full learning history (append-only) |
|
|
5541
|
+
| \`memory/personalities/\` | Communication style + preferences per person |
|
|
5542
|
+
| \`memory/conversations.jsonl\` | Conversation history |
|
|
5543
|
+
| \`skills/custom/\` | Custom skill definitions |
|
|
5544
|
+
|
|
5545
|
+
## Cross-device sync
|
|
5546
|
+
|
|
5547
|
+
\`\`\`bash
|
|
5548
|
+
# On a new machine:
|
|
5549
|
+
0agent memory connect github --repo ${this.config.owner}/${this.config.repo}
|
|
5550
|
+
\`\`\`
|
|
5551
|
+
|
|
5552
|
+
## Rollback
|
|
5553
|
+
|
|
5554
|
+
\`\`\`bash
|
|
5555
|
+
# Restore to a previous state:
|
|
5556
|
+
git log --oneline # find the commit
|
|
5557
|
+
git checkout <commit> graph/ # restore graph files
|
|
5558
|
+
0agent memory import # import into local SQLite
|
|
5559
|
+
\`\`\`
|
|
5560
|
+
|
|
5561
|
+
---
|
|
5562
|
+
*This repo is managed by 0agent. Do not edit files manually.*
|
|
5563
|
+
`;
|
|
5564
|
+
}
|
|
5565
|
+
};
|
|
5566
|
+
|
|
5151
5567
|
// packages/daemon/src/ZeroAgentDaemon.ts
|
|
5152
5568
|
var ZeroAgentDaemon = class {
|
|
5153
5569
|
config = null;
|
|
@@ -5160,15 +5576,18 @@ var ZeroAgentDaemon = class {
|
|
|
5160
5576
|
httpServer = null;
|
|
5161
5577
|
skillRegistry = null;
|
|
5162
5578
|
backgroundWorkers = null;
|
|
5579
|
+
githubMemorySync = null;
|
|
5580
|
+
memorySyncTimer = null;
|
|
5581
|
+
proactiveSurfaceInstance = null;
|
|
5163
5582
|
startedAt = 0;
|
|
5164
5583
|
pidFilePath;
|
|
5165
5584
|
constructor() {
|
|
5166
|
-
this.pidFilePath =
|
|
5585
|
+
this.pidFilePath = resolve10(homedir6(), ".0agent", "daemon.pid");
|
|
5167
5586
|
}
|
|
5168
5587
|
async start(opts) {
|
|
5169
5588
|
this.config = await loadConfig(opts?.config_path);
|
|
5170
|
-
const dotDir =
|
|
5171
|
-
if (!
|
|
5589
|
+
const dotDir = resolve10(homedir6(), ".0agent");
|
|
5590
|
+
if (!existsSync11(dotDir)) {
|
|
5172
5591
|
mkdirSync6(dotDir, { recursive: true });
|
|
5173
5592
|
}
|
|
5174
5593
|
this.adapter = new SQLiteAdapter({ db_path: this.config.graph.db_path });
|
|
@@ -5192,6 +5611,19 @@ var ZeroAgentDaemon = class {
|
|
|
5192
5611
|
} else {
|
|
5193
5612
|
console.warn("[0agent] No LLM API key configured \u2014 tasks will not call the LLM");
|
|
5194
5613
|
}
|
|
5614
|
+
const ghMemCfg = this.config["github_memory"];
|
|
5615
|
+
if (ghMemCfg?.enabled && ghMemCfg.token && ghMemCfg.owner && ghMemCfg.repo) {
|
|
5616
|
+
this.githubMemorySync = new GitHubMemorySync(
|
|
5617
|
+
{ token: ghMemCfg.token, owner: ghMemCfg.owner, repo: ghMemCfg.repo },
|
|
5618
|
+
this.adapter,
|
|
5619
|
+
this.graph
|
|
5620
|
+
);
|
|
5621
|
+
console.log(`[0agent] Memory sync: github.com/${ghMemCfg.owner}/${ghMemCfg.repo}`);
|
|
5622
|
+
this.githubMemorySync.pull().then((r) => {
|
|
5623
|
+
if (r.pulled) console.log(`[0agent] Memory pulled: +${r.nodes_synced} nodes, +${r.edges_synced} edges`);
|
|
5624
|
+
}).catch(() => {
|
|
5625
|
+
});
|
|
5626
|
+
}
|
|
5195
5627
|
const cwd = process.env["ZEROAGENT_CWD"] ?? process.cwd();
|
|
5196
5628
|
const identityManager = new IdentityManager(this.graph);
|
|
5197
5629
|
const identity = await identityManager.init().catch(() => null);
|
|
@@ -5221,6 +5653,18 @@ var ZeroAgentDaemon = class {
|
|
|
5221
5653
|
// enables ConversationStore + weight feedback
|
|
5222
5654
|
});
|
|
5223
5655
|
const teamSync = identity && teams.length > 0 ? new TeamSync(teamManager, this.adapter, identity.entity_node_id) : null;
|
|
5656
|
+
if (this.githubMemorySync) {
|
|
5657
|
+
const memSync = this.githubMemorySync;
|
|
5658
|
+
this.memorySyncTimer = setInterval(async () => {
|
|
5659
|
+
if (memSync.hasPendingChanges()) {
|
|
5660
|
+
const result = await memSync.push().catch(() => null);
|
|
5661
|
+
if (result?.pushed) {
|
|
5662
|
+
console.log(`[0agent] Memory auto-synced: ${result.nodes_synced} nodes`);
|
|
5663
|
+
}
|
|
5664
|
+
}
|
|
5665
|
+
}, 30 * 60 * 1e3);
|
|
5666
|
+
if (typeof this.memorySyncTimer === "object") this.memorySyncTimer.unref?.();
|
|
5667
|
+
}
|
|
5224
5668
|
let proactiveSurface = null;
|
|
5225
5669
|
try {
|
|
5226
5670
|
const { ProactiveSurface: ProactiveSurface2 } = await Promise.resolve().then(() => (init_ProactiveSurface(), ProactiveSurface_exports));
|
|
@@ -5239,6 +5683,7 @@ var ZeroAgentDaemon = class {
|
|
|
5239
5683
|
active_sessions: this.sessionManager.activeSessionCount()
|
|
5240
5684
|
}));
|
|
5241
5685
|
this.startedAt = Date.now();
|
|
5686
|
+
const memSyncRef = this.githubMemorySync;
|
|
5242
5687
|
this.httpServer = new HTTPServer({
|
|
5243
5688
|
port: this.config.server.port,
|
|
5244
5689
|
host: this.config.server.host,
|
|
@@ -5246,10 +5691,12 @@ var ZeroAgentDaemon = class {
|
|
|
5246
5691
|
graph: this.graph,
|
|
5247
5692
|
traceStore: this.traceStore,
|
|
5248
5693
|
skillRegistry: this.skillRegistry,
|
|
5249
|
-
getStatus: () => this.getStatus()
|
|
5694
|
+
getStatus: () => this.getStatus(),
|
|
5695
|
+
getMemorySync: () => memSyncRef,
|
|
5696
|
+
proactiveSurface
|
|
5250
5697
|
});
|
|
5251
5698
|
await this.httpServer.start();
|
|
5252
|
-
|
|
5699
|
+
writeFileSync7(this.pidFilePath, String(process.pid), "utf8");
|
|
5253
5700
|
console.log(
|
|
5254
5701
|
`[0agent] Daemon started on ${this.config.server.host}:${this.config.server.port} (PID: ${process.pid})`
|
|
5255
5702
|
);
|
|
@@ -5274,6 +5721,15 @@ var ZeroAgentDaemon = class {
|
|
|
5274
5721
|
this.backgroundWorkers.stop();
|
|
5275
5722
|
this.backgroundWorkers = null;
|
|
5276
5723
|
}
|
|
5724
|
+
if (this.githubMemorySync?.hasPendingChanges()) {
|
|
5725
|
+
await this.githubMemorySync.push("sync: daemon shutdown").catch(() => {
|
|
5726
|
+
});
|
|
5727
|
+
}
|
|
5728
|
+
if (this.memorySyncTimer) {
|
|
5729
|
+
clearInterval(this.memorySyncTimer);
|
|
5730
|
+
this.memorySyncTimer = null;
|
|
5731
|
+
}
|
|
5732
|
+
this.githubMemorySync = null;
|
|
5277
5733
|
this.sessionManager = null;
|
|
5278
5734
|
this.skillRegistry = null;
|
|
5279
5735
|
this.inferenceEngine = null;
|
|
@@ -5283,7 +5739,7 @@ var ZeroAgentDaemon = class {
|
|
|
5283
5739
|
this.graph = null;
|
|
5284
5740
|
}
|
|
5285
5741
|
this.adapter = null;
|
|
5286
|
-
if (
|
|
5742
|
+
if (existsSync11(this.pidFilePath)) {
|
|
5287
5743
|
try {
|
|
5288
5744
|
unlinkSync2(this.pidFilePath);
|
|
5289
5745
|
} catch {
|
|
@@ -5313,11 +5769,11 @@ var ZeroAgentDaemon = class {
|
|
|
5313
5769
|
};
|
|
5314
5770
|
|
|
5315
5771
|
// packages/daemon/src/start.ts
|
|
5316
|
-
import { resolve as
|
|
5317
|
-
import { homedir as
|
|
5318
|
-
import { existsSync as
|
|
5319
|
-
var CONFIG_PATH = process.env["ZEROAGENT_CONFIG"] ??
|
|
5320
|
-
if (!
|
|
5772
|
+
import { resolve as resolve11 } from "node:path";
|
|
5773
|
+
import { homedir as homedir7 } from "node:os";
|
|
5774
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
5775
|
+
var CONFIG_PATH = process.env["ZEROAGENT_CONFIG"] ?? resolve11(homedir7(), ".0agent", "config.yaml");
|
|
5776
|
+
if (!existsSync12(CONFIG_PATH)) {
|
|
5321
5777
|
console.error(`
|
|
5322
5778
|
0agent is not initialised.
|
|
5323
5779
|
|