| Age | Commit message (Collapse) | Author |
|
Remove browser instruction text repeated across tool descriptions, parameter schemas, tool
guidelines and the global browser prompt. Tool names, schemas, handlers and concurrency are
unchanged; shared recovery and ownership guidance is now available before and after the browser
connects, and the prompt names which tools are serialized versus observations.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
|
Adds a manifest guard (scripts/check-manifest.ts + test/util/manifest-test.ts) so npm run check
fails if a host-provided package such as typebox lands in dependencies, plus a daemon-startup
troubleshooting section in the skill.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
|
persistState appended a browser-harness-state entry on every ownership callback, session_tree
and shutdown, so the append-only session file and Pi Web's "+N custom" chain grew without bound.
A snapshot is now written only when it differs from the last one persisted or restored.
Fixes #26
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
|
node_modules (#28)
Inside a Bun-compiled pi binary (pi.exe), createRequire().resolve("tsx/cli") is rooted at the
embedded virtual filesystem and cannot see the extension's real node_modules, so browser_setup
failed instantly. The resolver result is now checked on disk and, when it fails, the tsx CLI is
found by walking up the node_modules chain. Under Bun the daemon runs on `node` from PATH
(process.execPath is the pi binary itself); PI_BROWSER_NODE overrides it.
Fixes #27
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
|
Two independent Windows/production defects reported in #22.
browser_run_script rejected every absolute path on Windows. The allowed-root
check compared with a hardcoded `${root}/`, but path.resolve returns
backslash-separated paths there, so a file inside os.tmpdir() was refused even
though the rejection message listed tmpdir as allowed. Decide containment with
path.relative instead: it is separator-agnostic and carries each platform's own
case rules, so it also accepts a differing drive-letter case that a path.sep
patch would still reject. Escapes via `..` and sibling directories sharing the
root's name prefix stay rejected, as before.
The daemon could not start under `npm omit=dev`: it is spawned through tsx,
which was declared only as a dev dependency. Moving tsx to dependencies is not
enough on its own — pi installs extensions into one flat hoisted tree, so tsx
lands beside the package and the fixed <pkg>/node_modules/.bin/tsx lookup still
misses, falling through to an `npx tsx` that resolves against the wrong cwd and
dies against stdio: "ignore". Resolve the CLI entry with Node's own resolver,
which walks up to the hoisted copy. That removes the shell entirely, and with it
the Windows .cmd shim, windowsVerbatimArguments, and the manual quoting that
paths containing spaces needed.
Both fixes live in platform-branching code, so their tests are registered in the
three-OS CI matrix that already exists for that purpose — a Linux-only run
cannot exercise either one.
|
|
Four routes coexisted: cdpCall, a raw client.session().call, a session
held in a local and called directly, and client.evaluateJs — and five
files mixed two or three of them. The CdpError-to-ToolErr mapping was
hand-copied at nine sites, plus a private copy in isolated-tab.ts.
domains/cdp-call.ts now owns all of it: cdpErrToToolErr, cdpCall,
cdpCallOnTarget, cdpCallBrowser, evalJs. Every other file under
src/domains goes through them, which also removes a pile of
`if (!r.success) return err({ kind: "cdp_error", ... })` boilerplate in
favour of returning the failed Result as-is.
Two scanner rules keep it that way, scoped to src/domains/ so the layer
that legitimately owns the session — cdp/ and client.ts — is unaffected.
Scoping is new to the rule model; without it the rules fired on the
transport itself.
One deliberate behaviour change: a CDP timeout now surfaces as
kind "timeout" rather than "cdp_error". isolated-tab.ts already did this
and the other 13 sites flattened it; the shared mapper uses the honest
label rather than preserving the mislabel.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Both were documented and inert.
--browser-debug-clicks was registered as a flag and never read; the only
real switch was the undocumented BH_DEBUG_CLICKS env var. The flag now
feeds util/debug.ts, which the click tool consults, and the env var keeps
working as an equivalent.
BU_CDP_WS was read in the client and handed to transport.connect(), but
the daemon transport discards that argument and always dials the daemon
socket — and the daemon's own discovery never looked at the variable, so
setting it did nothing. The override now lives in discoverEndpoint, which
is what both processes go through, and the client's redundant read is
gone. The variable must be set before the daemon spawns.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The scanner banned as any, as {, as unknown as, non-null assertions and
ts-ignore, but said nothing about `as SomeNamedType` — so a new cast over
CDP or JSON data could be added and CI would stay green, which is the exact
regression the refactor exists to prevent. It also could not see a cast
whose target type sits on the following line.
Adds named-cast, function-cast and trailing-cast. All six surviving
assertions in src/ are now visible to the scanner and individually
exempted with a stated reason, so the allowlist is the complete inventory.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The tool gate ran access() on DAEMON_SOCKET_PATH, which on Windows is the named
pipe \\.\pipe\pi-browser-daemon. Named pipes are not filesystem entries, so
access() always fails there and every browser_* call returned "Browser harness
not initialized" no matter how healthy the daemon was. spawn.ts already skipped
the identical check for this reason; that knowledge is now one shared
daemonSocketMayExist() used by both call sites, with platform and path as
parameters so the branch is testable off-platform.
Report: docs/superpowers/reports/2026-07-26-bug-audit.md#f1
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
A clean tsc does not prove the absence of casts. This scans src/ for
as any, ts-ignore, non-null assertions, inline object casts, and double
casts, wired into a new npm run check that CI runs as a single step.
The escape hatch is an external allowlist module rather than an inline
comment marker: comments are not used in this codebase, and an entry
carries a count, so adding a new cast to an already-exempted file still
fails until someone raises a number that shows up in the diff. Exactly
one exemption is needed today, for the dynamic sharp import.
scripts/ was gitignored wholesale, so the two new files are un-ignored
individually and everything else dropped there stays ignored.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Types the event-queue iterator's TReturn as undefined so the terminal
result needs no cast; declares the isError field the pi runtime reads by
duck-typing; binds socket to a local const in the daemon transport;
replaces the @ts-ignore on the optional sharp import with a non-literal
specifier so src/ still typechecks where sharp was not installed.
Adds src/util/guards.ts as the single home for narrowing over unknown —
isRecord plus scalar/array readers — and converts the two private
isObject copies in cdp/types.ts and daemon/protocol.ts along with every
inline `as Record<string, unknown>` in domains/js.ts and domains/render.ts.
Gives Accessibility.getFullAXTree a real result schema, closing the last
unvalidated CDP boundary: only nodeId is required, every object carries
additionalProperties, and each AX value stays Unknown, so the tree
decodes as a whole or fails as a whole rather than being filtered.
Guards the five tool-result `details` readers, the daemon attach
response, the persisted session state, the browser-namespace flag, and
the fs errno narrowing, each feeding an already-present failure path.
The daemon-transport socket binding is not a bug fix: send() runs
synchronously inside sendWithTimeout's promise executor, so socket cannot
be reassigned between the check and the write. It removes the assertion
and makes the write immune to a later refactor that adds an await.
Two assertions survive by design and are documented in the task report:
the never->unknown re-widening in util/tool.ts, which the type-erased
tool registry makes unavoidable, and the AsyncFunction constructor lookup
in domains/js.ts, which JavaScript exposes no typed binding for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Ten sites cast or implicitly typed JSON.parse output; JSON.parse("null")
would have satisfied all of them. parseJson returns a Result and validates
against a typebox schema. js.ts keeps unknown — user script output has no
fixed shape. daemon/bridge.ts assigned JSON.parse straight to CdpRawMessage
with no guard at all; it now uses isCdpRawMessage like the transport does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|