Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"test": "vitest run --maxWorkers=1"
},
"dependencies": {
"@first-tree-ai/context-tree": "0.1.12"
"@first-tree-ai/context-tree": "0.1.13"
},
"devDependencies": {
"@opentag/client": "workspace:*",
Expand Down
24 changes: 9 additions & 15 deletions docs/design/context-tree-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ workspace once per recorded target, cached in memory:
cwd = await workspace.cwd(agentId)
connect <target> --project-path <cwd> --json # clones on first use when the kind is github
install --host claude --project <cwd> # -> <cwd>/.claude/skills/context-tree-*
install --host codex # -> $CODEX_HOME/skills/context-tree-*
install --host codex # -> $HOME/.agents/skills/context-tree-*
```

`connect` is idempotent for an identical connection, so this is the ensure operation. It also
Expand Down Expand Up @@ -233,14 +233,12 @@ Two consequences to hold in view:

### Codex

Codex reads skills from `$CODEX_HOME/skills`, independently of the `plugins` and `hooks` features
Codex reads skills from `$HOME/.agents/skills`, independently of the `plugins` and `hooks` features
OpenTag disables, so no marketplace or feature change is required. For `install --host codex`,
OpenTag sets `HOME` to the parent of the resolved Codex home, making the package install into the
same `.codex/skills` directory from which the spawned Runtime reads. That mapping is exact only
when the Codex home's basename is `.codex`. A home with any other name (for example
`CODEX_HOME=/opt/opentag/codex-home`) cannot be expressed by the redirect, so preparation reports
`unavailable: CODEX_HOME_UNSUPPORTED` before any CLI work rather than risking a misplaced
install; a Codex host the CLI reports as `skipped` is likewise reported as `unavailable` with the
OpenTag passes the account `HOME` and resolved `CODEX_HOME` used by the Codex runtime.
The package detects Codex at `CODEX_HOME` and installs skills into `$HOME/.agents/skills`,
including when the Codex home has a custom location or name.
A Codex host the CLI reports as `skipped` is reported as `unavailable` with the
CLI's own reason instead of a reassuring `ready`. OpenTag writes only
`context-tree-*` directories there; the operation is idempotent and reversible.

Expand Down Expand Up @@ -363,7 +361,7 @@ Both delivery mechanisms were confirmed against the real CLIs before the surroun

- Claude Code under `--print --input-format stream-json --setting-sources project` discovers
`<workspace>/.claude/skills/context-tree-*`; under `--setting-sources ""` it does not.
- Codex discovers `~/.codex/skills/context-tree-*` with `plugins` and `hooks` disabled. Its
- Codex discovers `~/.agents/skills/context-tree-*` with `plugins` and `hooks` disabled. Its
`skip_host_skill_discovery` feature is separate from both.

Automated coverage: target routing and config round-trip; rendered platform string, revision
Expand All @@ -376,8 +374,8 @@ in every state.

Four end-to-end tests run offline against the real packaged CLI and a real Git tree, with `HOME`
and `OPENTAG_HOME` redirected: two Agent workspaces on one Computer resolving to the same checkout
and invoking `context-tree` by name through the shim; Codex skills landing in a custom `.codex`
home while the account home stays untouched; one Agent writing `members/<slug>/memory.md` through
and invoking `context-tree` by name through the shim; Codex skills landing in the `.agents`
sibling of a custom `.codex` home while the account home stays untouched; one Agent writing `members/<slug>/memory.md` through
the real isolated-worktree protocol while a second Agent in a different workspace reads it back;
and a Session still starting after the configured tree is deleted from underneath it.

Expand All @@ -399,10 +397,6 @@ the dependency from the published bundle.
- Validating a GitHub target during `opentag context-tree connect`, for example with a cheap
`git ls-remote` probe. That adds network work to a command deliberately kept read-only;
background preparation plus durable doctor visibility removes the Session-start harm meanwhile.
- Supporting an arbitrary `CODEX_HOME` whose basename is not `.codex`; the Context Tree CLI needs
an explicit skills-root flag because its host install can target only `<HOME>/.codex/skills`.
Until then the two guards above report the unsupported home and a skipped host as `unavailable`
with a specific reason, so the state is diagnosable through the prompt and `opentag doctor`.
- Sanitizing Claude project inputs (`settings.json`, `settings.local.json`, `hooks/`, `agents/`,
`commands/`, and `CLAUDE.md`) on every `prepareAgent`, so project settings contribute only
OpenTag-written content.
Expand Down
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"zod": "^4.5.1"
},
"devDependencies": {
"@first-tree-ai/context-tree": "0.1.12",
"@first-tree-ai/context-tree": "0.1.13",
"@types/semver": "^7.8.0",
"@types/ws": "^8.18.1",
"vitest": "^4.1.11"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe("shared Agent Home and Context Tree", () => {
readFile(join(agentHome, ".claude", "skills", "context-tree-read", "SKILL.md"), "utf8"),
).resolves.toContain("context-tree sync");
await expect(
readFile(join(fixture.accountHome, ".codex", "skills", "context-tree-write", "SKILL.md"), "utf8"),
readFile(join(fixture.accountHome, ".agents", "skills", "context-tree-write", "SKILL.md"), "utf8"),
).resolves.toContain("context-tree prepare-write");

const unanchored = await runCli(["resolve", "--project-path", taskA, "--json"], taskA);
Expand Down
45 changes: 10 additions & 35 deletions packages/client/src/__tests__/context-tree.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@
).resolves.toContain("context-tree");
}
await expect(
readFile(join(accountHome, ".codex", "skills", "context-tree-write", "SKILL.md"), "utf8"),
readFile(join(accountHome, ".agents", "skills", "context-tree-write", "SKILL.md"), "utf8"),
).resolves.toContain("context-tree");

// Exactly what a Session does: run the bare command name with the shim directory on PATH.
Expand All @@ -152,58 +152,33 @@
expect(JSON.parse(stdout.trim())).toMatchObject({ tree: { path: treePath } });
});

it("installs Codex skills into a custom CODEX_HOME named .codex", async () => {
const { accountHome, openTagHome, treePath } = await isolatedAccount("opentag-ct-codex-home");
it.each([".codex", "codex-home"])("installs skills into account HOME with custom CODEX_HOME %s", async (name) => {
const { accountHome, openTagHome, treePath, environment } = await isolatedAccount("opentag-ct-codex-home");
const customRoot = await temporaryDirectory("opentag-custom-codex-root-");
const codexHome = join(customRoot, ".codex");
const codexHome = join(customRoot, name);
await mkdir(codexHome, { mode: 0o700, recursive: true });
const manager = new ContextTreeManager({
environment,
codexHome,
home: openTagHome,
...(contextTreePackage ? { contextTreePackage } : {}),
});

await expect(manager.ensureAgent(await temporaryDirectory("opentag-ct-custom-codex-agent-"))).resolves.toEqual({

Check failure on line 167 in packages/client/src/__tests__/context-tree.integration.test.ts

View workflow job for this annotation

GitHub Actions / Generate Quality Scoreboard

src/__tests__/context-tree.integration.test.ts > Context Tree end-to-end > installs skills into account HOME with custom CODEX_HOME codex-home

AssertionError: expected { status: 'unavailable', …(1) } to deeply equal { status: 'ready', …(1) } - Expected + Received { - "status": "ready", - "treePath": "/tmp/opentag-ct-codex-home-account-Ey6p1s/.context-tree/trees/opentag-ct-codex-home-seed-zy3em3-context-tree", + "reason": "/tmp/opentag-ct-codex-home-account-Ey6p1s/.codex does not exist; install codex first, then run context-tree install.", + "status": "unavailable", } ❯ src/__tests__/context-tree.integration.test.ts:167:98

Check failure on line 167 in packages/client/src/__tests__/context-tree.integration.test.ts

View workflow job for this annotation

GitHub Actions / Generate Quality Scoreboard

src/__tests__/context-tree.integration.test.ts > Context Tree end-to-end > installs skills into account HOME with custom CODEX_HOME .codex

AssertionError: expected { status: 'unavailable', …(1) } to deeply equal { status: 'ready', …(1) } - Expected + Received { - "status": "ready", - "treePath": "/tmp/opentag-ct-codex-home-account-fhfEjm/.context-tree/trees/opentag-ct-codex-home-seed-8h5atj-context-tree", + "reason": "/tmp/opentag-ct-codex-home-account-fhfEjm/.codex does not exist; install codex first, then run context-tree install.", + "status": "unavailable", } ❯ src/__tests__/context-tree.integration.test.ts:167:98

Check failure on line 167 in packages/client/src/__tests__/context-tree.integration.test.ts

View workflow job for this annotation

GitHub Actions / Checks

src/__tests__/context-tree.integration.test.ts > Context Tree end-to-end > installs skills into account HOME with custom CODEX_HOME codex-home

AssertionError: expected { status: 'unavailable', …(1) } to deeply equal { status: 'ready', …(1) } - Expected + Received { - "status": "ready", - "treePath": "/tmp/opentag-ct-codex-home-account-rWqXfn/.context-tree/trees/opentag-ct-codex-home-seed-pgsbr4-context-tree", + "reason": "/tmp/opentag-ct-codex-home-account-rWqXfn/.codex does not exist; install codex first, then run context-tree install.", + "status": "unavailable", } ❯ src/__tests__/context-tree.integration.test.ts:167:98

Check failure on line 167 in packages/client/src/__tests__/context-tree.integration.test.ts

View workflow job for this annotation

GitHub Actions / Checks

src/__tests__/context-tree.integration.test.ts > Context Tree end-to-end > installs skills into account HOME with custom CODEX_HOME .codex

AssertionError: expected { status: 'unavailable', …(1) } to deeply equal { status: 'ready', …(1) } - Expected + Received { - "status": "ready", - "treePath": "/tmp/opentag-ct-codex-home-account-qqNOIx/.context-tree/trees/opentag-ct-codex-home-seed-lsmwku-context-tree", + "reason": "/tmp/opentag-ct-codex-home-account-qqNOIx/.codex does not exist; install codex first, then run context-tree install.", + "status": "unavailable", } ❯ src/__tests__/context-tree.integration.test.ts:167:98

Check failure on line 167 in packages/client/src/__tests__/context-tree.integration.test.ts

View workflow job for this annotation

GitHub Actions / Node 24 Compatibility

src/__tests__/context-tree.integration.test.ts > Context Tree end-to-end > installs skills into account HOME with custom CODEX_HOME codex-home

AssertionError: expected { status: 'unavailable', …(1) } to deeply equal { status: 'ready', …(1) } - Expected + Received { - "status": "ready", - "treePath": "/tmp/opentag-ct-codex-home-account-28Kmzk/.context-tree/trees/opentag-ct-codex-home-seed-pfumxm-context-tree", + "reason": "/tmp/opentag-ct-codex-home-account-28Kmzk/.codex does not exist; install codex first, then run context-tree install.", + "status": "unavailable", } ❯ src/__tests__/context-tree.integration.test.ts:167:98

Check failure on line 167 in packages/client/src/__tests__/context-tree.integration.test.ts

View workflow job for this annotation

GitHub Actions / Node 24 Compatibility

src/__tests__/context-tree.integration.test.ts > Context Tree end-to-end > installs skills into account HOME with custom CODEX_HOME .codex

AssertionError: expected { status: 'unavailable', …(1) } to deeply equal { status: 'ready', …(1) } - Expected + Received { - "status": "ready", - "treePath": "/tmp/opentag-ct-codex-home-account-To3RUT/.context-tree/trees/opentag-ct-codex-home-seed-2kdlyp-context-tree", + "reason": "/tmp/opentag-ct-codex-home-account-To3RUT/.codex does not exist; install codex first, then run context-tree install.", + "status": "unavailable", } ❯ src/__tests__/context-tree.integration.test.ts:167:98

Check failure on line 167 in packages/client/src/__tests__/context-tree.integration.test.ts

View workflow job for this annotation

GitHub Actions / Node 26 Compatibility

src/__tests__/context-tree.integration.test.ts > Context Tree end-to-end > installs skills into account HOME with custom CODEX_HOME codex-home

AssertionError: expected { status: 'unavailable', …(1) } to deeply equal { status: 'ready', …(1) } - Expected + Received { - "status": "ready", - "treePath": "/tmp/opentag-ct-codex-home-account-8IH7lO/.context-tree/trees/opentag-ct-codex-home-seed-jyxs7f-context-tree", + "reason": "/tmp/opentag-ct-codex-home-account-8IH7lO/.codex does not exist; install codex first, then run context-tree install.", + "status": "unavailable", } ❯ src/__tests__/context-tree.integration.test.ts:167:98

Check failure on line 167 in packages/client/src/__tests__/context-tree.integration.test.ts

View workflow job for this annotation

GitHub Actions / Node 26 Compatibility

src/__tests__/context-tree.integration.test.ts > Context Tree end-to-end > installs skills into account HOME with custom CODEX_HOME .codex

AssertionError: expected { status: 'unavailable', …(1) } to deeply equal { status: 'ready', …(1) } - Expected + Received { - "status": "ready", - "treePath": "/tmp/opentag-ct-codex-home-account-xDClji/.context-tree/trees/opentag-ct-codex-home-seed-7n3hc4-context-tree", + "reason": "/tmp/opentag-ct-codex-home-account-xDClji/.codex does not exist; install codex first, then run context-tree install.", + "status": "unavailable", } ❯ src/__tests__/context-tree.integration.test.ts:167:98

Check failure on line 167 in packages/client/src/__tests__/context-tree.integration.test.ts

View workflow job for this annotation

GitHub Actions / Node 22.22.2 Compatibility

src/__tests__/context-tree.integration.test.ts > Context Tree end-to-end > installs skills into account HOME with custom CODEX_HOME codex-home

AssertionError: expected { status: 'unavailable', …(1) } to deeply equal { status: 'ready', …(1) } - Expected + Received { - "status": "ready", - "treePath": "/tmp/opentag-ct-codex-home-account-8e2KEy/.context-tree/trees/opentag-ct-codex-home-seed-pcjygg-context-tree", + "reason": "/tmp/opentag-ct-codex-home-account-8e2KEy/.codex does not exist; install codex first, then run context-tree install.", + "status": "unavailable", } ❯ src/__tests__/context-tree.integration.test.ts:167:98

Check failure on line 167 in packages/client/src/__tests__/context-tree.integration.test.ts

View workflow job for this annotation

GitHub Actions / Node 22.22.2 Compatibility

src/__tests__/context-tree.integration.test.ts > Context Tree end-to-end > installs skills into account HOME with custom CODEX_HOME .codex

AssertionError: expected { status: 'unavailable', …(1) } to deeply equal { status: 'ready', …(1) } - Expected + Received { - "status": "ready", - "treePath": "/tmp/opentag-ct-codex-home-account-pbO6md/.context-tree/trees/opentag-ct-codex-home-seed-1c9oxb-context-tree", + "reason": "/tmp/opentag-ct-codex-home-account-pbO6md/.codex does not exist; install codex first, then run context-tree install.", + "status": "unavailable", } ❯ src/__tests__/context-tree.integration.test.ts:167:98
status: "ready",
treePath,
});
await expect(readFile(join(codexHome, "skills", "context-tree-read", "SKILL.md"), "utf8")).resolves.toContain(
"context-tree",
);
// The install lands in the custom home, never in the OS account home.
await expect(
readFile(join(accountHome, ".codex", "skills", "context-tree-read", "SKILL.md"), "utf8"),
readFile(join(accountHome, ".agents", "skills", "context-tree-read", "SKILL.md"), "utf8"),
).resolves.toContain("context-tree");
// Codex scans the account home, independently of where its configuration lives.
await expect(
readFile(join(customRoot, ".agents", "skills", "context-tree-read", "SKILL.md"), "utf8"),
).rejects.toMatchObject({
code: "ENOENT",
});
});

it("reports an unsupported CODE_HOME instead of misinstalling skills", async () => {
const { accountHome, openTagHome } = await isolatedAccount("opentag-ct-codex-home-unsupported");
const customRoot = await temporaryDirectory("opentag-custom-codex-root-");
// The supported configuration from the review: a Codex home whose basename is not `.codex`,
// with a sibling `.codex` that must never be written by the HOME redirect.
const codexHome = join(customRoot, "codex-home");
await mkdir(codexHome, { mode: 0o700, recursive: true });
await mkdir(join(customRoot, ".codex"), { mode: 0o700, recursive: true });
const manager = new ContextTreeManager({
codexHome,
home: openTagHome,
...(contextTreePackage ? { contextTreePackage } : {}),
});

await expect(manager.ensureAgent(await temporaryDirectory("opentag-ct-custom-codex-agent-"))).resolves.toEqual({
status: "unavailable",
reason: "CODEX_HOME_UNSUPPORTED",
});
// Nothing may land in the configured Codex home, in the sibling `.codex`, or in the account home.
for (const root of [codexHome, join(customRoot, ".codex"), join(accountHome, ".codex")]) {
await expect(readFile(join(root, "skills", "context-tree-read", "SKILL.md"), "utf8")).rejects.toMatchObject({
code: "ENOENT",
});
}
});

it("lets one Agent record member memory that another Agent then reads", async () => {
const { accountHome, treePath, environment, manager } = await isolatedAccount("opentag-ct-write");
// The CLI installs Codex skills only for a host that is present; this account simulates one,
Expand Down
50 changes: 21 additions & 29 deletions packages/client/src/__tests__/context-tree.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
import { mkdir, mkdtemp, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { codexAgentRuntimeEnvironment } from "../providers/codex/agent-runtime.js";
import {
type ContextTreeExecFile,
ContextTreeManager,
Expand All @@ -23,7 +24,7 @@ const installReply = {
installed: [
{
host: "codex",
path: "/home/user/.codex/skills",
path: "/home/user/.agents/skills",
skills: [
"context-tree-connect",
"context-tree-create",
Expand Down Expand Up @@ -160,33 +161,24 @@ describe("ContextTreeManager", () => {
expect(calls).toHaveLength(3);
});

it("uses the account environment for tree commands and redirects only Codex installation", async () => {
const environments: NodeJS.ProcessEnv[] = [];
const { home, cwd, manager } = await computer({
target: managed,
codexHome: "/custom/.codex",
execFile: async (_file, args, options) => {
environments.push(options.env ?? {});
return { stdout: JSON.stringify(args[1] === "connect" ? treeReply("/srv/tree") : installReply) };
},
});
await expect(manager.ensureAgent(cwd)).resolves.toEqual({ status: "ready", treePath: "/srv/tree" });
expect(environments.map((env) => env.HOME)).toEqual([home, home, "/custom"]);
expect(environments[2]).toEqual({ ...environments[0], HOME: "/custom" });
});

it("reports an unsupported Codex home instead of a misleading ready state", async () => {
const { execFile, calls } = recording({ connect: treeReply("/srv/t"), install: installReply });
const { cwd, manager } = await computer({ execFile, target: managed, codexHome: "/opt/opentag/codex-home" });

await expect(manager.ensureAgent(cwd)).resolves.toEqual({
status: "unavailable",
reason: "CODEX_HOME_UNSUPPORTED",
});
// The HOME redirect cannot express this home, so the CLI must not run at all: an install
// could otherwise land in a sibling `.codex` the Runtime is not launched against.
expect(calls).toEqual([]);
});
it.each(["/custom/.codex", "/custom/codex-home"])(
"uses the runtime account HOME for installation with %s",
async (codexHome) => {
const environments: NodeJS.ProcessEnv[] = [];
const { home, cwd, manager } = await computer({
target: managed,
codexHome,
execFile: async (_file, args, options) => {
environments.push(options.env ?? {});
return { stdout: JSON.stringify(args[1] === "connect" ? treeReply("/srv/tree") : installReply) };
},
});
await expect(manager.ensureAgent(cwd)).resolves.toEqual({ status: "ready", treePath: "/srv/tree" });
const runtimeEnvironment = codexAgentRuntimeEnvironment({ ...process.env, HOME: home, CODEX_HOME: codexHome });
expect(environments.map((env) => env.HOME)).toEqual([home, home, await realpath(runtimeEnvironment.HOME ?? "")]);
expect(environments[2]?.CODEX_HOME).toBe(runtimeEnvironment.CODEX_HOME);
},
);

it.each([
// The CLI reports a missing host as a skipped entry with an explanatory reason.
Expand Down
20 changes: 5 additions & 15 deletions packages/client/src/runtime/context-tree.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { execFile } from "node:child_process";
import { createRequire } from "node:module";
import { basename, dirname, join, resolve } from "node:path";
import { dirname, join, resolve } from "node:path";
import { promisify } from "node:util";
import {
type ContextTreeConfig,
Expand Down Expand Up @@ -164,7 +164,7 @@ export interface ContextTreeManagerOptions {
platform?: NodeJS.Platform;
/** Absolute path to the Node.js runtime the generated shim should exec. */
nodePath?: string;
/** Resolved home passed to Codex itself; host skill installation must use the same root. */
/** Resolved Codex config home; the Context Tree CLI installs skills beside it under `.agents`. */
codexHome?: string;
sessionStartBudgetMs?: number;
failureCooldownMs?: number;
Expand All @@ -189,13 +189,6 @@ export class ContextTreeManager {
readonly #platform: NodeJS.Platform;
readonly #nodePath: string;
readonly #codexHome: string;
/**
* The Context Tree CLI installs Codex skills only into `<HOME>/.codex/skills`, so the `HOME`
* redirect below is correct exactly when the Codex home's basename is `.codex`. Any other home
* (for example `CODEX_HOME=/opt/opentag/codex-home`) cannot be expressed and is reported
* rather than silently misinstalled next to it.
*/
readonly #codexHomeIsDefaultNamed: boolean;
readonly #sessionStartBudgetMs: number;
readonly #failureCooldownMs: number;
readonly #ready = new Map<string, { target: string; status: ContextTreeStatus }>();
Expand All @@ -218,7 +211,6 @@ export class ContextTreeManager {
this.#platform = options.platform ?? process.platform;
this.#nodePath = options.nodePath ?? process.execPath;
this.#codexHome = resolve(options.codexHome ?? join(resolveAccountHome(this.#environment), ".codex"));
this.#codexHomeIsDefaultNamed = basename(this.#codexHome) === ".codex";
this.#sessionStartBudgetMs = options.sessionStartBudgetMs ?? SESSION_START_BUDGET_MS;
this.#failureCooldownMs = options.failureCooldownMs ?? FAILURE_COOLDOWN_MS;
}
Expand Down Expand Up @@ -279,9 +271,6 @@ export class ContextTreeManager {

async #ensureAgentOnce(cwd: string, config: ContextTreeConfig): Promise<ContextTreeStatus> {
if (!this.#package) return this.#unavailable("PACKAGE_MISSING", config);
// The CLI never reads `CODEX_HOME`; `install --host codex` targets `<HOME>/.codex/skills`.
// An unsupported home is diagnosed before any CLI work, so nothing can land in the wrong place.
if (!this.#codexHomeIsDefaultNamed) return this.#unavailable("CODEX_HOME_UNSUPPORTED", config);

try {
// `connect` is idempotent for an identical connection and already returns the resolved
Expand All @@ -290,11 +279,12 @@ export class ContextTreeManager {
const treePath = (connected as { tree?: { path?: unknown } }).tree?.path;
if (typeof treePath !== "string" || treePath.length === 0) return this.#unavailable("CONNECT_FAILED", config);
// Claude Code loads skills from the workspace because OpenTag passes `--setting-sources
// project`; Codex loads them from its own home, and only for a host that is present.
// project`; Codex loads them from the account home's `.agents/skills` directory.
await this.#run(["install", "--host", "claude", "--project", cwd], cwd, false);
const codexInstall = await this.#run(["install", "--host", "codex"], cwd, false, {
...this.#environment,
HOME: dirname(this.#codexHome),
HOME: resolveAccountHome(this.#environment),
CODEX_HOME: this.#codexHome,
});
// A skipped Codex host is a diagnosable state, never a silent `ready`.
const skipReason = codexInstallSkipReason(codexInstall);
Expand Down
14 changes: 7 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions scripts/__tests__/portable-runtime-dependencies.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ test("the portable runtime dependency contract accepts only the supported exact
test("the checked-in apps/cli manifest is accepted under the portable shipping contract", () => {
const cliManifest = JSON.parse(readFileSync(join(repoRoot, "apps", "cli", "package.json"), "utf8"));
assert.deepEqual(readPortableDirectDependencyPins(cliManifest), [
{ name: "@first-tree-ai/context-tree", version: "0.1.12" },
{ name: "@first-tree-ai/context-tree", version: "0.1.13" },
]);
});

Expand Down Expand Up @@ -531,13 +531,13 @@ test("the real frozen install closure assembles, verifies, and runs the embedded
});
assert.equal(closure.direct.length, 1);
assert.equal(closure.packages[0].name, "@first-tree-ai/context-tree");
assert.equal(closure.packages[0].version, "0.1.12");
assert.equal(closure.packages[0].version, "0.1.13");
assert.ok(closure.packages.length >= 20, `real closure is unexpectedly small: ${closure.packages.length}`);
writeJson(join(appDir, "package.json"), {
name: "open-tag",
version: "0.0.2",
type: "module",
dependencies: { "@first-tree-ai/context-tree": "0.1.12" },
dependencies: { "@first-tree-ai/context-tree": "0.1.13" },
});
writeDependencyClosureFile(appDir, closure);
writeText(join(appDir, "cli", "index.mjs"), "export {};\n");
Expand Down
Loading