diff --git a/docs/es/guide/bot-builder.md b/docs/es/guide/bot-builder.md index 6783d80b..3eed0942 100644 --- a/docs/es/guide/bot-builder.md +++ b/docs/es/guide/bot-builder.md @@ -89,11 +89,11 @@ Por defecto solo una sesión por agente corre a la vez, y cada sesión compite p ### 4. Acota las herramientas de un agente para una sola sesión -El acotado de herramientas por sesión vive en el **tablero**: abre la tarjeta en la que trabaja una sesión (o inicia la sesión desde una tarjeta) y abre **Envoltura y herramientas** en el panel lateral de la tarjeta. Verás el envelope completo del agente: todas las herramientas que tiene permitidas, cada una con una casilla, además de su modelo y sus skills. (El hub no tiene panel de envelope — las concesiones son solo del Bot Builder, y los recortes van por el panel de la tarjeta.) +El acotado de herramientas por sesión vive en el hub: abre una sesión, ve a su pestaña **Sesión** y despliega **`Envoltura y herramientas`**. Verás el envelope completo del agente: todas las herramientas que tiene permitidas, cada una con una casilla, además de su modelo y sus skills. El mismo panel sigue en el tablero — abre el panel lateral de la tarjeta en la que trabaja una sesión — y ambos leen y escriben el mismo acotado por sesión. Las concesiones son solo del Bot Builder; el panel solo puede quitar. Desmarca una herramienta y queda apagada **solo para esa sesión**, a partir del siguiente mensaje. La definición del agente no se toca, y todas las demás sesiones conservan el conjunto completo. Esto es para el momento en que quieres que un agente responda sin tocar tus archivos, sin editar nada, sin salir a la red: en esta sesión, ahora mismo. -Las herramientas que aparecen con un candado son las que el agente no tiene permitidas en absoluto. Ahí no se pueden activar; enlazan al Bot Builder, que es el único lugar que otorga una herramienta. La sesión solo puede quitar, nunca dar. +Las herramientas que aparecen con un candado son las que el agente no tiene permitidas en absoluto. No se pueden activar desde ninguno de los dos paneles; solo el Bot Builder otorga una herramienta. La sesión solo puede quitar, nunca dar. ### 5. Responder una pregunta que te hace el agente diff --git a/docs/guide/bot-builder.md b/docs/guide/bot-builder.md index 43f24417..5e4715a4 100644 --- a/docs/guide/bot-builder.md +++ b/docs/guide/bot-builder.md @@ -89,11 +89,11 @@ Only one session per agent runs at a time by default, and every session competes ### 4. Narrow an agent's tools for one session -Per-session tool narrowing lives on the **board**: open the card a session is working (or start the session from a card) and open **Envelope & tools** in the card's drawer. You see the agent's full envelope: every tool it is allowed to use, each with a checkbox, plus its model and skills. (The hub itself has no envelope pane — grants are Bot Builder's alone, and removals ride the card drawer.) +Per-session tool narrowing lives in the hub: open a session, go to its **Session** tab, and toggle **`Envelope & tools`**. You see the agent's full envelope: every tool it is allowed to use, each with a checkbox, plus its model and skills. The same pane is still on the board — open the drawer of the card a session is working — and both read and write the same per-session narrowing. Grants themselves are Bot Builder's alone; the pane can only take away. Uncheck a tool and it is switched off **for that session only**, from the next message onward. The agent's definition is untouched, and every other session keeps the full set. This is for the moment when you want an agent to answer without touching your files, without editing anything, without reaching out over the network — for this one session, right now. -Tools shown with a padlock are ones the agent is not allowed at all. They are not togglable here; they link to Bot Builder, which is the only place that grants a tool. The session can only ever take away. +Tools shown with a padlock are ones the agent is not allowed at all. They are not togglable from either pane; only Bot Builder grants a tool. The session can only ever take away. ### 5. Answering a question the agent asks you diff --git a/servers/gateway/dashboard/perch-hub/client.js b/servers/gateway/dashboard/perch-hub/client.js index 64dc2798..25230e87 100644 --- a/servers/gateway/dashboard/perch-hub/client.js +++ b/servers/gateway/dashboard/perch-hub/client.js @@ -1614,6 +1614,11 @@ export function perchHubJs(lang = "en") { var planCb=el('perch-plan-mode'); if(planCb) planCb.checked=false; var cwdEl=el('perch-session-cwd'); if(cwdEl) cwdEl.textContent=''; /* nor its directory */ cwdPath=''; closeFileViewer(); /* PR-B: nor its cwd browse cursor or open text viewer */ + /* PR-D: nor its narrowing pane — collapse + clear so the next session's + envelope is fetched fresh, never shown from the previous bot. */ + narrowOpen=false; + var nb=el('perch-narrow-body'); if(nb){ clearEl(nb); nb.hidden=true; } + var nt=el('perch-narrow-toggle'); if(nt) nt.setAttribute('aria-expanded','false'); setTurnInFlight(false); /* the PREVIOUS session's Steer/Stop state must not bleed in */ turnRendered=false; /* nor its "this turn already rendered" bookkeeping */ renderedTurn=null; /* nor the turn id that bookkeeping now keys on */ @@ -2176,6 +2181,125 @@ export function perchHubJs(lang = "en") { } var fvClose=el('perch-fv-close'); if(fvClose) fvClose.onclick=closeFileViewer; + /* ---- PR-D (audit item 15): envelope + per-session tool narrowing pane ---- + Ported from the board card drawer (drawer.js:265-380) so hub-only sessions + get per-session narrowing too. Bot Builder stays the ONLY WRITER of the + envelope; this pane can only REMOVE tools for the session (the POST + .../narrow route rejects widening), effective from the next message — a + wake rebuilds the world. Reuses the drawer's botboard.bd* i18n strings + (shared table, already EN+ES) and its tri-state narrowing semantics: a Set + is a real narrowing, null is "reported, nothing narrowed", undefined is + "not reported" — the middle must never collapse into the last. Envelope + + the session's saved narrowing come in ONE call via ?threadId=. Checkboxes + are tracked in a closure array (not querySelectorAll) so the pane is + testable in the vm harness. The load continuation carries the file-wide + mySid identity guard; saveNarrowing writes only its own (possibly detached + after a session switch) message element, so it needs none. */ + var narrowOpen=false; + function savedNarrowingFromEnvelope(env){ + if(!env||!Object.prototype.hasOwnProperty.call(env,'savedNarrowing')) return undefined; + var list=env.savedNarrowing; + if(list==null) return null; + if(typeof list==='string'){ try{ list=JSON.parse(list); }catch(e){ return undefined; } } + if(!Array.isArray(list)) return undefined; + var s={}; list.forEach(function(id){ s[String(id)]=true; }); + return s; + } + function toggleNarrowPane(){ + var body=el('perch-narrow-body'), tog=el('perch-narrow-toggle'); + if(!body) return; + if(narrowOpen){ + narrowOpen=false; body.hidden=true; + if(tog) tog.setAttribute('aria-expanded','false'); + return; + } + narrowOpen=true; body.hidden=false; + if(tog) tog.setAttribute('aria-expanded','true'); + loadNarrowPane(); + } + function narrowPaneErr(){ + var body=el('perch-narrow-body'); if(!body) return; + clearEl(body); + var e=document.createElement('div'); e.className='narrow-msg err'; + e.textContent='${tJs("botboard.loadFailed", lang)}'; + body.appendChild(e); + } + function loadNarrowPane(){ + var body=el('perch-narrow-body'); + if(!body||!current.sid||!current.botId) return; + var mySid=current.sid; + clearEl(body); + var loading=document.createElement('div'); loading.className='narrow-msg'; loading.textContent='\\u2026'; + body.appendChild(loading); + perchApi('GET','/bots/'+encodeURIComponent(current.botId)+'/envelope?threadId='+encodeURIComponent(mySid)).then(function(r){ + if(current.sid!==mySid) return; /* identity guard, as everywhere */ + if(!r.ok||!r.j){ narrowPaneErr(); return; } + renderNarrowPane(r.j); + }); + } + function renderNarrowPane(envelope){ + var body=el('perch-narrow-body'); if(!body) return; + clearEl(body); + var allowed=envelope.tools||[]; + var denied=envelope.denied||[]; + var saved=savedNarrowingFromEnvelope(envelope); + var disabledSet=(saved&&typeof saved==='object')?saved:{}; + var head=document.createElement('div'); head.className='narrow-head'; + var skillsTxt=(envelope.skills||[]).length?(' \\u00b7 ${tJs("botboard.bdEnvelopeSkillsPrefix", lang)}'+envelope.skills.join(', ')):''; + head.textContent='${tJs("botboard.bdEnvelopeModelPrefix", lang)}'+(envelope.model||'${tJs("botboard.bdEnvelopeModelUnset", lang)}')+skillsTxt; + body.appendChild(head); + var toolsWrap=document.createElement('div'); toolsWrap.className='narrow-tools'; + var toolBoxes=[]; + if(!allowed.length&&!denied.length){ + var none=document.createElement('div'); none.className='narrow-locked'; none.textContent='${tJs("botboard.bdToolsNone", lang)}'; + toolsWrap.appendChild(none); + } + allowed.forEach(function(tool){ + var label=document.createElement('label'); label.className='narrow-tool'; + var cb=document.createElement('input'); cb.type='checkbox'; + cb.checked=!disabledSet[String(tool.id)]; + cb.setAttribute('data-narrow-tool',tool.id); + label.appendChild(cb); + label.appendChild(document.createTextNode(' '+(tool.label||tool.id))); + toolsWrap.appendChild(label); + toolBoxes.push(cb); + }); + denied.forEach(function(tool){ + var locked=document.createElement('div'); locked.className='narrow-locked'; + locked.textContent='\\uD83D\\uDD12 '+(tool.label||tool.id); + toolsWrap.appendChild(locked); + }); + body.appendChild(toolsWrap); + var note=document.createElement('div'); note.className='narrow-note'; + note.textContent=(saved&&typeof saved==='object')?'${tJs("botboard.bdNarrowNoteSaved", lang)}' + : saved===null?'${tJs("botboard.bdNarrowNoteEmpty", lang)}' + : '${tJs("botboard.bdNarrowNoteUnknown", lang)}'; + body.appendChild(note); + var narrowMsg=document.createElement('div'); narrowMsg.className='narrow-msg'; + body.appendChild(narrowMsg); + toolsWrap.addEventListener('change',function(ev){ + if(ev.target&&ev.target.hasAttribute&&ev.target.hasAttribute('data-narrow-tool')) saveNarrowing(toolBoxes,narrowMsg,ev.target); + }); + } + function saveNarrowing(toolBoxes,narrowMsg,changedInput){ + if(!current.botId||!current.sid) return; + var botId=current.botId, sid=current.sid; + var disabled=toolBoxes.filter(function(b){ return !b.checked; }).map(function(b){ return b.getAttribute('data-narrow-tool'); }); + narrowMsg.className='narrow-msg'; narrowMsg.textContent='\\u2026'; + perchApi('POST','/bots/'+encodeURIComponent(botId)+'/sessions/'+encodeURIComponent(sid)+'/narrow',{disabled_tools:disabled}).then(function(r){ + if(r.ok){ + narrowMsg.textContent=disabled.length + ? ('${tJs("botboard.bdNarrowedToPrefix", lang)}'+(toolBoxes.length-disabled.length)+'${tJs("botboard.bdNarrowedToMid", lang)}'+toolBoxes.length+'${tJs("botboard.bdNarrowedToSuffix", lang)}') + : '${tJs("botboard.bdFullEnvelopeRestored", lang)}'; + } else { + changedInput.checked=!changedInput.checked; + narrowMsg.className='narrow-msg err'; + narrowMsg.textContent=(r.j&&r.j.error==='widening_rejected')?'${tJs("botboard.bdNarrowRejected", lang)}':'${tJs("botboard.bdNarrowFailed", lang)}'; + } + }); + } + var narrowToggle=el('perch-narrow-toggle'); if(narrowToggle) narrowToggle.onclick=toggleNarrowPane; + /* ---- Wave 2: Session-tab facts + plan progress ------------------------- All readings ride the state/plan_state frames the engine already emits (contextUsage = pi's own numbers captured at turn end; uptime/RSS are diff --git a/servers/gateway/dashboard/perch-hub/css.js b/servers/gateway/dashboard/perch-hub/css.js index e848a8ba..adcaa3a9 100644 --- a/servers/gateway/dashboard/perch-hub/css.js +++ b/servers/gateway/dashboard/perch-hub/css.js @@ -318,6 +318,25 @@ min-height:44px;padding:6px 4px;font-size:11px;border-color:transparent;backgrou the two controls keep their own 44px two-id rules further up. */ #perch-hub-root .session-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:6px 0 12px} #perch-hub-root .session-row .state{flex:1;min-width:0} +/* PR-D (audit item 15): the envelope + per-session tool narrowing pane. A + quiet full-width toggle above a collapsible body of tool checkboxes (an + allowed tool, ticked = kept) and locked rows (denied by the envelope). All + selectors scoped; the checkbox override beats the generic full-width input + rule so ticks look like ticks. */ +#perch-hub-root .narrow-pane{margin:6px 0 12px} +#perch-hub-root .narrow-toggle{width:100%;text-align:left;color:var(--dim);font-size:12.5px; +text-transform:uppercase;letter-spacing:.06em;background:none;border:0;border-top:1px solid var(--line); +border-radius:0;padding:12px 0;min-height:44px} +#perch-hub-root .narrow-body[hidden]{display:none} +#perch-hub-root .narrow-head{color:var(--dim);font-size:12.5px;margin:4px 0 8px;word-break:break-word; +font-family:"JetBrains Mono",ui-monospace,monospace} +#perch-hub-root .narrow-tools{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:4px} +#perch-hub-root .narrow-tool{display:flex;align-items:center;gap:7px;font-size:13.5px;padding:6px 4px;min-height:36px} +#perch-hub-root .narrow-tool input{width:auto;padding:0;min-height:0;flex-shrink:0} +#perch-hub-root .narrow-locked{color:var(--dim);font-size:13px;padding:6px 4px} +#perch-hub-root .narrow-note{color:var(--dim);font-size:12px;margin-top:8px;line-height:1.45} +#perch-hub-root .narrow-msg{font-size:12.5px;margin-top:6px} +#perch-hub-root .narrow-msg.err{color:var(--attn)} /* Files tab rows: the whole row is the download link (thumb target), name breaks long, meta never does. */ #perch-hub-root .files-bar{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:6px 0} diff --git a/servers/gateway/dashboard/perch-hub/html.js b/servers/gateway/dashboard/perch-hub/html.js index c844fe86..085329b6 100644 --- a/servers/gateway/dashboard/perch-hub/html.js +++ b/servers/gateway/dashboard/perch-hub/html.js @@ -200,6 +200,15 @@ ${engineBanner(engine, lang)}
+ +