@osmosis-ai/mesh-server 0.1.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/dist/config.d.ts +10 -0
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -0
- package/dist/entrypoint.d.ts +1 -0
- package/dist/entrypoint.js +31 -0
- package/dist/entrypoint.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.js +116 -0
- package/dist/server.js.map +1 -0
- package/package.json +23 -0
- package/src/__tests__/mesh-server.test.ts +107 -0
- package/src/config.ts +18 -0
- package/src/entrypoint.ts +35 -0
- package/src/index.ts +29 -0
- package/src/server.ts +130 -0
- package/tsconfig.json +23 -0
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface MeshServerConfig {
|
|
2
|
+
/** Port to listen on (default: 7433) */
|
|
3
|
+
port: number;
|
|
4
|
+
/** Path to SQLite database for mesh storage (default: ':memory:') */
|
|
5
|
+
dbPath: string;
|
|
6
|
+
/** Allow anonymous contributions (default: true) */
|
|
7
|
+
allowAnonymous: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare const DEFAULT_MESH_CONFIG: MeshServerConfig;
|
|
10
|
+
export declare function resolveMeshConfig(partial?: Partial<MeshServerConfig>): MeshServerConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AASA,MAAM,CAAC,MAAM,mBAAmB,GAAqB;IACnD,IAAI,EAAE,IAAI;IACV,MAAM,EAAE,UAAU;IAClB,cAAc,EAAE,IAAI;CACrB,CAAC;AAEF,MAAM,UAAU,iBAAiB,CAAC,OAAmC;IACnE,OAAO,EAAE,GAAG,mBAAmB,EAAE,GAAG,OAAO,EAAE,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production entrypoint for the Osmosis mesh server.
|
|
3
|
+
* Reads config from environment variables and starts the server.
|
|
4
|
+
*/
|
|
5
|
+
import { startMeshServer } from './index.js';
|
|
6
|
+
import { mkdirSync } from 'node:fs';
|
|
7
|
+
import { dirname } from 'node:path';
|
|
8
|
+
const port = parseInt(process.env.MESH_PORT || '7433', 10);
|
|
9
|
+
const dbPath = process.env.MESH_DB_PATH || '/data/mesh.db';
|
|
10
|
+
// Ensure data directory exists
|
|
11
|
+
try {
|
|
12
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
catch { }
|
|
15
|
+
// startMeshServer already calls server.listen() internally
|
|
16
|
+
const handle = startMeshServer({ port, dbPath, allowAnonymous: true });
|
|
17
|
+
console.log(`🧠Osmosis mesh server running on port ${port}`);
|
|
18
|
+
console.log(` Database: ${dbPath}`);
|
|
19
|
+
console.log(` Ready to accept contributions`);
|
|
20
|
+
// Graceful shutdown
|
|
21
|
+
process.on('SIGTERM', () => {
|
|
22
|
+
console.log('Shutting down...');
|
|
23
|
+
handle.stop();
|
|
24
|
+
process.exit(0);
|
|
25
|
+
});
|
|
26
|
+
process.on('SIGINT', () => {
|
|
27
|
+
console.log('Shutting down...');
|
|
28
|
+
handle.stop();
|
|
29
|
+
process.exit(0);
|
|
30
|
+
});
|
|
31
|
+
//# sourceMappingURL=entrypoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entrypoint.js","sourceRoot":"","sources":["../src/entrypoint.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;AAC3D,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,eAAe,CAAC;AAE3D,+BAA+B;AAC/B,IAAI,CAAC;IACH,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAClD,CAAC;AAAC,MAAM,CAAC,CAAA,CAAC;AAEV,2DAA2D;AAC3D,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;AAEvE,OAAO,CAAC,GAAG,CAAC,0CAA0C,IAAI,EAAE,CAAC,CAAC;AAC9D,OAAO,CAAC,GAAG,CAAC,gBAAgB,MAAM,EAAE,CAAC,CAAC;AACtC,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;AAEhD,oBAAoB;AACpB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IACzB,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAChC,MAAM,CAAC,IAAI,EAAE,CAAC;IACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;IACxB,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAChC,MAAM,CAAC,IAAI,EAAE,CAAC;IACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AtomStore } from '@osmosis-ai/core';
|
|
2
|
+
import { type MeshServerConfig } from './config.js';
|
|
3
|
+
import type { Server } from 'node:http';
|
|
4
|
+
export { createMeshServer } from './server.js';
|
|
5
|
+
export { resolveMeshConfig, DEFAULT_MESH_CONFIG } from './config.js';
|
|
6
|
+
export type { MeshServerConfig } from './config.js';
|
|
7
|
+
export interface MeshServerHandle {
|
|
8
|
+
store: AtomStore;
|
|
9
|
+
server: Server;
|
|
10
|
+
stop(): void;
|
|
11
|
+
}
|
|
12
|
+
export declare function startMeshServer(partial?: Partial<MeshServerConfig>): MeshServerHandle;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AtomStore } from '@osmosis-ai/core';
|
|
2
|
+
import { createMeshServer } from './server.js';
|
|
3
|
+
import { resolveMeshConfig } from './config.js';
|
|
4
|
+
export { createMeshServer } from './server.js';
|
|
5
|
+
export { resolveMeshConfig, DEFAULT_MESH_CONFIG } from './config.js';
|
|
6
|
+
export function startMeshServer(partial) {
|
|
7
|
+
const config = resolveMeshConfig(partial);
|
|
8
|
+
const store = new AtomStore(config.dbPath);
|
|
9
|
+
const server = createMeshServer(store, config);
|
|
10
|
+
return {
|
|
11
|
+
store,
|
|
12
|
+
server,
|
|
13
|
+
stop() {
|
|
14
|
+
server.close();
|
|
15
|
+
store.close();
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAyB,MAAM,aAAa,CAAC;AAGvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AASrE,MAAM,UAAU,eAAe,CAAC,OAAmC;IACjE,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAE/C,OAAO;QACL,KAAK;QACL,MAAM;QACN,IAAI;YACF,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Server } from 'node:http';
|
|
2
|
+
import { AtomStore } from '@osmosis-ai/core';
|
|
3
|
+
import type { MeshServerConfig } from './config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Create the centralized mesh HTTP server.
|
|
6
|
+
*
|
|
7
|
+
* Routes:
|
|
8
|
+
* POST /mesh/contribute — submit atoms to the mesh
|
|
9
|
+
* GET /mesh/atoms?since=<ISO> — get atoms since timestamp
|
|
10
|
+
* GET /mesh/query?q=&type=&limit= — query mesh for relevant knowledge
|
|
11
|
+
* GET /mesh/stats — mesh statistics
|
|
12
|
+
*/
|
|
13
|
+
export declare function createMeshServer(store: AtomStore, config: MeshServerConfig): Server;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { createServer as createHttpServer } from 'node:http';
|
|
2
|
+
function json(res, status, data) {
|
|
3
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
4
|
+
res.end(JSON.stringify(data));
|
|
5
|
+
}
|
|
6
|
+
function readBody(req) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
const chunks = [];
|
|
9
|
+
req.on('data', (c) => chunks.push(c));
|
|
10
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString()));
|
|
11
|
+
req.on('error', reject);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Create the centralized mesh HTTP server.
|
|
16
|
+
*
|
|
17
|
+
* Routes:
|
|
18
|
+
* POST /mesh/contribute — submit atoms to the mesh
|
|
19
|
+
* GET /mesh/atoms?since=<ISO> — get atoms since timestamp
|
|
20
|
+
* GET /mesh/query?q=&type=&limit= — query mesh for relevant knowledge
|
|
21
|
+
* GET /mesh/stats — mesh statistics
|
|
22
|
+
*/
|
|
23
|
+
export function createMeshServer(store, config) {
|
|
24
|
+
const server = createHttpServer(async (req, res) => {
|
|
25
|
+
const url = new URL(req.url ?? '/', `http://localhost:${config.port}`);
|
|
26
|
+
const path = url.pathname;
|
|
27
|
+
const method = req.method ?? 'GET';
|
|
28
|
+
try {
|
|
29
|
+
// POST /mesh/contribute — accept atoms from clients
|
|
30
|
+
if (method === 'POST' && path === '/mesh/contribute') {
|
|
31
|
+
const body = JSON.parse(await readBody(req));
|
|
32
|
+
const atoms = Array.isArray(body) ? body : [body];
|
|
33
|
+
const results = [];
|
|
34
|
+
for (const atomData of atoms) {
|
|
35
|
+
// Strip client-side auto fields; mesh generates its own
|
|
36
|
+
const { id, created_at, updated_at, ...data } = atomData;
|
|
37
|
+
// Force quarantine trust tier on mesh
|
|
38
|
+
data.trust_tier = 'quarantine';
|
|
39
|
+
let result;
|
|
40
|
+
if (data.type === 'tool') {
|
|
41
|
+
result = store.createToolAtom(data);
|
|
42
|
+
}
|
|
43
|
+
else if (data.type === 'negative') {
|
|
44
|
+
result = store.createNegativeAtom(data);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
result = store.createAtom(data);
|
|
48
|
+
}
|
|
49
|
+
// Detect dedup: if evidence_count > 1, it was merged
|
|
50
|
+
const ec = result.evidence_count;
|
|
51
|
+
results.push({
|
|
52
|
+
id: result.id,
|
|
53
|
+
status: ec && ec > 1 ? 'deduped' : 'created',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return json(res, 200, { accepted: results.length, results });
|
|
57
|
+
}
|
|
58
|
+
// GET /mesh/atoms?since=<ISO>
|
|
59
|
+
if (method === 'GET' && path === '/mesh/atoms') {
|
|
60
|
+
const since = url.searchParams.get('since');
|
|
61
|
+
let atoms = store.getAll();
|
|
62
|
+
if (since) {
|
|
63
|
+
atoms = atoms.filter(a => a.updated_at > since);
|
|
64
|
+
}
|
|
65
|
+
return json(res, 200, atoms);
|
|
66
|
+
}
|
|
67
|
+
// GET /mesh/query?q=<search>&type=<type>&limit=<n>
|
|
68
|
+
if (method === 'GET' && path === '/mesh/query') {
|
|
69
|
+
const q = url.searchParams.get('q') ?? '';
|
|
70
|
+
const type = url.searchParams.get('type');
|
|
71
|
+
const limit = parseInt(url.searchParams.get('limit') ?? '20', 10);
|
|
72
|
+
let atoms;
|
|
73
|
+
if (q) {
|
|
74
|
+
atoms = store.search(q);
|
|
75
|
+
}
|
|
76
|
+
else if (type) {
|
|
77
|
+
atoms = store.queryByType(type);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
atoms = store.getAll();
|
|
81
|
+
}
|
|
82
|
+
if (type && q) {
|
|
83
|
+
atoms = atoms.filter(a => a.type === type);
|
|
84
|
+
}
|
|
85
|
+
atoms = atoms.slice(0, limit);
|
|
86
|
+
return json(res, 200, atoms);
|
|
87
|
+
}
|
|
88
|
+
// GET /mesh/stats
|
|
89
|
+
if (method === 'GET' && path === '/mesh/stats') {
|
|
90
|
+
const all = store.getAll();
|
|
91
|
+
const contributors = new Set(all.map(a => a.source_agent_hash));
|
|
92
|
+
const topAtoms = [...all]
|
|
93
|
+
.sort((a, b) => b.fitness_score - a.fitness_score)
|
|
94
|
+
.slice(0, 10);
|
|
95
|
+
return json(res, 200, {
|
|
96
|
+
totalAtoms: all.length,
|
|
97
|
+
contributors: contributors.size,
|
|
98
|
+
topAtoms: topAtoms.map(a => ({
|
|
99
|
+
id: a.id,
|
|
100
|
+
type: a.type,
|
|
101
|
+
observation: a.observation.slice(0, 120),
|
|
102
|
+
fitness_score: a.fitness_score,
|
|
103
|
+
})),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
json(res, 404, { error: 'Not found' });
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
const status = err.name === 'ZodError' ? 400 : 500;
|
|
110
|
+
json(res, status, { error: err.message ?? 'Internal error' });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
server.listen(config.port);
|
|
114
|
+
return server;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@osmosis-ai/mesh-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Centralized mesh server for Osmosis knowledge sharing",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"test": "vitest run",
|
|
11
|
+
"test:watch": "vitest"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@osmosis-ai/core": "*"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"better-sqlite3": "^11.7.0",
|
|
18
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
19
|
+
"typescript": "^5.7.0",
|
|
20
|
+
"vitest": "^3.0.0"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT"
|
|
23
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { AtomStore } from '@osmosis-ai/core';
|
|
3
|
+
import { startMeshServer, type MeshServerHandle } from '../index.js';
|
|
4
|
+
|
|
5
|
+
const PORT = 19600;
|
|
6
|
+
|
|
7
|
+
function makeToolAtom(toolName: string, observation: string) {
|
|
8
|
+
return {
|
|
9
|
+
type: 'tool' as const,
|
|
10
|
+
observation,
|
|
11
|
+
context: `testing ${toolName}`,
|
|
12
|
+
confidence: 0.8,
|
|
13
|
+
fitness_score: 0.7,
|
|
14
|
+
trust_tier: 'local' as const,
|
|
15
|
+
source_agent_hash: 'test-agent-' + toolName,
|
|
16
|
+
decay_rate: 0.99,
|
|
17
|
+
tool_name: toolName,
|
|
18
|
+
params_hash: 'abc123',
|
|
19
|
+
outcome: 'success' as const,
|
|
20
|
+
error_signature: null,
|
|
21
|
+
latency_ms: 100,
|
|
22
|
+
reliability_score: 0.9,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('mesh server', () => {
|
|
27
|
+
let mesh: MeshServerHandle;
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
try { mesh?.stop(); } catch {}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('POST /mesh/contribute accepts atoms', async () => {
|
|
34
|
+
mesh = startMeshServer({ port: PORT, dbPath: ':memory:' });
|
|
35
|
+
const res = await fetch(`http://localhost:${PORT}/mesh/contribute`, {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: { 'Content-Type': 'application/json' },
|
|
38
|
+
body: JSON.stringify(makeToolAtom('test', 'test observation')),
|
|
39
|
+
});
|
|
40
|
+
expect(res.ok).toBe(true);
|
|
41
|
+
const body = await res.json() as any;
|
|
42
|
+
expect(body.accepted).toBe(1);
|
|
43
|
+
expect(mesh.store.getAll().length).toBe(1);
|
|
44
|
+
// Contributed atoms are quarantined
|
|
45
|
+
expect(mesh.store.getAll()[0]!.trust_tier).toBe('quarantine');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('GET /mesh/atoms returns all atoms', async () => {
|
|
49
|
+
mesh = startMeshServer({ port: PORT, dbPath: ':memory:' });
|
|
50
|
+
mesh.store.createToolAtom(makeToolAtom('t1', 'obs one'));
|
|
51
|
+
mesh.store.createToolAtom(makeToolAtom('t2', 'obs two completely different'));
|
|
52
|
+
|
|
53
|
+
const res = await fetch(`http://localhost:${PORT}/mesh/atoms`);
|
|
54
|
+
const atoms = await res.json() as any[];
|
|
55
|
+
expect(atoms.length).toBe(2);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('GET /mesh/atoms?since= filters by time', async () => {
|
|
59
|
+
mesh = startMeshServer({ port: PORT, dbPath: ':memory:' });
|
|
60
|
+
mesh.store.createToolAtom(makeToolAtom('old', 'old observation'));
|
|
61
|
+
const since = new Date().toISOString();
|
|
62
|
+
await new Promise(r => setTimeout(r, 50));
|
|
63
|
+
mesh.store.createToolAtom(makeToolAtom('new', 'completely new different observation'));
|
|
64
|
+
|
|
65
|
+
const res = await fetch(`http://localhost:${PORT}/mesh/atoms?since=${encodeURIComponent(since)}`);
|
|
66
|
+
const atoms = await res.json() as any[];
|
|
67
|
+
expect(atoms.length).toBe(1);
|
|
68
|
+
expect(atoms[0].tool_name).toBe('new');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('GET /mesh/query searches atoms', async () => {
|
|
72
|
+
mesh = startMeshServer({ port: PORT, dbPath: ':memory:' });
|
|
73
|
+
mesh.store.createToolAtom(makeToolAtom('browser', 'browser click fails'));
|
|
74
|
+
mesh.store.createToolAtom(makeToolAtom('fetch', 'fetch times out'));
|
|
75
|
+
|
|
76
|
+
const res = await fetch(`http://localhost:${PORT}/mesh/query?q=browser`);
|
|
77
|
+
const atoms = await res.json() as any[];
|
|
78
|
+
expect(atoms.length).toBe(1);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('GET /mesh/stats returns statistics', async () => {
|
|
82
|
+
mesh = startMeshServer({ port: PORT, dbPath: ':memory:' });
|
|
83
|
+
mesh.store.createToolAtom(makeToolAtom('t1', 'obs one'));
|
|
84
|
+
mesh.store.createToolAtom(makeToolAtom('t2', 'obs two completely different'));
|
|
85
|
+
|
|
86
|
+
const res = await fetch(`http://localhost:${PORT}/mesh/stats`);
|
|
87
|
+
const stats = await res.json() as any;
|
|
88
|
+
expect(stats.totalAtoms).toBe(2);
|
|
89
|
+
expect(stats.contributors).toBeGreaterThanOrEqual(1);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('batch contribute accepts array', async () => {
|
|
93
|
+
mesh = startMeshServer({ port: PORT, dbPath: ':memory:' });
|
|
94
|
+
const atoms = [
|
|
95
|
+
makeToolAtom('t1', 'first observation'),
|
|
96
|
+
makeToolAtom('t2', 'second completely different observation'),
|
|
97
|
+
];
|
|
98
|
+
const res = await fetch(`http://localhost:${PORT}/mesh/contribute`, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: { 'Content-Type': 'application/json' },
|
|
101
|
+
body: JSON.stringify(atoms),
|
|
102
|
+
});
|
|
103
|
+
const body = await res.json() as any;
|
|
104
|
+
expect(body.accepted).toBe(2);
|
|
105
|
+
expect(mesh.store.getAll().length).toBe(2);
|
|
106
|
+
});
|
|
107
|
+
});
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface MeshServerConfig {
|
|
2
|
+
/** Port to listen on (default: 7433) */
|
|
3
|
+
port: number;
|
|
4
|
+
/** Path to SQLite database for mesh storage (default: ':memory:') */
|
|
5
|
+
dbPath: string;
|
|
6
|
+
/** Allow anonymous contributions (default: true) */
|
|
7
|
+
allowAnonymous: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_MESH_CONFIG: MeshServerConfig = {
|
|
11
|
+
port: 7433,
|
|
12
|
+
dbPath: ':memory:',
|
|
13
|
+
allowAnonymous: true,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function resolveMeshConfig(partial?: Partial<MeshServerConfig>): MeshServerConfig {
|
|
17
|
+
return { ...DEFAULT_MESH_CONFIG, ...partial };
|
|
18
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production entrypoint for the Osmosis mesh server.
|
|
3
|
+
* Reads config from environment variables and starts the server.
|
|
4
|
+
*/
|
|
5
|
+
import { startMeshServer } from './index.js';
|
|
6
|
+
import { mkdirSync } from 'node:fs';
|
|
7
|
+
import { dirname } from 'node:path';
|
|
8
|
+
|
|
9
|
+
const port = parseInt(process.env.MESH_PORT || '7433', 10);
|
|
10
|
+
const dbPath = process.env.MESH_DB_PATH || '/data/mesh.db';
|
|
11
|
+
|
|
12
|
+
// Ensure data directory exists
|
|
13
|
+
try {
|
|
14
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
15
|
+
} catch {}
|
|
16
|
+
|
|
17
|
+
// startMeshServer already calls server.listen() internally
|
|
18
|
+
const handle = startMeshServer({ port, dbPath, allowAnonymous: true });
|
|
19
|
+
|
|
20
|
+
console.log(`🧠Osmosis mesh server running on port ${port}`);
|
|
21
|
+
console.log(` Database: ${dbPath}`);
|
|
22
|
+
console.log(` Ready to accept contributions`);
|
|
23
|
+
|
|
24
|
+
// Graceful shutdown
|
|
25
|
+
process.on('SIGTERM', () => {
|
|
26
|
+
console.log('Shutting down...');
|
|
27
|
+
handle.stop();
|
|
28
|
+
process.exit(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
process.on('SIGINT', () => {
|
|
32
|
+
console.log('Shutting down...');
|
|
33
|
+
handle.stop();
|
|
34
|
+
process.exit(0);
|
|
35
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AtomStore } from '@osmosis-ai/core';
|
|
2
|
+
import { createMeshServer } from './server.js';
|
|
3
|
+
import { resolveMeshConfig, type MeshServerConfig } from './config.js';
|
|
4
|
+
import type { Server } from 'node:http';
|
|
5
|
+
|
|
6
|
+
export { createMeshServer } from './server.js';
|
|
7
|
+
export { resolveMeshConfig, DEFAULT_MESH_CONFIG } from './config.js';
|
|
8
|
+
export type { MeshServerConfig } from './config.js';
|
|
9
|
+
|
|
10
|
+
export interface MeshServerHandle {
|
|
11
|
+
store: AtomStore;
|
|
12
|
+
server: Server;
|
|
13
|
+
stop(): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function startMeshServer(partial?: Partial<MeshServerConfig>): MeshServerHandle {
|
|
17
|
+
const config = resolveMeshConfig(partial);
|
|
18
|
+
const store = new AtomStore(config.dbPath);
|
|
19
|
+
const server = createMeshServer(store, config);
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
store,
|
|
23
|
+
server,
|
|
24
|
+
stop() {
|
|
25
|
+
server.close();
|
|
26
|
+
store.close();
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { createServer as createHttpServer, type IncomingMessage, type ServerResponse, type Server } from 'node:http';
|
|
2
|
+
import { AtomStore } from '@osmosis-ai/core';
|
|
3
|
+
import type { KnowledgeAtom } from '@osmosis-ai/core';
|
|
4
|
+
import type { MeshServerConfig } from './config.js';
|
|
5
|
+
|
|
6
|
+
function json(res: ServerResponse, status: number, data: unknown): void {
|
|
7
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
8
|
+
res.end(JSON.stringify(data));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function readBody(req: IncomingMessage): Promise<string> {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const chunks: Buffer[] = [];
|
|
14
|
+
req.on('data', (c: Buffer) => chunks.push(c));
|
|
15
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString()));
|
|
16
|
+
req.on('error', reject);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create the centralized mesh HTTP server.
|
|
22
|
+
*
|
|
23
|
+
* Routes:
|
|
24
|
+
* POST /mesh/contribute — submit atoms to the mesh
|
|
25
|
+
* GET /mesh/atoms?since=<ISO> — get atoms since timestamp
|
|
26
|
+
* GET /mesh/query?q=&type=&limit= — query mesh for relevant knowledge
|
|
27
|
+
* GET /mesh/stats — mesh statistics
|
|
28
|
+
*/
|
|
29
|
+
export function createMeshServer(store: AtomStore, config: MeshServerConfig): Server {
|
|
30
|
+
const server = createHttpServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
31
|
+
const url = new URL(req.url ?? '/', `http://localhost:${config.port}`);
|
|
32
|
+
const path = url.pathname;
|
|
33
|
+
const method = req.method ?? 'GET';
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// POST /mesh/contribute — accept atoms from clients
|
|
37
|
+
if (method === 'POST' && path === '/mesh/contribute') {
|
|
38
|
+
const body = JSON.parse(await readBody(req));
|
|
39
|
+
const atoms: any[] = Array.isArray(body) ? body : [body];
|
|
40
|
+
const results: Array<{ id: string; status: 'created' | 'deduped' }> = [];
|
|
41
|
+
|
|
42
|
+
for (const atomData of atoms) {
|
|
43
|
+
// Strip client-side auto fields; mesh generates its own
|
|
44
|
+
const { id, created_at, updated_at, ...data } = atomData;
|
|
45
|
+
// Force quarantine trust tier on mesh
|
|
46
|
+
data.trust_tier = 'quarantine';
|
|
47
|
+
|
|
48
|
+
let result: KnowledgeAtom;
|
|
49
|
+
if (data.type === 'tool') {
|
|
50
|
+
result = store.createToolAtom(data);
|
|
51
|
+
} else if (data.type === 'negative') {
|
|
52
|
+
result = store.createNegativeAtom(data);
|
|
53
|
+
} else {
|
|
54
|
+
result = store.createAtom(data);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Detect dedup: if evidence_count > 1, it was merged
|
|
58
|
+
const ec = (result as any).evidence_count;
|
|
59
|
+
results.push({
|
|
60
|
+
id: result.id,
|
|
61
|
+
status: ec && ec > 1 ? 'deduped' : 'created',
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return json(res, 200, { accepted: results.length, results });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// GET /mesh/atoms?since=<ISO>
|
|
69
|
+
if (method === 'GET' && path === '/mesh/atoms') {
|
|
70
|
+
const since = url.searchParams.get('since');
|
|
71
|
+
let atoms = store.getAll();
|
|
72
|
+
if (since) {
|
|
73
|
+
atoms = atoms.filter(a => a.updated_at > since);
|
|
74
|
+
}
|
|
75
|
+
return json(res, 200, atoms);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// GET /mesh/query?q=<search>&type=<type>&limit=<n>
|
|
79
|
+
if (method === 'GET' && path === '/mesh/query') {
|
|
80
|
+
const q = url.searchParams.get('q') ?? '';
|
|
81
|
+
const type = url.searchParams.get('type');
|
|
82
|
+
const limit = parseInt(url.searchParams.get('limit') ?? '20', 10);
|
|
83
|
+
|
|
84
|
+
let atoms: KnowledgeAtom[];
|
|
85
|
+
if (q) {
|
|
86
|
+
atoms = store.search(q);
|
|
87
|
+
} else if (type) {
|
|
88
|
+
atoms = store.queryByType(type as any);
|
|
89
|
+
} else {
|
|
90
|
+
atoms = store.getAll();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (type && q) {
|
|
94
|
+
atoms = atoms.filter(a => a.type === type);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
atoms = atoms.slice(0, limit);
|
|
98
|
+
return json(res, 200, atoms);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// GET /mesh/stats
|
|
102
|
+
if (method === 'GET' && path === '/mesh/stats') {
|
|
103
|
+
const all = store.getAll();
|
|
104
|
+
const contributors = new Set(all.map(a => a.source_agent_hash));
|
|
105
|
+
const topAtoms = [...all]
|
|
106
|
+
.sort((a, b) => b.fitness_score - a.fitness_score)
|
|
107
|
+
.slice(0, 10);
|
|
108
|
+
|
|
109
|
+
return json(res, 200, {
|
|
110
|
+
totalAtoms: all.length,
|
|
111
|
+
contributors: contributors.size,
|
|
112
|
+
topAtoms: topAtoms.map(a => ({
|
|
113
|
+
id: a.id,
|
|
114
|
+
type: a.type,
|
|
115
|
+
observation: a.observation.slice(0, 120),
|
|
116
|
+
fitness_score: a.fitness_score,
|
|
117
|
+
})),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
json(res, 404, { error: 'Not found' });
|
|
122
|
+
} catch (err: any) {
|
|
123
|
+
const status = err.name === 'ZodError' ? 400 : 500;
|
|
124
|
+
json(res, status, { error: err.message ?? 'Internal error' });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
server.listen(config.port);
|
|
129
|
+
return server;
|
|
130
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"rootDir": "src",
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"composite": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src"],
|
|
19
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"],
|
|
20
|
+
"references": [
|
|
21
|
+
{ "path": "../core" }
|
|
22
|
+
]
|
|
23
|
+
}
|