@peonai/swarm 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/bin/swarm.mjs +69 -6
  2. package/package.json +7 -2
  3. package/.dockerignore +0 -6
  4. package/Dockerfile +0 -9
  5. package/app/api/health/route.ts +0 -2
  6. package/app/api/v1/admin/agents/[id]/route.ts +0 -12
  7. package/app/api/v1/admin/agents/route.ts +0 -31
  8. package/app/api/v1/admin/audit/route.ts +0 -23
  9. package/app/api/v1/admin/cleanup/route.ts +0 -21
  10. package/app/api/v1/admin/export/route.ts +0 -15
  11. package/app/api/v1/admin/history/route.ts +0 -23
  12. package/app/api/v1/admin/profile/route.ts +0 -23
  13. package/app/api/v1/admin/settings/route.ts +0 -44
  14. package/app/api/v1/auth/route.ts +0 -5
  15. package/app/api/v1/memory/route.ts +0 -105
  16. package/app/api/v1/persona/[agentId]/route.ts +0 -13
  17. package/app/api/v1/persona/me/route.ts +0 -12
  18. package/app/api/v1/profile/observe/route.ts +0 -34
  19. package/app/api/v1/profile/route.ts +0 -72
  20. package/app/api/v1/reflect/route.ts +0 -96
  21. package/app/globals.css +0 -190
  22. package/app/i18n.ts +0 -161
  23. package/app/layout.tsx +0 -12
  24. package/app/page.tsx +0 -561
  25. package/docker-compose.yml +0 -34
  26. package/docs/CNAME +0 -1
  27. package/docs/DEBATE-ROUND1.md +0 -244
  28. package/docs/DEBATE-ROUND2.md +0 -158
  29. package/docs/REQUIREMENTS.md +0 -162
  30. package/docs/docs.html +0 -272
  31. package/docs/index.html +0 -228
  32. package/docs/script.js +0 -103
  33. package/docs/style.css +0 -418
  34. package/lib/auth.ts +0 -63
  35. package/lib/db.ts +0 -63
  36. package/lib/embedding.ts +0 -29
  37. package/lib/schema.ts +0 -134
  38. package/mcp-server.ts +0 -56
  39. package/next-env.d.ts +0 -6
  40. package/next.config.ts +0 -5
  41. package/packages/cli/README.md +0 -33
  42. package/packages/cli/bin/swarm.mjs +0 -274
  43. package/packages/cli/package.json +0 -10
  44. package/postcss.config.mjs +0 -2
  45. package/skill/CLAUDE.md +0 -40
  46. package/skill/CODEX.md +0 -43
  47. package/skill/GEMINI.md +0 -38
  48. package/skill/IFLOW.md +0 -38
  49. package/skill/OPENCODE.md +0 -38
  50. package/skill/swarm-ai-skill/SKILL.md +0 -74
  51. package/skill/swarm-ai-skill/env.sh +0 -4
  52. package/skill/swarm-ai-skill/scripts/bootstrap.sh +0 -21
  53. package/skill/swarm-ai-skill/scripts/env.sh +0 -4
  54. package/skill/swarm-ai-skill/scripts/env.sh.example +0 -3
  55. package/skill/swarm-ai-skill/scripts/memory-read.sh +0 -8
  56. package/skill/swarm-ai-skill/scripts/memory-write.sh +0 -10
  57. package/skill/swarm-ai-skill/scripts/observe.sh +0 -9
  58. package/skill/swarm-ai-skill/scripts/profile-read.sh +0 -9
  59. package/skill/swarm-ai-skill/scripts/profile-update.sh +0 -9
  60. package/skill/swarm-ai-skill/scripts/session-start.sh +0 -19
  61. package/tsconfig.json +0 -21
  62. package/tsconfig.tsbuildinfo +0 -1
package/bin/swarm.mjs CHANGED
@@ -1,9 +1,72 @@
1
1
  #!/usr/bin/env node
2
+ import { createInterface } from 'readline';
2
3
  import { execSync } from 'child_process';
3
- import { existsSync } from 'fs';
4
- import { join, dirname } from 'path';
5
- import { fileURLToPath } from 'url';
4
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
6
5
 
7
- const root = join(dirname(fileURLToPath(import.meta.url)), '..');
8
- const cmd = process.argv.includes('--dev') ? 'npm run dev' : 'npm run build && npm run start';
9
- execSync(cmd, { cwd: root, stdio: 'inherit', env: { ...process.env } });
6
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
7
+ const ask = (q, def) => new Promise(r => rl.question(`${q}${def ? ` [${def}]` : ''}: `, a => r(a.trim() || def || '')));
8
+
9
+ async function main() {
10
+ console.log('\nšŸ Swarm AI Setup\n');
11
+
12
+ const installDir = await ask('Install directory', '/opt/swarm');
13
+ const port = await ask('Port', '3777');
14
+
15
+ console.log('\nšŸ“ Embedding (optional, for semantic search)');
16
+ const embedUrl = await ask('Embedding API URL (empty to skip)', '');
17
+ let embedKey = '', embedModel = '';
18
+ if (embedUrl) {
19
+ embedKey = await ask('API Key', '');
20
+ embedModel = await ask('Model', 'embedding-3');
21
+ }
22
+
23
+ const adminToken = await ask('\nšŸ”‘ Admin token', 'swarm-' + Math.random().toString(36).slice(2, 10));
24
+
25
+ // Clone & build
26
+ console.log(`\nšŸ“¦ Installing to ${installDir}...`);
27
+ mkdirSync(installDir, { recursive: true });
28
+
29
+ if (!existsSync(`${installDir}/package.json`)) {
30
+ console.log(' Cloning from GitHub...');
31
+ execSync(`git clone --depth 1 https://github.com/peonai/swarm.git ${installDir}`, { stdio: 'inherit' });
32
+ }
33
+
34
+ console.log(' Installing dependencies...');
35
+ execSync('npm install --production=false', { cwd: installDir, stdio: 'inherit' });
36
+
37
+ console.log(' Building...');
38
+ execSync('npm run build', { cwd: installDir, stdio: 'inherit' });
39
+
40
+ // Write .env
41
+ const env = [
42
+ `PORT=${port}`,
43
+ `SWARM_ADMIN_TOKEN=${adminToken}`,
44
+ embedUrl && `EMBED_URL=${embedUrl}`,
45
+ embedKey && `EMBED_KEY=${embedKey}`,
46
+ embedModel && `EMBED_MODEL=${embedModel}`,
47
+ ].filter(Boolean).join('\n');
48
+ writeFileSync(`${installDir}/.env`, env);
49
+
50
+ // Systemd
51
+ const useSvc = (await ask('\nInstall as systemd service? (y/n)', 'y')).toLowerCase();
52
+ if (useSvc === 'y') {
53
+ const unit = `[Unit]\nDescription=Swarm AI\nAfter=network.target\n\n[Service]\nType=simple\nWorkingDirectory=${installDir}\nEnvironmentFile=${installDir}/.env\nExecStart=/usr/bin/env node ${installDir}/.next/standalone/server.js\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target`;
54
+ try {
55
+ writeFileSync('/etc/systemd/system/swarm.service', unit);
56
+ execSync('systemctl daemon-reload && systemctl enable swarm && systemctl start swarm');
57
+ console.log(`\nāœ… Swarm AI running on port ${port}`);
58
+ console.log(' systemctl status swarm');
59
+ } catch {
60
+ console.log('\nāš ļø Need root for systemd. Run with sudo, or start manually.');
61
+ }
62
+ } else {
63
+ console.log(`\nšŸš€ Start: cd ${installDir} && node .next/standalone/server.js`);
64
+ }
65
+
66
+ console.log(`\nšŸ”‘ Admin: ${adminToken}`);
67
+ console.log(`šŸ“” API: http://localhost:${port}/api/v1/`);
68
+ console.log(`🌐 Dashboard: http://localhost:${port}\n`);
69
+ rl.close();
70
+ }
71
+
72
+ main().catch(e => { console.error(e); process.exit(1); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peonai/swarm",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Cross-agent user profile sync \u2014 let every AI agent know your user without re-teaching",
5
5
  "repository": {
6
6
  "type": "git",
@@ -40,5 +40,10 @@
40
40
  "bin": {
41
41
  "swarm": "./bin/swarm.mjs"
42
42
  },
43
- "type": "module"
43
+ "type": "module",
44
+ "files": [
45
+ "bin/",
46
+ "README.md",
47
+ "package.json"
48
+ ]
44
49
  }
package/.dockerignore DELETED
@@ -1,6 +0,0 @@
1
- node_modules
2
- .next
3
- data
4
- *.db
5
- .git
6
- .env
package/Dockerfile DELETED
@@ -1,9 +0,0 @@
1
- FROM node:24-alpine
2
- WORKDIR /app
3
- COPY package.json package-lock.json ./
4
- RUN npm ci --production=false
5
- COPY . .
6
- RUN npm run build
7
- ENV NODE_ENV=production PORT=3777
8
- EXPOSE 3777
9
- CMD ["npm", "start"]
@@ -1,2 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
- export function GET() { return NextResponse.json({ ok: true, version: '0.1.0' }); }
@@ -1,12 +0,0 @@
1
- export const dynamic = "force-dynamic";
2
- import { NextRequest, NextResponse } from 'next/server';
3
- import db from '@/lib/db';
4
- import { initSchema } from '@/lib/schema';
5
- import { withAdmin } from '@/lib/auth';
6
-
7
- export const DELETE = withAdmin(async (req, userId) => {
8
- await initSchema();
9
- const id = req.nextUrl.pathname.split('/').pop() ?? '';
10
- await db.prepare('DELETE FROM agents WHERE id = ? AND user_id = ?').run(id, userId);
11
- return NextResponse.json({ ok: true });
12
- });
@@ -1,31 +0,0 @@
1
- export const dynamic = "force-dynamic";
2
- import { NextResponse } from 'next/server';
3
- import db from '@/lib/db';
4
- import { initSchema } from '@/lib/schema';
5
- import { withAdmin } from '@/lib/auth';
6
-
7
- export const GET = withAdmin(async (_req, userId) => {
8
- await initSchema();
9
- const rows = await db.prepare('SELECT id, name, permissions, persona, created_at FROM agents WHERE user_id = ?').all(userId) as any[];
10
- return NextResponse.json(rows.map(a => ({ ...a, persona: a.persona ? JSON.parse(a.persona) : null })));
11
- });
12
-
13
- export const POST = withAdmin(async (req, userId) => {
14
- await initSchema();
15
- const { id, name, permissions = 'read,write' } = await req.json();
16
- const agentId = id || crypto.randomUUID().slice(0, 12);
17
- const apiKey = `swarm_${crypto.randomUUID().replace(/-/g, '')}`;
18
- await db.prepare('INSERT INTO agents (id, user_id, name, api_key, permissions) VALUES (?,?,?,?,?)').run(agentId, userId, name || agentId, apiKey, permissions);
19
- return NextResponse.json({ id: agentId, apiKey, permissions });
20
- });
21
-
22
- export const PATCH = withAdmin(async (req, userId) => {
23
- await initSchema();
24
- const { id, persona, name } = await req.json();
25
- if (!id) return NextResponse.json({ error: 'Missing agent id' }, { status: 400 });
26
- if (persona !== undefined)
27
- await db.prepare('UPDATE agents SET persona = ? WHERE id = ? AND user_id = ?').run(JSON.stringify(persona), id, userId);
28
- if (name !== undefined)
29
- await db.prepare('UPDATE agents SET name = ? WHERE id = ? AND user_id = ?').run(name, id, userId);
30
- return NextResponse.json({ ok: true });
31
- });
@@ -1,23 +0,0 @@
1
- export const dynamic = 'force-dynamic';
2
- import { NextResponse } from 'next/server';
3
- import db from '@/lib/db';
4
- import { initSchema } from '@/lib/schema';
5
- import { withAdmin } from '@/lib/auth';
6
-
7
- export const GET = withAdmin(async (req, userId) => {
8
- await initSchema();
9
- const { searchParams } = req.nextUrl;
10
- const limit = Number(searchParams.get('limit') || 50);
11
- const action = searchParams.get('action');
12
- const agent = searchParams.get('agent');
13
-
14
- let sql = 'SELECT * FROM audit_log WHERE user_id = ?';
15
- const params: any[] = [userId];
16
- if (action) { sql += ' AND action = ?'; params.push(action); }
17
- if (agent) { sql += ' AND agent_id = ?'; params.push(agent); }
18
- sql += ' ORDER BY created_at DESC LIMIT ?';
19
- params.push(limit);
20
-
21
- const rows = await db.prepare(sql).all(...params);
22
- return NextResponse.json(rows);
23
- });
@@ -1,21 +0,0 @@
1
- export const dynamic = 'force-dynamic';
2
- import { NextResponse } from 'next/server';
3
- import db, { isPg } from '@/lib/db';
4
- import { initSchema, logAudit, ensureDefaultUser } from '@/lib/schema';
5
- import { withAdmin } from '@/lib/auth';
6
-
7
- const NOW = isPg ? 'NOW()' : "datetime('now')";
8
-
9
- export const POST = withAdmin(async (_req, userId) => {
10
- await initSchema();
11
- const expired = await db.prepare(
12
- `SELECT COUNT(*) as count FROM profiles WHERE expires_at IS NOT NULL AND expires_at < ${NOW}`
13
- ).get() as any;
14
-
15
- await db.prepare(
16
- `DELETE FROM profiles WHERE expires_at IS NOT NULL AND expires_at < ${NOW}`
17
- ).run();
18
-
19
- await logAudit(userId, null, 'cleanup', 'profiles', undefined, `${expired?.count || 0} expired entries removed`);
20
- return NextResponse.json({ ok: true, removed: expired?.count || 0 });
21
- });
@@ -1,15 +0,0 @@
1
- export const dynamic = "force-dynamic";
2
- import { NextResponse } from 'next/server';
3
- import db from '@/lib/db';
4
- import { initSchema } from '@/lib/schema';
5
- import { withAdmin } from '@/lib/auth';
6
-
7
- export const GET = withAdmin(async (_req, userId) => {
8
- await initSchema();
9
- const profiles = await db.prepare('SELECT layer, key, value, confidence, source, tags, updated_at FROM profiles WHERE user_id = ?').all(userId);
10
- const agents = await db.prepare('SELECT id, name, permissions, persona, created_at FROM agents WHERE user_id = ?').all(userId);
11
- const memories = await db.prepare('SELECT key, content, source, tags, type, importance, entities, created_at FROM memories WHERE user_id = ?').all(userId);
12
- return NextResponse.json({ exported_at: new Date().toISOString(), profiles, agents, memories }, {
13
- headers: { 'Content-Disposition': 'attachment; filename="swarm-export.json"' },
14
- });
15
- });
@@ -1,23 +0,0 @@
1
- export const dynamic = 'force-dynamic';
2
- import { NextResponse } from 'next/server';
3
- import db from '@/lib/db';
4
- import { initSchema } from '@/lib/schema';
5
- import { withAdmin } from '@/lib/auth';
6
-
7
- export const GET = withAdmin(async (req, userId) => {
8
- await initSchema();
9
- const { searchParams } = req.nextUrl;
10
- const limit = Number(searchParams.get('limit') || 50);
11
- const layer = searchParams.get('layer');
12
- const key = searchParams.get('key');
13
-
14
- let sql = 'SELECT * FROM profile_history WHERE user_id = ?';
15
- const params: any[] = [userId];
16
- if (layer) { sql += ' AND layer = ?'; params.push(layer); }
17
- if (key) { sql += ' AND key = ?'; params.push(key); }
18
- sql += ' ORDER BY created_at DESC LIMIT ?';
19
- params.push(limit);
20
-
21
- const rows = await db.prepare(sql).all(...params);
22
- return NextResponse.json(rows);
23
- });
@@ -1,23 +0,0 @@
1
- export const dynamic = "force-dynamic";
2
- import { NextRequest, NextResponse } from 'next/server';
3
- import db, { isPg } from '@/lib/db';
4
- import { initSchema } from '@/lib/schema';
5
- import { withAdmin } from '@/lib/auth';
6
-
7
- const NOW = isPg ? 'NOW()' : "datetime('now')";
8
-
9
- export const GET = withAdmin(async (_req, userId) => {
10
- await initSchema();
11
- const rows = await db.prepare('SELECT * FROM profiles WHERE user_id = ? ORDER BY layer, key').all(userId) as any[];
12
- return NextResponse.json(rows.map((r: any) => ({ ...r, value: JSON.parse(r.value) })));
13
- });
14
-
15
- export const PUT = withAdmin(async (req, userId) => {
16
- await initSchema();
17
- const { entries } = await req.json();
18
- const sql = `INSERT INTO profiles (user_id, layer, key, value, source, updated_at)
19
- VALUES (?, ?, ?, ?, 'admin', ${NOW})
20
- ON CONFLICT(user_id, layer, key) DO UPDATE SET value=${isPg ? 'EXCLUDED' : 'excluded'}.value, source='admin', updated_at=${NOW}`;
21
- for (const e of entries) await db.prepare(sql).run(userId, e.layer, e.key, JSON.stringify(e.value));
22
- return NextResponse.json({ ok: true });
23
- });
@@ -1,44 +0,0 @@
1
- export const dynamic = 'force-dynamic';
2
- import { NextResponse } from 'next/server';
3
- import { withAdmin } from '@/lib/auth';
4
- import { getEmbeddingConfig } from '@/lib/embedding';
5
- import { writeFileSync, readFileSync, existsSync } from 'fs';
6
- import { join } from 'path';
7
-
8
- const ENV_PATH = join(process.cwd(), '.env.local');
9
-
10
- function readEnv(): Record<string, string> {
11
- if (!existsSync(ENV_PATH)) return {};
12
- const lines = readFileSync(ENV_PATH, 'utf8').split('\n');
13
- const env: Record<string, string> = {};
14
- for (const l of lines) {
15
- const m = l.match(/^([^=]+)=(.*)$/);
16
- if (m) env[m[1]] = m[2];
17
- }
18
- return env;
19
- }
20
-
21
- function writeEnv(env: Record<string, string>) {
22
- writeFileSync(ENV_PATH, Object.entries(env).map(([k, v]) => `${k}=${v}`).join('\n') + '\n');
23
- }
24
-
25
- export const GET = withAdmin(async () => {
26
- return NextResponse.json({
27
- embedding: getEmbeddingConfig(),
28
- adminToken: process.env.SWARM_ADMIN_TOKEN || 'swarm-admin-dev',
29
- port: process.env.PORT || '3777',
30
- });
31
- });
32
-
33
- export const PATCH = withAdmin(async (req) => {
34
- const { embedding } = await req.json();
35
- if (!embedding) return NextResponse.json({ error: 'Missing embedding' }, { status: 400 });
36
-
37
- const env = readEnv();
38
- if (embedding.url !== undefined) env.EMBED_URL = embedding.url;
39
- if (embedding.key !== undefined) env.EMBED_KEY = embedding.key;
40
- if (embedding.model !== undefined) env.EMBED_MODEL = embedding.model;
41
- writeEnv(env);
42
-
43
- return NextResponse.json({ ok: true, note: 'Restart server to apply changes' });
44
- });
@@ -1,5 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
-
3
- export async function POST() {
4
- return NextResponse.json({ error: 'Auth disabled' }, { status: 410 });
5
- }
@@ -1,105 +0,0 @@
1
- export const dynamic = "force-dynamic";
2
- import { NextResponse } from 'next/server';
3
- import db, { isPg } from '@/lib/db';
4
- import { initSchema, logAudit } from '@/lib/schema';
5
- import { withAuthOrAdmin } from '@/lib/auth';
6
- import { embed, cosine } from '@/lib/embedding';
7
-
8
- export const GET = withAuthOrAdmin(async (req, agent) => {
9
- await initSchema();
10
- const { searchParams } = req.nextUrl;
11
- const q = searchParams.get('q');
12
- const tag = searchParams.get('tag');
13
- const type = searchParams.get('type');
14
- const entity = searchParams.get('entity');
15
- const since = searchParams.get('since');
16
- const limit = Number(searchParams.get('limit') || 50);
17
-
18
- const mode = searchParams.get('mode'); // 'semantic' | null (default: fts)
19
-
20
- // Semantic search mode
21
- if (q && mode === 'semantic') {
22
- const qVec = await embed(q);
23
- const all = await db.prepare('SELECT * FROM memories WHERE user_id = ? AND embedding IS NOT NULL ORDER BY created_at DESC')
24
- .all(agent.userId) as any[];
25
- const scored = all.map(m => ({ ...m, score: cosine(qVec, JSON.parse(m.embedding)) }))
26
- .sort((a, b) => b.score - a.score).slice(0, limit);
27
- return NextResponse.json(scored.map(r => ({
28
- ...r, embedding: undefined,
29
- tags: r.tags?.split(',').filter(Boolean) || [],
30
- entities: r.entities?.split(',').filter(Boolean) || [],
31
- })));
32
- }
33
-
34
- let sql: string, params: any[];
35
-
36
- if (q) {
37
- const hasCJK = /[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff]/.test(q);
38
- if (isPg) {
39
- sql = hasCJK
40
- ? `SELECT * FROM memories WHERE user_id = ? AND content LIKE ? ORDER BY created_at DESC LIMIT ?`
41
- : `SELECT *, ts_rank(to_tsvector('english', content), plainto_tsquery('english', ?)) AS rank
42
- FROM memories WHERE user_id = ? AND to_tsvector('english', content) @@ plainto_tsquery('english', ?)
43
- ORDER BY rank DESC LIMIT ?`;
44
- params = hasCJK ? [agent.userId, `%${q}%`, limit] : [q, agent.userId, q, limit];
45
- } else {
46
- if (hasCJK) {
47
- sql = `SELECT * FROM memories WHERE user_id = ? AND content LIKE ? ORDER BY created_at DESC LIMIT ?`;
48
- params = [agent.userId, `%${q}%`, limit];
49
- } else {
50
- sql = `SELECT m.*, rank FROM memories m JOIN memories_fts ON memories_fts.rowid = m.id
51
- WHERE m.user_id = ? AND memories_fts MATCH ? ORDER BY rank LIMIT ?`;
52
- params = [agent.userId, q, limit];
53
- }
54
- }
55
- } else {
56
- sql = 'SELECT * FROM memories WHERE user_id = ?';
57
- params = [agent.userId];
58
- if (tag) { sql += ' AND tags LIKE ?'; params.push(`%${tag}%`); }
59
- if (type) { sql += ' AND type = ?'; params.push(type); }
60
- if (entity) { sql += ' AND entities LIKE ?'; params.push(`%${entity}%`); }
61
- if (since) { sql += ' AND created_at >= ?'; params.push(since); }
62
- sql += ' ORDER BY created_at DESC LIMIT ?';
63
- params.push(limit);
64
- }
65
-
66
- const rows = await db.prepare(sql).all(...params) as any[];
67
- return NextResponse.json(rows.map(r => ({
68
- ...r,
69
- tags: r.tags?.split(',').filter(Boolean) || [],
70
- entities: r.entities?.split(',').filter(Boolean) || [],
71
- })));
72
- });
73
-
74
- export const POST = withAuthOrAdmin(async (req, agent) => {
75
- await initSchema();
76
- if (!agent.permissions.includes('write')) return NextResponse.json({ error: 'No write permission' }, { status: 403 });
77
- const { key, content, tags, type, importance, entities } = await req.json();
78
- if (!content) return NextResponse.json({ error: 'Missing content' }, { status: 400 });
79
-
80
- const tagsStr = Array.isArray(tags) ? tags.join(',') : tags || null;
81
- const entStr = Array.isArray(entities) ? entities.join(',') : entities || null;
82
-
83
- await db.prepare(`INSERT INTO memories (user_id, key, content, source, tags, type, importance, entities, embedding)
84
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
85
- .run(agent.userId, key || null, content, agent.id, tagsStr, type || 'observation', importance ?? 0.5, entStr, null);
86
-
87
- // Background embed — don't block response
88
- embed(content).then(async vec => {
89
- const rows = await db.prepare('SELECT id FROM memories WHERE user_id = ? AND content = ? ORDER BY id DESC LIMIT 1').all(agent.userId, content) as any[];
90
- if (rows[0]) await db.prepare('UPDATE memories SET embedding = ? WHERE id = ?').run(JSON.stringify(vec), rows[0].id);
91
- }).catch(() => {});
92
-
93
- await logAudit(agent.userId, agent.id, 'memory.write', 'memory', key || undefined, content.slice(0, 100));
94
- return NextResponse.json({ ok: true });
95
- });
96
-
97
- export const DELETE = withAuthOrAdmin(async (req, agent) => {
98
- await initSchema();
99
- if (!agent.permissions.includes('write')) return NextResponse.json({ error: 'No write permission' }, { status: 403 });
100
- const { id } = await req.json();
101
- if (!id) return NextResponse.json({ error: 'Missing id' }, { status: 400 });
102
- await db.prepare('DELETE FROM memories WHERE id = ? AND user_id = ?').run(id, agent.userId);
103
- await logAudit(agent.userId, agent.id, 'memory.delete', 'memory', String(id));
104
- return NextResponse.json({ ok: true });
105
- });
@@ -1,13 +0,0 @@
1
- export const dynamic = "force-dynamic";
2
- import { NextRequest, NextResponse } from 'next/server';
3
- import db from '@/lib/db';
4
- import { initSchema } from '@/lib/schema';
5
- import { withAuth } from '@/lib/auth';
6
-
7
- export const GET = withAuth(async (req: NextRequest & { agentId?: string }, agent) => {
8
- await initSchema();
9
- const agentId = req.nextUrl.pathname.split('/').pop() ?? '';
10
- const row = await db.prepare('SELECT id, name, persona FROM agents WHERE id = ? AND user_id = ?').get(agentId, agent.userId) as any;
11
- if (!row) return NextResponse.json({ error: 'Not found' }, { status: 404 });
12
- return NextResponse.json({ ...row, persona: row.persona ? JSON.parse(row.persona) : null });
13
- });
@@ -1,12 +0,0 @@
1
- export const dynamic = "force-dynamic";
2
- import { NextResponse } from 'next/server';
3
- import db from '@/lib/db';
4
- import { initSchema } from '@/lib/schema';
5
- import { withAuth } from '@/lib/auth';
6
-
7
- export const GET = withAuth(async (_req, agent) => {
8
- await initSchema();
9
- const row = await db.prepare('SELECT id, name, persona, permissions FROM agents WHERE id = ?').get(agent.id) as any;
10
- if (!row) return NextResponse.json({ error: 'Not found' }, { status: 404 });
11
- return NextResponse.json({ ...row, persona: row.persona ? JSON.parse(row.persona) : null });
12
- });
@@ -1,34 +0,0 @@
1
- export const dynamic = "force-dynamic";
2
- import { NextResponse } from 'next/server';
3
- import db, { isPg } from '@/lib/db';
4
- import { initSchema, logAudit } from '@/lib/schema';
5
- import { withAuth } from '@/lib/auth';
6
-
7
- const NOW_SQL = isPg ? 'NOW()' : "datetime('now')";
8
-
9
- export const POST = withAuth(async (req, agent) => {
10
- await initSchema();
11
- if (!agent.permissions.includes('write')) return NextResponse.json({ error: 'No write permission' }, { status: 403 });
12
- const { observations } = await req.json();
13
- if (!Array.isArray(observations)) return NextResponse.json({ error: 'Missing observations array' }, { status: 400 });
14
-
15
- const upsertSql = `INSERT INTO profiles (user_id, layer, key, value, confidence, source, tags, expires_at, updated_at)
16
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ${NOW_SQL})
17
- ON CONFLICT(user_id, layer, key) DO UPDATE SET
18
- value = CASE WHEN ${isPg ? 'EXCLUDED' : 'excluded'}.confidence > profiles.confidence THEN ${isPg ? 'EXCLUDED' : 'excluded'}.value ELSE profiles.value END,
19
- confidence = ${isPg ? `GREATEST(profiles.confidence, EXCLUDED.confidence)` : `MAX(profiles.confidence, excluded.confidence)`},
20
- source = CASE WHEN ${isPg ? 'EXCLUDED' : 'excluded'}.confidence > profiles.confidence THEN ${isPg ? 'EXCLUDED' : 'excluded'}.source ELSE profiles.source END,
21
- tags = COALESCE(${isPg ? 'EXCLUDED' : 'excluded'}.tags, profiles.tags),
22
- expires_at = COALESCE(${isPg ? 'EXCLUDED' : 'excluded'}.expires_at, profiles.expires_at),
23
- updated_at = ${NOW_SQL}`;
24
-
25
- for (const obs of observations) {
26
- const tags = Array.isArray(obs.tags) ? obs.tags.join(',') : obs.tags || null;
27
- const defaultExpiry = (obs.layer || 'context') === 'context' && !obs.expiresAt
28
- ? new Date(Date.now() + 86400000).toISOString() : null;
29
- await db.prepare(upsertSql).run(agent.userId, obs.layer || 'context', obs.key, JSON.stringify(obs.value),
30
- obs.confidence ?? 0.5, agent.id, tags, obs.expiresAt || defaultExpiry);
31
- }
32
- await logAudit(agent.userId, agent.id, 'profile.observe', 'profile', undefined, `${observations.length} observations`);
33
- return NextResponse.json({ ok: true, count: observations.length });
34
- });
@@ -1,72 +0,0 @@
1
- export const dynamic = "force-dynamic";
2
- import { NextRequest, NextResponse } from 'next/server';
3
- import db, { isPg } from '@/lib/db';
4
- import { initSchema, logAudit, logProfileHistory } from '@/lib/schema';
5
- import { withAuth } from '@/lib/auth';
6
-
7
- const NOW = isPg ? 'NOW()' : "datetime('now')";
8
-
9
- export const GET = withAuth(async (req, agent) => {
10
- await initSchema();
11
- const layer = req.nextUrl.searchParams.get('layer');
12
- const tag = req.nextUrl.searchParams.get('tag');
13
-
14
- let sql = 'SELECT layer, key, value, confidence, source, tags, expires_at, updated_at FROM profiles WHERE user_id = ?';
15
- const params: any[] = [agent.userId];
16
- sql += ` AND (expires_at IS NULL OR expires_at > ${NOW})`;
17
- if (layer) { sql += ' AND layer = ?'; params.push(layer); }
18
- if (tag) { sql += ' AND tags LIKE ?'; params.push(`%${tag}%`); }
19
- sql += ' ORDER BY layer, key';
20
-
21
- const rows = await db.prepare(sql).all(...params) as any[];
22
- const profile: Record<string, Record<string, any>> = {};
23
- for (const r of rows) {
24
- if (!profile[r.layer]) profile[r.layer] = {};
25
- profile[r.layer][r.key] = {
26
- value: JSON.parse(r.value), confidence: r.confidence,
27
- source: r.source, tags: r.tags?.split(',').filter(Boolean) || [],
28
- expiresAt: r.expires_at, updatedAt: r.updated_at,
29
- };
30
- }
31
- return NextResponse.json(profile);
32
- });
33
-
34
- export const PATCH = withAuth(async (req, agent) => {
35
- await initSchema();
36
- if (!agent.permissions.includes('write')) return NextResponse.json({ error: 'No write permission' }, { status: 403 });
37
- const { layer, entries } = await req.json();
38
- if (!layer || !entries) return NextResponse.json({ error: 'Missing layer or entries' }, { status: 400 });
39
-
40
- const upsertSql = isPg
41
- ? `INSERT INTO profiles (user_id, layer, key, value, confidence, source, tags, expires_at, updated_at)
42
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())
43
- ON CONFLICT(user_id, layer, key) DO UPDATE SET value=EXCLUDED.value, confidence=EXCLUDED.confidence,
44
- source=EXCLUDED.source, tags=EXCLUDED.tags, expires_at=EXCLUDED.expires_at, updated_at=NOW()`
45
- : `INSERT INTO profiles (user_id, layer, key, value, confidence, source, tags, expires_at, updated_at)
46
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
47
- ON CONFLICT(user_id, layer, key) DO UPDATE SET value=excluded.value, confidence=excluded.confidence,
48
- source=excluded.source, tags=excluded.tags, expires_at=excluded.expires_at, updated_at=excluded.updated_at`;
49
-
50
- for (const [key, val] of Object.entries(entries)) {
51
- const v = typeof val === 'object' && val !== null && 'value' in (val as any) ? (val as any) : { value: val };
52
- const tags = Array.isArray(v.tags) ? v.tags.join(',') : v.tags || null;
53
- // Get old value for history
54
- const old = await db.prepare('SELECT value FROM profiles WHERE user_id = ? AND layer = ? AND key = ?').get(agent.userId, layer, key) as any;
55
- await db.prepare(upsertSql).run(agent.userId, layer, key, JSON.stringify(v.value), v.confidence ?? 1.0, agent.id, tags, v.expiresAt || null);
56
- await logProfileHistory(agent.userId, layer, key, old?.value || null, JSON.stringify(v.value), agent.id);
57
- }
58
- await logAudit(agent.userId, agent.id, 'profile.update', 'profile', layer, `${Object.keys(entries).length} entries`);
59
- return NextResponse.json({ ok: true });
60
- });
61
-
62
- export const DELETE = withAuth(async (req, agent) => {
63
- await initSchema();
64
- if (!agent.permissions.includes('write')) return NextResponse.json({ error: 'No write permission' }, { status: 403 });
65
- const { layer, key } = await req.json();
66
- if (!layer || !key) return NextResponse.json({ error: 'Missing layer or key' }, { status: 400 });
67
- const old = await db.prepare('SELECT value FROM profiles WHERE user_id = ? AND layer = ? AND key = ?').get(agent.userId, layer, key) as any;
68
- await db.prepare('DELETE FROM profiles WHERE user_id = ? AND layer = ? AND key = ?').run(agent.userId, layer, key);
69
- if (old) await logProfileHistory(agent.userId, layer, key, old.value, '(deleted)', agent.id);
70
- await logAudit(agent.userId, agent.id, 'profile.delete', 'profile', `${layer}.${key}`);
71
- return NextResponse.json({ ok: true });
72
- });