spectoflow 0.31.1 → 0.33.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.
@@ -73,7 +73,7 @@ window.fetch=(url,opts)=>{
73
73
  return _fetch(url,opts);
74
74
  };
75
75
  // Served by the online relay? Its project.read always carries `online`; the local hub's never does.
76
- // Online, the Second brain tab is hidden: the brain is the machine owner's, never a project's.
76
+ // Online, the Second brain tab shows only the project memory: the personal brain is the machine owner's.
77
77
  let REMOTE=false;
78
78
  function setOffline(p){
79
79
  REMOTE=typeof p.online==='boolean';
@@ -161,14 +161,14 @@ function applyNavTabs() {
161
161
  list.forEach((entry) => {
162
162
  const btn = nav.querySelector('.tab[data-tab="' + entry.id + '"]');
163
163
  if (!btn) return;
164
- btn.hidden = !entry.enabled || (entry.id === 'brain' && REMOTE);
164
+ btn.hidden = !entry.enabled;
165
165
  nav.insertBefore(btn, anchor);
166
166
  });
167
167
  // If the currently active NATIVE tab just became disabled (e.g. the user disabled the tab they're
168
168
  // viewing, or another viewer did and this tab just reloaded), navigate to a sensible fallback
169
169
  // instead of leaving a hidden/disabled panel showing.
170
170
  const activeEntry = list.find((e) => e.id === activeTab);
171
- if (activeEntry && (!activeEntry.enabled || (activeEntry.id === 'brain' && REMOTE))) {
171
+ if (activeEntry && !activeEntry.enabled) {
172
172
  const fallback = list.find((e) => e.id === 'board' && e.enabled) || list.find((e) => e.enabled);
173
173
  if (fallback) navigateTab(fallback.id);
174
174
  }
@@ -299,9 +299,10 @@ const runtimeTests=(id)=> (P.runtime&&P.runtime.tests&&P.runtime.tests[id])||nul
299
299
  async function load(){
300
300
  const r = await fetch(withProject('/api/project')); P = await r.json();
301
301
  syncSettingsFromServer();
302
- REMOTE=typeof P.online==='boolean'; // known before the first paint: the brain tab is hidden online
303
- render(); setOffline(P);
304
- if(!REMOTE && !brainData) loadBrain();
302
+ REMOTE=typeof P.online==='boolean'; // known before the first paint: the personal brain is hidden online
303
+ render(); setOffline(P); renderUpdateBar(); notifyOrchestration();
304
+ if(!REMOTE && !MEMORIES.user.data) loadBrain();
305
+ loadMemory('project'); // .spectoflow/memory.md may be what just changed
305
306
  if(openTaskId) openDrawer(openTaskId,true);
306
307
  }
307
308
  // Coalesce bursts of SSE 'change'/'message' events into one reload so the board doesn't
@@ -315,7 +316,7 @@ function connect(){
315
316
  let m; try{ m=JSON.parse(ev.data); }catch{ return; }
316
317
  if(m.type==='change'||m.type==='message') return scheduleLoad(); // messages live from runtime.messages
317
318
  if(m.type==='brain') return loadBrain(); // ~/.spectoflow/brain.md changed (page, MCP, run line)
318
- if(m.type==='run-start'||m.type==='run-end') { chatState.forEach(st=>{ st.rawBlock=null; }); sseBusy=(m.type==='run-start'); updateChatBusyUI(); return; }
319
+ if(m.type==='run-start'||m.type==='run-end') { chatState.forEach(st=>{ st.rawBlock=null; }); sseBusy=(m.type==='run-start'); updateChatBusyUI(); if(m.type==='run-end') notify(t(m.code===0?'notify.done':'notify.failed',{project:P&&P.projectName||'spectoflow'}), ''); return; }
319
320
  if(m.type==='run-line') return appendRaw(m.chunk); // raw output is ephemeral (not logged)
320
321
  };
321
322
  es.onerror = ()=>{ $('#sync').classList.add('offline'); $('#syncLabel').textContent='offline'; };
@@ -386,7 +387,26 @@ function showChatError(message){
386
387
  scrollChat(container);
387
388
  });
388
389
  }
390
+ // Native browser notifications when an agent finishes or a step waits for approval — only while this tab
391
+ // isn't in front (D77). Permission is asked on the user's own click that starts agent work, never unprompted.
392
+ function askNotifyPermission(){ try{ if('Notification' in window && Notification.permission==='default') Notification.requestPermission(); }catch(_){} }
393
+ function notify(title,body){
394
+ try{
395
+ if(!document.hidden || !('Notification' in window) || Notification.permission!=='granted') return;
396
+ const n=new Notification(title,{body,tag:'spectoflow-'+(P&&P.projectName||'')});
397
+ n.onclick=()=>{ window.focus(); n.close(); };
398
+ }catch(_){}
399
+ }
400
+ let notifiedApproval='';
401
+ function notifyOrchestration(){
402
+ const o=P&&P.runtime&&P.runtime.orchestration; if(!o) return;
403
+ const step=o.steps&&o.steps[o.currentStep];
404
+ const key=o.status==='awaiting_approval'?`${o.id}:${o.currentStep}`:'';
405
+ if(key && key!==notifiedApproval){ notify(t('notify.approval',{project:P.projectName||'spectoflow'}), step?step.name:''); }
406
+ notifiedApproval=key;
407
+ }
389
408
  async function postRunRequest(url,body){
409
+ askNotifyPermission();
390
410
  const r=await fetch(withProject(url),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
391
411
  if(!r.ok){ const d=await r.json().catch(()=>({})); showChatError(d.error||t('files.saveError')); if(/needs your OK/.test(d.error||'')) scheduleLoad(); }
392
412
  return r.ok;
@@ -1090,6 +1110,27 @@ function renderUntrustedRunners(){
1090
1110
  row.append(b); box.append(row);
1091
1111
  });
1092
1112
  }
1113
+ // The project's framework files are older than the installed spectoflow (D77). Local only.
1114
+ const semverLess=(a,b)=>{ const x=String(a||'').split('.').map(Number), y=String(b||'').split('.').map(Number); for(let i=0;i<3;i++){ if((x[i]||0)!==(y[i]||0)) return (x[i]||0)<(y[i]||0); } return false; };
1115
+ let updateNote='';
1116
+ function renderUpdateBar(){
1117
+ const bar=$('#updateBar'); if(!bar) return;
1118
+ const stale=!REMOTE && P && P.version && P.kitVersion && semverLess(P.version,P.kitVersion);
1119
+ bar.hidden=!stale && !updateNote;
1120
+ $('#updateText').textContent=updateNote || (stale ? t('update.banner',{from:'v'+P.version,to:'v'+P.kitVersion}) : '');
1121
+ $('#updateBtn').hidden=!stale;
1122
+ }
1123
+ async function updateProject(){
1124
+ const b=$('#updateBtn'); b.disabled=true; flash();
1125
+ try{
1126
+ const r=await fetch(withProject('/api/project/update'),{method:'POST'});
1127
+ const d=await r.json().catch(()=>({}));
1128
+ if(!r.ok) throw new Error(d.error||'error');
1129
+ updateNote=t('update.done',{n:d.refreshed})+(d.review&&d.review.length?' '+t('update.review',{n:d.review.length}):'');
1130
+ }catch(e){ updateNote=t('update.error'); }
1131
+ b.disabled=false; scheduleLoad();
1132
+ setTimeout(()=>{ updateNote=''; renderUpdateBar(); },8000);
1133
+ }
1093
1134
  function wfDetailRow(k,v){ const r=el('div','wf-detail-row'); r.append(el('span','wf-detail-k',k), el('span','wf-detail-v',v)); return r; }
1094
1135
  function wfPopFill(pop, s, idx){
1095
1136
  const skill=(P.skills||[]).find(x=>x.name===s.skill);
@@ -1541,7 +1582,7 @@ function navigateTab(tabId,push){
1541
1582
  // was scrolled to last (only on an actual switch INTO the tab — applyActiveTab() alone runs on
1542
1583
  // every SSE render tick too, and re-scrolling/re-focusing there would fight the user's typing).
1543
1584
  if(tabId==='chat') setTimeout(()=>{ scrollChat($('#chatTabLog')); $('#tabRunPrompt').focus(); },60);
1544
- if(tabId==='brain'){ renderBrain(); if(!brainData) loadBrain(); }
1585
+ if(tabId==='brain'){ renderBrain(); if(!MEMORIES.user.data) loadBrain(); if(!MEMORIES.project.data) loadMemory('project'); }
1545
1586
  if(tabId==='files') renderFiles(); // the tree is fetched lazily — only load it on an actual switch in
1546
1587
  }
1547
1588
  function closeNav(){ document.body.classList.remove('nav-open'); const nt=$('#navToggle'); if(nt) nt.setAttribute('aria-expanded','false'); }
@@ -2119,60 +2160,79 @@ function notesSetStatus(state){
2119
2160
  }
2120
2161
 
2121
2162
  // ---- Second brain ------------------------------------------------------------------------------
2122
- // What spectoflow has learned about the user: ~/.spectoflow/brain.md, shared by all their projects —
2123
- // NOT this project's, so it is fetched on its own (/api/brain), never part of /api/project. The hub
2124
- // pushes a 'brain' SSE event whenever the file changes, whoever wrote it (this page, an agent through
2125
- // `spectoflow mcp`, or a `::spectoflow learn` run line).
2126
- const BRAIN_CATEGORIES=['profile','preferences','workflow','avoid'];
2163
+ // Two memories on one page, one renderer. "user": what spectoflow has learned about the user —
2164
+ // ~/.spectoflow/brain.md, shared by all their projects, NOT this project's, so it is fetched on its own
2165
+ // (/api/brain) and hidden online; the hub pushes a 'brain' SSE event whenever that file changes, whoever
2166
+ // wrote it (this page, an agent through `spectoflow mcp`, a `::spectoflow learn` run line). "project":
2167
+ // facts about this project — .spectoflow/memory.md, committed; a write to it (page, agent, git) is an
2168
+ // ordinary 'change' event, and load() refetches it.
2127
2169
  const BRAIN_SOFT_LIMIT=60;
2128
- let brainData=null, brainError=null, brainActionError=null;
2129
- async function loadBrain(){
2130
- if(REMOTE) return;
2170
+ const MEMORIES={
2171
+ user:{ api:'/api/brain', cats:['profile','preferences','workflow','avoid'], other:'project', empty:'brain.empty', dup:'brain.duplicate',
2172
+ path:(d)=>t('brain.path',{path:d.path||'~/.spectoflow/brain.md'}),
2173
+ setAuto:(v)=>memCall('POST','/api/brain/settings',{autoAdd:v}) },
2174
+ project:{ api:'/api/memory', cats:['conventions','pitfalls','glossary','constraints'], other:'user', empty:'brain.projectEmpty', dup:'brain.projectDuplicate',
2175
+ path:(d)=>t('brain.projectPath',{path:d.path||'.spectoflow/memory.md'}),
2176
+ setAuto:(v)=>{ if(P&&P.config) P.config.memoryAutoAdd=v; return memCall('POST','/api/settings',{memoryAutoAdd:v}); } },
2177
+ };
2178
+ Object.values(MEMORIES).forEach(m=>{ m.data=null; m.error=null; m.actionError=null; });
2179
+ const memSection=(key)=>document.querySelector(`[data-memory="${key}"]`);
2180
+ const memUrl=(key,id,suffix)=>`${MEMORIES[key].api}/${encodeURIComponent(id)}${suffix||''}`;
2181
+ async function loadMemory(key){
2182
+ const m=MEMORIES[key];
2183
+ if(key==='user' && REMOTE) return;
2131
2184
  try{
2132
- const r=await fetch(withProject('/api/brain'));
2185
+ const r=await fetch(withProject(m.api));
2133
2186
  const d=await r.json().catch(()=>({}));
2134
2187
  if(!r.ok) throw new Error(d.error||'error');
2135
- brainData=d; brainError=null;
2136
- }catch(err){ brainError=err.message||'error'; }
2188
+ m.data=d; m.error=null;
2189
+ }catch(err){ m.error=err.message||'error'; }
2137
2190
  renderBrain();
2138
2191
  }
2139
- async function brainCall(method,url,body){
2192
+ function loadBrain(){ return loadMemory('user'); }
2193
+ async function memCall(method,url,body){
2140
2194
  flash();
2141
2195
  const r=await fetch(withProject(url),{method,headers:{'Content-Type':'application/json'},body:body===undefined?undefined:JSON.stringify(body)});
2142
2196
  const d=await r.json().catch(()=>({}));
2143
2197
  if(!r.ok) throw new Error(d.error||t('brain.error'));
2144
2198
  return d;
2145
2199
  }
2146
- // A re-render (SSE tick, another tab's write) must never wipe what the user is typing.
2200
+ // A re-render (SSE tick, another tab's write) must never wipe what the user is typing or picking.
2147
2201
  function brainIsEditing(){
2148
2202
  const a=document.activeElement;
2149
- return !!(a && (a.tagName==='INPUT'||a.tagName==='TEXTAREA') && a.closest('#brainGrid, #brainPending'));
2203
+ return !!(a && ['INPUT','TEXTAREA','SELECT'].includes(a.tagName) && a.closest('.mem-grid, .mem-pending'));
2150
2204
  }
2151
2205
  function renderBrain(){
2152
2206
  const badge=$('#brainBadge');
2153
- const pendingN=brainData ? brainData.pending.length : 0;
2207
+ const pendingN=Object.entries(MEMORIES).reduce((n,[k,m])=> n+((m.data && !(k==='user'&&REMOTE)) ? m.data.pending.length : 0),0);
2154
2208
  if(badge){ badge.textContent=pendingN; badge.hidden=pendingN===0; }
2155
2209
  if(activeTab!=='brain' || brainIsEditing()) return;
2156
- const grid=$('#brainGrid'); if(!grid) return;
2157
- if(brainError && !brainData){ grid.innerHTML=''; grid.append(el('div','empty',t('brain.error'))); return; }
2158
- if(!brainData){ grid.innerHTML=''; grid.append(el('div','empty',t('drawer.loading'))); return; }
2159
- const d=brainData;
2160
- $('#brainCount').textContent=d.entries.length;
2161
- const auto=$('#brainAutoAdd'); auto.checked=!!d.autoAdd;
2162
- $('#brainAutoHint').textContent=d.autoAdd ? t('brain.autoAddOn') : t('brain.autoAddOff');
2163
- renderBrainAgents(d.agents||[]);
2164
- const notice=$('#brainNotice');
2165
- const noticeText=brainActionError || (d.entries.length>BRAIN_SOFT_LIMIT ? t('brain.tooMany',{n:d.entries.length}) : '');
2210
+ Object.keys(MEMORIES).forEach(renderMemory);
2211
+ }
2212
+ function renderMemory(key){
2213
+ const m=MEMORIES[key], sec=memSection(key); if(!sec) return;
2214
+ sec.hidden = key==='user' && REMOTE;
2215
+ if(sec.hidden) return;
2216
+ const grid=sec.querySelector('.mem-grid');
2217
+ if(m.error && !m.data){ grid.innerHTML=''; grid.append(el('div','empty',t('brain.error'))); return; }
2218
+ if(!m.data){ grid.innerHTML=''; grid.append(el('div','empty',t('drawer.loading'))); return; }
2219
+ const d=m.data;
2220
+ sec.querySelector('.mem-count').textContent=d.entries.length;
2221
+ sec.querySelector('.mem-auto').checked=!!d.autoAdd;
2222
+ sec.querySelector('.mem-auto-hint').textContent=d.autoAdd ? t('brain.autoAddOn') : t('brain.autoAddOff');
2223
+ const agents=sec.querySelector('.mem-agents'); if(agents) renderBrainAgents(agents, d.agents||[]);
2224
+ const notice=sec.querySelector('.mem-notice');
2225
+ const noticeText=m.actionError || (d.entries.length>BRAIN_SOFT_LIMIT ? t('brain.tooMany',{n:d.entries.length}) : '');
2166
2226
  notice.hidden=!noticeText; notice.textContent=noticeText;
2167
- notice.classList.toggle('is-error',!!brainActionError);
2168
- renderBrainPending(d.pending);
2227
+ notice.classList.toggle('is-error',!!m.actionError);
2228
+ renderMemoryPending(key, sec.querySelector('.mem-pending'), d.pending);
2169
2229
  grid.innerHTML='';
2170
- if(!d.entries.length && !d.pending.length) grid.append(el('div','brain-empty',t('brain.empty')));
2171
- BRAIN_CATEGORIES.forEach(cat=> grid.append(brainCard(cat, d.entries.filter(e=>e.category===cat))));
2172
- $('#brainPath').textContent=t('brain.path',{path:d.path||'~/.spectoflow/brain.md'});
2230
+ if(!d.entries.length && !d.pending.length) grid.append(el('div','brain-empty',t(m.empty)));
2231
+ m.cats.forEach(cat=> grid.append(memoryCard(key, cat, d.entries.filter(e=>e.category===cat))));
2232
+ sec.querySelector('.mem-path').textContent=m.path(d);
2173
2233
  }
2174
- function renderBrainAgents(agents){
2175
- const box=$('#brainAgents'); box.innerHTML='';
2234
+ function renderBrainAgents(box, agents){
2235
+ box.innerHTML='';
2176
2236
  if(!agents.length){ box.append(el('span','brain-agents-none',t('brain.noAgents'))); return; }
2177
2237
  box.append(el('span','brain-agents-label',t('brain.agents')));
2178
2238
  agents.forEach(a=>{
@@ -2190,13 +2250,26 @@ function brainMeta(e){
2190
2250
  const who=e.by==='agent' ? t('brain.byAgent') : t('brain.byYou');
2191
2251
  return e.at ? `${who} · ${e.at}` : who;
2192
2252
  }
2193
- function renderBrainPending(pending){
2194
- const box=$('#brainPending'); box.innerHTML='';
2253
+ // "Move to…": the agent picked the wrong memory. Lists the other memory's categories; local only (one of the
2254
+ // two files is the user's personal brain), so never offered online.
2255
+ function memoryMoveSelect(key, e){
2256
+ if(REMOTE) return null;
2257
+ const target=MEMORIES[key].other;
2258
+ const sel=el('select','brain-move'); sel.title=t('brain.moveHint');
2259
+ const ph=el('option',null,t('brain.moveTo')); ph.value=''; ph.selected=true; ph.disabled=true; sel.append(ph);
2260
+ const group=document.createElement('optgroup'); group.label=t(target==='user'?'brain.you':'brain.project');
2261
+ MEMORIES[target].cats.forEach(c=>{ const o=el('option',null,t('brain.cat.'+c)); o.value=c; group.append(o); });
2262
+ sel.append(group);
2263
+ sel.addEventListener('change',()=>{ const category=sel.value; sel.blur(); memAct(key,()=>memCall('POST','/api/memory/move',{from:key,id:e.id,category}),true); });
2264
+ return sel;
2265
+ }
2266
+ function renderMemoryPending(key, box, pending){
2267
+ box.innerHTML='';
2195
2268
  box.hidden=!pending.length; if(!pending.length) return;
2196
2269
  const head=el('div','brain-pending-head');
2197
2270
  head.append(el('h3','brain-pending-title',t('brain.toConfirm')));
2198
2271
  const all=el('button','btn primary',t('brain.confirmAll'));
2199
- all.addEventListener('click',async()=>{ all.disabled=true; try{ for(const e of pending) await brainCall('POST',`/api/brain/${encodeURIComponent(e.id)}/confirm`,{}); }catch(err){} loadBrain(); });
2272
+ all.addEventListener('click',async()=>{ all.disabled=true; try{ for(const e of pending) await memCall('POST',memUrl(key,e.id,'/confirm'),{}); }catch(err){} loadMemory(key); });
2200
2273
  head.append(all); box.append(head);
2201
2274
  pending.forEach(e=>{
2202
2275
  const row=el('div','brain-row is-pending');
@@ -2204,14 +2277,17 @@ function renderBrainPending(pending){
2204
2277
  const txt=el('div','brain-text',e.text); row.append(txt);
2205
2278
  row.append(el('div','brain-meta',brainMeta(e)));
2206
2279
  const acts=el('div','brain-actions');
2207
- const ok=el('button','btn primary',t('brain.confirm')); ok.addEventListener('click',()=>brainAct(()=>brainCall('POST',`/api/brain/${encodeURIComponent(e.id)}/confirm`,{})));
2208
- const ed=el('button','btn',t('action.edit')); ed.addEventListener('click',()=>brainEdit(e,txt));
2209
- const no=el('button','btn danger',t('brain.reject')); no.addEventListener('click',()=>brainAct(()=>brainCall('DELETE',`/api/brain/${encodeURIComponent(e.id)}`)));
2210
- acts.append(ok,ed,no); row.append(acts);
2280
+ const ok=el('button','btn primary',t('brain.confirm')); ok.addEventListener('click',()=>memAct(key,()=>memCall('POST',memUrl(key,e.id,'/confirm'),{})));
2281
+ const ed=el('button','btn',t('action.edit')); ed.addEventListener('click',()=>memEdit(key,e,txt));
2282
+ const no=el('button','btn danger',t('brain.reject')); no.addEventListener('click',()=>memAct(key,()=>memCall('DELETE',memUrl(key,e.id))));
2283
+ acts.append(ok,ed,no);
2284
+ const mv=memoryMoveSelect(key,e); if(mv) acts.append(mv);
2285
+ row.append(acts);
2211
2286
  box.append(row);
2212
2287
  });
2213
2288
  }
2214
- function brainCard(cat, entries){
2289
+ function memoryCard(key, cat, entries){
2290
+ const m=MEMORIES[key];
2215
2291
  const card=el('div','brain-card');
2216
2292
  const head=el('div','brain-card-head');
2217
2293
  head.append(el('h3','brain-card-title',t('brain.cat.'+cat)), el('span','count',String(entries.length)));
@@ -2224,9 +2300,11 @@ function brainCard(cat, entries){
2224
2300
  const foot=el('div','brain-row-foot');
2225
2301
  foot.append(el('span','brain-meta',brainMeta(e)));
2226
2302
  const acts=el('span','brain-actions');
2227
- const ed=el('button','btn btn-xs',t('action.edit')); ed.addEventListener('click',()=>brainEdit(e,txt));
2228
- const del=el('button','btn btn-xs danger',t('action.delete')); del.addEventListener('click',()=>brainAct(()=>brainCall('DELETE',`/api/brain/${encodeURIComponent(e.id)}`)));
2229
- acts.append(ed,del); foot.append(acts); row.append(foot);
2303
+ const ed=el('button','btn btn-xs',t('action.edit')); ed.addEventListener('click',()=>memEdit(key,e,txt));
2304
+ const del=el('button','btn btn-xs danger',t('action.delete')); del.addEventListener('click',()=>memAct(key,()=>memCall('DELETE',memUrl(key,e.id))));
2305
+ acts.append(ed,del);
2306
+ const mv=memoryMoveSelect(key,e); if(mv) acts.append(mv);
2307
+ foot.append(acts); row.append(foot);
2230
2308
  list.append(row);
2231
2309
  });
2232
2310
  card.append(list);
@@ -2240,28 +2318,28 @@ function brainCard(cat, entries){
2240
2318
  const text=input.value.trim(); if(!text){ input.focus(); return; }
2241
2319
  btn.disabled=true; err.hidden=true;
2242
2320
  try{
2243
- const r=await brainCall('POST','/api/brain',{category:cat,text});
2321
+ const r=await memCall('POST',m.api,{category:cat,text});
2244
2322
  input.value=''; input.blur();
2245
- if(r.duplicate){ err.textContent=t('brain.duplicate'); err.hidden=false; }
2323
+ if(r.duplicate){ err.textContent=t(m.dup); err.hidden=false; }
2246
2324
  }catch(e){ err.textContent=e.message; err.hidden=false; }
2247
- btn.disabled=false; loadBrain();
2325
+ btn.disabled=false; loadMemory(key);
2248
2326
  });
2249
2327
  card.append(add);
2250
2328
  return card;
2251
2329
  }
2252
- async function brainAct(fn){
2253
- try{ await fn(); brainActionError=null; }catch(err){ brainActionError=err.message; }
2254
- loadBrain();
2330
+ async function memAct(key, fn, both){
2331
+ try{ await fn(); MEMORIES[key].actionError=null; }catch(err){ MEMORIES[key].actionError=err.message; }
2332
+ loadMemory(key); if(both) loadMemory(MEMORIES[key].other);
2255
2333
  }
2256
2334
  // Inline edit, same interaction as the Attention tab: blur or Ctrl/Cmd+Enter saves, Escape cancels.
2257
- function brainEdit(e, txtNode){
2335
+ function memEdit(key, e, txtNode){
2258
2336
  const ta=el('textarea','brain-edit'); ta.value=e.text; ta.maxLength=500; txtNode.replaceWith(ta); ta.focus();
2259
2337
  let done=false;
2260
2338
  const finish=async(save)=>{
2261
2339
  if(done) return; done=true;
2262
2340
  const v=ta.value.trim();
2263
2341
  ta.blur();
2264
- if(save && v && v!==e.text) await brainAct(()=>brainCall('PATCH',`/api/brain/${encodeURIComponent(e.id)}`,{text:v}));
2342
+ if(save && v && v!==e.text) await memAct(key,()=>memCall('PATCH',memUrl(key,e.id),{text:v}));
2265
2343
  else renderBrain();
2266
2344
  };
2267
2345
  ta.addEventListener('blur',()=>finish(true));
@@ -2641,9 +2719,11 @@ $('#blAddCancel').addEventListener('click', closeBacklogAddForm);
2641
2719
  $('#blAddSubmit').addEventListener('click', submitBacklogAdd);
2642
2720
  $('#blAddTitle').addEventListener('keydown', e=>{ if(e.key==='Enter') submitBacklogAdd(); });
2643
2721
  // attention tab: add a note + filter chips
2722
+ $$('.chat-stop').forEach(b=>b.addEventListener('click',async()=>{ b.disabled=true; flash(); try{ await fetch(withProject('/api/run/stop'),{method:'POST'}); }finally{ setTimeout(()=>{ b.disabled=false; },800); } }));
2644
2723
  $('#wfAnalyzeBtn').addEventListener('click',analyzeWorkflow);
2724
+ $('#updateBtn').addEventListener('click',updateProject);
2645
2725
  $('#setWorkflowAuto').addEventListener('change',(e)=>{ if(P&&P.config) P.config.workflowAutoEnable=e.target.checked; saveSetting({workflowAutoEnable:e.target.checked}); });
2646
- $('#brainAutoAdd').addEventListener('change',(e)=>brainAct(()=>brainCall('POST','/api/brain/settings',{autoAdd:e.target.checked})));
2726
+ $$('[data-memory]').forEach(sec=>{ const key=sec.dataset.memory; sec.querySelector('.mem-auto').addEventListener('change',(e)=>memAct(key,()=>MEMORIES[key].setAuto(e.target.checked))); });
2647
2727
  $('#attnAddBtn').addEventListener('click',()=>{ const t=$('#attnInput'); const v=t.value.trim(); if(v){ addAttn(v); t.value=''; } });
2648
2728
  $('#attnInput').addEventListener('keydown',e=>{ if((e.metaKey||e.ctrlKey)&&e.key==='Enter'){ const v=e.target.value.trim(); if(v){ addAttn(v); e.target.value=''; } } });
2649
2729
  $$('.attn-filters .fchip').forEach(b=> b.addEventListener('click',()=>{ attnFilter=b.dataset.attn; renderAttention(); }));
@@ -90,9 +90,12 @@ en: {
90
90
  'drawer.agent':'Agent','drawer.skill':'Skill','drawer.file':'File · {rel}',
91
91
  'drawer.loading':'Loading…','drawer.loadError':'Could not load this file.','files.title':'Files','files.sub':'Browse the project\'s files — view Markdown & HTML, edit any text file, create new ones.','files.newFile':'+ File','files.newFolder':'+ Folder','files.pickFile':'Select a file to view it.','files.empty':'No files yet.','files.edit':'Edit','files.preview':'Preview','files.save':'Save','files.saved':'✓ saved','files.saveError':'Could not save this file.','files.loadError':'Could not load this file.','files.binary':'This file can\'t be previewed here (not text).','files.discardConfirm':'Discard unsaved changes?','files.newFilePrompt':'New file name (e.g. todo.md):','files.newFolderPrompt':'New folder name:','files.projectRoot':'project root','files.creatingIn':'Creating in: {path}','files.refresh':'Refresh','files.discard':'Discard','files.create':'Create',
92
92
  'notes.sub':'A freeform scratchpad for this project — Markdown, autosaved as you type. Only you (and whoever else opens this dashboard) can see it.','notes.saving':'Saving…',
93
+ 'notify.done':'{project}: the agent finished','notify.failed':'{project}: the agent stopped with an error','notify.approval':'{project}: a step is waiting for your approval',
94
+ 'update.banner':'This project uses spectoflow {from}; {to} is installed.','update.button':'Update the project','update.done':'Project updated: {n} framework file(s) refreshed.','update.review':'{n} file(s) you had edited: the new version is saved next to each as .new.','update.error':'Could not update the project.',
95
+ 'chat.stop':'Stop',
93
96
  'runners.title':'A custom command needs your OK','runners.hint':'This project’s config.json launches an agent with a command other than its default. It only runs once you allow it on this machine.','runners.allow':'Allow on this machine',
94
97
  'workflow.analyze':'Analyze the project','workflow.analyzeTitle':'Look at the project and suggest which steps fit it now','workflow.detected':'Detected: {type} · {phase}','workflow.type.app':'application','workflow.type.infra':'infrastructure','workflow.type.data':'data','workflow.phase.design':'design phase, no code yet','workflow.phase.build':'has code','workflow.matches':'Your workflow already matches the project.','workflow.apply':'Apply','workflow.dismiss':'Close','workflow.applied':'{n} step(s) updated.','workflow.analyzeError':'Could not analyze the project.','workflow.on':'on','workflow.off':'off','workflow.reason.always':'always useful','workflow.reason.design-no-code':'no code yet','workflow.reason.has-code':'the project has code','workflow.reason.has-tests':'the project has tests','workflow.reason.infra-no-tests':'infrastructure project with no tests','workflow.reason.data-quality':'data project — data quality tests','workflow.reason.has-integration-tests':'integration tests exist','workflow.reason.no-integration-tests':'no integration tests yet','workflow.reason.has-e2e-setup':'an end-to-end test setup exists','workflow.reason.no-e2e-setup':'no end-to-end test setup','settings.workflowAuto':'Let the agent enable workflow steps when needed','settings.workflowAutoHint':'Off: it asks you first. It never disables a step on its own.',
95
- 'brain.sub':'What spectoflow has learned about you, shared by all your projects and given to your agent in every session. Add or fix anything.','brain.autoAdd':'Add what the agent learns directly','brain.autoAddOn':'New facts are added right away — you can fix or delete them here.','brain.autoAddOff':'New facts wait in “To confirm” until you accept them.','brain.agents':'Reachable by:','brain.agentWired':'Connected: this agent reads and grows your second brain','brain.agentNotWired':'Not connected yet','brain.setupHint':'Run {cmd} to connect the others.','brain.noAgents':'No coding agent found on this machine.','brain.tooMany':'{n} entries — all of it is given to your agent in every session. Consider removing what is no longer true.','brain.empty':'Nothing yet. As you work, your agent notes durable things about you here — your role, your preferences, how you like to work, what to avoid. You can also add them yourself below.','brain.toConfirm':'To confirm','brain.confirm':'Confirm','brain.confirmAll':'Confirm all','brain.reject':'Reject','brain.cat.profile':'Profile','brain.cat.preferences':'Preferences','brain.cat.workflow':'Working style','brain.cat.avoid':'Avoid','brain.catHint.profile':'Who you are: role, skills, context.','brain.catHint.preferences':'Tools, languages, code style, formats you prefer.','brain.catHint.workflow':'How you like the agent to work with you.','brain.catHint.avoid':'What the agent should never do.','brain.catEmpty':'Nothing here yet.','brain.addPlaceholder':'Add a fact…','brain.duplicate':'Already in your second brain.','brain.byAgent':'learned by the agent','brain.byYou':'added by you','brain.path':'Stored in {path} — never inside a project.','brain.error':'Could not reach your second brain.',
98
+ 'brain.sub':'What your agent knows: about you, in every project — and about this project, for everyone who works on it. Add or fix anything.','brain.autoAdd':'Add what the agent learns directly','brain.autoAddOn':'New facts are added right away — you can fix or delete them here.','brain.autoAddOff':'New facts wait in “To confirm” until you accept them.','brain.agents':'Reachable by:','brain.agentWired':'Connected: this agent reads and grows your second brain','brain.agentNotWired':'Not connected yet','brain.setupHint':'Run {cmd} to connect the others.','brain.noAgents':'No coding agent found on this machine.','brain.tooMany':'{n} entries — all of it is given to your agent in every session. Consider removing what is no longer true.','brain.empty':'Nothing yet. As you work, your agent notes durable things about you here — your role, your preferences, how you like to work, what to avoid. You can also add them yourself below.','brain.toConfirm':'To confirm','brain.confirm':'Confirm','brain.confirmAll':'Confirm all','brain.reject':'Reject','brain.cat.profile':'Profile','brain.cat.preferences':'Preferences','brain.cat.workflow':'Working style','brain.cat.avoid':'Avoid','brain.catHint.profile':'Who you are: role, skills, context.','brain.catHint.preferences':'Tools, languages, code style, formats you prefer.','brain.catHint.workflow':'How you like the agent to work with you.','brain.catHint.avoid':'What the agent should never do.','brain.catEmpty':'Nothing here yet.','brain.addPlaceholder':'Add a fact…','brain.duplicate':'Already in your second brain.','brain.byAgent':'learned by the agent','brain.byYou':'added by you','brain.path':'Stored in {path} — never inside a project.','brain.error':'Could not reach your second brain.','brain.you':'You','brain.youSub':'Private, shared by all your projects: your role, preferences, how you like to work.','brain.project':'This project','brain.projectSub':'Committed with the project, so your team and their agents share it. Nothing personal here.','brain.projectEmpty':'Nothing yet. As it works, your agent notes durable facts about this project here — its conventions, what breaks, its vocabulary, its constraints. You can also add them yourself below.','brain.projectDuplicate':'Already in this project’s memory.','brain.projectPath':'Stored in {path}, committed with the project.','brain.moveTo':'Move to…','brain.moveHint':'Wrong memory? Move this fact to the other one.','brain.cat.conventions':'Conventions','brain.cat.pitfalls':'Pitfalls','brain.cat.glossary':'Glossary','brain.cat.constraints':'Constraints','brain.catHint.conventions':'Naming, tools, style this project imposes.','brain.catHint.pitfalls':'What breaks, and the known workarounds.','brain.catHint.glossary':'The domain’s words and what they mean.','brain.catHint.constraints':'Technical, legal or client constraints.',
96
99
  'meeting.sub':'One dated note per day for this project — write it yourself, or have the active agent draft it from recent tasks and chat activity.','meeting.history':'History','meeting.today':'today','meeting.generate':'Generate','meeting.generateTitle':'Generate today\'s note from recent activity','meeting.overwriteWarn':'This will overwrite today\'s note.','meeting.generateAnyway':'Generate anyway',
97
100
  'chat.widgetTitle':'Run an agent','chat.widgetSub':'Quick access · full view in the Chat tab',
98
101
  'chat.tabSub':'Full conversation with the runner — the same run as the widget, more room to read it.',
@@ -212,9 +215,12 @@ fr: {
212
215
  'drawer.agent':'Agent','drawer.skill':'Compétence','drawer.file':'Fichier · {rel}',
213
216
  'drawer.loading':'Chargement…','drawer.loadError':'Impossible de charger ce fichier.','files.title':'Fichiers','files.sub':'Parcourez les fichiers du projet — visualisez Markdown et HTML, modifiez tout fichier texte, créez-en de nouveaux.','files.newFile':'+ Fichier','files.newFolder':'+ Dossier','files.pickFile':'Sélectionnez un fichier pour l’afficher.','files.empty':'Aucun fichier pour l’instant.','files.edit':'Modifier','files.preview':'Aperçu','files.save':'Enregistrer','files.saved':'✓ enregistré','files.saveError':'Impossible d’enregistrer ce fichier.','files.loadError':'Impossible de charger ce fichier.','files.binary':'Ce fichier ne peut pas être prévisualisé ici (non textuel).','files.discardConfirm':'Abandonner les modifications non enregistrées ?','files.newFilePrompt':'Nom du nouveau fichier (ex. todo.md) :','files.newFolderPrompt':'Nom du nouveau dossier :','files.projectRoot':'racine du projet','files.creatingIn':'Création dans : {path}','files.refresh':'Actualiser','files.discard':'Annuler','files.create':'Créer',
214
217
  'notes.sub':'Un bloc-note libre pour ce projet — Markdown, enregistré automatiquement au fil de la frappe. Visible uniquement par vous (et quiconque ouvre ce tableau de bord).','notes.saving':'Enregistrement…',
218
+ 'notify.done':'{project} : l’agent a terminé','notify.failed':'{project} : l’agent s’est arrêté sur une erreur','notify.approval':'{project} : une étape attend votre approbation',
219
+ 'update.banner':'Ce projet utilise spectoflow {from} ; {to} est installé.','update.button':'Mettre à jour le projet','update.done':'Projet mis à jour : {n} fichier(s) du framework rafraîchi(s).','update.review':'{n} fichier(s) que vous aviez modifié(s) : la nouvelle version est enregistrée à côté en .new.','update.error':'Impossible de mettre à jour le projet.',
220
+ 'chat.stop':'Arrêter',
215
221
  'runners.title':'Une commande personnalisée attend votre accord','runners.hint':'Le config.json de ce projet lance un agent avec une commande différente de celle par défaut. Elle ne s’exécute qu’une fois autorisée sur cette machine.','runners.allow':'Autoriser sur cette machine',
216
222
  'workflow.analyze':'Analyser le projet','workflow.analyzeTitle':'Examiner le projet et proposer les étapes qui lui conviennent maintenant','workflow.detected':'Détecté : {type} · {phase}','workflow.type.app':'application','workflow.type.infra':'infrastructure','workflow.type.data':'données','workflow.phase.design':'phase de conception, pas encore de code','workflow.phase.build':'contient du code','workflow.matches':'Votre workflow correspond déjà au projet.','workflow.apply':'Appliquer','workflow.dismiss':'Fermer','workflow.applied':'{n} étape(s) mise(s) à jour.','workflow.analyzeError':'Impossible d’analyser le projet.','workflow.on':'activée','workflow.off':'désactivée','workflow.reason.always':'toujours utile','workflow.reason.design-no-code':'pas encore de code','workflow.reason.has-code':'le projet contient du code','workflow.reason.has-tests':'le projet a des tests','workflow.reason.infra-no-tests':'projet d’infrastructure sans tests','workflow.reason.data-quality':'projet de données — tests de qualité des données','workflow.reason.has-integration-tests':'des tests d’intégration existent','workflow.reason.no-integration-tests':'pas encore de tests d’intégration','workflow.reason.has-e2e-setup':'une configuration de tests de bout en bout existe','workflow.reason.no-e2e-setup':'pas de configuration de tests de bout en bout','settings.workflowAuto':'Laisser l’agent activer les étapes du workflow quand il faut','settings.workflowAutoHint':'Désactivé : il vous demande d’abord. Il ne désactive jamais une étape de lui-même.',
217
- 'brain.sub':'Ce que spectoflow a appris sur vous, partagé par tous vos projets et donné à votre agent à chaque session. Ajoutez ou corrigez ce que vous voulez.','brain.autoAdd':'Ajouter directement ce que l’agent apprend','brain.autoAddOn':'Les nouveaux faits sont ajoutés tout de suite — vous pouvez les corriger ou les supprimer ici.','brain.autoAddOff':'Les nouveaux faits attendent dans « À confirmer » jusqu’à votre validation.','brain.agents':'Accessible par :','brain.agentWired':'Connecté : cet agent lit et enrichit votre second cerveau','brain.agentNotWired':'Pas encore connecté','brain.setupHint':'Lancez {cmd} pour connecter les autres.','brain.noAgents':'Aucun agent de code trouvé sur cette machine.','brain.tooMany':'{n} entrées — tout est donné à votre agent à chaque session. Pensez à retirer ce qui n’est plus vrai.','brain.empty':'Rien pour l’instant. Au fil de votre travail, votre agent note ici des choses durables sur vous — votre rôle, vos préférences, votre façon de travailler, ce qu’il faut éviter. Vous pouvez aussi les ajouter vous-même ci-dessous.','brain.toConfirm':'À confirmer','brain.confirm':'Confirmer','brain.confirmAll':'Tout confirmer','brain.reject':'Rejeter','brain.cat.profile':'Profil','brain.cat.preferences':'Préférences','brain.cat.workflow':'Façon de travailler','brain.cat.avoid':'À éviter','brain.catHint.profile':'Qui vous êtes : rôle, compétences, contexte.','brain.catHint.preferences':'Outils, langues, style de code, formats que vous préférez.','brain.catHint.workflow':'Comment vous aimez que l’agent travaille avec vous.','brain.catHint.avoid':'Ce que l’agent ne doit jamais faire.','brain.catEmpty':'Rien ici pour l’instant.','brain.addPlaceholder':'Ajouter un fait…','brain.duplicate':'Déjà dans votre second cerveau.','brain.byAgent':'appris par l’agent','brain.byYou':'ajouté par vous','brain.path':'Stocké dans {path} — jamais dans un projet.','brain.error':'Impossible d’accéder à votre second cerveau.',
223
+ 'brain.sub':'Ce que votre agent sait : sur vous, dans tous vos projets — et sur ce projet, pour tous ceux qui y travaillent. Ajoutez ou corrigez ce que vous voulez.','brain.autoAdd':'Ajouter directement ce que l’agent apprend','brain.autoAddOn':'Les nouveaux faits sont ajoutés tout de suite — vous pouvez les corriger ou les supprimer ici.','brain.autoAddOff':'Les nouveaux faits attendent dans « À confirmer » jusqu’à votre validation.','brain.agents':'Accessible par :','brain.agentWired':'Connecté : cet agent lit et enrichit votre second cerveau','brain.agentNotWired':'Pas encore connecté','brain.setupHint':'Lancez {cmd} pour connecter les autres.','brain.noAgents':'Aucun agent de code trouvé sur cette machine.','brain.tooMany':'{n} entrées — tout est donné à votre agent à chaque session. Pensez à retirer ce qui n’est plus vrai.','brain.empty':'Rien pour l’instant. Au fil de votre travail, votre agent note ici des choses durables sur vous — votre rôle, vos préférences, votre façon de travailler, ce qu’il faut éviter. Vous pouvez aussi les ajouter vous-même ci-dessous.','brain.toConfirm':'À confirmer','brain.confirm':'Confirmer','brain.confirmAll':'Tout confirmer','brain.reject':'Rejeter','brain.cat.profile':'Profil','brain.cat.preferences':'Préférences','brain.cat.workflow':'Façon de travailler','brain.cat.avoid':'À éviter','brain.catHint.profile':'Qui vous êtes : rôle, compétences, contexte.','brain.catHint.preferences':'Outils, langues, style de code, formats que vous préférez.','brain.catHint.workflow':'Comment vous aimez que l’agent travaille avec vous.','brain.catHint.avoid':'Ce que l’agent ne doit jamais faire.','brain.catEmpty':'Rien ici pour l’instant.','brain.addPlaceholder':'Ajouter un fait…','brain.duplicate':'Déjà dans votre second cerveau.','brain.byAgent':'appris par l’agent','brain.byYou':'ajouté par vous','brain.path':'Stocké dans {path} — jamais dans un projet.','brain.error':'Impossible d’accéder à votre second cerveau.','brain.you':'Vous','brain.youSub':'Privé, partagé par tous vos projets : votre rôle, vos préférences, votre façon de travailler.','brain.project':'Ce projet','brain.projectSub':'Commité avec le projet : votre équipe et ses agents le partagent. Rien de personnel ici.','brain.projectEmpty':'Rien pour l’instant. Au fil du travail, votre agent note ici des faits durables sur ce projet — ses conventions, ce qui casse, son vocabulaire, ses contraintes. Vous pouvez aussi les ajouter vous-même ci-dessous.','brain.projectDuplicate':'Déjà dans la mémoire de ce projet.','brain.projectPath':'Stocké dans {path}, commité avec le projet.','brain.moveTo':'Déplacer vers…','brain.moveHint':'Mauvaise mémoire ? Déplacez ce fait vers l’autre.','brain.cat.conventions':'Conventions','brain.cat.pitfalls':'Pièges','brain.cat.glossary':'Glossaire','brain.cat.constraints':'Contraintes','brain.catHint.conventions':'Nommage, outils, style imposés par ce projet.','brain.catHint.pitfalls':'Ce qui casse, et les contournements connus.','brain.catHint.glossary':'Les mots du métier et leur sens.','brain.catHint.constraints':'Contraintes techniques, légales ou client.',
218
224
  'meeting.sub':'Une note datée par jour pour ce projet — rédigez-la vous-même, ou laissez l’agent actif la rédiger à partir des tâches récentes et du chat.','meeting.history':'Historique','meeting.today':'aujourd’hui','meeting.generate':'Générer','meeting.generateTitle':'Générer la note du jour à partir de l’activité récente','meeting.overwriteWarn':'Cela va écraser la note d’aujourd’hui.','meeting.generateAnyway':'Générer quand même',
219
225
  'chat.widgetTitle':'Lancer un agent','chat.widgetSub':'Accès rapide · vue complète dans l’onglet Chat',
220
226
  'chat.tabSub':'Conversation complète avec l’exécuteur — la même exécution que le widget, avec plus de place pour la lire.',
@@ -334,9 +340,12 @@ es: {
334
340
  'drawer.agent':'Agente','drawer.skill':'Habilidad','drawer.file':'Archivo · {rel}',
335
341
  'drawer.loading':'Cargando…','drawer.loadError':'No se pudo cargar este archivo.','files.title':'Archivos','files.sub':'Explora los archivos del proyecto — visualiza Markdown y HTML, edita cualquier archivo de texto, crea otros nuevos.','files.newFile':'+ Archivo','files.newFolder':'+ Carpeta','files.pickFile':'Selecciona un archivo para verlo.','files.empty':'Aún no hay archivos.','files.edit':'Editar','files.preview':'Vista previa','files.save':'Guardar','files.saved':'✓ guardado','files.saveError':'No se pudo guardar este archivo.','files.loadError':'No se pudo cargar este archivo.','files.binary':'Este archivo no se puede previsualizar aquí (no es texto).','files.discardConfirm':'¿Descartar los cambios sin guardar?','files.newFilePrompt':'Nombre del nuevo archivo (p. ej. todo.md):','files.newFolderPrompt':'Nombre de la nueva carpeta:','files.projectRoot':'raíz del proyecto','files.creatingIn':'Creando en: {path}','files.refresh':'Actualizar','files.discard':'Descartar','files.create':'Crear',
336
342
  'notes.sub':'Un bloc de notas libre para este proyecto — Markdown, guardado automáticamente mientras escribes. Solo tú (y quien más abra este panel) puedes verlo.','notes.saving':'Guardando…',
343
+ 'notify.done':'{project}: el agente ha terminado','notify.failed':'{project}: el agente se detuvo con un error','notify.approval':'{project}: un paso espera tu aprobación',
344
+ 'update.banner':'Este proyecto usa spectoflow {from}; está instalado {to}.','update.button':'Actualizar el proyecto','update.done':'Proyecto actualizado: {n} archivo(s) del framework renovado(s).','update.review':'{n} archivo(s) que habías editado: la nueva versión se guarda al lado como .new.','update.error':'No se pudo actualizar el proyecto.',
345
+ 'chat.stop':'Detener',
337
346
  'runners.title':'Un comando personalizado espera tu aprobación','runners.hint':'El config.json de este proyecto lanza un agente con un comando distinto del predeterminado. Solo se ejecuta cuando lo autorizas en esta máquina.','runners.allow':'Autorizar en esta máquina',
338
347
  'workflow.analyze':'Analizar el proyecto','workflow.analyzeTitle':'Examinar el proyecto y proponer los pasos que le convienen ahora','workflow.detected':'Detectado: {type} · {phase}','workflow.type.app':'aplicación','workflow.type.infra':'infraestructura','workflow.type.data':'datos','workflow.phase.design':'fase de diseño, aún sin código','workflow.phase.build':'tiene código','workflow.matches':'Tu workflow ya corresponde al proyecto.','workflow.apply':'Aplicar','workflow.dismiss':'Cerrar','workflow.applied':'{n} paso(s) actualizado(s).','workflow.analyzeError':'No se pudo analizar el proyecto.','workflow.on':'activado','workflow.off':'desactivado','workflow.reason.always':'siempre útil','workflow.reason.design-no-code':'aún no hay código','workflow.reason.has-code':'el proyecto tiene código','workflow.reason.has-tests':'el proyecto tiene tests','workflow.reason.infra-no-tests':'proyecto de infraestructura sin tests','workflow.reason.data-quality':'proyecto de datos — tests de calidad de datos','workflow.reason.has-integration-tests':'existen tests de integración','workflow.reason.no-integration-tests':'aún no hay tests de integración','workflow.reason.has-e2e-setup':'existe una configuración de tests end-to-end','workflow.reason.no-e2e-setup':'no hay configuración de tests end-to-end','settings.workflowAuto':'Dejar que el agente active pasos del workflow cuando haga falta','settings.workflowAutoHint':'Desactivado: te pregunta antes. Nunca desactiva un paso por su cuenta.',
339
- 'brain.sub':'Lo que spectoflow ha aprendido sobre ti, compartido por todos tus proyectos y dado a tu agente en cada sesión. Añade o corrige lo que quieras.','brain.autoAdd':'Añadir directamente lo que aprende el agente','brain.autoAddOn':'Los nuevos datos se añaden al instante — puedes corregirlos o borrarlos aquí.','brain.autoAddOff':'Los nuevos datos esperan en «Por confirmar» hasta que los aceptes.','brain.agents':'Accesible por:','brain.agentWired':'Conectado: este agente lee y amplía tu segundo cerebro','brain.agentNotWired':'Aún no conectado','brain.setupHint':'Ejecuta {cmd} para conectar los demás.','brain.noAgents':'No se encontró ningún agente de código en esta máquina.','brain.tooMany':'{n} entradas — todo se da a tu agente en cada sesión. Plantéate quitar lo que ya no sea cierto.','brain.empty':'Nada todavía. Mientras trabajas, tu agente anota aquí cosas duraderas sobre ti — tu rol, tus preferencias, cómo te gusta trabajar, qué evitar. También puedes añadirlas tú abajo.','brain.toConfirm':'Por confirmar','brain.confirm':'Confirmar','brain.confirmAll':'Confirmar todo','brain.reject':'Rechazar','brain.cat.profile':'Perfil','brain.cat.preferences':'Preferencias','brain.cat.workflow':'Forma de trabajar','brain.cat.avoid':'Evitar','brain.catHint.profile':'Quién eres: rol, habilidades, contexto.','brain.catHint.preferences':'Herramientas, idiomas, estilo de código, formatos que prefieres.','brain.catHint.workflow':'Cómo te gusta que el agente trabaje contigo.','brain.catHint.avoid':'Lo que el agente nunca debe hacer.','brain.catEmpty':'Nada aquí todavía.','brain.addPlaceholder':'Añadir un dato…','brain.duplicate':'Ya está en tu segundo cerebro.','brain.byAgent':'aprendido por el agente','brain.byYou':'añadido por ti','brain.path':'Guardado en {path} — nunca dentro de un proyecto.','brain.error':'No se pudo acceder a tu segundo cerebro.',
348
+ 'brain.sub':'Lo que sabe tu agente: sobre ti, en todos tus proyectos — y sobre este proyecto, para todos los que trabajan en él. Añade o corrige lo que quieras.','brain.autoAdd':'Añadir directamente lo que aprende el agente','brain.autoAddOn':'Los nuevos datos se añaden al instante — puedes corregirlos o borrarlos aquí.','brain.autoAddOff':'Los nuevos datos esperan en «Por confirmar» hasta que los aceptes.','brain.agents':'Accesible por:','brain.agentWired':'Conectado: este agente lee y amplía tu segundo cerebro','brain.agentNotWired':'Aún no conectado','brain.setupHint':'Ejecuta {cmd} para conectar los demás.','brain.noAgents':'No se encontró ningún agente de código en esta máquina.','brain.tooMany':'{n} entradas — todo se da a tu agente en cada sesión. Plantéate quitar lo que ya no sea cierto.','brain.empty':'Nada todavía. Mientras trabajas, tu agente anota aquí cosas duraderas sobre ti — tu rol, tus preferencias, cómo te gusta trabajar, qué evitar. También puedes añadirlas tú abajo.','brain.toConfirm':'Por confirmar','brain.confirm':'Confirmar','brain.confirmAll':'Confirmar todo','brain.reject':'Rechazar','brain.cat.profile':'Perfil','brain.cat.preferences':'Preferencias','brain.cat.workflow':'Forma de trabajar','brain.cat.avoid':'Evitar','brain.catHint.profile':'Quién eres: rol, habilidades, contexto.','brain.catHint.preferences':'Herramientas, idiomas, estilo de código, formatos que prefieres.','brain.catHint.workflow':'Cómo te gusta que el agente trabaje contigo.','brain.catHint.avoid':'Lo que el agente nunca debe hacer.','brain.catEmpty':'Nada aquí todavía.','brain.addPlaceholder':'Añadir un dato…','brain.duplicate':'Ya está en tu segundo cerebro.','brain.byAgent':'aprendido por el agente','brain.byYou':'añadido por ti','brain.path':'Guardado en {path} — nunca dentro de un proyecto.','brain.error':'No se pudo acceder a tu segundo cerebro.','brain.you':'Tú','brain.youSub':'Privado, compartido por todos tus proyectos: tu rol, tus preferencias, cómo te gusta trabajar.','brain.project':'Este proyecto','brain.projectSub':'Se confirma (commit) con el proyecto: tu equipo y sus agentes lo comparten. Nada personal aquí.','brain.projectEmpty':'Nada todavía. Mientras trabaja, tu agente anota aquí datos duraderos sobre este proyecto — sus convenciones, lo que falla, su vocabulario, sus restricciones. También puedes añadirlos tú abajo.','brain.projectDuplicate':'Ya está en la memoria de este proyecto.','brain.projectPath':'Guardado en {path}, versionado con el proyecto.','brain.moveTo':'Mover a…','brain.moveHint':'¿Memoria equivocada? Mueve este dato a la otra.','brain.cat.conventions':'Convenciones','brain.cat.pitfalls':'Trampas','brain.cat.glossary':'Glosario','brain.cat.constraints':'Restricciones','brain.catHint.conventions':'Nombres, herramientas, estilo que impone este proyecto.','brain.catHint.pitfalls':'Lo que falla y las soluciones conocidas.','brain.catHint.glossary':'Las palabras del dominio y su significado.','brain.catHint.constraints':'Restricciones técnicas, legales o del cliente.',
340
349
  'meeting.sub':'Una nota fechada por día para este proyecto — escríbela tú mismo, o deja que el agente activo la redacte a partir de las tareas y el chat recientes.','meeting.history':'Historial','meeting.today':'hoy','meeting.generate':'Generar','meeting.generateTitle':'Generar la nota de hoy a partir de la actividad reciente','meeting.overwriteWarn':'Esto sobrescribirá la nota de hoy.','meeting.generateAnyway':'Generar de todos modos',
341
350
  'chat.widgetTitle':'Ejecutar un agente','chat.widgetSub':'Acceso rápido · vista completa en la pestaña Chat',
342
351
  'chat.tabSub':'Conversación completa con el ejecutor — la misma ejecución que el widget, con más espacio para leerla.',
@@ -456,9 +465,12 @@ de: {
456
465
  'drawer.agent':'Agent','drawer.skill':'Skill','drawer.file':'Datei · {rel}',
457
466
  'drawer.loading':'Lädt…','drawer.loadError':'Diese Datei konnte nicht geladen werden.','files.title':'Dateien','files.sub':'Durchsuche die Projektdateien — Markdown & HTML ansehen, jede Textdatei bearbeiten, neue erstellen.','files.newFile':'+ Datei','files.newFolder':'+ Ordner','files.pickFile':'Wähle eine Datei aus, um sie anzuzeigen.','files.empty':'Noch keine Dateien.','files.edit':'Bearbeiten','files.preview':'Vorschau','files.save':'Speichern','files.saved':'✓ gespeichert','files.saveError':'Diese Datei konnte nicht gespeichert werden.','files.loadError':'Diese Datei konnte nicht geladen werden.','files.binary':'Diese Datei kann hier nicht angezeigt werden (kein Text).','files.discardConfirm':'Nicht gespeicherte Änderungen verwerfen?','files.newFilePrompt':'Name der neuen Datei (z. B. todo.md):','files.newFolderPrompt':'Name des neuen Ordners:','files.projectRoot':'Projektstamm','files.creatingIn':'Erstellen in: {path}','files.refresh':'Aktualisieren','files.discard':'Verwerfen','files.create':'Erstellen',
458
467
  'notes.sub':'Ein freier Notizblock für dieses Projekt — Markdown, automatisch gespeichert während der Eingabe. Nur du (und wer sonst dieses Dashboard öffnet) siehst ihn.','notes.saving':'Speichert…',
468
+ 'notify.done':'{project}: der Agent ist fertig','notify.failed':'{project}: der Agent hat mit einem Fehler aufgehört','notify.approval':'{project}: ein Schritt wartet auf deine Freigabe',
469
+ 'update.banner':'Dieses Projekt nutzt spectoflow {from}; installiert ist {to}.','update.button':'Projekt aktualisieren','update.done':'Projekt aktualisiert: {n} Framework-Datei(en) erneuert.','update.review':'{n} von dir bearbeitete Datei(en): die neue Version liegt jeweils daneben als .new.','update.error':'Das Projekt konnte nicht aktualisiert werden.',
470
+ 'chat.stop':'Stoppen',
459
471
  'runners.title':'Ein eigener Befehl braucht deine Zustimmung','runners.hint':'Die config.json dieses Projekts startet einen Agenten mit einem anderen als dem Standardbefehl. Er läuft erst, wenn du ihn auf diesem Rechner erlaubst.','runners.allow':'Auf diesem Rechner erlauben',
460
472
  'workflow.analyze':'Projekt analysieren','workflow.analyzeTitle':'Das Projekt ansehen und vorschlagen, welche Schritte jetzt passen','workflow.detected':'Erkannt: {type} · {phase}','workflow.type.app':'Anwendung','workflow.type.infra':'Infrastruktur','workflow.type.data':'Daten','workflow.phase.design':'Entwurfsphase, noch kein Code','workflow.phase.build':'enthält Code','workflow.matches':'Dein Workflow passt bereits zum Projekt.','workflow.apply':'Anwenden','workflow.dismiss':'Schließen','workflow.applied':'{n} Schritt(e) aktualisiert.','workflow.analyzeError':'Das Projekt konnte nicht analysiert werden.','workflow.on':'an','workflow.off':'aus','workflow.reason.always':'immer nützlich','workflow.reason.design-no-code':'noch kein Code','workflow.reason.has-code':'das Projekt enthält Code','workflow.reason.has-tests':'das Projekt hat Tests','workflow.reason.infra-no-tests':'Infrastrukturprojekt ohne Tests','workflow.reason.data-quality':'Datenprojekt — Datenqualitätstests','workflow.reason.has-integration-tests':'Integrationstests vorhanden','workflow.reason.no-integration-tests':'noch keine Integrationstests','workflow.reason.has-e2e-setup':'ein End-to-End-Test-Setup ist vorhanden','workflow.reason.no-e2e-setup':'kein End-to-End-Test-Setup','settings.workflowAuto':'Den Agenten Workflow-Schritte bei Bedarf aktivieren lassen','settings.workflowAutoHint':'Aus: er fragt dich vorher. Er deaktiviert nie selbst einen Schritt.',
461
- 'brain.sub':'Was spectoflow über dich gelernt hat — geteilt von all deinen Projekten und deinem Agenten in jeder Sitzung mitgegeben. Ergänze oder korrigiere, was du willst.','brain.autoAdd':'Was der Agent lernt, direkt hinzufügen','brain.autoAddOn':'Neue Fakten werden sofort hinzugefügt — du kannst sie hier korrigieren oder löschen.','brain.autoAddOff':'Neue Fakten warten unter „Zu bestätigen“, bis du sie annimmst.','brain.agents':'Erreichbar für:','brain.agentWired':'Verbunden: dieser Agent liest und erweitert dein zweites Gehirn','brain.agentNotWired':'Noch nicht verbunden','brain.setupHint':'Führe {cmd} aus, um die anderen zu verbinden.','brain.noAgents':'Kein Coding-Agent auf diesem Rechner gefunden.','brain.tooMany':'{n} Einträge — alles wird deinem Agenten in jeder Sitzung mitgegeben. Entferne, was nicht mehr stimmt.','brain.empty':'Noch nichts. Während du arbeitest, notiert dein Agent hier Dauerhaftes über dich — deine Rolle, deine Vorlieben, wie du gern arbeitest, was zu vermeiden ist. Du kannst sie auch unten selbst hinzufügen.','brain.toConfirm':'Zu bestätigen','brain.confirm':'Bestätigen','brain.confirmAll':'Alle bestätigen','brain.reject':'Ablehnen','brain.cat.profile':'Profil','brain.cat.preferences':'Vorlieben','brain.cat.workflow':'Arbeitsweise','brain.cat.avoid':'Vermeiden','brain.catHint.profile':'Wer du bist: Rolle, Fähigkeiten, Kontext.','brain.catHint.preferences':'Werkzeuge, Sprachen, Code-Stil, Formate, die du bevorzugst.','brain.catHint.workflow':'Wie der Agent mit dir arbeiten soll.','brain.catHint.avoid':'Was der Agent nie tun soll.','brain.catEmpty':'Hier ist noch nichts.','brain.addPlaceholder':'Fakt hinzufügen…','brain.duplicate':'Schon in deinem zweiten Gehirn.','brain.byAgent':'vom Agenten gelernt','brain.byYou':'von dir hinzugefügt','brain.path':'Gespeichert in {path} — nie in einem Projekt.','brain.error':'Dein zweites Gehirn ist nicht erreichbar.',
473
+ 'brain.sub':'Was dein Agent weiß: über dich, in jedem Projekt — und über dieses Projekt, für alle, die daran arbeiten. Ergänze oder korrigiere, was du willst.','brain.autoAdd':'Was der Agent lernt, direkt hinzufügen','brain.autoAddOn':'Neue Fakten werden sofort hinzugefügt — du kannst sie hier korrigieren oder löschen.','brain.autoAddOff':'Neue Fakten warten unter „Zu bestätigen“, bis du sie annimmst.','brain.agents':'Erreichbar für:','brain.agentWired':'Verbunden: dieser Agent liest und erweitert dein zweites Gehirn','brain.agentNotWired':'Noch nicht verbunden','brain.setupHint':'Führe {cmd} aus, um die anderen zu verbinden.','brain.noAgents':'Kein Coding-Agent auf diesem Rechner gefunden.','brain.tooMany':'{n} Einträge — alles wird deinem Agenten in jeder Sitzung mitgegeben. Entferne, was nicht mehr stimmt.','brain.empty':'Noch nichts. Während du arbeitest, notiert dein Agent hier Dauerhaftes über dich — deine Rolle, deine Vorlieben, wie du gern arbeitest, was zu vermeiden ist. Du kannst sie auch unten selbst hinzufügen.','brain.toConfirm':'Zu bestätigen','brain.confirm':'Bestätigen','brain.confirmAll':'Alle bestätigen','brain.reject':'Ablehnen','brain.cat.profile':'Profil','brain.cat.preferences':'Vorlieben','brain.cat.workflow':'Arbeitsweise','brain.cat.avoid':'Vermeiden','brain.catHint.profile':'Wer du bist: Rolle, Fähigkeiten, Kontext.','brain.catHint.preferences':'Werkzeuge, Sprachen, Code-Stil, Formate, die du bevorzugst.','brain.catHint.workflow':'Wie der Agent mit dir arbeiten soll.','brain.catHint.avoid':'Was der Agent nie tun soll.','brain.catEmpty':'Hier ist noch nichts.','brain.addPlaceholder':'Fakt hinzufügen…','brain.duplicate':'Schon in deinem zweiten Gehirn.','brain.byAgent':'vom Agenten gelernt','brain.byYou':'von dir hinzugefügt','brain.path':'Gespeichert in {path} — nie in einem Projekt.','brain.error':'Dein zweites Gehirn ist nicht erreichbar.','brain.you':'Du','brain.youSub':'Privat, geteilt von all deinen Projekten: deine Rolle, Vorlieben, wie du gern arbeitest.','brain.project':'Dieses Projekt','brain.projectSub':'Mit dem Projekt committet: dein Team und seine Agenten teilen es. Nichts Persönliches hier.','brain.projectEmpty':'Noch nichts. Während er arbeitet, notiert dein Agent hier Dauerhaftes über dieses Projekt — seine Konventionen, was kaputtgeht, sein Vokabular, seine Vorgaben. Du kannst sie auch unten selbst hinzufügen.','brain.projectDuplicate':'Schon im Gedächtnis dieses Projekts.','brain.projectPath':'Gespeichert in {path}, mit dem Projekt committet.','brain.moveTo':'Verschieben nach…','brain.moveHint':'Falsches Gedächtnis? Verschiebe diesen Fakt ins andere.','brain.cat.conventions':'Konventionen','brain.cat.pitfalls':'Stolperfallen','brain.cat.glossary':'Glossar','brain.cat.constraints':'Vorgaben','brain.catHint.conventions':'Benennung, Werkzeuge, Stil, die dieses Projekt vorgibt.','brain.catHint.pitfalls':'Was kaputtgeht, und bekannte Workarounds.','brain.catHint.glossary':'Die Begriffe der Domäne und ihre Bedeutung.','brain.catHint.constraints':'Technische, rechtliche oder Kundenvorgaben.',
462
474
  'meeting.sub':'Eine datierte Notiz pro Tag für dieses Projekt — schreibe sie selbst, oder lass sie vom aktiven Agenten aus den letzten Aufgaben und dem Chat entwerfen.','meeting.history':'Verlauf','meeting.today':'heute','meeting.generate':'Generieren','meeting.generateTitle':'Die heutige Notiz aus der letzten Aktivität generieren','meeting.overwriteWarn':'Dies überschreibt die heutige Notiz.','meeting.generateAnyway':'Trotzdem generieren',
463
475
  'chat.widgetTitle':'Agenten ausführen','chat.widgetSub':'Schnellzugriff · vollständige Ansicht im Chat-Tab',
464
476
  'chat.tabSub':'Vollständiges Gespräch mit dem Runner — derselbe Lauf wie im Widget, mit mehr Platz zum Lesen.',
@@ -578,9 +590,12 @@ pt: {
578
590
  'drawer.agent':'Agente','drawer.skill':'Habilidade','drawer.file':'Ficheiro · {rel}',
579
591
  'drawer.loading':'A carregar…','drawer.loadError':'Não foi possível carregar este ficheiro.','files.title':'Ficheiros','files.sub':'Percorra os ficheiros do projeto — veja Markdown e HTML, edite qualquer ficheiro de texto, crie novos.','files.newFile':'+ Ficheiro','files.newFolder':'+ Pasta','files.pickFile':'Selecione um ficheiro para o ver.','files.empty':'Ainda não há ficheiros.','files.edit':'Editar','files.preview':'Pré-visualizar','files.save':'Guardar','files.saved':'✓ guardado','files.saveError':'Não foi possível guardar este ficheiro.','files.loadError':'Não foi possível carregar este ficheiro.','files.binary':'Este ficheiro não pode ser pré-visualizado aqui (não é texto).','files.discardConfirm':'Descartar alterações não guardadas?','files.newFilePrompt':'Nome do novo ficheiro (ex. todo.md):','files.newFolderPrompt':'Nome da nova pasta:','files.projectRoot':'raiz do projeto','files.creatingIn':'A criar em: {path}','files.refresh':'Atualizar','files.discard':'Descartar','files.create':'Criar',
580
592
  'notes.sub':'Um bloco de notas livre para este projeto — Markdown, guardado automaticamente enquanto escreve. Só você (e quem mais abrir este painel) o vê.','notes.saving':'A guardar…',
593
+ 'notify.done':'{project}: o agente terminou','notify.failed':'{project}: o agente parou com um erro','notify.approval':'{project}: um passo aguarda a sua aprovação',
594
+ 'update.banner':'Este projeto usa spectoflow {from}; está instalado {to}.','update.button':'Atualizar o projeto','update.done':'Projeto atualizado: {n} ficheiro(s) do framework renovado(s).','update.review':'{n} ficheiro(s) que tinha editado: a nova versão fica ao lado como .new.','update.error':'Não foi possível atualizar o projeto.',
595
+ 'chat.stop':'Parar',
581
596
  'runners.title':'Um comando personalizado aguarda a sua aprovação','runners.hint':'O config.json deste projeto inicia um agente com um comando diferente do predefinido. Só é executado depois de o autorizar nesta máquina.','runners.allow':'Autorizar nesta máquina',
582
597
  'workflow.analyze':'Analisar o projeto','workflow.analyzeTitle':'Examinar o projeto e propor os passos que lhe convêm agora','workflow.detected':'Detetado: {type} · {phase}','workflow.type.app':'aplicação','workflow.type.infra':'infraestrutura','workflow.type.data':'dados','workflow.phase.design':'fase de conceção, ainda sem código','workflow.phase.build':'tem código','workflow.matches':'O seu workflow já corresponde ao projeto.','workflow.apply':'Aplicar','workflow.dismiss':'Fechar','workflow.applied':'{n} passo(s) atualizado(s).','workflow.analyzeError':'Não foi possível analisar o projeto.','workflow.on':'ativado','workflow.off':'desativado','workflow.reason.always':'sempre útil','workflow.reason.design-no-code':'ainda sem código','workflow.reason.has-code':'o projeto tem código','workflow.reason.has-tests':'o projeto tem testes','workflow.reason.infra-no-tests':'projeto de infraestrutura sem testes','workflow.reason.data-quality':'projeto de dados — testes de qualidade de dados','workflow.reason.has-integration-tests':'existem testes de integração','workflow.reason.no-integration-tests':'ainda sem testes de integração','workflow.reason.has-e2e-setup':'existe uma configuração de testes ponta a ponta','workflow.reason.no-e2e-setup':'sem configuração de testes ponta a ponta','settings.workflowAuto':'Deixar o agente ativar passos do workflow quando necessário','settings.workflowAutoHint':'Desativado: pergunta-lhe primeiro. Nunca desativa um passo por conta própria.',
583
- 'brain.sub':'O que o spectoflow aprendeu sobre si, partilhado por todos os seus projetos e dado ao seu agente em cada sessão. Acrescente ou corrija o que quiser.','brain.autoAdd':'Adicionar diretamente o que o agente aprende','brain.autoAddOn':'Os novos factos são adicionados de imediato — pode corrigi-los ou apagá-los aqui.','brain.autoAddOff':'Os novos factos esperam em «A confirmar» até os aceitar.','brain.agents':'Acessível por:','brain.agentWired':'Ligado: este agente lê e enriquece o seu segundo cérebro','brain.agentNotWired':'Ainda não ligado','brain.setupHint':'Execute {cmd} para ligar os outros.','brain.noAgents':'Nenhum agente de código encontrado nesta máquina.','brain.tooMany':'{n} entradas — tudo é dado ao seu agente em cada sessão. Pense em retirar o que já não é verdade.','brain.empty':'Ainda nada. À medida que trabalha, o seu agente anota aqui coisas duradouras sobre si — o seu papel, as suas preferências, como gosta de trabalhar, o que evitar. Também as pode adicionar abaixo.','brain.toConfirm':'A confirmar','brain.confirm':'Confirmar','brain.confirmAll':'Confirmar tudo','brain.reject':'Rejeitar','brain.cat.profile':'Perfil','brain.cat.preferences':'Preferências','brain.cat.workflow':'Forma de trabalhar','brain.cat.avoid':'A evitar','brain.catHint.profile':'Quem é: papel, competências, contexto.','brain.catHint.preferences':'Ferramentas, línguas, estilo de código, formatos que prefere.','brain.catHint.workflow':'Como gosta que o agente trabalhe consigo.','brain.catHint.avoid':'O que o agente nunca deve fazer.','brain.catEmpty':'Ainda nada aqui.','brain.addPlaceholder':'Adicionar um facto…','brain.duplicate':'Já está no seu segundo cérebro.','brain.byAgent':'aprendido pelo agente','brain.byYou':'adicionado por si','brain.path':'Guardado em {path} — nunca dentro de um projeto.','brain.error':'Não foi possível aceder ao seu segundo cérebro.',
598
+ 'brain.sub':'O que o seu agente sabe: sobre si, em todos os projetos — e sobre este projeto, para todos os que nele trabalham. Acrescente ou corrija o que quiser.','brain.autoAdd':'Adicionar diretamente o que o agente aprende','brain.autoAddOn':'Os novos factos são adicionados de imediato — pode corrigi-los ou apagá-los aqui.','brain.autoAddOff':'Os novos factos esperam em «A confirmar» até os aceitar.','brain.agents':'Acessível por:','brain.agentWired':'Ligado: este agente lê e enriquece o seu segundo cérebro','brain.agentNotWired':'Ainda não ligado','brain.setupHint':'Execute {cmd} para ligar os outros.','brain.noAgents':'Nenhum agente de código encontrado nesta máquina.','brain.tooMany':'{n} entradas — tudo é dado ao seu agente em cada sessão. Pense em retirar o que já não é verdade.','brain.empty':'Ainda nada. À medida que trabalha, o seu agente anota aqui coisas duradouras sobre si — o seu papel, as suas preferências, como gosta de trabalhar, o que evitar. Também as pode adicionar abaixo.','brain.toConfirm':'A confirmar','brain.confirm':'Confirmar','brain.confirmAll':'Confirmar tudo','brain.reject':'Rejeitar','brain.cat.profile':'Perfil','brain.cat.preferences':'Preferências','brain.cat.workflow':'Forma de trabalhar','brain.cat.avoid':'A evitar','brain.catHint.profile':'Quem é: papel, competências, contexto.','brain.catHint.preferences':'Ferramentas, línguas, estilo de código, formatos que prefere.','brain.catHint.workflow':'Como gosta que o agente trabalhe consigo.','brain.catHint.avoid':'O que o agente nunca deve fazer.','brain.catEmpty':'Ainda nada aqui.','brain.addPlaceholder':'Adicionar um facto…','brain.duplicate':'Já está no seu segundo cérebro.','brain.byAgent':'aprendido pelo agente','brain.byYou':'adicionado por si','brain.path':'Guardado em {path} — nunca dentro de um projeto.','brain.error':'Não foi possível aceder ao seu segundo cérebro.','brain.you':'Você','brain.youSub':'Privado, partilhado por todos os seus projetos: o seu papel, preferências, como gosta de trabalhar.','brain.project':'Este projeto','brain.projectSub':'Versionado com o projeto: a sua equipa e os agentes dela partilham-no. Nada pessoal aqui.','brain.projectEmpty':'Ainda nada. À medida que trabalha, o seu agente anota aqui factos duradouros sobre este projeto — as suas convenções, o que falha, o seu vocabulário, as suas restrições. Também os pode adicionar abaixo.','brain.projectDuplicate':'Já está na memória deste projeto.','brain.projectPath':'Guardado em {path}, versionado com o projeto.','brain.moveTo':'Mover para…','brain.moveHint':'Memória errada? Mova este facto para a outra.','brain.cat.conventions':'Convenções','brain.cat.pitfalls':'Armadilhas','brain.cat.glossary':'Glossário','brain.cat.constraints':'Restrições','brain.catHint.conventions':'Nomes, ferramentas, estilo que este projeto impõe.','brain.catHint.pitfalls':'O que falha, e as soluções conhecidas.','brain.catHint.glossary':'As palavras do domínio e o seu significado.','brain.catHint.constraints':'Restrições técnicas, legais ou do cliente.',
584
599
  'meeting.sub':'Uma nota datada por dia para este projeto — escreva-a você mesmo, ou deixe o agente ativo redigi-la a partir das tarefas e do chat recentes.','meeting.history':'Histórico','meeting.today':'hoje','meeting.generate':'Gerar','meeting.generateTitle':'Gerar a nota de hoje a partir da atividade recente','meeting.overwriteWarn':'Isto vai substituir a nota de hoje.','meeting.generateAnyway':'Gerar mesmo assim',
585
600
  'chat.widgetTitle':'Executar um agente','chat.widgetSub':'Acesso rápido · vista completa no separador Chat',
586
601
  'chat.tabSub':'Conversa completa com o executor — a mesma execução do widget, com mais espaço para ler.',
@@ -700,9 +715,12 @@ it: {
700
715
  'drawer.agent':'Agente','drawer.skill':'Skill','drawer.file':'File · {rel}',
701
716
  'drawer.loading':'Caricamento…','drawer.loadError':'Impossibile caricare questo file.','files.title':'File','files.sub':'Sfoglia i file del progetto — visualizza Markdown e HTML, modifica qualsiasi file di testo, creane di nuovi.','files.newFile':'+ File','files.newFolder':'+ Cartella','files.pickFile':'Seleziona un file per visualizzarlo.','files.empty':'Nessun file ancora.','files.edit':'Modifica','files.preview':'Anteprima','files.save':'Salva','files.saved':'✓ salvato','files.saveError':'Impossibile salvare questo file.','files.loadError':'Impossibile caricare questo file.','files.binary':'Questo file non può essere visualizzato qui (non è testo).','files.discardConfirm':'Scartare le modifiche non salvate?','files.newFilePrompt':'Nome del nuovo file (es. todo.md):','files.newFolderPrompt':'Nome della nuova cartella:','files.projectRoot':'radice del progetto','files.creatingIn':'Creazione in: {path}','files.refresh':'Aggiorna','files.discard':'Scarta','files.create':'Crea',
702
717
  'notes.sub':'Un blocco note libero per questo progetto — Markdown, salvato automaticamente mentre scrivi. Solo tu (e chiunque altro apra questa dashboard) puoi vederlo.','notes.saving':'Salvataggio…',
718
+ 'notify.done':'{project}: l’agente ha finito','notify.failed':'{project}: l’agente si è fermato con un errore','notify.approval':'{project}: un passo attende la tua approvazione',
719
+ 'update.banner':'Questo progetto usa spectoflow {from}; è installato {to}.','update.button':'Aggiorna il progetto','update.done':'Progetto aggiornato: {n} file del framework rinnovati.','update.review':'{n} file che avevi modificato: la nuova versione è salvata accanto come .new.','update.error':'Impossibile aggiornare il progetto.',
720
+ 'chat.stop':'Ferma',
703
721
  'runners.title':'Un comando personalizzato attende la tua approvazione','runners.hint':'Il config.json di questo progetto avvia un agente con un comando diverso da quello predefinito. Viene eseguito solo dopo che lo autorizzi su questa macchina.','runners.allow':'Autorizza su questa macchina',
704
722
  'workflow.analyze':'Analizza il progetto','workflow.analyzeTitle':'Esaminare il progetto e proporre i passi adatti ora','workflow.detected':'Rilevato: {type} · {phase}','workflow.type.app':'applicazione','workflow.type.infra':'infrastruttura','workflow.type.data':'dati','workflow.phase.design':'fase di progettazione, ancora nessun codice','workflow.phase.build':'contiene codice','workflow.matches':'Il tuo workflow corrisponde già al progetto.','workflow.apply':'Applica','workflow.dismiss':'Chiudi','workflow.applied':'{n} passo/i aggiornato/i.','workflow.analyzeError':'Impossibile analizzare il progetto.','workflow.on':'attivo','workflow.off':'disattivo','workflow.reason.always':'sempre utile','workflow.reason.design-no-code':'ancora nessun codice','workflow.reason.has-code':'il progetto contiene codice','workflow.reason.has-tests':'il progetto ha dei test','workflow.reason.infra-no-tests':'progetto di infrastruttura senza test','workflow.reason.data-quality':'progetto di dati — test di qualità dei dati','workflow.reason.has-integration-tests':'esistono test di integrazione','workflow.reason.no-integration-tests':'ancora nessun test di integrazione','workflow.reason.has-e2e-setup':'esiste una configurazione di test end-to-end','workflow.reason.no-e2e-setup':'nessuna configurazione di test end-to-end','settings.workflowAuto':'Lascia che l’agente attivi i passi del workflow quando serve','settings.workflowAutoHint':'Disattivato: ti chiede prima. Non disattiva mai un passo da solo.',
705
- 'brain.sub':'Ciò che spectoflow ha imparato su di te, condiviso da tutti i tuoi progetti e dato al tuo agente in ogni sessione. Aggiungi o correggi ciò che vuoi.','brain.autoAdd':'Aggiungi direttamente ciò che l’agente impara','brain.autoAddOn':'I nuovi fatti vengono aggiunti subito — puoi correggerli o eliminarli qui.','brain.autoAddOff':'I nuovi fatti attendono in «Da confermare» finché non li accetti.','brain.agents':'Raggiungibile da:','brain.agentWired':'Collegato: questo agente legge e arricchisce il tuo secondo cervello','brain.agentNotWired':'Non ancora collegato','brain.setupHint':'Esegui {cmd} per collegare gli altri.','brain.noAgents':'Nessun agente di programmazione trovato su questa macchina.','brain.tooMany':'{n} voci — tutto viene dato al tuo agente in ogni sessione. Valuta di rimuovere ciò che non è più vero.','brain.empty':'Ancora niente. Mentre lavori, il tuo agente annota qui cose durature su di te — il tuo ruolo, le tue preferenze, come ti piace lavorare, cosa evitare. Puoi anche aggiungerle tu qui sotto.','brain.toConfirm':'Da confermare','brain.confirm':'Conferma','brain.confirmAll':'Conferma tutto','brain.reject':'Rifiuta','brain.cat.profile':'Profilo','brain.cat.preferences':'Preferenze','brain.cat.workflow':'Modo di lavorare','brain.cat.avoid':'Da evitare','brain.catHint.profile':'Chi sei: ruolo, competenze, contesto.','brain.catHint.preferences':'Strumenti, lingue, stile di codice, formati che preferisci.','brain.catHint.workflow':'Come ti piace che l’agente lavori con te.','brain.catHint.avoid':'Ciò che l’agente non deve mai fare.','brain.catEmpty':'Ancora niente qui.','brain.addPlaceholder':'Aggiungi un fatto…','brain.duplicate':'Già nel tuo secondo cervello.','brain.byAgent':'imparato dall’agente','brain.byYou':'aggiunto da te','brain.path':'Salvato in {path} — mai dentro un progetto.','brain.error':'Impossibile raggiungere il tuo secondo cervello.',
723
+ 'brain.sub':'Ciò che il tuo agente sa: su di te, in ogni progetto — e su questo progetto, per chiunque ci lavori. Aggiungi o correggi ciò che vuoi.','brain.autoAdd':'Aggiungi direttamente ciò che l’agente impara','brain.autoAddOn':'I nuovi fatti vengono aggiunti subito — puoi correggerli o eliminarli qui.','brain.autoAddOff':'I nuovi fatti attendono in «Da confermare» finché non li accetti.','brain.agents':'Raggiungibile da:','brain.agentWired':'Collegato: questo agente legge e arricchisce il tuo secondo cervello','brain.agentNotWired':'Non ancora collegato','brain.setupHint':'Esegui {cmd} per collegare gli altri.','brain.noAgents':'Nessun agente di programmazione trovato su questa macchina.','brain.tooMany':'{n} voci — tutto viene dato al tuo agente in ogni sessione. Valuta di rimuovere ciò che non è più vero.','brain.empty':'Ancora niente. Mentre lavori, il tuo agente annota qui cose durature su di te — il tuo ruolo, le tue preferenze, come ti piace lavorare, cosa evitare. Puoi anche aggiungerle tu qui sotto.','brain.toConfirm':'Da confermare','brain.confirm':'Conferma','brain.confirmAll':'Conferma tutto','brain.reject':'Rifiuta','brain.cat.profile':'Profilo','brain.cat.preferences':'Preferenze','brain.cat.workflow':'Modo di lavorare','brain.cat.avoid':'Da evitare','brain.catHint.profile':'Chi sei: ruolo, competenze, contesto.','brain.catHint.preferences':'Strumenti, lingue, stile di codice, formati che preferisci.','brain.catHint.workflow':'Come ti piace che l’agente lavori con te.','brain.catHint.avoid':'Ciò che l’agente non deve mai fare.','brain.catEmpty':'Ancora niente qui.','brain.addPlaceholder':'Aggiungi un fatto…','brain.duplicate':'Già nel tuo secondo cervello.','brain.byAgent':'imparato dall’agente','brain.byYou':'aggiunto da te','brain.path':'Salvato in {path} — mai dentro un progetto.','brain.error':'Impossibile raggiungere il tuo secondo cervello.','brain.you':'Tu','brain.youSub':'Privato, condiviso da tutti i tuoi progetti: il tuo ruolo, le preferenze, come ti piace lavorare.','brain.project':'Questo progetto','brain.projectSub':'Versionato con il progetto: il tuo team e i suoi agenti lo condividono. Niente di personale qui.','brain.projectEmpty':'Ancora niente. Mentre lavora, il tuo agente annota qui fatti duraturi su questo progetto — le sue convenzioni, cosa si rompe, il suo vocabolario, i suoi vincoli. Puoi anche aggiungerli tu qui sotto.','brain.projectDuplicate':'Già nella memoria di questo progetto.','brain.projectPath':'Salvato in {path}, versionato con il progetto.','brain.moveTo':'Sposta in…','brain.moveHint':'Memoria sbagliata? Sposta questo fatto nell’altra.','brain.cat.conventions':'Convenzioni','brain.cat.pitfalls':'Insidie','brain.cat.glossary':'Glossario','brain.cat.constraints':'Vincoli','brain.catHint.conventions':'Nomi, strumenti, stile che questo progetto impone.','brain.catHint.pitfalls':'Cosa si rompe, e i rimedi noti.','brain.catHint.glossary':'Le parole del dominio e il loro significato.','brain.catHint.constraints':'Vincoli tecnici, legali o del cliente.',
706
724
  'meeting.sub':'Una nota datata al giorno per questo progetto — scrivila tu stesso, oppure lascia che l’agente attivo la scriva a partire dalle attività e dalla chat recenti.','meeting.history':'Cronologia','meeting.today':'oggi','meeting.generate':'Genera','meeting.generateTitle':'Genera la nota di oggi dall’attività recente','meeting.overwriteWarn':'Questo sovrascriverà la nota di oggi.','meeting.generateAnyway':'Genera comunque',
707
725
  'chat.widgetTitle':'Avvia un agente','chat.widgetSub':'Accesso rapido · vista completa nella scheda Chat',
708
726
  'chat.tabSub':'Conversazione completa con l’esecutore — la stessa esecuzione del widget, con più spazio per leggerla.',