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
7 changes: 7 additions & 0 deletions .github/workflows/staged-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ jobs:
working-directory: apps/staged
env:
VITE_UPDATER_ENABLED: 'true'
# Optional build-time network fallbacks (see apps/staged/README.md,
# "npm registry and Node.js download sources"). Repository
# variables, not literals, so no internal URL lives in the tree;
# unset means the build has no fallback and relies on npm's own
# config and nodejs.org.
STAGED_FALLBACK_NPM_REGISTRY: ${{ vars.STAGED_FALLBACK_NPM_REGISTRY }}
STAGED_FALLBACK_NODE_DIST_URL: ${{ vars.STAGED_FALLBACK_NODE_DIST_URL }}
run: pnpm tauri build --no-sign --target "$TAURI_TARGET" --config src-tauri/tauri.release.conf.json

- name: Codesign and Notarize
Expand Down
48 changes: 48 additions & 0 deletions apps/staged/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,59 @@ name on your PATH, and **Settings -> Doctor** can install the npm-based ones
into the same Staged-private prefix.

Open **Settings -> Doctor** to verify agent checks are detected as installed.
A Claude or Codex row that shows "Managed by Staged" is running Staged's copy.
Until the first launch install has completed, a row that found another copy
on your PATH (a Homebrew or npm global install, say) says Staged's managed
install "hasn't completed yet" — not a failure; it corrects itself when the
install lands, and a **Fix** installs the managed copy right away if you would
rather not wait. If the managed install failed, the row instead warns that the
managed copy is missing, with the error code the last install attempt
recorded; **Copy details** has the full error — which names the registry or
URL it used — a hint on what to change (a blocked `registry.npmjs.org`, a
mirror without the package, an unreachable host, a wrong Node.js download
URL) and the npm registry Doctor detected. The **Node.js Runtime** row
likewise reports a failed runtime download rather than staying hidden. When
managed installs are disabled (see `STAGED_ACP_TOOLS_DIR` below), the rows say
so instead.

To develop against a local bridge checkout, export `STAGED_ACP_TOOLS_DIR`
pointing at a directory with the bridge binaries — this disables the managed
installs and resolves bridges from that directory instead.

#### npm registry and Node.js download sources

Staged does not hardcode where packages come from:

- **npm registry.** Staged passes no `--registry` of its own, so npm uses
its normal configuration (`NPM_CONFIG_REGISTRY`, `~/.npmrc` including
scoped `@scope:registry` entries and auth tokens, then
`registry.npmjs.org`). A corporate laptop whose `~/.npmrc` already points
at an internal mirror keeps using it. Set `STAGED_NPM_REGISTRY` to force a
registry for every Staged-run npm command (bridge installs, Doctor's
npm-based installs, and update checks); it is passed as `--registry` and,
for a scoped package such as `@agentclientprotocol/*`, as that scope's
`--@scope:registry` too, so it also overrides a scoped `~/.npmrc` entry.
Doctor's cached "latest version" answers are keyed by this configuration
(override, environment, and `~/.npmrc` identity), so editing either is
picked up on the next check rather than after the cache's hour.
- **Node.js runtime.** The pinned runtime (`node-runtime.lock.json`) is
downloaded from `https://nodejs.org/dist` and verified against the lock's
SHA-256. Set `STAGED_NODE_DIST_URL` to a mirror with the same path layout
(`<base>/<version>/<tarball>`) if your network blocks `nodejs.org`.

A build can additionally compile in fallbacks for networks where the above
fail: `STAGED_FALLBACK_NPM_REGISTRY` is retried once when npm's configured
registry turns out to be `registry.npmjs.org` behind a network gateway that
answers with a block page (and no `STAGED_NPM_REGISTRY` was set), and
`STAGED_FALLBACK_NODE_DIST_URL` is retried once when the download from
`nodejs.org` fails to reach the host (and no `STAGED_NODE_DIST_URL` was set —
an explicit override is never second-guessed, for either). Both are read at
*compile time* and are unset for from-source builds. Block's release workflow
passes them from the
`STAGED_FALLBACK_NPM_REGISTRY` / `STAGED_FALLBACK_NODE_DIST_URL` repository
variables, which must be set for Block builds to keep working on managed
laptops that have no `~/.npmrc`.

## Development

### Prerequisites
Expand Down
10 changes: 5 additions & 5 deletions apps/staged/scripts/update-node-runtime-lock.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import process from "node:process";
const appRoot = path.resolve(import.meta.dirname, "..");
const defaultLockFile = path.join(appRoot, "node-runtime.lock.json");

// Block's Artifactory `nodejs` repo is a read-through mirror of
// https://nodejs.org/dist with an identical path shape, so SHASUMS256.txt is
// the official Node.js checksum file either way.
const DEFAULT_BASE_URL =
"https://global.block-artifacts.com/artifactory/nodejs";
// Upstream Node.js releases — the same host the app downloads the runtime
// from by default. `--base-url` accepts any mirror with an identical path
// shape (`<base>/<version>/SHASUMS256.txt`); the checksums it serves must be
// byte-identical to upstream's, since they become the app's trust root.
const DEFAULT_BASE_URL = "https://nodejs.org/dist";

// The rust target triples Staged builds for (macOS release targets plus
// Linux local builds), mapped to the platform component of Node's release
Expand Down
6 changes: 0 additions & 6 deletions apps/staged/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,6 @@ tokio = { version = "1.53.1", features = ["test-util"] }
tauri = { version = "2.12.1", features = ["test"] }

[features]
# no-block-npm-registry: downloads the managed Node.js runtime from upstream
# nodejs.org instead of Block's Artifactory mirror, and lets npm-backed
# doctor checks and fixes use npm's default public registry instead of
# Artifactory's square-npm proxy.
no-block-npm-registry = []

# no-managed-acp-tools: compiles the managed ACP bridge set empty for
# restricted builds — the startup reconciler installs nothing, doctor fixes
# fall back to the doctor crate's regular commands, and the managed shim dir
Expand Down
6 changes: 5 additions & 1 deletion apps/staged/src-tauri/src/acp_tools_reconciler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,15 @@ async fn reconcile(app: AppHandle) {
return;
}

// The same login-shell snapshot doctor's checks and fixes use, so the
// reconciler's npm resolves the user's registry config (`~/.npmrc`, an
// `NPM_CONFIG_REGISTRY` from the shell rc) exactly as a doctor fix would.
let env_vars = crate::doctor::doctor_env_vars().await;
let mut errors = Vec::new();
for tool in &tools {
let log_prefix = format!("[acp-tools reconcile {}]", tool.id);
let on_line = |line: &str| log::info!("{log_prefix} {line}");
match managed_acp_tools::install_managed_tool(tool.id, &on_line).await {
match managed_acp_tools::install_managed_tool(tool.id, &env_vars, &on_line).await {
Ok(()) => log::info!("{log_prefix} {} is up to date", tool.package),
Err(error) => {
log::warn!("{log_prefix} install failed (will retry next launch): {error}");
Expand Down
Loading
Loading