Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,13 @@ detected agents, then asks which capabilities to set up: agent integration
Nothing is preselected. Choosing agent integration opens a second empty
checklist for the specific detected agents that should receive a registration.

If bare `npx` finds a project-local REA package, its dispatcher restarts setup
through `rea-agents@latest` before planning any changes. This prevents an older
local dependency from silently writing stale integration state. An already
published older release cannot contain that dispatcher protection; run
`npx -y rea-agents@latest setup` once to repair registrations created by one.
Intentional rollbacks remain available through an exact package request.
If bare `npx` selects an older REA package from a project or its local cache, its
dispatcher restarts setup through `rea-agents@latest` with online metadata before
planning any changes. This prevents a stale bootstrap from silently writing
integration state. An already published older release cannot contain that
dispatcher protection; run `npx -y rea-agents@latest setup` once to repair
registrations created by one. Intentional rollbacks remain available through an
exact package request.

REA keeps the journey inline so its history remains in the terminal. Selecting
a capability does not select every detected target or authorize a change.
Expand Down
14 changes: 7 additions & 7 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ current package-runner invocation. REA still prints its own setup plan and asks
for separate approval before changing agent configuration or installing a
product-owned component.

When bare `npx` resolves `rea-agents` from the current project's
`node_modules`, the selected dispatcher restarts setup through
`rea-agents@latest` before REA plans or writes configuration. This protects
current and future releases from silently persisting stale integration state.
Code in an already-published older release cannot acquire that check
retroactively; invoke `npx -y rea-agents@latest setup` once to repair any
registration it created.
When bare `npx` resolves an older `rea-agents` package from the current project
or its local cache, the selected dispatcher restarts setup through
`rea-agents@latest` with online metadata before REA plans or writes
configuration. This protects current and future releases from silently
persisting stale integration state. Code in an already-published older release
cannot acquire that check retroactively; invoke `npx -y rea-agents@latest setup`
once to repair any registration it created.

For an intentional rollback, make the package request explicit:

Expand Down
22 changes: 3 additions & 19 deletions scripts/package-runner-bootstrap.mjs
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
import { spawn } from "node:child_process";
import { resolve } from "node:path";

const BOOTSTRAP_MARKER = "REA_PACKAGE_RUNNER_BOOTSTRAPPED";

/**
* Plan a current-release setup bootstrap when bare npx selected a project-local
* package. Explicit `npm exec --package=<name>@<version>` rollbacks bypass it.
* Plan a current-release setup bootstrap when bare npx selected a package.
Comment thread
morluto marked this conversation as resolved.
Outdated
* Explicit `npm exec --package=<name>@<version>` rollbacks bypass it.
*/
export function packageRunnerSetupPlan(input) {
if (input.args[0] !== "setup") return undefined;
if (input.environment[BOOTSTRAP_MARKER] === "1") return undefined;
if (input.environment.npm_lifecycle_event !== "npx") return undefined;

const localPrefix = input.environment.npm_config_local_prefix;
if (localPrefix === undefined || localPrefix.length === 0) return undefined;
if (
!isPackageBelowLocalPrefix(
input.packageRoot,
localPrefix,
input.packageName,
)
)
return undefined;

const requestedPackage = input.environment.npm_config_package;
if (
requestedPackage !== undefined &&
Expand All @@ -35,6 +23,7 @@ export function packageRunnerSetupPlan(input) {
args: [
"exec",
"--yes",
"--prefer-online",
`--package=${input.packageName}@latest`,
"--",
"rea",
Expand All @@ -58,11 +47,6 @@ export async function runPackageRunnerSetupBootstrap(input) {
});
}

function isPackageBelowLocalPrefix(packageRoot, localPrefix, packageName) {
const expectedRoot = resolve(localPrefix, "node_modules", packageName);
return resolve(packageRoot) === expectedRoot;
}

function isExactPackageVersion(packageSpecifier, packageName) {
const version = packageSpecifier.slice(`${packageName}@`.length);
return (
Expand Down
15 changes: 12 additions & 3 deletions tests/boundary/cli/packageRunnerBootstrap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";

describe("package-runner setup bootstrap", () => {
it("reruns bare setup through the current release when npm selected a local package", () => {
it("reruns bare setup through the current release when npm selected a package", () => {
expect(
packageRunnerSetupPlan({
args: ["setup", "--client", "codex"],
Expand All @@ -26,6 +26,7 @@ describe("package-runner setup bootstrap", () => {
args: [
"exec",
"--yes",
"--prefer-online",
"--package=rea-agents@latest",
"--",
"rea",
Expand All @@ -36,7 +37,7 @@ describe("package-runner setup bootstrap", () => {
});
});

it("leaves explicit rollback and non-local invocations untouched", () => {
it("leaves explicit rollback invocations untouched and refreshes cached packages", () => {
const base = {
args: ["setup"],
packageRoot: "/work/project/node_modules/rea-agents",
Expand Down Expand Up @@ -76,7 +77,14 @@ describe("package-runner setup bootstrap", () => {
npm_config_local_prefix: "/work/project",
},
}),
).toBeUndefined();
).toEqual(
expect.objectContaining({
args: expect.arrayContaining([
"--prefer-online",
"--package=rea-agents@latest",
]),
}),
);
});

it("does not redirect recursively or affect commands other than setup", () => {
Expand Down Expand Up @@ -141,6 +149,7 @@ writeFileSync(process.env.REA_BOOTSTRAP_TEST_LOG, JSON.stringify({
args: [
"exec",
"--yes",
"--prefer-online",
"--package=rea-agents@latest",
"--",
"rea",
Expand Down
Loading