mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-13 08:29:04 +02:00
v1.81.0.0 feat: Aside is the browser gstack drives first; every browsing skill, the PDF/diagram renderer, and web research; the bundled browser stays the automatic fallback (#2810)
* feat(aside): browser-driver contract, cookbook, research and fallback resolvers
{{ASIDE_SETUP}} (readiness probe + ten rules for driving the user's real browser), {{ASIDE_COOKBOOK}} (script shapes verified live against Aside CLI 1.26: one flow per aside repl script, CDP console hook before navigation, evidence lines, session-directory artifact handoff, GSTACK_STEP_OK sentinel), {{ASIDE_RESEARCH}} (research through aside exec, WebSearch when Aside is absent, knowledge otherwise) and {{BROWSE_FALLBACK}} (the fifteen-row Aside-step to $B-command table plus the rules that differ, so every browsing skill keeps working on gstack's own headless browser). test/aside-driver.test.ts pins the sentences and asserts every browsing skill carries the Aside block followed by the fallback; test/helpers/aside-available.ts is the shared live-Aside probe.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* feat(render): Aside-first local-HTML renderer with the bundled browser as fallback
lib/aside-render.ts serves the HTML's directory on loopback (Aside refuses file:// URLs), opens it with waitUntil load, prints through CDP Page.printToPDF so tagged output, outlines, header/footer templates and page numbers survive, emulates device metrics for sized screenshots, and writes in-page evaluations to files; when Aside is absent it runs the same spec through the browse daemon (newtab, load, js, pdf, screenshot, closetab) and reports ENGINE=aside|browse. bin/gstack-render.ts is the CLI skill templates call. lib/claude-bin.ts and lib/error-handling.ts become the canonical copies (browse/src re-exports them).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(browse): /browse drives Aside first, with the $B reference behind the fallback
Contract, cookbook, mode choice (aside repl by default, aside exec for reading), report format, the fallback section, and the full command reference carved on demand.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(qa): /qa and /qa-only drive Aside, fall back to $B
QA_METHODOLOGY runs every phase as Aside scripts (orient, explore, document, re-test, mobile viewport via CDP emulation, links via HEAD fetch); the authenticate phase is 'you are already signed in'; a 13th rule requires consent before mutating actions on non-local targets; the fallback section translates each step onto $B. The qa E2E tests run on whichever engine is present.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(design): design-review, design-consultation, design-shotgun, plan-design-review, design-html drive Aside
Design-system extraction is one script printing FONTS/COLORS/HEADINGS/TOUCH_TARGETS/NAV; competitor research confirms the exact URLs before opening them in the real browser and runs on the bundled browser when Aside is absent; design-html's viewport screenshots, sketches and comparison boards render through gstack-render.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(deploy): benchmark, canary, land-and-deploy Step 7, devex-review drive Aside
One aside repl script per page prints NAV/PAINT/LCP/RESOURCES/SCRIPTS/CSS/SUMMARY (benchmark), CONSOLE_ERRORS/NAV/TEXT + screenshot (canary, re-run every 60s), and the post-deploy check reads responseStatus from the navigation entry; each carries the $B fallback.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* feat(third-party-actions): Aside is the recommended driver; gstack's visible browser stays the fallback
The readiness probe is lifted from {{ASIDE_SETUP}} at gen time (byte-identity pinned) and rule 3 points at browse/SKILL.md for how to drive; the consent question offers Aside first and gstack's own visible browser (handoff/resume for sign-in) as the fallback, as v1.72 framed it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(scrape): /scrape reads pages through Aside; the browser-skills runtime rides the fallback
Look-then-extract scripts build the JSON inside the page and print it between JSON_START/JSON_END; aside exec for fuzzy intents; on the $B fallback the browser-skills match/prototype flow and /skillify apply as before.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(make-pdf): print through Aside first, the bundled browser otherwise
asideClient.ts replaces the direct $B client with one render() call per PDF (the exact option mapping the browse pdf command had: paper, margins, header/footer/page numbers, tagged, outline, printBackground, preferCSSPageSize, Paged.js wait); the diagram pre-pass, oversized-image downscale and DOCX rasters each run as one render script with per-fence try/catch; exit 4 now means no browser is available and names both remedies; $P setup reports which engine it found. The e2e gates run on whichever engine is present, so the Linux lane exercises the fallback.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(diagram): the triplet is one gstack-render call
SVG, PNG and excalidraw from one invocation over the content-addressed bundle staged under /tmp/gstack-render; every diagram type gets an excalidraw export; gstack-render picks the engine and prints ENGINE=; the diagram E2E gates on either engine.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* feat(research): web research runs in Aside first, WebSearch second
The planning, review, design, security and investigate skills research through {{ASIDE_RESEARCH}}; WebSearch stays in allowed-tools as the fallback; testing.ts's bootstrap step follows; skeleton ceilings ratcheted for the research block.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* feat(setup,gen-skill-docs): prune renders of skills that no longer exist
setup gains _prune_stale_generated for every host tree and the doc generator removes gstack-* output dirs it did not write, so a skill removed from the source tree can never linger in an install.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test: registries, budgets and suite reconciled for Aside-first with the $B fallback
Touchfiles + E2E tiers gain the Aside keys, coverage matrix and eval baselines updated, size budget re-baselined to parity-baseline-v1.80.0.0.json (the contract plus fallback ride in every browsing skill), parity ceilings ratcheted with measured values, LLM-judge prompts and the E2E fixtures speak Aside-first, browse-fallback.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs: Aside first, gstack browser fallback
README, BROWSER.md, docs/, CONTRIBUTING, CLAUDE.md, ARCHITECTURE, AGENTS.md, TODOS and the root router describe the one product story: Aside is the browser gstack drives first; the bundled headless browser is the automatic fallback (Linux, Windows, app closed) where cookie import, GStack Browser, pair-agent and browser-skills still apply.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* chore: regenerate SKILL.md docs, llms.txt, agents digest, ship goldens, context-budget fixture
bun run gen:skill-docs over the templates; goldens re-rendered; context-budget ceilings recaptured.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* v1.80.0.0: Aside is the browser gstack drives first; the bundled browser is the fallback
MINOR: new capability across ten skills, the renderer and research; nothing removed. CHANGELOG release summary + itemized changes; VERSION 1.80.0.0; package.json 1.80.0.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs(todos): file non-Claude host ownership-gate and version-heading pin follow-ups
Two follow-ups from the /plan-ceo-review + /plan-eng-review pass on merging
PR #2804 with main's v1.80.0.0 ownership gate: bring the Codex/Factory/
OpenCode/Cursor/Kiro copy loops and the stale-render prune under the
.gstack-owned marker rule, and a free test pinning that the CHANGELOG top
heading equals VERSION (the collision that git cannot see).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix: pre-landing review fixes for the Aside-first branch
Review army + adversarial passes (Claude and Codex) on the merged branch:
setup
- _prune_stale_generated scans the host dirs too (the generator already
removed the render before setup ran, so the host branch was dead), skips
symlinks in the render tree (rm -rf on a slash-terminated link empties its
target), removes a host symlink only when it resolves into gstack, cleans a
bannered real dir through _cleanup_weak_dir, recognizes frontmatter-renamed
skills, and logs through log. The always-run codex render passes every host
dir that may link to it.
- NEEDS_BUILD checks all three binaries (with $_EXE) and lib/ sources; the
browser hint and the bootstrap summary honor GSTACK_SKIP_ASIDE, treat a
requested skip as a request, and derive one skill list.
lib/aside-render.ts + bin/gstack-render.ts
- The loopback server carries a per-render secret path, checks containment on
the real path (symlink escapes are 403), and rejects malformed encoding.
- Inline eval results are one base64 line, so page text cannot forge
ASIDE_DIR= or the sentinel; the last ASIDE_DIR wins.
- runProc escalates SIGTERM to SIGKILL, bounds every wait, and clears every
timer (an uncleared one kept gstack-render alive after printing OK).
- renderTmpDir refuses a shared /tmp name owned by someone else; the work dir
and server are created inside try; goto's budget follows the render budget.
- probeAside classifies a present-but-failing CLI as ASIDE_NOT_RUNNING like
the skills' bash probe; render() retries on gstack's own browser when Aside
could not start or its private CDP bridge is gone (never on a page error
or a timeout of a running script); the CLI reports the engine that actually
rendered, exits 0 on --help, rejects non-numeric flags, documents
--wait-timeout, fences EVAL/PAGE_ERRORS as untrusted content, and names the
daemon's cookie-import JS lock remedy.
- The browse path passes --scale only when asked (a scale change rebuilds
the daemon context) and restores the viewport after a sized screenshot.
resolvers / templates
- The bash probe honors GSTACK_SKIP_ASIDE and has a perl deadline on stock
macOS; .local is no longer LOCAL (mDNS); same-origin filters compare parsed
origins; link status is HEAD-checked only on LOCAL targets; every
aside exec goes through the receipted _aside_exec prelude
({{ASIDE_EXEC_PRELUDE}}), including nine template blocks that called it
bare; the design sketch and diagram staging use private directories.
- The generator prunes only bannered renders and never a host whose
generation failed.
Docs, stale comments and dead code cleaned; goldens re-rendered; tests
updated and added for every behavior above.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test: coverage for the render CLI, setup rebuild check, make-pdf exit codes, and prose $B spans
New free tests from the ship coverage audit: test/gstack-render-cli.test.ts
(argv guards, --help, output contract with a fake daemon, failure and
serve-root paths, no-browser case, prompt exit), test/setup-needs-build.test.ts
(every binary and source set flips NEEDS_BUILD, Windows suffixes),
make-pdf/test/cli-exit-codes.test.ts and setup-smoke.test.ts (error to exit
code mapping, runSetup stages, renderPdf's engine), and prose-span cases for
extractBrowseCommands in test/skill-parser.test.ts.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs: CHANGELOG and TODOS cover the review fixes (v1.81.0.0)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs: sync project docs with the v1.81.0.0 review fixes
BROWSER.md, ARCHITECTURE.md, CONTRIBUTING.md, README.md, CLAUDE.md,
docs/TESTING_INTERNALS.md and docs/PROJECT_STRUCTURE.md now describe the
shipped renderer and setup: the loopback render server's per-render secret
path and real-path containment, ENGINE= naming the engine that actually
rendered (mid-run retry on gstack's own browser), EVAL/PAGE_ERRORS fenced as
untrusted content, --wait-timeout and the CLI's argv guards, the receipted
_aside_exec prelude ({{ASIDE_EXEC_PRELUDE}} in the placeholder table), the
LOCAL host rule without .local, LOCAL-only HEAD checks in the links script,
GSTACK_SKIP_ASIDE across probe/renderer/setup, the ownership-gated
retired-skill prune, the widened NEEDS_BUILD check, and the new free tests
(gstack-render-cli, setup-prune-stale-generated, setup-browser-hint,
setup-needs-build, make-pdf cli-exit-codes and setup-smoke).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs: CHANGELOG states the precise mid-run retry rule
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(test): skill-e2e-bws slices the $B setup block from the Browser fallback section
browse/SKILL.md no longer has '## SETUP' / '## Core QA Patterns' (Aside is the
primary driver; the $B block moved under 'Browser fallback'), so the gate test
sliced an empty block and handed the agent nothing to run. Anchor on
'### Find the `$B` binary' up to the next heading. 7/7 pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(test): gate POSIX-only fixtures off Windows
windows-free-tests: the gstack-render CLI tests drive a shebang fake browse
that CreateProcess cannot exec, and two NEEDS_BUILD cases assert an execute
bit and a bare-name miss that MSYS bash does not have (test -x ignores mode
bits and resolves design -> design.exe). Those describes and cases now
self-skip on win32; argument guards, --help, the no-browser case, and every
other rebuild-check case still run there.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(render): runProc waits for the exit code until the kill deadline; newtab retries once on a cold daemon
A process whose pipes have reached EOF is exiting, but runProc gave the exit
code only five seconds to arrive and then returned null, which run() reports
as a failed command. Under CI's six-shard load one such render failed with the
artifact already written. The SIGTERM/SIGKILL timers already bound the wait,
so the exit race now runs to the kill deadline.
The first CLI call auto-starts the browse daemon; on a cold start it can
answer 'Unable to connect' once while the server is still coming up. That
single case is retried after 1.5s; every other newtab failure is not.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(aside-render): warm the daemon before live fallback cases; failures name the render error
- Live fallback cases run 'goto about:blank' up to twice before asserting and
skip (never fail) when the daemon cannot come up.
- expectOk() puts r.error and the browse transcript into the assertion so a
failed render is diagnosable from the CI log.
- The argv-contract cases dump the fake's log on a miss.
- File default timeout is 30s: the subject is the CLI contract, not latency.
- Two cases pin the cold-daemon newtab retry and that other errors are not
retried.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs: CHANGELOG notes the cold-start tolerance of the bundled-browser renderer
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Sina <sdroid674+github@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
Sina
parent
c241216637
commit
0530392821
@@ -10,15 +10,39 @@ Waves B–E2 of that plan are scheduled work, not TODOs; these are the items the
|
||||
reviews deliberately deferred, each with rationale:
|
||||
|
||||
- **Shared `_gstack_owned_link` helper** — the ownership gate now exists in
|
||||
six places (setup's `_claude_entry_is_ours` / `_claude_entry_owned_strongly`
|
||||
seven places (setup's `_claude_entry_is_ours` / `_claude_entry_owned_strongly`
|
||||
used by link_claude_skill_dirs and _install_alias_skill_md, while
|
||||
cleanup_old_claude_symlinks and cleanup_prefixed_claude_symlinks inline their
|
||||
own marker/cmp/banner chain and readlink `case`; bin/gstack-relink
|
||||
`_entry_is_ours`; bin/gstack-uninstall's per-entry loop). Extract one sourced
|
||||
`_entry_is_ours`; bin/gstack-uninstall's per-entry loop; and, since the
|
||||
Aside-first wave, setup's `_prune_stale_generated`, which removes a retired
|
||||
host entry behind the banner-only `_owned_for_windows_refresh` check —
|
||||
symlinks outright, real dirs through `_cleanup_weak_dir` — and must route
|
||||
through the same helper). Extract one sourced
|
||||
helper so the destructive-path guard cannot drift, and while there: make the
|
||||
`.gstack-owned` marker's recorded install path load-bearing (today any marker
|
||||
counts, so a Windows fork copy carrying gstack's generated header is still
|
||||
treated as ours on a mode flip). Effort S. Priority P2. Depends on: none.
|
||||
- **Non-Claude host loops + stale-render prune under the marker rule** — the
|
||||
Codex, Factory, OpenCode, Cursor and Kiro link loops (setup's
|
||||
`link_*_skill_dirs`, the `_owned_for_windows_refresh` gate at each) still
|
||||
`rm -rf` + re-copy a REAL host directory on banner-only proof, and
|
||||
`_prune_stale_generated` routes a bannered real dir through
|
||||
`_cleanup_weak_dir` only because those hosts never receive a `.gstack-owned`
|
||||
marker. Write the marker for every host's copy install, then switch all five
|
||||
loops and the prune to the strong/weak split the Claude host and
|
||||
`gstack-relink` already use (#2119). Effort M (human ~2 days / CC ~1h).
|
||||
Priority P2. Depends on: the shared `_gstack_owned_link` helper above (same
|
||||
code motion; do them together).
|
||||
- **Free test: CHANGELOG top heading equals VERSION** — a fork PR that claimed
|
||||
a version main had since shipped auto-merged VERSION, package.json and the
|
||||
digest header with no git conflict (both sides identical); only
|
||||
`bin/gstack-next-version` and the PR-time queue check saw it. A tiny free
|
||||
test asserting the first `## [X]` in CHANGELOG.md equals VERSION would make
|
||||
the collision a red test on any branch. Decide first whether mid-branch
|
||||
VERSION bumps without a CHANGELOG entry are a workflow the suite must
|
||||
tolerate (`/ship` writes both in one step, so probably not). Effort S
|
||||
(human ~2h / CC ~10min). Priority P3. Depends on: none.
|
||||
- **Config-key reader tripwire** — `transcript_ingest_mode=off` sat unread for
|
||||
months while setup-gbrain advertised it. A free test that asserts every key
|
||||
in bin/gstack-config's default table is read by at least one binary (or is
|
||||
@@ -511,8 +535,13 @@ references — include it in this fix's coverage list.
|
||||
|
||||
### QA logged-in-evidence path via Aside (Phase 2)
|
||||
|
||||
**What:** Consent-gated `aside repl` as an alternative evidence source in /qa,
|
||||
/qa-only, and /browse when cookie-import can't reach a session (SSO,
|
||||
**Landed (Aside-first):** `aside repl` is now the PRIMARY evidence source for
|
||||
/qa, /qa-only, and /browse whenever Aside is installed and running; gstack's own
|
||||
browser (with cookie import) is the automatic fallback when it is not. Kept for
|
||||
the rationale; the remaining loose ends are under "Aside-first follow-ups".
|
||||
|
||||
**What:** Consent-gated `aside repl` as the evidence source in /qa, /qa-only,
|
||||
and /browse for sessions a headless browser could never reach (SSO,
|
||||
device-bound auth, Safari-side logins Chromium export can't see).
|
||||
|
||||
**Why:** Fills the exact gap `docs/designs/CHROME_VS_CHROMIUM_EXPLORATION.md`
|
||||
@@ -3824,7 +3853,7 @@ path to the fixture during the run.
|
||||
|
||||
**What:** Cache rendered diagram SVG/PNG in `~/.gstack/cache/diagram-render/`,
|
||||
keyed on `sha256(fence source + bundle version + render options)`, so repeat
|
||||
`make-pdf` runs skip the browse render tab for unchanged diagrams.
|
||||
`make-pdf` runs skip the render (Aside or the fallback browse tab) for unchanged diagrams.
|
||||
|
||||
**Why:** Every run currently re-renders every fence (~150-300ms each). Docs with
|
||||
10+ diagrams pay seconds per iteration during write-preview loops. Codex
|
||||
@@ -4006,3 +4035,189 @@ globs (D). What remains, re-filed individually:
|
||||
bounded (GSTACK_PLAYWRIGHT_INSTALL_TIMEOUT, default 600s), lock contention
|
||||
is a reason code, skills always register. Close #2233, #1900, #1901, #1902,
|
||||
#913 with the receipt (test/setup-playwright-best-effort.test.ts).
|
||||
|
||||
## Aside-first follow-ups (filed when Aside became the primary browser)
|
||||
|
||||
Every gstack skill that touches a web page drives the Aside AI browser first
|
||||
(`scripts/resolvers/aside.ts` is the contract; `lib/aside-render.ts` /
|
||||
`bin/gstack-render.ts` render local HTML through it; `{{ASIDE_RESEARCH}}` runs
|
||||
web research through it). gstack's own browser engine — the `browse` daemon,
|
||||
GStack Browser headed mode, cookie import, `/pair-agent`, browser-skills /
|
||||
`/skillify` — is kept as the automatic fallback whenever Aside is not installed
|
||||
or not running (Linux, Windows, a closed Aside app), and web research falls
|
||||
back to the WebSearch tool when the host provides one. Nothing was removed.
|
||||
Loose ends:
|
||||
|
||||
### P1: Aside-first fallback parity — keep the `$B` equivalence table in sync with the cookbook
|
||||
|
||||
**What:** The fallback block (`BROWSE_FALLBACK` in the browser resolvers) maps
|
||||
each verified `aside repl` cookbook shape (read a page, drive a flow, annotated
|
||||
screenshot, responsive captures, links + status, performance, PDF, element
|
||||
screenshot, `aside exec` research) to its `$B` equivalent so a skill produces
|
||||
the same evidence lines on either path. Every time a cookbook shape is added,
|
||||
renamed, or changes its output labels (`CONSOLE_ERRORS=`, `DIFF_START`,
|
||||
`ASIDE_DIR=`, `GSTACK_STEP_OK`), update the table in the same commit and add a
|
||||
pin in `test/aside-driver.test.ts` that the two lists name the same shapes.
|
||||
|
||||
**Why:** A skill that reads `DIFF_START` on the Aside path and gets nothing on
|
||||
the fallback path "fixes" the missing output blindly. Parity is the whole point
|
||||
of keeping the engine; a silent gap is worse than no fallback.
|
||||
|
||||
**Effort:** S per change (human ~half day, CC ~15min). **Priority:** P1. **Depends on:** nothing.
|
||||
|
||||
### P2: Aside CLI 1.26 lacks subcommands Aside's own skill doc lists
|
||||
|
||||
**What:** Aside's skill doc lists `session`, `memory`, `skills`, `host`, and
|
||||
`--permission`; Aside CLI 1.26 has none of them (`aside --help`). Skills must
|
||||
not depend on them until the CLI ships them. Re-probe on each Aside release;
|
||||
when they land, evaluate `session` for multi-script flows and `--permission`
|
||||
for the mutating-action consent gate.
|
||||
|
||||
**Why:** A skill written against the doc instead of the binary dies at runtime
|
||||
on an unknown-command error the agent will then try to "fix" blindly.
|
||||
|
||||
**Effort:** S (human ~half day, CC ~20min per re-probe). **Priority:** P2. **Depends on:** Aside releases.
|
||||
|
||||
### P2: Aside E2E tests run only where Aside is installed
|
||||
|
||||
**What:** The Aside-only E2E lane — `test/skill-e2e-aside.test.ts`, the Aside
|
||||
qa/design cases, the live render in `test/aside-render.test.ts` — self-skips
|
||||
when `aside` is absent (`asideAvailable()` in `test/helpers/aside-available.ts`),
|
||||
so CI's Linux runners never drive Aside; the make-pdf and /diagram render gates
|
||||
already run there on the browse binary. The Aside path runs only on macOS dev
|
||||
machines.
|
||||
Evaluate a self-hosted macOS runner (or a scheduled job on a Mac mini) that
|
||||
runs the Aside lane weekly under the same hermetic env as the other E2E lanes.
|
||||
|
||||
**Why:** A browser contract nobody runs in CI drifts silently — exactly the
|
||||
class `test/aside-driver.test.ts` pins statically but cannot prove live.
|
||||
|
||||
**Effort:** M (human ~2 days, CC ~1h plus the machine). **Priority:** P2. **Depends on:** a macOS host with Aside signed in.
|
||||
|
||||
### P3: Evaluate `aside mcp` for multi-step flows
|
||||
|
||||
**What:** `aside repl` is one flow per script — a fresh session per call, tabs
|
||||
closed when it ends. `aside mcp` keeps a persistent REPL page across calls.
|
||||
Once the CLI stabilizes, measure whether an MCP path makes long QA audits
|
||||
cheaper (no re-navigation per script) without losing the "leave the browser as
|
||||
you found it" guarantee.
|
||||
|
||||
**Why:** Re-navigating from the URL per script is the honest tax of the current
|
||||
model; a persistent page could cut it but adds a session that must be cleaned up.
|
||||
|
||||
**Effort:** M (human ~2 days, CC ~1h). **Priority:** P3. **Depends on:** Aside CLI stability.
|
||||
|
||||
### P3: Eval that skills treat `aside exec` output as untrusted
|
||||
|
||||
**What:** `aside exec "<task>"` returns another agent's answer. Add an LLM-judge
|
||||
or E2E eval that plants an instruction inside an `aside exec` result and checks
|
||||
the skill takes syntax from it, never scope, permissions, or consent.
|
||||
|
||||
**Why:** The rule is pinned as prose; nothing yet proves a skill obeys it when
|
||||
the injected text arrives through the one channel that reads like a colleague.
|
||||
|
||||
**Effort:** S (human ~1 day, CC ~30min). **Priority:** P3. **Depends on:** the Aside E2E lane above.
|
||||
|
||||
### P1: make-pdf renders user documents inside the real browser profile — add a CSP
|
||||
|
||||
**What:** `/make-pdf` prints markdown-derived HTML through Aside (the user's
|
||||
signed-in browser) on a `127.0.0.1` origin. The only barrier between a hostile
|
||||
document (a README from a cloned repo) and script execution in that profile is
|
||||
the regex sanitizer in `make-pdf/src/render.ts`, whose header assumes marked
|
||||
output is never malformed — raw-HTML passthrough breaks that assumption. Inject
|
||||
gstack's own CSP `<meta>` into the print template (`default-src 'none';
|
||||
img-src data: 'self'; style-src 'unsafe-inline' 'self'; font-src data: 'self';
|
||||
script-src 'nonce-<per-render>'` for Paged.js), since user `<meta>` is stripped
|
||||
and gstack's is not; alternatively keep make-pdf on the bundled engine by
|
||||
default.
|
||||
|
||||
**Why:** Under the old cookieless headless engine a sanitizer bypass was
|
||||
near-harmless; in the real profile it is a CSRF-class primitive. Cross-model
|
||||
finding (Claude adversarial + Codex).
|
||||
|
||||
**Effort:** M (human ~2 days, CC ~1h). **Priority:** P1. **Depends on:** none.
|
||||
|
||||
### P1: diagram pre-pass buffers every oversized image before downscaling
|
||||
|
||||
**What:** `make-pdf/src/diagram-prepass.ts` caps each image at 64 MB but keeps
|
||||
every pending buffer in `downscales` and duplicates it as base64 before the
|
||||
batch runs; a document referencing a few dozen large images can take gigabytes.
|
||||
Cap total pending bytes (e.g. 256 MB) and process in bounded batches, or
|
||||
downscale sequentially.
|
||||
|
||||
**Why:** A hostile or merely image-heavy document crashes the tool instead of
|
||||
degrading.
|
||||
|
||||
**Effort:** S (human ~1 day, CC ~30min). **Priority:** P1. **Depends on:** none.
|
||||
|
||||
### P2: fallback renders die after any cookie import in the daemon's lifetime
|
||||
|
||||
**What:** `renderWithBrowse` drives readiness and evals through `$B js`, and the
|
||||
daemon's cookie-import JS lock (`browse/src/read-commands.ts`) refuses `js` on
|
||||
every origin outside the imported set — `127.0.0.1` included, forever (the set
|
||||
is add-only). The renderer now names the remedy (`$B stop`), but the real fix is
|
||||
a fresh incognito context for local-HTML renders, or a loopback exemption once
|
||||
its threat model is written down.
|
||||
|
||||
**Why:** On Linux/Windows (no Aside) one `/setup-browser-cookies` run makes
|
||||
every later `/diagram` and `/make-pdf` render fail.
|
||||
|
||||
**Effort:** M (human ~2 days, CC ~1h). **Priority:** P2. **Depends on:** none.
|
||||
|
||||
### P2: carve the Aside contract + fallback block into one shared section
|
||||
|
||||
**What:** `{{ASIDE_SETUP}}` (~5.6 KB) plus `{{BROWSE_FALLBACK}}` (~4.1 KB) are
|
||||
rendered verbatim into ten browsing skills (~97 KB of identical prose loaded on
|
||||
every invocation). Keep the probe and the three decision steps inline; move
|
||||
"Rules for driving a real browser" and the Aside-to-`$B` translation table into
|
||||
one carved reference (the `browse/sections/command-list.md` pattern), then
|
||||
re-run `capture-context-budget.ts` so the ceilings ratchet back down.
|
||||
|
||||
**Why:** Every skill invocation pays for prose that is skill-invariant.
|
||||
|
||||
**Effort:** M (human ~2 days, CC ~1h). **Priority:** P2. **Depends on:** none.
|
||||
|
||||
### P2: `$B js` / `$B eval` output is not wrapped in the untrusted envelope
|
||||
|
||||
**What:** `js` and `eval` are not in `PAGE_CONTENT_COMMANDS`
|
||||
(`browse/src/commands.ts`), so page-controlled return values reach the agent
|
||||
unfenced while the fallback table routes exactly the page-controlled reads
|
||||
through them. `gstack-render` now fences its own `EVAL`/`PAGE_ERRORS` lines and
|
||||
the fallback prose says `$B js` is unwrapped; the durable fix is to add both
|
||||
commands to the envelope set.
|
||||
|
||||
**Why:** A hostile page can deliver injection text through the one channel the
|
||||
skills were told is fenced.
|
||||
|
||||
**Effort:** S (human ~half day, CC ~15min). **Priority:** P2. **Depends on:** none.
|
||||
|
||||
### P3: Aside-first renderer follow-ups (perf and DRY)
|
||||
|
||||
- **Readiness polling** spawns a `browse js` process every 150 ms; the daemon's
|
||||
`wait <sel>` command blocks server-side in one spawn — use it for
|
||||
`waitFor.selector`. Effort S.
|
||||
- **Bundle re-staging:** every `runScript()` batch copies the ~9 MB diagram
|
||||
bundle into a fresh mkdtemp and starts a new loopback server; stage once per
|
||||
run (content-addressed) and, on the browse engine, keep one tab across the
|
||||
fence/downscale/DOCX batches. Effort M.
|
||||
- **Probe cost:** `probeAside()` runs two blocking spawns per process and the
|
||||
engine cache is per-process; persist the outcome with a short TTL under
|
||||
`GSTACK_HOME` and lower the repl probe timeout on the code path. Effort S.
|
||||
- **DRY:** the console-error `HOOK` IIFE exists in seven copies across
|
||||
`scripts/resolvers/*.ts` and `lib/aside-render.ts` (two divergent variants);
|
||||
cookbook recipes (responsive loop, links, read-a-page) are duplicated across
|
||||
`aside.ts`, `design.ts`, `utility.ts`; the readiness probe is recovered from
|
||||
rendered markdown by regex in two places instead of a shared constant. Export
|
||||
one source for each. Effort S each.
|
||||
- **Egress scanner:** `test/egress-receipt-wiring.test.ts` scans `curl`, `git
|
||||
push`, and `fetch`; add `aside exec` as a sink class so a bare call fails CI
|
||||
the way the others do. Effort S.
|
||||
- `_browser_hint` treats any `aside` on PATH as the Aside browser (no version
|
||||
check). Effort S.
|
||||
- **`gen-skill-docs --dry-run` is not write-free for external hosts:**
|
||||
`processExternalHost` runs `mkdirSync(outputDir)` and writes
|
||||
`agents/openai.yaml` with no `DRY_RUN` guard (only SKILL.md is skipped), so a
|
||||
dry run against an empty `--out-dir` leaves 54 `openai.yaml` files behind.
|
||||
Guard both writes. Effort S.
|
||||
|
||||
**Priority:** P3. **Depends on:** none.
|
||||
|
||||
Reference in New Issue
Block a user