docs: scrub the sidebar-agent ghost from comments and CLAUDE.md

20+ comments across 10 files still described the deleted sidebar-agent.ts as a
live process — including load-bearing architecture claims ('IMPORTED ONLY BY
sidebar-agent.ts', 'sidebar-agent fills this in on first prompt-injection
load', 'kill sidebar-agent' in shutdown docs) and ~60 lines of tombstone
blocks in server.ts enumerating deleted identifiers by name (a false grep
surface: searching processAgentEvent hit server.ts and looked live).

CLAUDE.md's security-stack section now documents the LIVE architecture: L1-L3
content filters + testsavant via the security sidecar subprocess; the
L4b/ensemble rows, the GSTACK_SECURITY_ENSEMBLE knob, and the 721MB DeBERTa
download are gone (deleted as dead code this wave) with an explicit
do-not-re-document note; attempts.jsonl is correctly attributed to
tunnel-denial-log.ts; the no-live-writer status of classifierStatus is stated.

Comments that survive now describe what IS, not what WAS: the promotion gate
in domain-skills.ts explains why classifier_score>0 is load-bearing given no
L4 load-time scan exists; file-permissions.ts names real sensitive files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan 2026-08-14 19:12:01 -07:00
parent 4328748136
commit d21f11af5d
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
10 changed files with 55 additions and 100 deletions

View File

@ -391,47 +391,39 @@ every `git pull`.
| Layer | Module | Lives in | | Layer | Module | Lives in |
|-------|--------|----------| |-------|--------|----------|
| L1-L3 | `content-security.ts` | both server and agent — datamarking, hidden element strip, ARIA regex, URL blocklist, envelope wrapping | | L1-L3 | `content-security.ts` | server + read path — datamarking, hidden element strip, ARIA regex, URL blocklist, envelope wrapping |
| L4 | `security-classifier.ts` (TestSavantAI ONNX) | **sidebar-agent only** | | L4 | `security-classifier.ts` (TestSavantAI ONNX) | **security sidecar subprocess only** (`security-sidecar-entry.ts`, driven by `security-sidecar-client.ts` from server.ts) |
| L4b | `security-classifier.ts` (Claude Haiku transcript) | **sidebar-agent only** | | Canary | `security.ts` (generate/inject/detect) | pure utilities — no production injector today (the chat prompt-builder that injected them was ripped) |
| L5 | `security.ts` (canary) | both — inject in compiled, check in agent | | Combiner | `security.ts` (combineVerdict + THRESHOLDS) | pure, tested; retains transcript/deberta vote handling for LayerSignal inputs no live layer produces anymore |
| L6 | `security.ts` (combineVerdict ensemble) | both |
History note: an L4b Haiku transcript classifier and an opt-in DeBERTa ensemble
(`GSTACK_SECURITY_ENSEMBLE=deberta`) existed until the chat-path agent that
invoked them was ripped; both were deleted as dead code (zero production
callers). Do not re-document them as live.
**Critical constraint:** `security-classifier.ts` CANNOT be imported from the **Critical constraint:** `security-classifier.ts` CANNOT be imported from the
compiled browse binary. `@huggingface/transformers` v4 requires `onnxruntime-node` compiled browse binary. `@huggingface/transformers` v4 requires `onnxruntime-node`
which fails to `dlopen` from Bun compile's temp extract dir. Only `security.ts` which fails to `dlopen` from Bun compile's temp extract dir — hence the sidecar
(pure-string operations — canary, verdict combiner, attack log, status) is safe subprocess. Only `security.ts` (pure-string operations — canary utilities,
for `server.ts`. See `~/.gstack/projects/garrytan-gstack/ceo-plans/2026-04-19-prompt-injection-guard.md` verdict combiner, status) is safe for `server.ts`. See
§"Pre-Impl Gate 1 Outcome" for full architectural decision. `~/.gstack/projects/garrytan-gstack/ceo-plans/2026-04-19-prompt-injection-guard.md`
§"Pre-Impl Gate 1 Outcome" for the original architectural decision.
**Thresholds** (in `security.ts`): **Thresholds** (in `security.ts`): `BLOCK: 0.85`, `WARN: 0.75`, `LOG_ONLY: 0.40`,
- `BLOCK: 0.85` — single-layer score that would cause BLOCK if cross-confirmed `SOLO_CONTENT_BLOCK: 0.92` (label-less content classifiers can't distinguish
- `WARN: 0.75` — cross-confirm threshold. When L4 AND L4b both >= 0.75 → BLOCK "injection" from "phishing aimed at the user", so their solo bar is higher).
- `LOG_ONLY: 0.40` — gates transcript classifier (skip Haiku when all layers < 0.40) The live L4 path applies these in server.ts's sidecar-scan handling; canary
- `SOLO_CONTENT_BLOCK: 0.92` — single-layer threshold for label-less content classifiers leak always BLOCKs (deterministic).
(testsavant, deberta). Intentionally higher than `BLOCK` because these layers can't
distinguish "this is an injection" from "this looks like phishing aimed at the user."
The transcript classifier keeps a separate, label-gated solo path at `BLOCK` (0.85).
**Ensemble rule:** BLOCK only when the ML content classifier AND the transcript
classifier both report >= WARN. Single-layer high confidence degrades to WARN —
this is the Stack Overflow instruction-writing FP mitigation. Canary leak
always BLOCKs (deterministic).
**Env knobs:** **Env knobs:**
- `GSTACK_SECURITY_OFF=1` — emergency kill switch. Classifier stays off even if - `GSTACK_SECURITY_OFF=1` — emergency kill switch. Classifier stays off even if
warmed. Canary is still injected; just the ML scan is skipped. warmed; the L1-L3 filters keep running.
- `GSTACK_SECURITY_ENSEMBLE=deberta` — opt-in DeBERTa-v3 ensemble. Adds
ProtectAI DeBERTa-v3-base-injection-onnx as L4c classifier for cross-model
agreement. 721MB first-run download. With ensemble enabled, BLOCK requires
2-of-3 ML classifiers agreeing at >= WARN (testsavant, deberta, transcript).
Without ensemble (default), BLOCK requires testsavant + transcript at >= WARN.
- Classifier model cache: `~/.gstack/models/testsavant-small/` (112MB, first run only) - Classifier model cache: `~/.gstack/models/testsavant-small/` (112MB, first run only)
plus `~/.gstack/models/deberta-v3-injection/` (721MB, only when ensemble enabled) - Attack log: `~/.gstack/security/attempts.jsonl` — written by
- Attack log: `~/.gstack/security/attempts.jsonl` (salted sha256 + domain only, `tunnel-denial-log.ts` (tunnel-surface rejections; rotates at 10MB, 5 generations)
rotates at 10MB, 5 generations) - Session state: `~/.gstack/security/session-state.json` (cross-process, atomic;
- Per-device salt: `~/.gstack/security/device-salt` (0600) NOTE: classifierStatus currently has no live writer — shield status derives
- Session state: `~/.gstack/security/session-state.json` (cross-process, atomic) from what's on disk)
## Dev symlink awareness ## Dev symlink awareness

View File

@ -244,7 +244,7 @@ export class BrowserManager {
// Called when the headed browser disconnects without intentional teardown // Called when the headed browser disconnects without intentional teardown
// (user closed the window). Wired up by server.ts to run full cleanup // (user closed the window). Wired up by server.ts to run full cleanup
// (sidebar-agent, state file, profile locks) before exiting with code 2. // (terminal agent, state file, profile locks) before exiting with code 2.
// Returns void or a Promise; rejections are caught and fall back to exit(2). // Returns void or a Promise; rejections are caught and fall back to exit(2).
// `exitCode` is the resolved process exit code from the disconnect cause: // `exitCode` is the resolved process exit code from the disconnect cause:
// 0 on clean user-initiated quit (e.g., Cmd+Q on headed Chromium), 2 on // 0 on clean user-initiated quit (e.g., Cmd+Q on headed Chromium), 2 on
@ -680,7 +680,7 @@ export class BrowserManager {
// restart loop. Crash → process.exit(2) preserves the legacy headed // restart loop. Crash → process.exit(2) preserves the legacy headed
// semantics that's distinct from launch()'s code 1. // semantics that's distinct from launch()'s code 1.
// Always calls onDisconnect() first to trigger full shutdown (kill // Always calls onDisconnect() first to trigger full shutdown (kill
// sidebar-agent, save session, clean profile locks + state file) so // terminal agent, save session, clean profile locks + state file) so
// crashes don't strand resources either. // crashes don't strand resources either.
if (this.browser) { if (this.browser) {
this.browser.on('disconnected', () => { this.browser.on('disconnected', () => {

View File

@ -3,8 +3,8 @@
* *
* Output for trusted methods is a plain JSON pretty-print. * Output for trusted methods is a plain JSON pretty-print.
* Output for untrusted methods is wrapped with the centralized UNTRUSTED EXTERNAL * Output for untrusted methods is wrapped with the centralized UNTRUSTED EXTERNAL
* CONTENT envelope so the sidebar-agent classifier sees it (matches the pattern * CONTENT envelope so downstream consumers treat it as data, not instructions
* used by other untrusted-content commands in commands.ts). * (matches the pattern used by other untrusted-content commands in commands.ts).
*/ */
import type { BrowserManager } from './browser-manager'; import type { BrowserManager } from './browser-manager';

View File

@ -442,8 +442,8 @@ async function ensureServer(flags?: GlobalFlags): Promise<ServerState> {
return state; return state;
} }
// BROWSE_NO_AUTOSTART: sidebar agent sets this so the child claude never // BROWSE_NO_AUTOSTART: agent-spawned children (e.g. the terminal-agent PTY
// spawns an invisible headless browser. If the headed server is down, // claude) set this so a child never spawns an invisible headless browser. If the headed server is down,
// fail fast with a clear error instead of silently starting a new one. // fail fast with a clear error instead of silently starting a new one.
if (process.env.BROWSE_NO_AUTOSTART === '1') { if (process.env.BROWSE_NO_AUTOSTART === '1') {
console.error('[browse] Server not available and BROWSE_NO_AUTOSTART is set.'); console.error('[browse] Server not available and BROWSE_NO_AUTOSTART is set.');
@ -527,7 +527,7 @@ export function extractTabId(args: string[]): { tabId: number | undefined; args:
async function sendCommand(state: ServerState, command: string, args: string[], retries = 0): Promise<void> { async function sendCommand(state: ServerState, command: string, args: string[], retries = 0): Promise<void> {
// Precedence: CLI --tab-id flag > BROWSE_TAB env var. // Precedence: CLI --tab-id flag > BROWSE_TAB env var.
// make-pdf always passes --tab-id; human users typically rely on BROWSE_TAB // make-pdf always passes --tab-id; human users typically rely on BROWSE_TAB
// (set by sidebar-agent per-tab) or the active tab. // or the active tab.
const extracted = extractTabId(args); const extracted = extractTabId(args);
args = extracted.args; args = extracted.args;
const envTab = process.env.BROWSE_TAB; const envTab = process.env.BROWSE_TAB;
@ -1116,10 +1116,6 @@ Refs: After 'snapshot', use @e1, @e2... as selectors:
console.log('(If you still don\'t see it, check Mission Control / other Spaces.)'); console.log('(If you still don\'t see it, check Mission Control / other Spaces.)');
} }
// sidebar-agent.ts spawn was here. Ripped alongside the chat queue —
// the Terminal pane runs an interactive PTY now, no more one-shot
// claude -p subprocesses to multiplex.
// Auto-start terminal agent (non-compiled bun process). Owns the PTY // Auto-start terminal agent (non-compiled bun process). Owns the PTY
// WebSocket for the sidebar Terminal pane. Routes through the shared // WebSocket for the sidebar Terminal pane. Routes through the shared
// spawnTerminalAgent helper so the CLI cold-start path and the // spawnTerminalAgent helper so the CLI cold-start path and the

View File

@ -14,8 +14,9 @@
* - host is ALWAYS derived from the active tab's top-level origin (T3 * - host is ALWAYS derived from the active tab's top-level origin (T3
* confused-deputy fix). Never accepted as an arg. * confused-deputy fix). Never accepted as an arg.
* - Save-time security uses content-security.ts L1-L3 filters (importable * - Save-time security uses content-security.ts L1-L3 filters (importable
* from the compiled binary, unlike the L4 ML classifier). The full L4 * from the compiled binary, unlike the L4 ML classifier). There is NO
* scan happens in sidebar-agent.ts when the skill is loaded into a prompt. * load-time L4 scan today it died with the chat path; the
* classifier_score>0 promotion gate in domain-skills.ts compensates.
* - Output is structured: every success/error includes problem + cause + * - Output is structured: every success/error includes problem + cause +
* suggested-action. Matches the gstack house style. * suggested-action. Matches the gstack house style.
* *
@ -117,8 +118,8 @@ async function handleSave(args: string[], bm: BrowserManager): Promise<string> {
); );
} }
// L1-L3 content filters (datamarking, hidden-element strip, ARIA regex, // L1-L3 content filters (datamarking, hidden-element strip, ARIA regex,
// URL blocklist). The full L4 ML classifier runs at sidebar-agent prompt // URL blocklist). No L4 ML scan here — the classifier can't import in the
// injection time, not here (CLAUDE.md: classifier can't import in compiled binary). // compiled binary, and the load-time scan path no longer exists.
const filterResult = runContentFilters(body, page.url(), 'domain-skill-save'); const filterResult = runContentFilters(body, page.url(), 'domain-skill-save');
if (filterResult.blocked) { if (filterResult.blocked) {
logTelemetry({ event: 'domain_skill_save_blocked', host, reason: filterResult.message }); logTelemetry({ event: 'domain_skill_save_blocked', host, reason: filterResult.message });
@ -128,9 +129,9 @@ async function handleSave(args: string[], bm: BrowserManager): Promise<string> {
'Action: review the body for suspicious instruction-like content; rewrite and retry.' 'Action: review the body for suspicious instruction-like content; rewrite and retry.'
); );
} }
// L1-L3 score is binary (passed or not). For the L4 score field we leave 0 // L1-L3 score is binary (passed or not). The L4 score field stays 0
// (meaning "not yet scanned by ML classifier") — sidebar-agent fills this // ("never ML-scanned") — nothing fills it in today, which is exactly why
// in on first prompt-injection load. // the promotion gate in domain-skills.ts requires classifier_score > 0.
const slug = getCurrentProjectSlug(); const slug = getCurrentProjectSlug();
const row = await writeSkill({ const row = await writeSkill({
host, host,

View File

@ -287,7 +287,8 @@ export async function writeSkill(input: WriteSkillInput): Promise<DomainSkillRow
/** /**
* Promote a quarantined skill to active in its project after N=3 uses without * Promote a quarantined skill to active in its project after N=3 uses without
* classifier flagging. Called by sidebar-agent on successful skill use. * classifier flagging. No production caller today the chat-path agent that
* invoked it on successful skill use was ripped with the chat queue.
* *
* Auto-promote logic: * Auto-promote logic:
* - increment use_count * - increment use_count
@ -296,11 +297,10 @@ export async function writeSkill(input: WriteSkillInput): Promise<DomainSkillRow
* - else stay quarantined with updated counter; user must run * - else stay quarantined with updated counter; user must run
* `domain-skill promote-to-global` manually * `domain-skill promote-to-global` manually
* *
* The classifier_score > 0 gate is load-bearing: handleSave currently writes * The classifier_score > 0 gate is load-bearing: handleSave writes
* classifier_score=0 with the comment "L4 deferred to load-time / sidebar-agent * classifier_score=0 (meaning "never ML-scanned"), and NOTHING updates the
* fills this in on first prompt-injection load," but sidebar-agent was ripped * score today the load-time L4 scan died with the chat path, so skills
* (CLAUDE.md "Sidebar architecture") and nothing else updates the score, so * authored via the production path never had their body scanned by L4.
* skills authored via the production path never had their body scanned by L4.
* Without this gate, three benign uses promote any quarantined skill including * Without this gate, three benign uses promote any quarantined skill including
* one written under the influence of a poisoned page into the prompt context * one written under the influence of a poisoned page into the prompt context
* for every subsequent visit. The gate re-opens automatically the day L4 is * for every subsequent visit. The gate re-opens automatically the day L4 is

View File

@ -4,8 +4,8 @@
* Why this exists * Why this exists
* ---------------- * ----------------
* POSIX mode bits (`0o600` for files, `0o700` for dirs) are how gstack marks * POSIX mode bits (`0o600` for files, `0o700` for dirs) are how gstack marks
* sensitive state files auth tokens, canary tokens, chat history, agent * sensitive state files auth tokens, PTY session state, tab context. On
* queue, device salt, per-tab security decisions. On Linux and macOS, * Linux and macOS,
* `fs.chmodSync(path, 0o600)` and `fs.writeFileSync(path, data, { mode: 0o600 })` * `fs.chmodSync(path, 0o600)` and `fs.writeFileSync(path, data, { mode: 0o600 })`
* do exactly what you'd hope: the file ends up readable and writable only * do exactly what you'd hope: the file ends up readable and writable only
* by the owning user, no access for group / other. * by the owning user, no access for group / other.

View File

@ -496,10 +496,6 @@ function isRootRequest(req: Request): boolean {
return token !== null && isRootToken(token); return token !== null && isRootToken(token);
} }
// Sidebar model router was here (sonnet vs opus by message intent). Ripped
// alongside the chat queue; the interactive PTY just runs whatever model
// the user's `claude` CLI is configured with.
// ─── Help text (auto-generated from COMMAND_DESCRIPTIONS) ──────── // ─── Help text (auto-generated from COMMAND_DESCRIPTIONS) ────────
function generateHelpText(): string { function generateHelpText(): string {
// Group commands by category // Group commands by category
@ -572,15 +568,6 @@ function tmpStatePath(): string {
// ─── Sidebar agent / chat state ripped ────────────────────────────── // ─── Sidebar agent / chat state ripped ──────────────────────────────
// ChatEntry, SidebarSession, TabAgentState interfaces; chatBuffer,
// chatBuffers, sidebarSession, agentProcess, agentStatus, agentStartTime,
// agentTabId, messageQueue, currentMessage, tabAgents; addChatEntry,
// loadSession, createSession, persistSession, processAgentEvent,
// killAgent, listSessions, getTabAgent, getTabAgentStatus, and the
// agentHealthInterval all lived here. Replaced by the live PTY in
// terminal-agent.ts; chat queue + per-tab agent multiplexing are no
// longer needed.
let lastConsoleFlushed = 0; let lastConsoleFlushed = 0;
let lastNetworkFlushed = 0; let lastNetworkFlushed = 0;
let lastDialogFlushed = 0; let lastDialogFlushed = 0;
@ -779,7 +766,7 @@ const browserManager = new BrowserManager();
// short-circuits idle-shutdown. // short-circuits idle-shutdown.
let activeBrowserManager: BrowserManager = browserManager; let activeBrowserManager: BrowserManager = browserManager;
// When the user closes the headed browser window, run full cleanup // When the user closes the headed browser window, run full cleanup
// (kill sidebar-agent, save session, remove profile locks, delete state file) // (kill terminal agent, save session, remove profile locks, delete state file)
// before exiting. Exit code 0 means user-initiated clean quit (Cmd+Q on // before exiting. Exit code 0 means user-initiated clean quit (Cmd+Q on
// macOS) so process supervisors like gbrowser's gbd skip the restart loop; // macOS) so process supervisors like gbrowser's gbd skip the restart loop;
// 2 means a real crash that should respawn. The fallback `?? 2` preserves // 2 means a real crash that should respawn. The fallback `?? 2` preserves
@ -1031,7 +1018,7 @@ async function handleCommandInternalImpl(
if (!opts?.skipRateCheck && tokenInfo.token) recordCommand(tokenInfo.token); if (!opts?.skipRateCheck && tokenInfo.token) recordCommand(tokenInfo.token);
} }
// Pin to a specific tab if requested (set by BROWSE_TAB env var in sidebar agents). // Pin to a specific tab if requested (set by BROWSE_TAB env var, e.g. per-tab agent contexts).
// This prevents parallel agents from interfering with each other's tab context. // This prevents parallel agents from interfering with each other's tab context.
// Safe because Bun's event loop is single-threaded — no concurrent handleCommand. // Safe because Bun's event loop is single-threaded — no concurrent handleCommand.
let savedTabId: number | null = null; let savedTabId: number | null = null;
@ -1833,9 +1820,7 @@ export function buildFetchHandler(cfg: ServerConfig): ServerHandle {
tabs: browserManager.getTabCount(), tabs: browserManager.getTabCount(),
// Security module status — drives the shield icon in the sidepanel. // Security module status — drives the shield icon in the sidepanel.
// Returns {status: 'protected'|'degraded'|'inactive', layers: {...}}. // Returns {status: 'protected'|'degraded'|'inactive', layers: {...}}.
// The chat-path classifier no longer feeds this since // Fed by the page-content side (testsavant sidecar, canary state).
// sidebar-agent.ts was ripped; only the page-content side
// (canary, content-security) keeps reporting in.
security: getSecurityStatus(), security: getSecurityStatus(),
// Terminal-agent discovery. ONLY a port number — never a token. // Terminal-agent discovery. ONLY a port number — never a token.
// Tokens flow via the /pty-session HttpOnly cookie path. See // Tokens flow via the /pty-session HttpOnly cookie path. See
@ -2559,15 +2544,6 @@ export function buildFetchHandler(cfg: ServerConfig): ServerHandle {
} }
// ─── Sidebar chat endpoints ripped ──────────────────────────────
// /sidebar-tabs, /sidebar-tabs/switch, /sidebar-chat[/clear],
// /sidebar-command, /sidebar-agent/{event,kill,stop},
// /sidebar-queue/dismiss, /sidebar-session{,/new,/list} all lived
// here. They drove the one-shot claude -p chat queue. Replaced by
// the interactive PTY in terminal-agent.ts; the queue + browser-tab
// multiplexing are no longer needed.
// ─── Batch endpoint — N commands, 1 HTTP round-trip ───────────── // ─── Batch endpoint — N commands, 1 HTTP round-trip ─────────────
// Accepts both root AND scoped tokens (same as /command). // Accepts both root AND scoped tokens (same as /command).
// Executes commands sequentially through the full security pipeline. // Executes commands sequentially through the full security pipeline.
@ -3110,11 +3086,6 @@ export async function start() {
console.log(`[browse] State file: ${config.stateFile}`); console.log(`[browse] State file: ${config.stateFile}`);
console.log(`[browse] Idle timeout: ${IDLE_TIMEOUT_MS / 1000}s`); console.log(`[browse] Idle timeout: ${IDLE_TIMEOUT_MS / 1000}s`);
// initSidebarSession() ripped alongside the chat queue (it loaded
// chat.jsonl into memory and started the agent-health watchdog —
// both functions are gone). The Terminal pane manages its own state
// directly via terminal-agent.ts.
// ─── Tunnel startup (optional) ──────────────────────────────── // ─── Tunnel startup (optional) ────────────────────────────────
// Start ngrok tunnel if BROWSE_TUNNEL=1 is set. Uses the dual-listener // Start ngrok tunnel if BROWSE_TUNNEL=1 is set. Uses the dual-listener
// pattern: bind a dedicated tunnel listener on an ephemeral port and // pattern: bind a dedicated tunnel listener on an ephemeral port and

View File

@ -3,8 +3,8 @@
* sidebar. Translates the phoenix gbrowser PTY (cmd/gbd/terminal.go) into * sidebar. Translates the phoenix gbrowser PTY (cmd/gbd/terminal.go) into
* Bun, with a few changes informed by codex's outside-voice review: * Bun, with a few changes informed by codex's outside-voice review:
* *
* - Lives in a separate non-compiled bun process from sidebar-agent.ts so * - Lives in a separate non-compiled bun process from the browse daemon so
* a bug in WS framing or PTY cleanup can't take down the chat path. * a bug in WS framing or PTY cleanup can't take down the command surface.
* - Binds 127.0.0.1 only never on the dual-listener tunnel surface. * - Binds 127.0.0.1 only never on the dual-listener tunnel surface.
* - Origin validation on the WS upgrade is REQUIRED (not defense-in-depth) * - Origin validation on the WS upgrade is REQUIRED (not defense-in-depth)
* because a localhost shell WS is a real cross-site WebSocket-hijacking * because a localhost shell WS is a real cross-site WebSocket-hijacking

View File

@ -2,8 +2,7 @@
* gstack browse Side Panel * gstack browse Side Panel
* *
* Terminal pane (default): live claude PTY via xterm.js, driven by * Terminal pane (default): live claude PTY via xterm.js, driven by
* sidepanel-terminal.js. The chat queue + sidebar-agent.ts were ripped * sidepanel-terminal.js.
* in favor of the interactive REPL no more one-shot claude -p.
* *
* Debug tabs (behind the `debug` toggle): activity feed (SSE) + refs + * Debug tabs (behind the `debug` toggle): activity feed (SSE) + refs +
* inspector. Quick-actions toolbar (Cleanup / Screenshot / Cookies) * inspector. Quick-actions toolbar (Cleanup / Screenshot / Cookies)
@ -994,8 +993,7 @@ inspectorSendBtn.addEventListener('click', async () => {
} }
// Inject into the running claude PTY so the user can ask claude to act // Inject into the running claude PTY so the user can ask claude to act
// on the inspector data. Replaces the old `sidebar-command` route which // on the inspector data.
// spawned a one-shot claude -p (sidebar-agent.ts is gone).
// //
// Pre-scan via /pty-inject-scan before injection (D6, closes #1370). // Pre-scan via /pty-inject-scan before injection (D6, closes #1370).
// gstackScanForPTYInject is async; gstackInjectToTerminal stays sync. // gstackScanForPTYInject is async; gstackInjectToTerminal stays sync.
@ -1022,9 +1020,6 @@ inspectorSendBtn.addEventListener('click', async () => {
* "Cleanup" injects a prompt into the running claude PTY. claude takes the * "Cleanup" injects a prompt into the running claude PTY. claude takes the
* prompt, snapshots the page, hides ads/banners/popups, leaves article * prompt, snapshots the page, hides ads/banners/popups, leaves article
* content. The user watches it happen in the Terminal pane. * content. The user watches it happen in the Terminal pane.
*
* Replaced the old chat-queue path (sidebar-agent.ts spawning a one-shot
* claude -p) we have a live REPL now, so route through that instead.
*/ */
async function runCleanup(...buttons) { async function runCleanup(...buttons) {
buttons.forEach(b => b?.classList.add('loading')); buttons.forEach(b => b?.classList.add('loading'));