From 8a487b1b900b694ece740e94121946c2cc5e08aa Mon Sep 17 00:00:00 2001 From: BASTERIX31 Date: Sat, 10 Oct 2026 19:44:36 +0200 Subject: [PATCH 01/22] feat(trinity): natural dialogue and safe deploy approval groundwork --- src/core/conversation.mjs | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/core/conversation.mjs diff --git a/src/core/conversation.mjs b/src/core/conversation.mjs new file mode 100644 index 0000000..18290b4 --- /dev/null +++ b/src/core/conversation.mjs @@ -0,0 +1,48 @@ +/** Pure language/intent helpers: never execute deployments. */ +const clean = text => String(text || '').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^a-z0-9._\s-]+/g, ' ').trim().replace(/\s+/g, ' '); +export const normalize = clean; + +export function approvalIntent(text) { + const s = clean(text); + // Negations must be checked before positive commands. + if (/^(nie|nechcem|neschvalujem|nesuhlasim|zamietam|zamietnut|odmietam|zrus|zrusit|stop|cancel)(\b|$)/.test(s)) return 'reject'; + if (/^(ano|hej|ok|okej|suhlasim|schvalujem|potvrdzujem|potvrdim|schvalene|approve)(\b|$)/.test(s)) return 'approve'; + if (/^(nasad to|spusti toto nasadenie|muzes to nasadit|mozes to nasadit)(\b|$)/.test(s)) return 'approve'; + return null; +} + +export function parseDeploy(message, explicit = undefined) { + const s = clean(message); + const asksDeployment = /\b(nasad|nasadit|nasadenie|deploy|deployment|publikuj|publikovat)\b/.test(s); + if (!asksDeployment && !explicit) return null; + const explicitIsObj = explicit && typeof explicit === 'object' && !Array.isArray(explicit); + const matchTarget = s.match(/\b(?:worker|sluzbu|aplikaciu|projekt|service)\s+([a-z0-9][a-z0-9._-]{1,79})/); + const namedWorker = s.match(/\b(trinity[-_][a-z0-9._-]{2,60})\b/); + const matchVersion = s.match(/\b(?:commit|sha|verziu|verzia|verzie|revision|revisions)\s+([a-z0-9._-]{5,64})/); + const target = explicitIsObj ? explicit.target : (matchTarget?.[1] || namedWorker?.[1]); + const environment = explicitIsObj ? explicit.environment : (/\b(produkci[aeu]|production|prod)\b/.test(s) ? 'production' : /\b(staging|test|testovaci[aeo])\b/.test(s) ? 'staging' : ''); + const version = explicitIsObj ? explicit.version : matchVersion?.[1]; + const safe = (value, rule) => typeof value === 'string' && rule.test(value) ? value : ''; + return { + target: safe(target, /^[a-zA-Z0-9][a-zA-Z0-9._-]{1,79}$/), + environment: safe(environment, /^(production|staging)$/), + version: safe(version, /^[a-zA-Z0-9][a-zA-Z0-9._-]{4,63}$/), + }; +} + +export function deployMissing(spec) { + return [!spec.target && 'cieľ (napríklad trinity-gateway)', !spec.environment && 'prostredie (produkcia alebo staging)', !spec.version && 'konkrétnu verziu alebo commit'].filter(Boolean); +} + +export function deploymentLabel(spec) { + const env = spec.environment === 'production' ? 'produkcie' : 'stagingu'; + return `Nasadiť ${spec.target}, verzia ${spec.version}, do ${env}`; +} + +export const SYSTEM_PROMPT = `Si Trinity, užitočná digitálna asistentka pre vývoj projektov. Píš po slovensky, prirodzene a jasne, bez robotických fráz. V nadväzujúcich otázkach rešpektuj kontext. Nezahlcuj používateľa technickými ID. Keď to pomôže, ponúkni jeden konkrétny ďalší krok. +Nepredstieraj vedomie, vlastné pocity, živé údaje, používateľské oprávnenia, spustenie kódu ani dokončené nasadenie. Ak je model alebo nástroj nedostupný, povedz to. Akcia sa vykonala len ak to potvrdzuje skutočný výsledok nástroja. Rizikové akcie si vyžadujú platné schválenie viazané na konkrétny cieľ, prostredie a verziu; bežný chat sám o sebe nič nenasadzuje. Nikdy nežiadaj interné ID schválenia. Odpovedaj stručne, ale vecne; pri práci s kódom buď presná.`; + +export function safeHistory(history) { + if (!Array.isArray(history)) return []; + return history.slice(-10).filter(x => x && ['user','assistant'].includes(x.role) && typeof x.text === 'string').map(x => ({role:x.role,text:x.text.slice(0,2500)})); +} From 4671364721a4b01bad5d954eac23869b7bebfc6a Mon Sep 17 00:00:00 2001 From: BASTERIX31 Date: Sat, 10 Oct 2026 19:44:38 +0200 Subject: [PATCH 02/22] feat(trinity): natural dialogue and safe deploy approval groundwork --- src/routes/dialogue.mjs | 106 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/routes/dialogue.mjs diff --git a/src/routes/dialogue.mjs b/src/routes/dialogue.mjs new file mode 100644 index 0000000..588529a --- /dev/null +++ b/src/routes/dialogue.mjs @@ -0,0 +1,106 @@ +import { approvalIntent, parseDeploy, deployMissing, deploymentLabel, safeHistory, SYSTEM_PROMPT } from '../core/conversation.mjs'; + +const json = (value, status=200) => new Response(JSON.stringify(value), {status, headers:{'content-type':'application/json; charset=utf-8', 'cache-control':'no-store'}}); +const nowIso = () => new Date().toISOString(); +const idValid = s => typeof s === 'string' && /^[a-zA-Z0-9_-]{1,64}$/.test(s); + +async function tryDatabase(env) { + const db = env?.TRINITY_DB; + if (!db || typeof db.prepare !== 'function') return null; + try { await db.prepare('SELECT 1 FROM trinity_chat_turns LIMIT 1').all(); return db; } + catch { return null; } // Migration not applied: degrade to ephemeral chat, never approve. +} + +async function storeTurn(db, owner, conversationId, message, response) { + if (!db) return; + try { + await db.prepare('INSERT INTO trinity_chat_turns (id, owner, conversation_id, message, response, created_at) VALUES (?, ?, ?, ?, ?, ?)') + .bind(crypto.randomUUID(), owner, conversationId, message, response, nowIso()).run(); + // Bound each conversation to a reasonable size; old items are not retained forever. + await db.prepare('DELETE FROM trinity_chat_turns WHERE owner = ? AND conversation_id = ? AND id NOT IN (SELECT id FROM trinity_chat_turns WHERE owner = ? AND conversation_id = ? ORDER BY created_at DESC LIMIT 60)') + .bind(owner, conversationId, owner, conversationId).run(); + } catch { /* Chat still works; response declares degraded persistence. */ } +} + +async function previousTurns(db, owner, conversationId) { + if (!db) return []; + const {results = []} = await db.prepare('SELECT message, response FROM trinity_chat_turns WHERE owner = ? AND conversation_id = ? ORDER BY created_at DESC LIMIT 5') + .bind(owner, conversationId).all(); + return results.reverse().flatMap(turn => [{role:'user',text:turn.message},{role:'assistant',text:turn.response}]); +} + +async function handleApproval(db, owner, conversationId, intent) { + if (!db) return {response:'Na bezpečné schvaľovanie ešte chýba databázové úložisko TRINITY_DB a migrácia. Nasadenie nebolo vykonané.',mode:'not_configured',engine:'approval'}; + const {results=[]} = await db.prepare("SELECT id, label FROM trinity_deployment_approvals WHERE owner = ? AND conversation_id = ? AND status = 'pending' AND expires_at > ? ORDER BY created_at DESC LIMIT 2") + .bind(owner, conversationId, nowIso()).all(); + if (results.length > 1) return {response:'Čaká viac návrhov nasadenia. Napíš názov služby, ktorú chceš schváliť. Interné ID netreba.',mode:'ambiguous',engine:'approval'}; + if (!results.length) return {response:'Nemám v tejto konverzácii otvorený návrh nasadenia na schválenie. Najprv pripravíme presný cieľ a verziu.',mode:'no_pending',engine:'approval'}; + const chosen = results[0]; + const status = intent === 'approve' ? 'approved' : 'rejected'; + const updated = await db.prepare("UPDATE trinity_deployment_approvals SET status = ?, decided_at = ? WHERE id = ? AND owner = ? AND conversation_id = ? AND status = 'pending' AND expires_at > ?") + .bind(status,nowIso(),chosen.id,owner,conversationId,nowIso()).run(); + if (updated.meta?.changes !== 1) return {response:'Schválenie sa nepodarilo uložiť (mohlo vypršať). Nespustila som žiadnu akciu.',mode:'conflict',engine:'approval'}; + const word = status === 'approved' ? 'Schválenie zaznamenané' : 'Návrh zamietnutý'; + return {response:`${word}: ${chosen.label}. ${status==='approved'?'Nasadenie sa ešte nespustilo. Vykoná ho až samostatný autorizovaný deploy nástroj.':'Žiadne nasadenie sa nespustilo.'}`,mode:status,engine:'approval'}; +} + +async function stageDeployment(db, owner, conversationId, spec) { + const missing = deployMissing(spec); + if (missing.length) return {response:`Rozumiem zámeru nasadiť službu. Na bezpečné schválenie ešte potrebujem: ${missing.join(', ')}. Identifikátor schválenia nemusíš zadávať.`,mode:'needs_details',engine:'planner'}; + if (!db) return {response:`Návrh: ${deploymentLabel(spec)}. Na uloženie schválenia je potrebné nakonfigurovať TRINITY_DB a spustiť migráciu; bez toho žiadne nasadenie ani schválenie nespustím.`,mode:'not_configured',engine:'planner'}; + const label=deploymentLabel(spec); + const expiration = new Date(Date.now()+15*60*1000).toISOString(); + await db.prepare('INSERT INTO trinity_deployment_approvals (id, owner, conversation_id, label, target, environment, version, status, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)') + .bind(crypto.randomUUID(),owner,conversationId,label,spec.target,spec.environment,spec.version,'pending',nowIso(),expiration).run(); + return {response:`Pripravený návrh: ${label}. Schváliš? Stačí odpovedať „Schvaľujem“ alebo kliknúť na tlačidlo. Návrh platí 15 minút. Nič ešte nie je nasadené.`,mode:'pending_approval',engine:'planner',approval:{summary:label,can_approve:true}}; +} + +async function askModel(message, history, env, fetcher) { + const key = env?.GEMINI_API_KEY || env?.GEMINI || env?.gemini; + if (!key) return {response:'AI model zatiaľ nie je pripojený. Môžem pripraviť plán, ale nemám teraz skutočnú odpoveď modelu ani výsledok nástroja.',mode:'model_unavailable',engine:'unavailable'}; + const model = typeof env?.GEMINI_MODEL === 'string' && /^[a-zA-Z0-9._-]{3,60}$/.test(env.GEMINI_MODEL) ? env.GEMINI_MODEL : 'gemini-flash-latest'; + const contents = [...safeHistory(history),{role:'user',text:message}].map(turn=>({role:turn.role==='assistant'?'model':'user',parts:[{text:turn.text}]})); + try { + const res = await fetcher(`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`,{ + method:'POST',headers:{'content-type':'application/json','x-goog-api-key':key}, + signal: AbortSignal.timeout(20000), + body:JSON.stringify({systemInstruction:{parts:[{text:SYSTEM_PROMPT}]},contents,generationConfig:{temperature:0.65,maxOutputTokens:1100}}) + }); + if(!res.ok) throw new Error(`Gemini HTTP ${res.status}`); + const payload=await res.json(); + const text=payload?.candidates?.[0]?.content?.parts?.map(p=>p.text||'').join('').trim(); + if(!text) throw new Error('Gemini returned no content'); + return {response:text.slice(0,6000),mode:'answer',engine:'gemini'}; + } catch { + return {response:'Model momentálne neodpovedá. Nespustila som žiadnu akciu ani nasadenie. Skús opakovať požiadavku.',mode:'model_unavailable',engine:'unavailable'}; + } +} + +/** Authenticated user is supplied by /api/ai routing; this endpoint never performs deployments. */ +export async function handleDialogue(request, env, principal, fetcher=fetch) { + if (!principal?.user) return json({error:'Vyžaduje sa prihlásenie.'},401); + let body; + try { body=await request.json(); } catch { return json({error:'Neplatný JSON.'},400); } + const message=body?.message ?? body?.command; + if (typeof message !== 'string' || !message.trim() || message.length>3000) return json({error:'Správa musí mať 1 až 3000 znakov.'},400); + const conversationId=body?.conversation_id ?? 'default'; + if(!idValid(conversationId)) return json({error:'Neplatná relácia konverzácie.'},400); + const db=await tryDatabase(env); + let result; + try { + const intent=approvalIntent(message); + const spec=parseDeploy(message,body?.deployment); + // An explicit deploy specification always starts a new proposal, never an approval. + if (body?.deployment) result=await stageDeployment(db,principal.user,conversationId,spec); + else if(intent) result=await handleApproval(db,principal.user,conversationId,intent); + else if(spec) result=await stageDeployment(db,principal.user,conversationId,spec); + else { + const history=db ? await previousTurns(db,principal.user,conversationId) : safeHistory(body?.history); + result=await askModel(message,history,env,fetcher); + } + await storeTurn(db,principal.user,conversationId,message,result.response); + return json({...result,conversation_id:conversationId,persistence:db?'d1':'ephemeral'}); + } catch { + return json({error:'Došlo k chybe pri spracovaní. Žiadne nasadenie sa nevykonalo.'},503); + } +} From 37b242a6fc34c4988b64c5bc1d1d0289829be412 Mon Sep 17 00:00:00 2001 From: BASTERIX31 Date: Sat, 10 Oct 2026 19:44:40 +0200 Subject: [PATCH 03/22] feat(trinity): natural dialogue and safe deploy approval groundwork --- tests/conversation.test.mjs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/conversation.test.mjs diff --git a/tests/conversation.test.mjs b/tests/conversation.test.mjs new file mode 100644 index 0000000..ac0d52a --- /dev/null +++ b/tests/conversation.test.mjs @@ -0,0 +1,32 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { approvalIntent, parseDeploy, deployMissing, deploymentLabel, safeHistory } from '../src/core/conversation.mjs'; + +test('approval accepts natural Slovak and no ID', () => { + for (const s of ['Schvaľujem', 'Potvrdzujem', 'Áno', 'Súhlasím', 'nasad to']) assert.equal(approvalIntent(s), 'approve'); +}); +test('reject takes precedence over schvaľujem substring', () => { + for (const s of ['Neschvaľujem', 'Nie', 'Zamietam', 'Zruš to']) assert.equal(approvalIntent(s), 'reject'); +}); +test('ordinary user commands are not approvals', () => { + assert.equal(approvalIntent('Nasad worker trinity-gateway'), null); + assert.equal(approvalIntent('Zistíš stav workerov?'), null); +}); +test('extract target environment and immutable version', () => { + const spec = parseDeploy('Nasad worker trinity-gateway do produkcie commit abcdef1234'); + assert.deepEqual(spec, {target:'trinity-gateway', environment:'production', version:'abcdef1234'}); + assert.equal(deploymentLabel(spec), 'Nasadiť trinity-gateway, verzia abcdef1234, do produkcie'); +}); +test('missing deployment details must be requested, never invented', () => { + assert.deepEqual(deployMissing(parseDeploy('nasad worker trinity-gateway')), ['prostredie (produkcia alebo staging)','konkrétnu verziu alebo commit']); +}); +test('structured deployment spec validated', () => { + const invalid = parseDeploy('', {target:'../root', environment:'production', version:'abc'}); + assert.equal(invalid.target,''); assert.equal(invalid.version,''); +}); +test('unrelated text is not deployment', () => assert.equal(parseDeploy('Ahoj Trinity'), null)); +test('history bounded and trimmed', () => { + const history = Array.from({length:15}, (_,i)=>({role:'user',text:'x'.repeat(3000)})); + assert.equal(safeHistory(history).length,10); + assert.equal(safeHistory(history)[0].text.length,2500); +}); From 289507dc298f6dffcbb5a97096d90dd07c40e66d Mon Sep 17 00:00:00 2001 From: BASTERIX31 Date: Sat, 10 Oct 2026 19:44:43 +0200 Subject: [PATCH 04/22] feat(trinity): natural dialogue and safe deploy approval groundwork --- tests/dialogue.test.mjs | 77 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/dialogue.test.mjs diff --git a/tests/dialogue.test.mjs b/tests/dialogue.test.mjs new file mode 100644 index 0000000..a1ebed2 --- /dev/null +++ b/tests/dialogue.test.mjs @@ -0,0 +1,77 @@ +import {test} from 'node:test'; +import assert from 'node:assert/strict'; +import {handleDialogue} from '../src/routes/dialogue.mjs'; +const principal = {user:'admin-test'}; +const req = (text, extras={}) => new Request('https://local/api/ai/command',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({command:text,conversation_id:'current',...extras})}); +const send = async (text, env={}, extras={}) => { + const res = await handleDialogue(req(text,extras),env,principal,async (_url,opts)=>({ok:true,json:async()=>({candidates:[{content:{parts:[{text:'Rozumiem. Tu je ďalší krok.'}]}}]})})); + return {status:res.status,body:await res.json()}; +}; + +class FakeDb { + approvals=[]; turns=[]; + prepare(sql) {return {all:async()=>({results:[]}),bind:(...args)=>({ + all:async()=>{ + if(sql.startsWith('SELECT 1'))return {results:[]}; + if(sql.includes('FROM trinity_deployment_approvals'))return {results:this.approvals.filter(a=>a.owner===args[0] && a.conversation_id===args[1] && a.status==='pending' && a.expires_at>args[2]).slice(-2).reverse()}; + if(sql.includes('FROM trinity_chat_turns'))return {results:this.turns.filter(t=>t.owner===args[0]&&t.conversation_id===args[1]).slice(-5).reverse()}; + throw new Error('Unexpected all: '+sql); + }, + run:async()=>{ + if(sql.startsWith('INSERT INTO trinity_chat_turns')) {this.turns.push({id:args[0],owner:args[1],conversation_id:args[2],message:args[3],response:args[4]});return {meta:{changes:1}};} + if(sql.startsWith('DELETE FROM trinity_chat_turns'))return {meta:{changes:0}}; + if(sql.startsWith('INSERT INTO trinity_deployment_approvals')) {this.approvals.push({id:args[0],owner:args[1],conversation_id:args[2],label:args[3],target:args[4],environment:args[5],version:args[6],status:args[7],created_at:args[8],expires_at:args[9]});return {meta:{changes:1}};} + if(sql.startsWith('UPDATE trinity_deployment_approvals')) {const found=this.approvals.find(a=>a.id===args[2]&&a.owner===args[3]&&a.conversation_id===args[4]&&a.status==='pending'&&a.expires_at>args[5]);if(!found)return {meta:{changes:0}};found.status=args[0];return {meta:{changes:1}};} + throw new Error('Unexpected run: '+sql); + } + })};} +} + +test('must authenticate',async()=>{ + const res=await handleDialogue(req('Ahoj'),{},null); assert.equal(res.status,401); +}); +test('no model never fabricates results',async()=>{ + const {body}=await send('Aký je aktuálny stav infraštruktúry?'); + assert.equal(body.mode,'model_unavailable'); assert.match(body.response,/model.*nie je pripojený/i); +}); +test('Gemini uses API key header and retains short history',async()=>{ + let passed; + const r=await handleDialogue(req('Čo ďalej?',{history:[{role:'user',text:'Stav projektu?'},{role:'assistant',text:'Príprava.'}]}),{GEMINI_API_KEY:'testkey'},principal,async(url,opts)=>{ + passed={url,headers:opts.headers,body:JSON.parse(opts.body)}; + return {ok:true,json:async()=>({candidates:[{content:{parts:[{text:'Navrhujem jeden krok.'}]}}]})}; + }); + assert.equal((await r.json()).response,'Navrhujem jeden krok.'); + assert.equal(passed.headers['x-goog-api-key'],'testkey'); + assert.ok(!passed.url.includes('testkey')); assert.equal(passed.body.contents.length,3); +}); +test('cannot stage approval without database',async()=>{ + const {body}=await send('Nasad worker trinity-gateway do produkcie commit abcdef1234'); + assert.equal(body.mode,'not_configured'); +}); +test('natural approval stages and marks correct proposal without running deployment',async()=>{ + const db=new FakeDb(); + const env={TRINITY_DB:db}; + let {body}=await send('Nasad worker trinity-gateway do produkcie commit abcdef1234',env); + assert.equal(body.mode,'pending_approval'); assert.equal(db.approvals[0].status,'pending'); + ({body}=await send('Schvaľujem',env)); + assert.equal(body.mode,'approved'); assert.equal(db.approvals[0].status,'approved'); + assert.match(body.response,/ešte nespustilo/); +}); +test('reject blocks deployment and cannot approve again',async()=>{ + const db=new FakeDb();const env={TRINITY_DB:db}; + await send('Nasad worker trinity-gateway do produkcie commit abcdef1234',env); + let {body}=await send('Zamietam',env);assert.equal(body.mode,'rejected'); + ({body}=await send('Schvaľujem',env));assert.equal(body.mode,'no_pending'); +}); +test('ambiguous pending approvals cannot approve by bare yes',async()=>{ + const db=new FakeDb();const env={TRINITY_DB:db}; + await send('Nasad worker trinity-gateway do produkcie commit abcdef1234',env); + await send('Nasad worker trinity-gemini do produkcie commit abcdef1234',env); + const {body}=await send('Áno',env);assert.equal(body.mode,'ambiguous');assert.ok(db.approvals.every(x=>x.status==='pending')); +}); +test('no approval leakage between user sessions',async()=>{ + const db=new FakeDb();const env={TRINITY_DB:db}; + await send('Nasad worker trinity-gateway do produkcie commit abcdef1234',env); + const res=await handleDialogue(req('Schvaľujem'),env,{user:'different-user'}); + assert.equal((await res.json()).mode,'no_pending'); +}); From 827b48365e236e18f6dd73027ee2b06cbe6ddce6 Mon Sep 17 00:00:00 2001 From: BASTERIX31 Date: Sat, 10 Oct 2026 19:44:45 +0200 Subject: [PATCH 05/22] feat(trinity): natural dialogue and safe deploy approval groundwork --- migrations/0001_trinity_dialogue.sql | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 migrations/0001_trinity_dialogue.sql diff --git a/migrations/0001_trinity_dialogue.sql b/migrations/0001_trinity_dialogue.sql new file mode 100644 index 0000000..91c00f2 --- /dev/null +++ b/migrations/0001_trinity_dialogue.sql @@ -0,0 +1,24 @@ +-- Deploy review and conversation state. No real deployment is triggered here. +CREATE TABLE IF NOT EXISTS trinity_chat_turns ( + id TEXT PRIMARY KEY, + owner TEXT NOT NULL, + conversation_id TEXT NOT NULL, + message TEXT NOT NULL, + response TEXT NOT NULL, + created_at TEXT NOT NULL +); +CREATE INDEX IF NOT EXISTS idx_trinity_chat_owner_conv ON trinity_chat_turns(owner,conversation_id,created_at); +CREATE TABLE IF NOT EXISTS trinity_deployment_approvals ( + id TEXT PRIMARY KEY, + owner TEXT NOT NULL, + conversation_id TEXT NOT NULL, + label TEXT NOT NULL, + target TEXT NOT NULL, + environment TEXT NOT NULL CHECK (environment IN ('production','staging')), + version TEXT NOT NULL, + status TEXT NOT NULL CHECK (status IN ('pending','approved','rejected','executed','expired')), + created_at TEXT NOT NULL, + expires_at TEXT NOT NULL, + decided_at TEXT +); +CREATE INDEX IF NOT EXISTS idx_trinity_approval_pending ON trinity_deployment_approvals(owner,conversation_id,status,expires_at); From 1915bf1bb8696e22359809721f1c66d1d69d2b8a Mon Sep 17 00:00:00 2001 From: BASTERIX31 Date: Sat, 10 Oct 2026 19:45:00 +0200 Subject: [PATCH 06/22] fix(trinity): replace simulated AI status with grounded dialogue and approval flow --- src/routes/ai.js | 65 ++++++------------------------------------------ 1 file changed, 7 insertions(+), 58 deletions(-) diff --git a/src/routes/ai.js b/src/routes/ai.js index efbd80a..758a129 100644 --- a/src/routes/ai.js +++ b/src/routes/ai.js @@ -6,6 +6,9 @@ * POST /api/ai/command — send command to Gemini (or simulated fallback) */ +import { handleDialogue } from './dialogue.mjs'; +import { requireAuth } from './auth.js'; + import { aiPermissions, aiBehaviorLog, aiKnowledgeBase, aiEvolutionScore, aiCapabilities, aiAutonomousLog, aiMetrics, aiActive, pillars, logAudit } from '../data/store.js'; function json(data, status = 200) { @@ -40,56 +43,10 @@ export async function handleAI(request, env) { return json({ logs: aiBehaviorLog.slice(0, 50) }); } - // POST /api/ai/command - if (section === 'command' && request.method === 'POST') { - const body = await request.json(); - const command = body.command || ''; - - // Check if the AI has permission to act - aiBehaviorLog.unshift({ - time: new Date().toISOString(), - command, - response: 'processing', - }); - - let response; - let engine = 'simulated'; - - // Try Gemini if key is available - const apiKey = env?.gemini || env?.GEMINI || env?.GEMINI_API_KEY; - if (apiKey) { - try { - const geminiRes = await fetch( - `https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-latest:generateContent?key=${apiKey}`, - { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ - contents: [{ parts: [{ text: `Si TRINITY AI Core — centrálna autonómna inteligencia pre Underground Street Collective. Si super-admin asistent. Odpovedaj stručne, profesionálne, v slovenčine. Príkaz od super-admina: ${command}` }] }], - }), - } - ); - const geminiData = await geminiRes.json(); - if (geminiData.error) { - // Gemini unavailable (overload, etc.) — fall back to simulated response - response = simulateResponse(command) + '\n\n[Note: Gemini je momentálne nedostupný (' + geminiData.error.message + '). Spúšťam simulovaný režim.]'; - engine = 'gemini-fallback'; - } else { - response = geminiData.candidates?.[0]?.content?.parts?.[0]?.text || 'Bez odpovede.'; - engine = 'gemini'; - } - } catch (e) { - response = simulateResponse(command) + '\n\n[Gemini nedostupný: ' + e.message + ']'; - engine = 'gemini-fallback'; - } - } else { - // Simulated response - response = simulateResponse(command); - } - - aiBehaviorLog[0].response = response; - logAudit('AI_COMMAND', 'super-admin', command.slice(0, 80)); - return json({ command, response, engine }); + // Authenticated conversation with contextual replies and safe approvals. + // /api/ai/command stays backward-compatible; /api/ai/dialogue is the new alias. + if ((section === 'command' || section === 'dialogue') && request.method === 'POST') { + return handleDialogue(request, env, requireAuth(request)); } // ===== AUTONOMOUS ENGINE ENDPOINTS ===== @@ -277,11 +234,3 @@ function runAutonomousCycle() { }; } -function simulateResponse(command) { - const c = command.toLowerCase(); - if (c.includes('web') || c.includes('stránk')) return 'Web pripravený — šablóna vygenerovaná v AI Studiu. Môžeš upraviť obsah a štýl.'; - if (c.includes('stav') || c.includes('status')) return 'Systém TRINITY: 4 piliere ONLINE, 2 STANDBY. Žiadne kritické incidenty. CI: 2 zlyhania v repozitároch (Trinity, USC).'; - if (c.includes('voz') || c.includes('car')) return 'Flotila Rent-a-Car: 18 vozidiel, 12 aktívnych, 4 rezervované, 2 v servise.'; - if (c.includes('logist')) return 'Logistika: 42 zásielok, 15 v preprave, 4 sklady aktívne, 1280 položiek na sklade.'; - return `Príkaz prijatý: "${command}". TRINITY AI Core analyzuje a pripravuje akčný plán. (Simulovaný režim — pre reálne AI odpovede pripoj Gemini kľúč.)`; -} From edfbe1ca31bc89e5d1593477a7f99447d0247876 Mon Sep 17 00:00:00 2001 From: BASTERIX31 Date: Sat, 10 Oct 2026 19:46:06 +0200 Subject: [PATCH 07/22] feat(trinity-ui): interactive multi-turn chat and no-ID deployment confirmations --- src/frontend/app.js | 58 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/src/frontend/app.js b/src/frontend/app.js index ca9c58f..928ac7f 100644 --- a/src/frontend/app.js +++ b/src/frontend/app.js @@ -48,6 +48,16 @@ export const HTML = `