nothumanallowed 15.1.32 → 15.1.34

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "15.1.32",
3
+ "version": "15.1.34",
4
4
  "description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/config.mjs CHANGED
@@ -318,6 +318,14 @@ export function setConfigValue(key, value) {
318
318
  'telegram-bot-token': 'responder.telegram.token',
319
319
  'discord-bot-token': 'responder.discord.token',
320
320
  'responder-auto-route': 'responder.autoRoute',
321
+ // Telegram bot persona — what name the bot uses when replying. If empty,
322
+ // falls back to the internal agent name (HERALD/ATHENA/...). Most users
323
+ // want a single consistent identity like "Agata" or "Jarvis".
324
+ 'bot-name': 'responder.telegram.botName',
325
+ 'botname': 'responder.telegram.botName',
326
+ 'telegram-bot-name': 'responder.telegram.botName',
327
+ 'persona-name': 'responder.telegram.botName',
328
+ 'persona-mode': 'responder.telegram.personaMode', // persona | persona-only | persona+role | agent
321
329
  'proactive': 'ops.proactive.enabled',
322
330
  'proactive-email': 'ops.proactive.emailFollowUp',
323
331
  'proactive-meeting': 'ops.proactive.meetingPrep',
package/src/constants.mjs CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
 
8
- export const VERSION = '15.1.32';
8
+ export const VERSION = '15.1.34';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -822,17 +822,50 @@ class TelegramResponder {
822
822
  const shortConfirm = /^(s[ìi]\.?|ok\.?|okay\.?|yes\.?|yep\.?|sure\.?|certo\.?|procedi\.?|fallo\.?|vai\.?|go\.?|do it\.?|conferma\.?|certo che s[ìi]\.?|fallo pure\.?)$/i.test(cleanText.trim());
823
823
 
824
824
  if (proposedAction && shortConfirm) {
825
+ // Extract the SPECIFIC action the assistant proposed (create/delete/
826
+ // update/move/send/...), so we can inject an explicit tool name in
827
+ // the instruction. Without this, Liara may pick a random tool —
828
+ // e.g. user confirms a CREATE proposal and the model fires DELETE
829
+ // with a hallucinated eventId.
830
+ const r = lowerReply;
831
+ let actionHint = '';
832
+ let toolHint = '';
833
+ if (/fissat[oa]|inserir[oe]|cre(a|er)[oa]|aggiunger[oeò]|prenota|registr[oa]|mett[oa] (?:in calendario|nel calendario)/.test(r)) {
834
+ actionHint = 'CREARE un nuovo appuntamento';
835
+ toolHint = 'calendar_create con i parametri ESATTI (summary, start, end) che hai descritto nel turno precedente';
836
+ } else if (/cancell|eliminar[eo]|rimuover/.test(r)) {
837
+ actionHint = 'CANCELLARE un appuntamento esistente';
838
+ toolHint = 'PRIMA calendar_find/calendar_date per ottenere l\'eventId REALE, POI calendar_delete con quell\'eventId. Mai inventare ID';
839
+ } else if (/spostar|riprogrammar|cambiar[eo] (?:l\')?orario|cambiar[eo] (?:la )?data/.test(r)) {
840
+ actionHint = 'SPOSTARE un appuntamento';
841
+ toolHint = 'PRIMA calendar_find per ottenere eventId, POI calendar_move(eventId, newStart, newEnd)';
842
+ } else if (/modificar|correggere|rinominar|cambiar[eo] (?:il )?titolo/.test(r)) {
843
+ actionHint = 'MODIFICARE un appuntamento';
844
+ toolHint = 'PRIMA calendar_find per eventId, POI calendar_update(eventId, ...) con SOLO i campi da cambiare';
845
+ } else if (/inviar|spedir|mandar (?:l\'|la )?email|send (?:the )?email/.test(r)) {
846
+ actionHint = 'INVIARE un\'email';
847
+ toolHint = 'gmail_send (o gmail_reply se è una risposta) con destinatario, oggetto e corpo già concordati';
848
+ } else if (/aggiunger[eo] (?:un )?task|creare (?:un )?task|inserir[eo] (?:il )?task/.test(r)) {
849
+ actionHint = 'CREARE un task';
850
+ toolHint = 'task_add con la descrizione concordata';
851
+ } else {
852
+ actionHint = 'l\'azione che hai proposto';
853
+ toolHint = 'il tool corrispondente all\'azione (calendar_create se hai detto "fisso"; calendar_delete se "cancello"; gmail_send se "invio"; ecc.). NON inventare eventId — usa SOLO ID reali ricevuti da tool precedenti';
854
+ }
855
+
825
856
  enrichedMessage =
826
- `[CONFERMA UTENTE — Esegui SUBITO l'azione che hai proposto nel turno precedente]\n\n` +
827
- `Tuo turno precedente:\n"${(lastCtx.agentReply || '').slice(0, 800)}"\n\n` +
857
+ `[CONFERMA UTENTE — Esegui SUBITO l'azione che hai proposto]\n\n` +
858
+ `Tuo turno precedente (proposta):\n"${(lastCtx.agentReply || '').slice(0, 800)}"\n\n` +
828
859
  `Risposta utente: "${cleanText.trim()}"\n\n` +
860
+ `AZIONE DA ESEGUIRE: ${actionHint}\n` +
861
+ `TOOL DA USARE: ${toolHint}\n\n` +
829
862
  `ISTRUZIONI OBBLIGATORIE:\n` +
830
- `1. L'utente sta confermando il piano che TU hai proposto. NON chiedere altri chiarimenti.\n` +
831
- `2. NON dire "procedo" senza eseguire emetti SUBITO i tool block JSON.\n` +
832
- `3. Se hai promesso N step (cercare, eliminare, mostrare la lista, etc.)emetti TUTTI gli N tool block in sequenza nel TUO prossimo messaggio.\n` +
833
- `4. Esempio: se hai detto "cercherò l'evento, lo eliminerò, mostrerò la lista" emetti calendar_find quando ricevi il risultato emetti calendar_delete con l'eventId trovato infine calendar_month/calendar_week.\n` +
834
- `5. NON ripetere "Procedo. Cosa vuoi?" sai già cosa vuole, lo hai proposto tu. Esegui.`;
835
- this.log(`[Telegram] ${fromUser}: pending-action force (proposal detected in prev reply, user confirmed short)`);
863
+ `1. Emetti SUBITO il blocco JSON \`\`\`json {"action":"...","params":{...}} \`\`\` per ESATTAMENTE l'azione qui sopra.\n` +
864
+ `2. Usa i parametri (data, ora, titolo, destinatario, eccetera) che HAI GIÀ menzionato nel tuo turno precedente. NON chiedere di nuovo.\n` +
865
+ `3. Se l'azione è CREATE/SEND/ADD: emetti SOLO quel tool. NIENTE find/delete/update preliminarequei tool servono per modificare cose esistenti, non per creare di nuove.\n` +
866
+ `4. Se l'azione è DELETE/UPDATE/MOVE: emetti PRIMA calendar_find o calendar_date per trovare l'eventId REALE (lungo, alfanumerico Google). MAI usare placeholder come "A1B2C3D4E5F6G7H8I9J0", "abc123", "ABC123".\n` +
867
+ `5. NON chiedere "cosa vuoi". NON dire "Procedo." senza eseguire. SOLO il blocco JSON e una breve conferma in italiano dopo l'esecuzione.`;
868
+ this.log(`[Telegram] ${fromUser}: pending-action force ${actionHint}`);
836
869
  }
837
870
 
838
871
  this.log(`[Telegram] ${fromUser}: continuation → ${agent.toUpperCase()} (ctx ${Math.round(stickyAge/1000)}s ago, history=${preHistory ? preHistory.length : 0})`);
@@ -887,12 +920,34 @@ class TelegramResponder {
887
920
  // routes the confirmation to a random agent (FORGE in the reported bug).
888
921
  this._persistContext();
889
922
 
923
+ // Bot persona name — user can configure a custom name (e.g. "Agata")
924
+ // so the bot speaks with a single consistent identity instead of
925
+ // leaking the internal multi-agent routing (HERALD/ATHENA/MERCURY).
926
+ // Mode determines the format:
927
+ // - 'persona-only' → just the body, no prefix (most natural)
928
+ // - 'persona' → "[BotName] body"
929
+ // - 'persona+role' → "[BotName · role] body" — keeps specialization hint
930
+ // - 'agent' → "[AGENT_NAME] body" — legacy / debugging mode
931
+ const personaName = this.config.responder?.telegram?.botName || this.config.responder?.botName || '';
932
+ const personaMode = this.config.responder?.telegram?.personaMode || (personaName ? 'persona' : 'agent');
933
+ const agentLabel = String(agent || '').toUpperCase();
934
+ let prefixedText;
935
+ if (personaMode === 'persona-only' && personaName) {
936
+ prefixedText = truncated;
937
+ } else if (personaMode === 'persona+role' && personaName) {
938
+ prefixedText = `[${personaName} · ${agentLabel.toLowerCase()}]\n\n${truncated}`;
939
+ } else if (personaMode === 'persona' && personaName) {
940
+ prefixedText = `[${personaName}]\n\n${truncated}`;
941
+ } else {
942
+ prefixedText = `[${agentLabel}]\n\n${truncated}`;
943
+ }
944
+
890
945
  await this._telegramCall('sendMessage', {
891
946
  chat_id: chatId,
892
- text: `[${agent.toUpperCase()}]\n\n${truncated}`,
947
+ text: prefixedText,
893
948
  });
894
949
 
895
- this.log(`[Telegram] Responded to ${fromUser} via ${agent.toUpperCase()} (${responseText.length} chars)${isCompletedAction(responseText) ? ' [action completed — context reset]' : ''}`);
950
+ this.log(`[Telegram] Responded to ${fromUser} via ${agentLabel}${personaName ? ` (as "${personaName}")` : ''} (${responseText.length} chars)${isCompletedAction(responseText) ? ' [action completed — context reset]' : ''}`);
896
951
  } catch (err) {
897
952
  this.log(`[Telegram] Agent call failed: ${err.message}`);
898
953
  // Send error message to user
@@ -1091,6 +1091,41 @@ export async function buildSystemPrompt(persona, personaDescription, config, ini
1091
1091
  * @param {object} config — NHA config
1092
1092
  * @returns {Promise<string>} result description
1093
1093
  */
1094
+
1095
+ /**
1096
+ * Detect IDs that the model has clearly invented vs real Google Calendar
1097
+ * eventIds. Real ones are LONG (≥10), LOWERCASE, alphanumeric (a-v + 0-9,
1098
+ * sometimes with `_` or `-`). Placeholders are typically short, uppercase,
1099
+ * dictionary words, or alternate-letter+digit pseudo-random patterns like
1100
+ * "A1B2C3D4E5F6G7H8I9J0".
1101
+ */
1102
+ function isPlaceholderEventId(id) {
1103
+ if (!id || typeof id !== 'string') return true;
1104
+ const s = id.trim();
1105
+ // 1. Explicit literal blocklist (covers everything we've seen so far)
1106
+ const LITERAL = new Set([
1107
+ 'abc123', 'def456', 'xyz789', 'event_123', 'event_123456789',
1108
+ '123', '456', 'foo', 'bar', 'baz', 'example', 'placeholder',
1109
+ 'eventid', 'event_id', 'id', 'null', 'undefined', 'some-google-id',
1110
+ 'event-id', 'id-from-tool', 'event_abc', 'event_xyz',
1111
+ ]);
1112
+ if (LITERAL.has(s.toLowerCase())) return true;
1113
+ // 2. Too short — Google eventIds are always ≥ 16 chars in practice
1114
+ if (s.length < 10) return true;
1115
+ // 3. Contains spaces — never valid
1116
+ if (/\s/.test(s)) return true;
1117
+ // 4. Pseudo-random alternating letter+digit pattern like "A1B2C3D4..."
1118
+ // Real Google eventIds NEVER look like this — they have natural
1119
+ // base32-ish randomness, not human-readable patterns.
1120
+ if (/^[A-Z](?:\d[A-Z]){3,}/i.test(s)) return true;
1121
+ // 5. Almost-all-uppercase: Google eventIds are lowercase. UPPERCASE
1122
+ // is a model invention signature.
1123
+ const upper = (s.match(/[A-Z]/g) || []).length;
1124
+ const lower = (s.match(/[a-z]/g) || []).length;
1125
+ if (upper >= 3 && upper > lower) return true;
1126
+ return false;
1127
+ }
1128
+
1094
1129
  export async function executeTool(action, params, config) {
1095
1130
  switch (action) {
1096
1131
  // ── Gmail ──────────────────────────────────────────────────────────────
@@ -1634,14 +1669,10 @@ export async function executeTool(action, params, config) {
1634
1669
  let eventId = String(params.eventId || '').trim();
1635
1670
  if (!eventId) return 'eventId required. Call calendar_find or calendar_date first to get the REAL eventId.';
1636
1671
 
1637
- // Reject obvious placeholder IDs (model copied from prompt examples)
1638
- const PLACEHOLDERS = new Set([
1639
- 'abc123', 'def456', 'xyz789', 'event_123', 'event_123456789',
1640
- '123', '456', 'foo', 'bar', 'baz', 'example', 'placeholder',
1641
- 'eventid', 'event_id', 'id', 'null', 'undefined',
1642
- ]);
1643
- if (PLACEHOLDERS.has(eventId.toLowerCase())) {
1644
- return `"${eventId}" is a placeholder, not a real eventId. Call calendar_find or calendar_date FIRST to get real eventIds.`;
1672
+ // Reject obvious placeholder IDs (model copied from prompt examples or
1673
+ // emitted obvious patterns like "A1B2C3D4E5F6G7H8I9J0").
1674
+ if (isPlaceholderEventId(eventId)) {
1675
+ return `"${eventId}" looks invented (placeholder pattern). Call calendar_find or calendar_date FIRST to get real eventIds.`;
1645
1676
  }
1646
1677
 
1647
1678
  // Smart eventId resolution: if it looks like a name instead of a Google Calendar ID, search for it
@@ -1695,16 +1726,11 @@ export async function executeTool(action, params, config) {
1695
1726
  let delEventId = String(params.eventId).trim();
1696
1727
 
1697
1728
  // Reject placeholder IDs the model may have copied verbatim from prompt
1698
- // examples or hallucinated wholesale. These are obvious giveaways — real
1699
- // Google Calendar eventIds are long lowercase alphanumeric strings, not
1700
- // dictionary words.
1701
- const PLACEHOLDER_IDS = new Set([
1702
- 'abc123', 'def456', 'xyz789', 'event_123', 'event_123456789',
1703
- '123', '456', 'foo', 'bar', 'baz', 'example', 'placeholder',
1704
- 'eventid', 'event_id', 'id', 'null', 'undefined',
1705
- ]);
1706
- if (PLACEHOLDER_IDS.has(delEventId.toLowerCase())) {
1707
- return `"${delEventId}" is a placeholder, not a real eventId. Call calendar_date(YYYY-MM-DD) or calendar_find(query) FIRST to get the real eventIds, then call calendar_delete with one of those values.`;
1729
+ // examples or hallucinated wholesale. Real Google Calendar eventIds
1730
+ // are long LOWERCASE alphanumeric strings — never UPPERCASE patterns
1731
+ // like "A1B2C3D4E5F6G7H8I9J0" (visibly invented).
1732
+ if (isPlaceholderEventId(delEventId)) {
1733
+ return `"${delEventId}" looks invented (placeholder pattern). Real Google Calendar eventIds are long lowercase alphanumeric strings (a-v + 0-9, often with - and _). Call calendar_date(YYYY-MM-DD) or calendar_find(query) FIRST to get the real eventIds, then call calendar_delete with one of those.`;
1708
1734
  }
1709
1735
 
1710
1736
  // Branch A — looks like a free-text search query (spaces, caps, very short).
@@ -788,7 +788,7 @@ FRONTEND:
788
788
  - index.html: Job board homepage — (1) Hero: gradient background, large search widget (keyword input + location input + category select + Search button), quick stats bar (12,847 Jobs / 3,420 Companies / 45K Candidates). (2) Featured jobs: horizontal scroll row of 4 "Featured" cards with company logo (colored emoji), job title, company name, location + type badge + salary range + "Easy Apply" button. (3) Main layout: left sidebar filters (Job Type checkboxes, Salary Range slider, Category checkboxes, Location input, Posted Within select, Remote Only toggle) + right: jobs list (job row cards with company logo/title/company/location/type/salary/time-ago posted/Save bookmark/Apply button). (4) "Load More" button + results count "Showing 12 of 247 jobs". (5) Top companies section: 8 company cards with logo/name/industry/openings count. (6) Category browsing: 6 category cards with icon/name/count/link. (7) Job seeker CTA banner. (8) Newsletter.
789
789
  - job.html: Job detail page — company header (logo + name + industry + website + size + "Follow" button), job title + badges (Remote, Senior, Urgent), salary + location + posted date, Apply Now button (sticky on scroll), job description (markdown-rendered sections: About/Responsibilities/Requirements/Nice-to-Have/Benefits), company sidebar card (description + stats), similar jobs list (4 cards)
790
790
  - apply.html: Application modal/page — job summary header, form (Full Name / Email / Phone / LinkedIn URL / Portfolio URL / Cover Letter textarea with character count / Resume paste textarea), Submit button with loading state, success page with application reference number
791
- - public/css/main.css: Modern job board design — tag/badge system (Remote=blue, Onsite=green, Urgent=red), salary range display, company logo placeholder styles, filter sidebar collapse on mobile, job card hover effects, application form validation styles`}],JT=[{key:`auth`,label:`Auth (register/login/JWT)`,icon:`🔒`},{key:`cookieBanner`,label:`GDPR Cookie Banner`,icon:`🍪`},{key:`securityMiddleware`,label:`Security Middleware`,icon:`🛡️`},{key:`emailVerification`,label:`Email Verification`,icon:`✉️`}],YT={js:`📄`,ts:`📄`,css:`🎨`,html:`🌐`,json:`📋`,md:`📑`,sql:`🗂`,env:`🔐`,conf:`⚙`,lock:`🔒`};function XT(e){return YT[e.split(`.`).pop()?.toLowerCase()??``]??`📄`}function ZT(e){let t=new TextEncoder().encode(e).length;return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(2)} MB`}function QT(e){return e===`memory`?`🧠`:e===`provider`?`🤖`:e===`log`?`📄`:`📋`}function $T(){let e=j(),[t,n]=(0,_.useState)(`new`),[r,i]=(0,_.useState)(`files`),[a,o]=(0,_.useState)(``),[s,c]=(0,_.useState)(``),[l,u]=(0,_.useState)({auth:!0,cookieBanner:!0,securityMiddleware:!0,emailVerification:!0}),[d,f]=(0,_.useState)([{label:`Email`,type:`email`,required:!0},{label:`Password`,type:`password`,required:!0},{label:`Name`,type:`text`,required:!0}]),[p,m]=(0,_.useState)([]),[h,g]=(0,_.useState)(0),[v,y]=(0,_.useState)(null),[b,x]=(0,_.useState)(null),[S,C]=(0,_.useState)(new Set),[w,ee]=(0,_.useState)(!1),[te,T]=(0,_.useState)(``),[ne,re]=(0,_.useState)(``),[ie,O]=(0,_.useState)(!1),[ae,A]=(0,_.useState)(null),[oe,se]=(0,_.useState)(!1),[ce,le]=(0,_.useState)([]),[ue,de]=(0,_.useState)([]),M=(0,_.useRef)(null),fe=(0,_.useRef)(null),pe=(0,_.useRef)(!0),me=(0,_.useRef)(null),he=(0,_.useRef)(null),ge=(0,_.useRef)(null),_e=(0,_.useRef)(null),[ve,ye]=(0,_.useState)(null),[be,xe]=(0,_.useState)(!1),[Se,N]=(0,_.useState)(!1),[Ce,we]=(0,_.useState)(0),[Te,Ee]=(0,_.useState)(0),[P,De]=(0,_.useState)(``),[Oe,ke]=(0,_.useState)({fi:0,total:0,name:``}),[F,I]=(0,_.useState)({tokIn:0,tokOut:0}),[Ae,je]=(0,_.useState)(null),[Me,Ne]=(0,_.useState)([]),[Pe,Fe]=(0,_.useState)(``),[Ie,Le]=(0,_.useState)(!1),[Re,ze]=(0,_.useState)([]),[L,Be]=(0,_.useState)(null),[Ve,He]=(0,_.useState)([]),[Ue,We]=(0,_.useState)(!1),[Ge,Ke]=(0,_.useState)(null),[qe,Je]=(0,_.useState)([]),[Ye,Xe]=(0,_.useState)(!1),[Ze,Qe]=(0,_.useState)(``),[$e,et]=(0,_.useState)([]),[tt,nt]=(0,_.useState)([]),[rt,it]=(0,_.useState)([]),[at,ot]=(0,_.useState)(null),[st,ct]=(0,_.useState)(!1),[lt,ut]=(0,_.useState)(!1),[dt,ft]=(0,_.useState)(null),[pt,mt]=(0,_.useState)([]),[ht,gt]=(0,_.useState)(`0s`),[_t,vt]=(0,_.useState)(`0s`),yt=(0,_.useRef)(0),R=(0,_.useRef)(0),bt=(0,_.useRef)(null),xt=(0,_.useRef)(null),St=(0,_.useRef)(null),Ct=(0,_.useRef)(null),wt=(0,_.useRef)(null),Tt=(0,_.useRef)(!1),Et=(0,_.useRef)(null);function z(e){console.log(`[WC-SCAN] scanning:`,e),D(`/api/studio/webcraft/scan`,{projectName:e}).then(e=>{console.log(`[WC-SCAN] result:`,e?.issues?.length,`issues`),e?.issues!==void 0&&de(e.issues)}).catch(e=>{console.error(`[WC-SCAN] error:`,e)})}(0,_.useEffect)(()=>{if(!at){le([]);return}let e=setInterval(()=>{E(`/api/studio/webcraft/sandbox/errors`).then(e=>{e?.errors?.length&&le(e.errors)}).catch(()=>{})},5e3);return()=>clearInterval(e)},[at]);let Dt=(0,_.useRef)(at);Dt.current=at,(0,_.useEffect)(()=>{let e=()=>{Dt.current&&(navigator.sendBeacon(`/api/studio/webcraft/sandbox/stop-beacon`,``),fetch(`/api/studio/webcraft/sandbox`,{method:`DELETE`,keepalive:!0}).catch(()=>{}))};return window.addEventListener(`beforeunload`,e),()=>{window.removeEventListener(`beforeunload`,e),e()}},[]),(0,_.useEffect)(()=>{let e=e=>{if((e.metaKey||e.ctrlKey)&&e.key===`f`&&(e.preventDefault(),ee(e=>!e)),(e.metaKey||e.ctrlKey)&&e.key===`s`&&(e.preventDefault(),b!==null&&p[h])){let e=p[h];m(e=>e.map((e,t)=>t===h?{...e,content:b}:e)),D(`/api/studio/webcraft/file/write`,{projectName:a,path:e.name,content:b}),x(null),C(t=>{let n=new Set(t);return n.delete(e.name),n})}};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[b,h,p,a]),(0,_.useEffect)(()=>{let e=()=>{let e=b===null?ge.current:he.current;e&&_e.current&&(_e.current.scrollTop=e.scrollTop)},t=b===null?ge.current:he.current;return t&&t.addEventListener(`scroll`,e),()=>{t&&t.removeEventListener(`scroll`,e)}},[b,h]);function Ot(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function kt(e){let t=Math.floor((Date.now()-e)/1e3),n=Math.floor(t/60);return(n>0?`${n}m `:``)+`${t%60}s`}(0,_.useEffect)(()=>(be||Se?bt.current=setInterval(()=>{be&&gt(kt(yt.current)),Se&&vt(kt(R.current))},1e3):bt.current&&=(clearInterval(bt.current),null),()=>{bt.current&&clearInterval(bt.current)}),[be,Se]);function At(){St.current&&(St.current.scrollTop=St.current.scrollHeight)}(0,_.useEffect)(()=>{At()},[Me]),(0,_.useEffect)(()=>{a&&!Ue&&(We(!0),E(`/api/studio/webcraft/skills/${encodeURIComponent(a)}`).then(e=>{e?.skills&&He(e.skills)}).catch(()=>{}))},[a,Ue]);async function jt(e,t){if(be||!e||e.length<5)return;xe(!0),m([]),g(0),y(null),ke({fi:0,total:0,name:``}),I({tokIn:0,tokOut:0}),yt.current=Date.now(),gt(`0s`),xt.current=new AbortController;let n=Date.now();try{let r=await fetch(`/api/studio/webcraft/generate`,{method:`POST`,headers:{"Content-Type":`application/json`},signal:xt.current.signal,body:JSON.stringify({projectName:t,description:e,blocks:l,authFields:d})});if(!r.ok||!r.body){xe(!1);return}let i=r.body.getReader(),s=xt.current,c=new TextDecoder,u=``,f=[];for(;;){if(s?.signal?.aborted){try{i.cancel()}catch{}break}let{done:e,value:t}=await i.read();if(e)break;u+=c.decode(t,{stream:!0});let r=u.split(`
791
+ - public/css/main.css: Modern job board design — tag/badge system (Remote=blue, Onsite=green, Urgent=red), salary range display, company logo placeholder styles, filter sidebar collapse on mobile, job card hover effects, application form validation styles`}],JT=[{key:`auth`,label:`Auth (register/login/JWT)`,icon:`🔒`},{key:`cookieBanner`,label:`GDPR Cookie Banner`,icon:`🍪`},{key:`securityMiddleware`,label:`Security Middleware`,icon:`🛡️`},{key:`emailVerification`,label:`Email Verification`,icon:`✉️`}],YT={js:`📄`,ts:`📄`,css:`🎨`,html:`🌐`,json:`📋`,md:`📑`,sql:`🗂`,env:`🔐`,conf:`⚙`,lock:`🔒`};function XT(e){return YT[e.split(`.`).pop()?.toLowerCase()??``]??`📄`}function ZT(e){let t=new TextEncoder().encode(e).length;return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(2)} MB`}function QT(e){return e===`memory`?`🧠`:e===`provider`?`🤖`:e===`log`?`📄`:`📋`}function $T(){let e=j(),[t,n]=(0,_.useState)(`new`),[r,i]=(0,_.useState)(`files`),[a,o]=(0,_.useState)(``),[s,c]=(0,_.useState)(``),[l,u]=(0,_.useState)({auth:!0,cookieBanner:!0,securityMiddleware:!0,emailVerification:!0}),[d,f]=(0,_.useState)([{label:`Email`,type:`email`,required:!0},{label:`Password`,type:`password`,required:!0},{label:`Name`,type:`text`,required:!0}]),[p,m]=(0,_.useState)([]),[h,g]=(0,_.useState)(0),[v,y]=(0,_.useState)(null),[b,x]=(0,_.useState)(null),[S,C]=(0,_.useState)(new Set),[w,ee]=(0,_.useState)(!1),[te,T]=(0,_.useState)(``),[ne,re]=(0,_.useState)(``),[ie,O]=(0,_.useState)(!1),[ae,A]=(0,_.useState)(null),[oe,se]=(0,_.useState)(!1),[ce,le]=(0,_.useState)([]),[ue,de]=(0,_.useState)([]),M=(0,_.useRef)(null),fe=(0,_.useRef)(null),pe=(0,_.useRef)(!0),me=(0,_.useRef)(null),he=(0,_.useRef)(null),ge=(0,_.useRef)(null),_e=(0,_.useRef)(null),[ve,ye]=(0,_.useState)(null),[be,xe]=(0,_.useState)(!1),[Se,N]=(0,_.useState)(!1),[Ce,we]=(0,_.useState)(0),[Te,Ee]=(0,_.useState)(0),[P,De]=(0,_.useState)(``),[Oe,ke]=(0,_.useState)({fi:0,total:0,name:``}),[F,I]=(0,_.useState)({tokIn:0,tokOut:0}),[Ae,je]=(0,_.useState)(null),[Me,Ne]=(0,_.useState)([]),[Pe,Fe]=(0,_.useState)(``),[Ie,Le]=(0,_.useState)(!1),[Re,ze]=(0,_.useState)([]),[L,Be]=(0,_.useState)(null),[Ve,He]=(0,_.useState)([]),[Ue,We]=(0,_.useState)(!1),[Ge,Ke]=(0,_.useState)(null),[qe,Je]=(0,_.useState)([]),[Ye,Xe]=(0,_.useState)(!1),[Ze,Qe]=(0,_.useState)(``),[$e,et]=(0,_.useState)([]),[tt,nt]=(0,_.useState)([]),[rt,it]=(0,_.useState)([]),[at,ot]=(0,_.useState)(null),[st,ct]=(0,_.useState)(!1),[lt,ut]=(0,_.useState)(!1),[dt,ft]=(0,_.useState)(null),[pt,mt]=(0,_.useState)([]),[ht,gt]=(0,_.useState)(`0s`),[_t,vt]=(0,_.useState)(`0s`),yt=(0,_.useRef)(0),R=(0,_.useRef)(0),bt=(0,_.useRef)(null),xt=(0,_.useRef)(null),St=(0,_.useRef)(null),Ct=(0,_.useRef)(null),wt=(0,_.useRef)(null),Tt=(0,_.useRef)(!1),Et=(0,_.useRef)(null);function z(e){console.log(`[WC-SCAN] scanning:`,e),D(`/api/studio/webcraft/scan`,{projectName:e}).then(e=>{console.log(`[WC-SCAN] result:`,e?.issues?.length,`issues`),e?.issues!==void 0&&de(e.issues)}).catch(e=>{console.error(`[WC-SCAN] error:`,e)})}(0,_.useEffect)(()=>{if(!at){le([]);return}let e=setInterval(()=>{E(`/api/studio/webcraft/sandbox/errors`).then(e=>{e?.errors?.length&&le(e.errors)}).catch(()=>{})},5e3);return()=>clearInterval(e)},[at]);let Dt=(0,_.useRef)(at);Dt.current=at,(0,_.useEffect)(()=>{let e=()=>{Dt.current&&(navigator.sendBeacon(`/api/studio/webcraft/sandbox/stop-beacon`,``),fetch(`/api/studio/webcraft/sandbox`,{method:`DELETE`,keepalive:!0}).catch(()=>{}))};return window.addEventListener(`beforeunload`,e),()=>{window.removeEventListener(`beforeunload`,e),e()}},[]),(0,_.useEffect)(()=>{let e=e=>{if((e.metaKey||e.ctrlKey)&&e.key===`f`&&(e.preventDefault(),ee(e=>!e)),(e.metaKey||e.ctrlKey)&&e.key===`s`&&(e.preventDefault(),b!==null&&p[h])){let e=p[h];m(e=>e.map((e,t)=>t===h?{...e,content:b}:e)),D(`/api/studio/webcraft/file/write`,{projectName:a,path:e.name,content:b}),x(null),C(t=>{let n=new Set(t);return n.delete(e.name),n})}};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[b,h,p,a]),(0,_.useEffect)(()=>{let e=()=>{let e=b===null?ge.current:he.current;e&&_e.current&&(_e.current.scrollTop=e.scrollTop)},t=b===null?ge.current:he.current;return t&&t.addEventListener(`scroll`,e),()=>{t&&t.removeEventListener(`scroll`,e)}},[b,h]);function Ot(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function kt(e){let t=Math.floor((Date.now()-e)/1e3),n=Math.floor(t/60);return(n>0?`${n}m `:``)+`${t%60}s`}(0,_.useEffect)(()=>(be||Se?bt.current=setInterval(()=>{be&&gt(kt(yt.current)),Se&&vt(kt(R.current))},1e3):bt.current&&=(clearInterval(bt.current),null),()=>{bt.current&&clearInterval(bt.current)}),[be,Se]);function At(){St.current&&(St.current.scrollTop=St.current.scrollHeight)}(0,_.useEffect)(()=>{At()},[Me]),(0,_.useEffect)(()=>{if(!a){He([]),We(!1);return}He([]),We(!1),E(`/api/studio/webcraft/skills/${encodeURIComponent(a)}`).then(e=>{e?.skills&&He(e.skills),We(!0)}).catch(()=>{We(!0)})},[a]);async function jt(e,t){if(be||!e||e.length<5)return;xe(!0),m([]),g(0),y(null),ke({fi:0,total:0,name:``}),I({tokIn:0,tokOut:0}),yt.current=Date.now(),gt(`0s`),xt.current=new AbortController;let n=Date.now();try{let r=await fetch(`/api/studio/webcraft/generate`,{method:`POST`,headers:{"Content-Type":`application/json`},signal:xt.current.signal,body:JSON.stringify({projectName:t,description:e,blocks:l,authFields:d})});if(!r.ok||!r.body){xe(!1);return}let i=r.body.getReader(),s=xt.current,c=new TextDecoder,u=``,f=[];for(;;){if(s?.signal?.aborted){try{i.cancel()}catch{}break}let{done:e,value:t}=await i.read();if(e)break;u+=c.decode(t,{stream:!0});let r=u.split(`
792
792
 
793
793
  `);u=r.pop()??``;for(let e of r){let t=e.replace(/^data: /,``).trim();if(t)try{let e=JSON.parse(t);if(e.type===`project_renamed`)o(e.name);else if(e.type===`processing`||e.type===`planning`)ke(t=>({...t,name:e.type===`planning`?`📋 Pianificazione struttura...`:e.msg||`Avvio...`}));else if(e.type===`file_start`)f.push({name:e.name,content:``,_pending:!0}),m([...f]),ke({fi:e.fi,total:e.total,name:e.name}),M.current=f.length-1,fe.current||(g(f.length-1),y(``)),pe.current=!0;else if(e.type===`file_chunk`){let t=f.find(t=>t.name===e.name);t&&(t.content+=e.chunk,t._pending=!1),m([...f]);let n=f.findIndex(t=>t.name===e.name);n>=0&&(M.current=n,y(t?t.content:null))}else if(e.type===`file_done`){let t=f.find(t=>t.name===e.name);t&&(t._pending=!1,e.syntaxError&&(t._syntaxError=e.syntaxError)),m([...f]),ke({fi:e.fi,total:e.total,name:e.name}),(e.cumTokIn||e.cumTokOut)&&I({tokIn:e.cumTokIn||0,tokOut:e.cumTokOut||0})}else if(e.type===`file_error`){let t=f.find(t=>t.name===e.name);t&&(t._error=!0,t._pending=!1),m([...f])}else e.type===`phase`||e.type===`status`&&!e.op?ke(t=>({...t,name:e.msg||e.phase||``})):e.type===`done`&&(je({seconds:Math.round((Date.now()-n)/1e3),tokIn:e.tokIn??0,tokOut:e.tokOut??0,files:f.length}),y(null),xe(!1),M.current=null,fe.current&&=(clearTimeout(fe.current),null),a&&z(a),se(!0),We(!1),He([]),f.some(e=>e._error||e._syntaxError)?setTimeout(()=>wt.current?.(),800):setTimeout(()=>Et.current?.(),800))}catch{}}}}catch(e){e.name!==`AbortError`&&Ne(t=>[...t,{role:`system`,text:`Errore generazione: `+e.message}]),xe(!1)}}async function B(){let e=Pe.trim();if(!(a&&p.length>0)){if(!e||e.length<5)return;let t=a||`MyProject`;o(t),c(e),Fe(``),await jt(e,t);return}if(!e&&Re.length===0||Ie||be)return;let t=[...Re];if(ze([]),Fe(``),e.toLowerCase().startsWith(`/plan `)||e.toLowerCase().startsWith(`piano: `)){let t=e.replace(/^\/plan[ ]*/i,``).replace(/^piano:[ ]*/i,``);Ne(t=>[...t,{role:`user`,text:e}]),await Mt(`[MODALITA PIANO] Descrivi cosa modificheresti per: "${t}". Elenca i file e cosa faresti. NON applicare modifiche ancora. Rispondi con il piano in bullet list.`,t,[]);return}Ne(n=>[...n,{role:`user`,text:e,attachments:t}]),await Mt(e,null,t)}async function Mt(e,t,n){if(Ie)return;Le(!0),a&&p.length>0&&D(`/api/studio/webcraft/snapshot`,{projectName:a}).catch(()=>{});let r={};p.forEach(e=>{r[e.name]=e.content});try{let i=new AbortController;xt.current=i;let o=await fetch(`/api/studio/webcraft/agent`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({projectName:a,message:e,attachments:n.map(e=>({name:e.name,mimeType:e.mimeType,base64:e.base64}))}),signal:i.signal});if(!o.ok||!o.body){Ne(e=>[...e,{role:`agent`,text:`Errore: ${o.status}`,tools:[]}]),Le(!1);return}let s={role:`agent`,text:``,tools:[]};Ne(e=>[...e,s]);let c=o.body.getReader(),l=new TextDecoder,u=``,d=!1,f=``;for(;;){if(i.signal.aborted){try{c.cancel()}catch{}break}let{done:e,value:n}=await c.read();if(e)break;u+=l.decode(n,{stream:!0});let o=u.split(`
794
794
 
@@ -8,7 +8,7 @@
8
8
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
9
9
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
10
10
  <title>NHA — NotHumanAllowed</title>
11
- <script type="module" crossorigin src="/assets/index-CeGnHfLi.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-CJMAWGRD.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-DnJMrYkq.css">
13
13
  </head>
14
14
  <body>