079project 6.0.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ const reportWebVitals = onPerfEntry => {
2
+ if (onPerfEntry && onPerfEntry instanceof Function) {
3
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4
+ getCLS(onPerfEntry);
5
+ getFID(onPerfEntry);
6
+ getFCP(onPerfEntry);
7
+ getLCP(onPerfEntry);
8
+ getTTFB(onPerfEntry);
9
+ });
10
+ }
11
+ };
12
+
13
+ export default reportWebVitals;
@@ -0,0 +1,5 @@
1
+ // jest-dom adds custom jest matchers for asserting on DOM nodes.
2
+ // allows you to do things like:
3
+ // expect(element).toHaveTextContent(/react/i)
4
+ // learn more: https://github.com/testing-library/jest-dom
5
+ import '@testing-library/jest-dom';
package/README.en.md ADDED
@@ -0,0 +1,234 @@
1
+ # 079Project
2
+
3
+ An experimental AI project that combines **graph-based reasoning**, **learning modules**, a **gateway API**, a **React control console**, and a **SQLite-backed identity system** in one repository.
4
+
5
+ The current runtime is split into **two Node.js processes**:
6
+
7
+ - **Main AI process**: `main.cjs` (serves `/api/*`; token required by default)
8
+ - **Identity + Web process**: `auth_frontend_server.cjs` (serves `/auth/*`, hosts the React production build, and reverse-proxies `/api/*` to the main AI process)
9
+
10
+ Note: This repository is still experimental; APIs/behavior may change.
11
+
12
+ ---
13
+
14
+ ## Key Structure
15
+
16
+ - `main.cjs`: Main AI process (graph runtime, SparkArray, controller pool, MemeBarrier, RL/ADV learning, snapshots & export, `/api` routes).
17
+ - `auth_frontend_server.cjs`: Identity + frontend hosting (SQLite users/sessions, JWT auth, `/auth` routes, `/api` reverse proxy).
18
+ - `079project_frontend/`: React frontend (Chat + Config console).
19
+ - `robots/`: Robot corpora (TXT).
20
+ - `tests/`: Test cases / word lists (TXT; can be added at runtime and refreshed into RL).
21
+ - `snapshots/`: Snapshot files.
22
+ - `runtime_store/`: Runtime cache/export folder; default location for `runtime_store/auth.sqlite`.
23
+ - `lmdb/`: LMDB data directory (falls back to JSON storage if unavailable).
24
+
25
+ ---
26
+
27
+ ## Requirements
28
+
29
+ - Windows + PowerShell (`pwsh.exe` examples below).
30
+ - Node.js >= 18 (LTS recommended).
31
+
32
+ ---
33
+
34
+ ## Install
35
+
36
+ Backend dependencies:
37
+
38
+ ```powershell
39
+ cd "079Project/"
40
+ npm install
41
+ ```
42
+
43
+ Frontend dependencies:
44
+
45
+ ```powershell
46
+ cd "079Project/079project_frontend"
47
+ npm install
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Run (Two Processes)
53
+
54
+ Default ports:
55
+
56
+ - Main AI: `127.0.0.1:5080`
57
+ - Identity + Web: `127.0.0.1:5081`
58
+
59
+ ### Mode A (Recommended): Identity+Web hosts UI and proxies `/api`
60
+
61
+ 1) Start the main AI process (serves `/api/*` only):
62
+
63
+ ```powershell
64
+ cd "079Project/"
65
+ $env:AI_AUTH_ENABLED="true"
66
+ $env:AI_AUTH_JWT_SECRET="change-me"
67
+ node .\main.cjs
68
+ ```
69
+
70
+ 2) Start the identity + web process (hosts UI and proxies `/api/*` to the main AI):
71
+
72
+ ```powershell
73
+ cd "079Project/"
74
+ $env:AUTH_JWT_SECRET="change-me"
75
+ $env:AI_API_BASE="http://127.0.0.1:5080"
76
+ node .\auth_frontend_server.cjs
77
+ ```
78
+
79
+ 3) Open in browser:
80
+
81
+ - `http://127.0.0.1:5081/`
82
+
83
+ On first run, choose “bootstrap” in the UI and create the initial admin user (only allowed once).
84
+
85
+ Important: `AUTH_JWT_SECRET` must match `AI_AUTH_JWT_SECRET`, otherwise tokens cannot be verified by the main AI process.
86
+
87
+ ### Mode B (Optional): Frontend dev server
88
+
89
+ The CRA dev server uses `079project_frontend/package.json` `proxy`.
90
+ If you use this mode, it is recommended to set `proxy` to `http://127.0.0.1:5081` so `/api/*` goes through the identity process (no CORS issues; consistent auth).
91
+
92
+ Start:
93
+
94
+ ```powershell
95
+ cd "079Project/079project_frontend"
96
+ npm start
97
+ ```
98
+
99
+ ### Mode C (Production): Build React and let identity process host it
100
+
101
+ 1) Build the frontend:
102
+
103
+ ```powershell
104
+ cd "079Project/079project_frontend"
105
+ npm run build
106
+ ```
107
+
108
+ 2) Start main AI:
109
+
110
+ ```powershell
111
+ cd "079Project/"
112
+ $env:AI_AUTH_ENABLED="true"
113
+ $env:AI_AUTH_JWT_SECRET="change-me"
114
+ node .\main.cjs
115
+ ```
116
+
117
+ 3) Start identity + web:
118
+
119
+ ```powershell
120
+ cd "079Project/"
121
+ $env:AUTH_JWT_SECRET="change-me"
122
+ $env:AI_API_BASE="http://127.0.0.1:5080"
123
+ node .\auth_frontend_server.cjs
124
+ ```
125
+
126
+ Open: `http://127.0.0.1:5081/`
127
+
128
+ ---
129
+
130
+ ## Main AI CLI Options (Common)
131
+
132
+ `main.cjs` supports some CLI / env options (partial list):
133
+
134
+ - `--gateway-host` / `AI_GATEWAY_HOST`: listen address (default `127.0.0.1`).
135
+ - `--port` / `CONTROLLER_PORT`: gateway port (default `5080`).
136
+ - `--disable-memebarrier`: disable MemeBarrier on startup (can be re-enabled at runtime).
137
+ - `--disable-learning`: disable learning modules on startup (can be re-enabled at runtime).
138
+ - `--disable-rl`: disable RL on startup (can be re-enabled at runtime).
139
+ - `--disable-adv`: disable ADV on startup (can be re-enabled at runtime).
140
+ - `--export-dir` / `AI_EXPORT_DIR`: default export directory for `/api/export/graph`.
141
+
142
+ Example: listen on `0.0.0.0:5080`:
143
+
144
+ ```powershell
145
+ node .\main.cjs --gateway-host=0.0.0.0 --port=5080
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Identity System (SQLite + JWT)
151
+
152
+ Default DB path: `079Project/runtime_store/auth.sqlite`.
153
+
154
+ Environment variables for `auth_frontend_server.cjs`:
155
+
156
+ - `AUTH_PORT`: default `5081`
157
+ - `AUTH_HOST`: default `127.0.0.1`
158
+ - `AUTH_DB_PATH`: default `079Project/runtime_store/auth.sqlite`
159
+ - `AUTH_JWT_SECRET`: JWT secret (must match main AI `AI_AUTH_JWT_SECRET`)
160
+ - `AI_API_BASE`: main AI base URL (default `http://127.0.0.1:5080`)
161
+
162
+ Environment variables for `main.cjs`:
163
+
164
+ - `AI_AUTH_ENABLED`: enabled by default (set to `false` to temporarily disable auth)
165
+ - `AI_AUTH_JWT_SECRET`: token verification secret
166
+
167
+ ---
168
+
169
+ ## Frontend
170
+
171
+ Left navigation:
172
+
173
+ - `Chat`: basic chat.
174
+ - `Config`: runtime operations console (MemeBarrier / RL / ADV, thresholds, tests refresh, robots retrain).
175
+
176
+ ---
177
+
178
+ ## Selected HTTP APIs
179
+
180
+ ### Chat
181
+
182
+ - `POST /api/chat`: single-model chat.
183
+ - `POST /api/array/chat`: SparkArray multi-AI / multi-layer chat.
184
+
185
+ ### Runtime toggles (Config UI)
186
+
187
+ - `GET /api/runtime/features`
188
+ - `PATCH /api/runtime/features`
189
+
190
+ ### MemeBarrier
191
+
192
+ - `POST /api/memebarrier/start`
193
+ - `POST /api/memebarrier/stop`
194
+ - `GET /api/memebarrier/stats`
195
+
196
+ ### Learning
197
+
198
+ - `POST /api/learn/reinforce` / `GET /api/learn/reinforce/latest`
199
+ - `POST /api/learn/adversarial` / `GET /api/learn/adversarial/latest`
200
+ - `POST /api/learn/thresholds`
201
+
202
+ ### Tests
203
+
204
+ - `GET /api/tests/list`
205
+ - `POST /api/tests/case`
206
+ - `POST /api/tests/refresh`
207
+
208
+ ### Robots
209
+
210
+ - `GET /robots/list`
211
+ - `POST /robots/ingest`
212
+ - `POST /api/robots/retrain`
213
+
214
+ ### Export
215
+
216
+ - `POST /api/export/graph`: export a windowed graph JSON to file and return inline content.
217
+
218
+ ---
219
+
220
+ ## FAQ
221
+
222
+ ### Why is there no UI on `5080/`?
223
+
224
+ This is expected: the UI is hosted by the identity + web process (default `http://127.0.0.1:5081/`).
225
+
226
+ ### LMDB cannot be opened
227
+
228
+ If LMDB fails, the system automatically falls back to JSON storage and continues to run. Check directory permissions and environment dependencies if you need LMDB.
229
+
230
+ ---
231
+
232
+ ## License
233
+
234
+ LGPL-3.0 (see `LICENSE`).
package/README.md CHANGED
Binary file
@@ -0,0 +1,312 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const express = require('express');
4
+ const bodyParser = require('body-parser');
5
+ const jwt = require('jsonwebtoken');
6
+ const bcrypt = require('bcryptjs');
7
+
8
+ let Database;
9
+ try {
10
+ Database = require('better-sqlite3');
11
+ } catch (e) {
12
+ Database = null;
13
+ }
14
+
15
+ function getEnv(name, fallback) {
16
+ const v = process.env[name];
17
+ return (v === undefined || v === null || v === '') ? fallback : v;
18
+ }
19
+
20
+ function nowIso() {
21
+ return new Date().toISOString();
22
+ }
23
+
24
+ function randomId() {
25
+ return `${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
26
+ }
27
+
28
+ function jsonError(res, status, error, extra) {
29
+ res.status(status).json({ ok: false, error, ...(extra || {}) });
30
+ }
31
+
32
+ class IdentityStore {
33
+ constructor(dbPath) {
34
+ if (!Database) {
35
+ throw new Error('better-sqlite3 not installed/available');
36
+ }
37
+ this.dbPath = dbPath;
38
+ this.db = new Database(dbPath);
39
+ this.db.pragma('journal_mode = WAL');
40
+ this._init();
41
+ }
42
+
43
+ _init() {
44
+ this.db.exec(`
45
+ CREATE TABLE IF NOT EXISTS users (
46
+ id TEXT PRIMARY KEY,
47
+ username TEXT NOT NULL UNIQUE,
48
+ password_hash TEXT NOT NULL,
49
+ role TEXT NOT NULL DEFAULT 'user',
50
+ created_at TEXT NOT NULL,
51
+ last_login_at TEXT
52
+ );
53
+
54
+ CREATE TABLE IF NOT EXISTS sessions (
55
+ id TEXT PRIMARY KEY,
56
+ user_id TEXT NOT NULL,
57
+ created_at TEXT NOT NULL,
58
+ expires_at TEXT NOT NULL,
59
+ revoked_at TEXT,
60
+ ip TEXT,
61
+ ua TEXT,
62
+ FOREIGN KEY(user_id) REFERENCES users(id)
63
+ );
64
+
65
+ CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
66
+ CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
67
+ `);
68
+ }
69
+
70
+ hasAnyUser() {
71
+ const row = this.db.prepare('SELECT COUNT(1) as c FROM users').get();
72
+ return (row?.c || 0) > 0;
73
+ }
74
+
75
+ createUser({ username, password, role = 'admin' }) {
76
+ const id = randomId();
77
+ const passwordHash = bcrypt.hashSync(String(password), 10);
78
+ const createdAt = nowIso();
79
+ this.db
80
+ .prepare('INSERT INTO users (id, username, password_hash, role, created_at) VALUES (?, ?, ?, ?, ?)')
81
+ .run(id, username, passwordHash, role, createdAt);
82
+ return { id, username, role, createdAt };
83
+ }
84
+
85
+ getUserByUsername(username) {
86
+ return this.db.prepare('SELECT * FROM users WHERE username = ?').get(username) || null;
87
+ }
88
+
89
+ getUserById(id) {
90
+ return this.db.prepare('SELECT * FROM users WHERE id = ?').get(id) || null;
91
+ }
92
+
93
+ verifyPassword(user, password) {
94
+ return bcrypt.compareSync(String(password), String(user.password_hash));
95
+ }
96
+
97
+ createSession({ userId, ttlSeconds = 60 * 60 * 24 * 7, ip, ua }) {
98
+ const id = randomId();
99
+ const createdAt = nowIso();
100
+ const expiresAt = new Date(Date.now() + ttlSeconds * 1000).toISOString();
101
+ this.db
102
+ .prepare('INSERT INTO sessions (id, user_id, created_at, expires_at, ip, ua) VALUES (?, ?, ?, ?, ?, ?)')
103
+ .run(id, userId, createdAt, expiresAt, ip || null, ua || null);
104
+ return { id, userId, createdAt, expiresAt };
105
+ }
106
+
107
+ revokeSession(sessionId) {
108
+ const revokedAt = nowIso();
109
+ this.db.prepare('UPDATE sessions SET revoked_at = ? WHERE id = ?').run(revokedAt, sessionId);
110
+ return { id: sessionId, revokedAt };
111
+ }
112
+
113
+ getSession(sessionId) {
114
+ const row = this.db.prepare('SELECT * FROM sessions WHERE id = ?').get(sessionId);
115
+ return row || null;
116
+ }
117
+ }
118
+
119
+ function parseBearer(req) {
120
+ const h = req.headers.authorization || '';
121
+ const m = /^Bearer\s+(.+)$/i.exec(String(h));
122
+ return m ? m[1] : '';
123
+ }
124
+
125
+ function start() {
126
+ const port = Number(getEnv('AUTH_PORT', '5081'));
127
+ const host = getEnv('AUTH_HOST', '127.0.0.1');
128
+ const webRoot = getEnv('WEB_ROOT', path.join(__dirname, '079project_frontend', 'build'));
129
+ const dbPath = getEnv('AUTH_DB_PATH', path.join(__dirname, 'runtime_store', 'auth.sqlite'));
130
+ const jwtSecret = getEnv('AUTH_JWT_SECRET', 'dev-secret-change-me');
131
+ const aiApiBase = getEnv('AI_API_BASE', 'http://127.0.0.1:5080');
132
+ const tokenTtlSeconds = Number(getEnv('AUTH_TOKEN_TTL_SECONDS', String(60 * 60 * 24 * 7)));
133
+
134
+ if (!fs.existsSync(path.dirname(dbPath))) {
135
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
136
+ }
137
+
138
+ const store = new IdentityStore(dbPath);
139
+
140
+ const app = express();
141
+ app.use(bodyParser.json({ limit: '2mb' }));
142
+
143
+ // Lightweight reverse proxy for /api/* to main AI process.
144
+ // This avoids CORS and keeps frontend API_BASE as same-origin (:5081).
145
+ app.all(/^\/api\/.*/, async (req, res) => {
146
+ try {
147
+ const base = aiApiBase.endsWith('/') ? aiApiBase.slice(0, -1) : aiApiBase;
148
+ const targetUrl = new URL(`${base}${req.originalUrl}`);
149
+ const headers = { ...req.headers };
150
+ delete headers.host;
151
+
152
+ let body;
153
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
154
+ body = JSON.stringify(req.body ?? {});
155
+ }
156
+
157
+ const r = await fetch(targetUrl.toString(), {
158
+ method: req.method,
159
+ headers: {
160
+ ...headers,
161
+ 'content-type': 'application/json'
162
+ },
163
+ body
164
+ });
165
+
166
+ const text = await r.text();
167
+ res.status(r.status);
168
+ // pass through content-type if present
169
+ const ct = r.headers.get('content-type');
170
+ if (ct) res.setHeader('content-type', ct);
171
+ res.send(text);
172
+ } catch (e) {
173
+ jsonError(res, 502, 'ai-proxy-failed', { message: e.message });
174
+ }
175
+ });
176
+
177
+ // health + config
178
+ app.get('/auth/health', (req, res) => {
179
+ res.json({ ok: true, ts: Date.now() });
180
+ });
181
+
182
+ app.get('/auth/config', (req, res) => {
183
+ res.json({ ok: true, aiApiBase });
184
+ });
185
+
186
+ // First-run bootstrap: create initial admin if no users.
187
+ app.post('/auth/bootstrap', (req, res) => {
188
+ try {
189
+ if (store.hasAnyUser()) {
190
+ jsonError(res, 409, 'already-bootstrapped');
191
+ return;
192
+ }
193
+ const username = String(req.body?.username || 'admin').trim();
194
+ const password = String(req.body?.password || '').trim();
195
+ if (!username) {
196
+ jsonError(res, 400, 'username-required');
197
+ return;
198
+ }
199
+ if (!password || password.length < 6) {
200
+ jsonError(res, 400, 'password-too-short', { minLen: 6 });
201
+ return;
202
+ }
203
+ const user = store.createUser({ username, password, role: 'admin' });
204
+ res.json({ ok: true, user: { id: user.id, username: user.username, role: user.role } });
205
+ } catch (e) {
206
+ jsonError(res, 500, 'bootstrap-failed', { message: e.message });
207
+ }
208
+ });
209
+
210
+ app.post('/auth/login', (req, res) => {
211
+ try {
212
+ const username = String(req.body?.username || '').trim();
213
+ const password = String(req.body?.password || '');
214
+ if (!username || !password) {
215
+ jsonError(res, 400, 'username-password-required');
216
+ return;
217
+ }
218
+ const user = store.getUserByUsername(username);
219
+ if (!user || !store.verifyPassword(user, password)) {
220
+ jsonError(res, 401, 'invalid-credentials');
221
+ return;
222
+ }
223
+ const session = store.createSession({
224
+ userId: user.id,
225
+ ttlSeconds: tokenTtlSeconds,
226
+ ip: req.ip,
227
+ ua: req.headers['user-agent']
228
+ });
229
+ const token = jwt.sign(
230
+ { sub: user.id, username: user.username, role: user.role, sid: session.id },
231
+ jwtSecret,
232
+ { expiresIn: tokenTtlSeconds }
233
+ );
234
+ res.json({
235
+ ok: true,
236
+ token,
237
+ user: { id: user.id, username: user.username, role: user.role },
238
+ expiresAt: session.expires_at
239
+ });
240
+ } catch (e) {
241
+ jsonError(res, 500, 'login-failed', { message: e.message });
242
+ }
243
+ });
244
+
245
+ app.post('/auth/logout', (req, res) => {
246
+ try {
247
+ const token = parseBearer(req);
248
+ if (!token) {
249
+ jsonError(res, 401, 'missing-token');
250
+ return;
251
+ }
252
+ const payload = jwt.verify(token, jwtSecret);
253
+ if (payload?.sid) {
254
+ store.revokeSession(String(payload.sid));
255
+ }
256
+ res.json({ ok: true });
257
+ } catch (e) {
258
+ jsonError(res, 401, 'invalid-token', { message: e.message });
259
+ }
260
+ });
261
+
262
+ app.get('/auth/me', (req, res) => {
263
+ try {
264
+ const token = parseBearer(req);
265
+ if (!token) {
266
+ jsonError(res, 401, 'missing-token');
267
+ return;
268
+ }
269
+ const payload = jwt.verify(token, jwtSecret);
270
+ const user = store.getUserById(String(payload.sub));
271
+ if (!user) {
272
+ jsonError(res, 401, 'user-not-found');
273
+ return;
274
+ }
275
+ const session = payload?.sid ? store.getSession(String(payload.sid)) : null;
276
+ if (session && session.revoked_at) {
277
+ jsonError(res, 401, 'session-revoked');
278
+ return;
279
+ }
280
+ res.json({ ok: true, user: { id: user.id, username: user.username, role: user.role } });
281
+ } catch (e) {
282
+ jsonError(res, 401, 'invalid-token', { message: e.message });
283
+ }
284
+ });
285
+
286
+ // Serve CRA build (separate from main AI process)
287
+ if (fs.existsSync(webRoot)) {
288
+ app.use(express.static(webRoot));
289
+
290
+ // SPA fallback
291
+ app.get(/^\/(?!api\/|auth\/|robots\/).*/, (req, res) => {
292
+ const indexFile = path.join(webRoot, 'index.html');
293
+ if (!fs.existsSync(indexFile)) {
294
+ res.status(404).send('index.html not found');
295
+ return;
296
+ }
297
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
298
+ res.send(fs.readFileSync(indexFile, 'utf8'));
299
+ });
300
+ } else {
301
+ console.warn('[auth+web] WEB_ROOT does not exist:', webRoot);
302
+ }
303
+
304
+ app.listen(port, host, () => {
305
+ console.log(`[auth+web] listening on http://${host}:${port}`);
306
+ console.log(`[auth+web] webRoot: ${webRoot}`);
307
+ console.log(`[auth+web] db: ${dbPath}`);
308
+ console.log(`[auth+web] AI_API_BASE: ${aiApiBase}`);
309
+ });
310
+ }
311
+
312
+ start();