Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/es/guide/bot-builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions docs/guide/bot-builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
124 changes: 124 additions & 0 deletions servers/gateway/dashboard/perch-hub/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions servers/gateway/dashboard/perch-hub/css.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
9 changes: 9 additions & 0 deletions servers/gateway/dashboard/perch-hub/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,15 @@ ${engineBanner(engine, lang)}
<div class="plan-head" id="perch-plan-head"></div>
<div id="perch-plan-steps"></div>
</div>
<!-- PR-D (audit item 15): the envelope + per-session tool narrowing pane,
ported from the board's card drawer so hub-only sessions get it too.
Bot Builder stays the only WRITER of the envelope; this pane can only
REMOVE tools per session, effective from the next message (a wake
rebuilds the world). Collapsed until toggled; loads on first open. -->
<div class="narrow-pane">
<button type="button" id="perch-narrow-toggle" class="narrow-toggle" aria-expanded="false" aria-controls="perch-narrow-body">${escapeHtml(t("botboard.bdEnvelopeToggle", lang))}</button>
<div id="perch-narrow-body" class="narrow-body" hidden></div>
</div>
<div class="session-row">
<div class="state" id="perch-state"></div>
<button type="button" id="perch-rename" class="quiet">${escapeHtml(t("perch.rename", lang))}</button>
Expand Down
16 changes: 15 additions & 1 deletion servers/gateway/routes/perch.js
Original file line number Diff line number Diff line change
Expand Up @@ -689,13 +689,27 @@ export default function perchApiRouter(dashboardAuth, { interactiveEngine = getI
});

// ---- GET /bots/:id/envelope — what Bot Builder grants ----
// PR-D (audit item 15): with an optional ?threadId=<sid>, the response also
// carries that session's SAVED narrowing (tri-state: field absent = not
// reported, null = reported-and-nothing-narrowed, JSON array = the narrowed
// set) so the hub's ported pane renders correct checkbox state in ONE call.
// The board drawer keeps calling the bare per-bot envelope (no threadId) and
// reads narrowing from its own row snapshot — unchanged.
router.get(P + "/bots/:id/envelope", async (req, res) => {
const botId = String(req.params.id);
const threadId = req.query.threadId == null ? null : String(req.query.threadId);
const db = createDbClient();
try {
const row = await loadBotRow(db, botId);
if (!row) return jsonError(res, 404, "unknown_bot");
res.json(await buildEnvelope(db, parseDef(row)));
const envelope = await buildEnvelope(db, parseDef(row));
if (threadId) {
const sess = await latestSession(db, botId, threadId);
if (sess && Object.prototype.hasOwnProperty.call(sess, "narrowed_tools")) {
envelope.savedNarrowing = sess.narrowed_tools == null ? null : sess.narrowed_tools;
}
}
res.json(envelope);
} catch (err) {
jsonError(res, 500, String((err && err.message) || err));
} finally {
Expand Down
Loading
Loading