cdx-manager 0.25.6 → 0.25.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <br clear="left"/>
6
6
 
7
- [![License](https://img.shields.io/badge/license-MIT-4C8BF5)](LICENSE) ![Version](https://img.shields.io/badge/version-v0.25.6-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
7
+ [![License](https://img.shields.io/badge/license-MIT-4C8BF5)](LICENSE) ![Version](https://img.shields.io/badge/version-v0.25.8-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
8
8
 
9
9
  **The right AI coding account, when you need it.** CDX Manager keeps your Codex and Claude accounts in separate profiles, shows which ones have quota, and helps you pick the next usable session from your terminal.
10
10
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.25.6
1
+ 0.25.8
@@ -0,0 +1,15 @@
1
+ # CDX Manager 0.25.7
2
+
3
+ Release date: 2026-11-02
4
+
5
+ This patch release corrects Antigravity CLI update detection and verification.
6
+
7
+ ## Fixes
8
+
9
+ - Stop comparing Antigravity CLI (`agy`) versions against the editor release feed. Report the latest CLI version as unknown and let the native `agy update` command check and apply updates after confirmation, including with `--yes`. Inventory collection remains read-only. (#89)
10
+ - Accept a successful updater's explicit `You are already on the latest version.` message on stdout or stderr when the installed version is unchanged. Otherwise require a verified version increase reaching the published version when known, and preserve updater failures. (#89)
11
+
12
+ ## Maintenance
13
+
14
+ - Record tray companion checksums for v0.25.6. (`53500823f04edb69be9563d327645148439644a6`)
15
+ - Record release archive checksums for v0.25.6. (`974dfb0cac31963883456bda4beddb19b8940152`)
@@ -0,0 +1,15 @@
1
+ # CDX Manager 0.25.8
2
+
3
+ Release date: 2026-11-02
4
+
5
+ This patch release restores Git history and RTK access in isolated workspaces.
6
+
7
+ ## Fixes
8
+
9
+ - Restore read-only Git history access in isolated local and remote workspaces, including linked worktrees with packed objects and refs. Keep Git metadata read-only and exclude host configuration, hooks, remotes, and credentials. (#91)
10
+ - Expose the installed RTK binary in isolated launches and persist its savings history in a dedicated shared cache, separate from the operator's normal RTK history. (#91)
11
+
12
+ ## Maintenance
13
+
14
+ - Record tray companion checksums for v0.25.7. (`31b7ecfb8f8189f29b01aaa88f9acf866acccd19`)
15
+ - Record release archive checksums for v0.25.7. (`03d339b9e93e2d7d917df6e614132390ce4436fe`)
@@ -1,11 +1,20 @@
1
1
  # Unreleased
2
2
 
3
+ ## Isolated mission Git and RTK
4
+
5
+ - Give local and remote isolated linked worktrees a read-only Git view with their own HEAD/index and shared objects/refs, without traversing private host parents or exposing repository/global configuration, hooks, SSH keys or Git push credentials. Preserve supervisor-owned commits and pushes.
6
+ - Add the mounted operator `~/.local/bin` to sandbox PATH and persist RTK savings in the execution host's shared cache at `/cache/rtk/history.db`, overriding inaccessible host history paths for isolated launches only.
7
+ - Cover real Git commands against selected metadata, local and shipped worker launch environments, and an opt-in required bubblewrap acceptance check for Git, RTK history writes, review mode and an unreadable synthetic SSH key. Document the namespace requirement and sandbox history location.
8
+
3
9
  ## Truthful workstation updates
4
10
 
5
- - Detect native updater subcommands in both stdout and stderr, including Antigravity's indented `Available subcommands:` help. Verify the installed CLI version after updating, fail unchanged or unverified updates, and expose before/after versions without hiding major upgrades.
11
+ - Stop querying the editor release feed for the Antigravity CLI: its versions do not match `agy`. Keep `latest_version: null` and `status: unknown`, while still running the native `agy update` with `--yes` or confirmation. No read-only latest-version check is documented by `agy update --help`.
12
+ - Accept a successful native updater's `You are already on the latest version.` message when the installed version is unchanged, or verify a version increase against the published version when known. Preserve updater failures and unknown inventory status. Cover the real Antigravity output line, version increases, failures, and JSON/text uncertainty.
13
+
14
+ - Detect advertised native updater subcommands in both stdout and stderr; use Antigravity's known `agy update` command directly. Verify the installed CLI version after updating, fail unverified updates unless an unchanged version is explicitly confirmed current by the updater, and expose before/after versions without hiding major upgrades.
6
15
  - Fall back from empty or failed Ollama vendor responses (including Linux HTTP 204) to the latest stable `ollama/ollama` GitHub release. Keep Linux updates manual with `update_available`, a null command, and a service-safety reason; never replace a potentially active binary or restart models automatically.
7
16
 
8
- - Verify latest versions from npm, RTK GitHub releases, and Antigravity/Ollama vendor update endpoints with short read-only probes. Report `unknown` on unavailable evidence; reserve `up_to_date` for identical known versions, including Ponytail revisions.
17
+ - Verify latest versions from npm, RTK GitHub releases, and Ollama vendor update endpoints with short read-only probes. Report `unknown` on unavailable evidence; reserve `up_to_date` for identical known versions, including Ponytail revisions.
9
18
  - Preserve the existing JSON fields and `update_available` status, show uncertainty and manual-update reasons in text, and apply only supported native commands or verified Homebrew package upgrades. Cover matching/outdated versions, source failures, missing tools, and command execution with faked sources.
10
19
 
11
20
  ## Dependency security
@@ -749,6 +749,28 @@
749
749
  "x86_64-pc-windows-msvc": "85945b30580cb82c64ece94652ae0633a6156dfd85e02108c0d4b7676cab9911",
750
750
  "x86_64-unknown-linux-musl": "7c5a23938b84c5952be71b5956ed3167c218d05ce661940b0c86f2dde2d8221f"
751
751
  }
752
+ },
753
+ "v0.25.6": {
754
+ "github_tarball_sha256": "27663ea4f9c70aafb1d2180d36563ab10d91b9cb336cb3285ed4fd610e698bed",
755
+ "github_zip_sha256": "ba645956971ddd9446389717ad48a74dfb426ff7290c5ebc100ca9e3c2a91d3f",
756
+ "tray_assets": {
757
+ "aarch64-apple-darwin": "41b8d1a0b098407a49fc66ef2b49fbf8283f46f6393f34fae05d0ee3b84fcd1b",
758
+ "aarch64-unknown-linux-musl": "ba88584f8ba6399572961feb4d7bcc056096d3aa398d863bc0e248e57086434e",
759
+ "x86_64-apple-darwin": "ddd032fe6306585397c4c4a461de28cafea5519eb08502224fb5ad87a249b05a",
760
+ "x86_64-pc-windows-msvc": "5c3c4be1dcf6801ed0ed73bef8840deb379e2724d9728f983498d0fe5a99c542",
761
+ "x86_64-unknown-linux-musl": "ba91c1378b78e529da3af35c7e649ed2063ba27963ef91949f1aabe9e3fe2a0f"
762
+ }
763
+ },
764
+ "v0.25.7": {
765
+ "github_tarball_sha256": "e9a943ca212e0706f5a3ad29d9be99b8bf5293836d4f082dddd797e122475310",
766
+ "github_zip_sha256": "19eb0266ed35aae2386fd9d90079a7be5dbc44a3b44249615c244fc7c4f7d85f",
767
+ "tray_assets": {
768
+ "aarch64-apple-darwin": "9fa198f0133ac40747fb00f4353af174b45b550ebef4662398b84aa9459e2388",
769
+ "aarch64-unknown-linux-musl": "c4ccba259e7f910bd5a2ce8abfa2045ba4ad9b975da7dea2c8dfc0ba1bd5bbfc",
770
+ "x86_64-apple-darwin": "1341e59d8da3489368e22c85c9ea3d974254c810d3b68077b2e1b02d2c9aee5a",
771
+ "x86_64-pc-windows-msvc": "be2e6ff8607351019447c6fbf0622254c2f8d8db81581007af71b74932e37f50",
772
+ "x86_64-unknown-linux-musl": "32de07569929ade136cf75ed8f0716e0f3b96a9c53c6632c628e13782d223518"
773
+ }
752
774
  }
753
775
  }
754
776
  }
package/docs/cli.md CHANGED
@@ -560,11 +560,14 @@ Versions and commands are explicitly `null` when unavailable.
560
560
  - `not_installed`: the executable is absent; no installation is guessed.
561
561
 
562
562
  Checks use the npm registry for Codex and `@anthropic-ai/claude-code`, GitHub's
563
- `rtk-ai/rtk` latest stable release, the original Antigravity editor's vendor
564
- `/releases` feed for `agy`, and Ollama's own `/api/update` endpoint with the local
563
+ `rtk-ai/rtk` latest stable release, and Ollama's own `/api/update` endpoint with the local
565
564
  OS/architecture and baseline version `0.0.0`. Ollama's local `/api/version` reports
566
565
  an installed server, not the latest published release, and is not used.
567
- Antigravity Hub and IDE 2 have separate feeds and are not substituted for `agy`.
566
+ Antigravity's editor `/releases` feed does not describe the `agy` CLI and is not
567
+ queried. `agy update --help` documents no read-only version check, so its latest
568
+ CLI version stays unknown (`latest_version: null`, `status: unknown`). CDX still
569
+ includes `agy update`, the CLI's own updater, in the plan. It runs only with
570
+ `--yes` or confirmation. Inventory collection never runs that updater.
568
571
  HTTP and read-only CLI probes have a five-second timeout; source failures leave
569
572
  `latest_version: null` and `status: unknown`. Ponytail also reports `unknown`
570
573
  when its remote revision is unavailable. An empty, failed, or invalid Ollama
@@ -573,7 +576,7 @@ release for `ollama/ollama`. If that source also fails, status is `unknown`;
573
576
  a different platform's vendor response is never substituted.
574
577
 
575
578
  For an outdated tool, CDX uses a native update command advertised by its
576
- `--help` on either stdout or stderr (including Claude, Antigravity, and RTK
579
+ `--help` on either stdout or stderr (including Claude and RTK
577
580
  when available), Codex's updater,
578
581
  or Homebrew only when the resolved executable belongs to a Caskroom/Cellar
579
582
  package. An outdated tool without such a path retains `update_available`,
@@ -591,8 +594,12 @@ every installed tool was verified current. With `--yes`, the returned plan is th
591
594
  pre-update inventory; `results` records attempted actions and their exit codes.
592
595
 
593
596
  After each successful tool update command, CDX probes the CLI version again.
594
- An unchanged, unreadable, downgraded, or still older-than-published version
595
- makes the action fail, even if the updater exited zero. Successful text output
597
+ A raised version succeeds when it reaches the published release, if known.
598
+ An unchanged version also succeeds when the updater explicitly reports
599
+ `You are already on the latest version.` on stdout or stderr and exits zero;
600
+ this does not change an `unknown` inventory status to `up_to_date`.
601
+ Otherwise an unchanged, unreadable, downgraded, or still older-than-published
602
+ version makes the action fail, even if the updater exited zero. Successful text output
596
603
  shows the before/after versions, including major upgrades. JSON keeps the
597
604
  pre-update plan and existing result fields; tool verification adds
598
605
  `version_before`, `version_after`, and `latest_version` to successful-command
@@ -242,6 +242,45 @@ credential sync.
242
242
  - **On Windows, `doctor` warns that `script` is missing** — this is expected on many setups. Codex still launches, but transcript capture stays disabled unless you point `CDX_SCRIPT_BIN` to a compatible wrapper.
243
243
 
244
244
 
245
+ ### Git and RTK in isolated workspaces
246
+
247
+ Local and remote `cdx run --isolate-workspace` use the same sandbox implementation.
248
+ For a linked Git worktree, CDX builds a read-only Git view at `/home/agent/.git`:
249
+ the mission's HEAD and index, plus the common objects, refs, packed refs, shallow
250
+ boundary and split-index files. `GIT_DIR`, `GIT_COMMON_DIR` and `GIT_WORK_TREE`
251
+ select that view without traversing the operator's private home directories.
252
+ `git log`, `show`, `diff`, `status`, `blame`, `branch` and `rev-parse` can read
253
+ the mission history, including packed objects and refs. Review runs also keep
254
+ checkout files read-only. Git metadata stays read-only in writable missions,
255
+ as before: committing and pushing remain the supervising mission flow's job.
256
+
257
+ Host repository configuration, hooks, other worktrees, global Git configuration,
258
+ SSH keys, credential helpers and token environment variables are not added.
259
+ The view has no configured remotes. Existing provider authentication rules are
260
+ unchanged; this does not grant Git push credentials.
261
+
262
+ The sandbox appends the mounted operator `~/.local/bin` to PATH, including for
263
+ noninteractive remote launches whose PATH omits it. An installed `rtk` is thus
264
+ available without installing a second copy. Isolated launches override
265
+ `RTK_DB_PATH` with `/cache/rtk/history.db`, backed by
266
+ `~/.cdx/shared-cache/rtk/history.db` on the execution host. Savings persist
267
+ across isolated attempts; the operator's normal RTK history is not mounted.
268
+ Inspect them with `RTK_DB_PATH="$HOME/.cdx/shared-cache/rtk/history.db" rtk gain`
269
+ on that host. Non-isolated launches keep their normal history selection.
270
+
271
+ Run the real confinement acceptance check on a Linux host with bubblewrap and
272
+ RTK installed (it uses disposable fixtures and synthetic secrets):
273
+
274
+ ```bash
275
+ CDX_REQUIRE_SANDBOX_TESTS=1 python3 -m pytest -q test/test_workspace_sandbox_tools_py.py
276
+ ```
277
+
278
+ It exercises the local and shipped worker implementations, writable and review
279
+ checkouts, Git history commands, RTK database writes, and denial of a synthetic
280
+ SSH key outside the checkout. The required mode fails rather than skipping
281
+ when nested namespaces or RTK are unavailable. Default test runs skip only
282
+ those unavailable integration cases; copied-metadata Git checks still run.
283
+
245
284
  ### Playwright in isolated workspaces
246
285
 
247
286
  Isolated preparation and provider environments force `PLAYWRIGHT_SKIP_BROWSER_GC=1`.
@@ -154,7 +154,7 @@ cdx power all default
154
154
  cdx model provider:ollama default
155
155
  ```
156
156
 
157
- `--model` maps to Codex `--model`, Claude `--model`, and Ollama `ollama run <model>`. `--power` maps to Codex `model_reasoning_effort` and Claude `--effort`; supported values are `minimal`, `low`, `medium`, `high`, and `xhigh`. `--permission` maps to provider-native permission flags. Codex Fast mode is separate from reasoning effort: `--fast on` opts a Codex session into the Codex Fast service tier, while new sessions, `--fast off`, and default launches force the non-Fast `flex` tier. Use `--power low` when you want low reasoning effort without enabling Codex Fast credits. Existing legacy sessions that stored `fast=on` before this split continue to behave as low effort unless the user explicitly sets `--fast on` again. `--priority` is a 0..100 selector preference used as a tie-breaker after readiness and availability. `--policy use|ask-first|never` (default `use`) says whether cdx may pick an account on your behalf: `cdx next`, `cdx select`, `cdx run --provider` and run failover only ever choose `use` accounts; `ask-first` accounts are offered by `cdx rescue` with a confirmation, `never` accounts are not offered at all. Naming an account explicitly (`cdx <name>`, `cdx run <name>`) is always allowed. `--rtk on` adds standing guidance that encourages assistants to use RTK (`rtk <command>`) for noisy terminal commands when RTK is available, while keeping raw commands for exact output. Logics guidance is auto-enabled when `logics-manager` is available; use `--logics off` to disable that guidance for a session, or `--logics on` to pin it explicitly. This guidance is passed as instructions, never as a first message: Claude receives it through `--append-system-prompt` and Codex through `-c developer_instructions=...`, appended after any `developer_instructions` already set in the profile's `config.toml` (on Python older than 3.11 without `tomli`, a profile that sets its own is left untouched and gets no cdx guidance). Launching or resuming a session therefore never submits a turn on your behalf. Antigravity and Ollama have no instruction channel and receive no guidance. Whatever the `--rtk` setting, every launch, resume and `cdx run` sets `RTK_DB_PATH` to the rtk history of your real home (`~/Library/Application Support/rtk/history.db` on macOS, `~/.local/share/rtk/history.db` on Linux, `%LOCALAPPDATA%\rtk\history.db` on Windows), so `rtk gain` in your own shell counts what every account saved instead of each profile keeping its own; an `RTK_DB_PATH` you set yourself is kept. If you also pass `--append-system-prompt` through `--extra-args`, Claude receives the option twice; prefer putting your own text in the profile's `CLAUDE.md`.
157
+ `--model` maps to Codex `--model`, Claude `--model`, and Ollama `ollama run <model>`. `--power` maps to Codex `model_reasoning_effort` and Claude `--effort`; supported values are `minimal`, `low`, `medium`, `high`, and `xhigh`. `--permission` maps to provider-native permission flags. Codex Fast mode is separate from reasoning effort: `--fast on` opts a Codex session into the Codex Fast service tier, while new sessions, `--fast off`, and default launches force the non-Fast `flex` tier. Use `--power low` when you want low reasoning effort without enabling Codex Fast credits. Existing legacy sessions that stored `fast=on` before this split continue to behave as low effort unless the user explicitly sets `--fast on` again. `--priority` is a 0..100 selector preference used as a tie-breaker after readiness and availability. `--policy use|ask-first|never` (default `use`) says whether cdx may pick an account on your behalf: `cdx next`, `cdx select`, `cdx run --provider` and run failover only ever choose `use` accounts; `ask-first` accounts are offered by `cdx rescue` with a confirmation, `never` accounts are not offered at all. Naming an account explicitly (`cdx <name>`, `cdx run <name>`) is always allowed. `--rtk on` adds standing guidance that encourages assistants to use RTK (`rtk <command>`) for noisy terminal commands when RTK is available, while keeping raw commands for exact output. Logics guidance is auto-enabled when `logics-manager` is available; use `--logics off` to disable that guidance for a session, or `--logics on` to pin it explicitly. This guidance is passed as instructions, never as a first message: Claude receives it through `--append-system-prompt` and Codex through `-c developer_instructions=...`, appended after any `developer_instructions` already set in the profile's `config.toml` (on Python older than 3.11 without `tomli`, a profile that sets its own is left untouched and gets no cdx guidance). Launching or resuming a session therefore never submits a turn on your behalf. Antigravity and Ollama have no instruction channel and receive no guidance. Whatever the `--rtk` setting, every launch, resume and `cdx run` sets `RTK_DB_PATH` to the rtk history of your real home (`~/Library/Application Support/rtk/history.db` on macOS, `~/.local/share/rtk/history.db` on Linux, `%LOCALAPPDATA%\rtk\history.db` on Windows), so `rtk gain` in your own shell counts what every account saved instead of each profile keeping its own; an `RTK_DB_PATH` you set yourself is kept. Isolated workspace runs instead use the execution host's shared-cache RTK database at `/cache/rtk/history.db`; see [Git and RTK in isolated workspaces](operations.md#git-and-rtk-in-isolated-workspaces). If you also pass `--append-system-prompt` through `--extra-args`, Claude receives the option twice; prefer putting your own text in the profile's `CLAUDE.md`.
158
158
 
159
159
  ## Terminal Titles
160
160
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdx-manager",
3
- "version": "0.25.6",
3
+ "version": "0.25.8",
4
4
  "description": "Terminal session manager for Codex and Claude accounts.",
5
5
  "license": "MIT",
6
6
  "author": "Alexandre Agostini",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cdx-manager"
7
- version = "0.25.6"
7
+ version = "0.25.8"
8
8
  description = "Terminal session manager for Codex and Claude accounts."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9.2"
package/src/update_all.py CHANGED
@@ -19,7 +19,6 @@ _TOOLS = (
19
19
  ("rtk", "rtk"),
20
20
  )
21
21
  _SOURCE_TIMEOUT = 5
22
- _ANTIGRAVITY_RELEASES = "https://antigravity-auto-updater-974169037036.us-central1.run.app/releases"
23
22
  _PONYTAIL_URL = "https://github.com/DietrichGebert/ponytail.git"
24
23
 
25
24
 
@@ -79,9 +78,6 @@ def _latest_version(name):
79
78
  if release.get("draft") or release.get("prerelease"):
80
79
  return None
81
80
  return _parse_version(release["tag_name"])
82
- if name == PROVIDER_ANTIGRAVITY:
83
- # agy belongs to the original editor, whose feed differs from Hub and IDE 2.
84
- return _parse_version(_fetch_json(_ANTIGRAVITY_RELEASES)[0]["version"])
85
81
  if name == PROVIDER_OLLAMA:
86
82
  arch = {"x86_64": "amd64", "aarch64": "arm64"}.get(platform.machine().lower(), platform.machine().lower())
87
83
  query = urlencode({"os": platform.system().lower(), "arch": arch, "version": "0.0.0"})
@@ -111,6 +107,8 @@ def _newer(latest, current):
111
107
 
112
108
 
113
109
  def _native_update_command(name, command, path, env, runner):
110
+ if name == PROVIDER_ANTIGRAVITY:
111
+ return [command, "update"]
114
112
  if name == PROVIDER_OLLAMA and platform.system().lower() == "linux":
115
113
  return None
116
114
  real_path = os.path.realpath(path)
@@ -171,6 +169,11 @@ def collect_update_all_plan(service, env=None, runner=None):
171
169
  else:
172
170
  item["status"] = "unknown"
173
171
  item["reason"] = "Latest version unavailable from the official source." if not latest else "Installed version unavailable or does not match the published release."
172
+ if name == PROVIDER_ANTIGRAVITY:
173
+ # The editor release feed does not describe agy's CLI versions.
174
+ item["reason"] = "Latest CLI version unknown; agy update checks and applies its own updates."
175
+ item["update_command"] = _native_update_command(name, command, path, env, runner)
176
+ steps.append({"name": name, "command": item["update_command"], "env": {}})
174
177
  items.append(item)
175
178
 
176
179
  rtk_available = any(item["name"] == "rtk" and item["installed"] for item in items)
@@ -233,10 +236,13 @@ def apply_update_all_plan(plan, service, env=None, runner=None, progress=None):
233
236
  if result["returncode"] == 0 and item:
234
237
  after = _version(item["command"], env, runner)
235
238
  result.update(version_before=item["version"], version_after=after, latest_version=item["latest_version"])
236
- if not (_newer(after, item["version"]) and
237
- (after == item["latest_version"] or _newer(after, item["latest_version"]))):
239
+ output = "\n".join((result["stdout"], result["stderr"]))
240
+ reports_current = bool(re.search(r"(?im)^\s*You are already on the latest version\.\s*$", output))
241
+ advanced = (_newer(after, item["version"]) and
242
+ (not item["latest_version"] or after == item["latest_version"] or _newer(after, item["latest_version"])))
243
+ if not (advanced or (after and after == item["version"] and reports_current)):
238
244
  result["returncode"] = 1
239
- result["reason"] = "Update verification failed: installed version did not advance to the published release."
245
+ result["reason"] = "Update verification failed: installed version did not advance to the expected release and updater did not confirm it is current."
240
246
  result["stderr"] = (result["stderr"] + "\n" + result["reason"]).strip()
241
247
  results.append(result)
242
248
  if result["returncode"] == 0 and step.get("id"):
@@ -71,12 +71,14 @@ def private_auth_home(auth):
71
71
  def clean_env(source: dict) -> dict:
72
72
  allowed = ("PATH", "LANG", "LC_ALL", "TERM", "TZ")
73
73
  env = {key: source[key] for key in allowed if key in source}
74
+ env["PATH"] = source.get("PATH", os.defpath) + os.pathsep + str(Path.home() / ".local/bin")
74
75
  env.update(HOME="/home/agent", XDG_CONFIG_HOME="/home/agent/.config",
75
76
  XDG_CACHE_HOME="/cache", GIT_CONFIG_NOSYSTEM="1",
76
77
  GIT_CONFIG_GLOBAL="/dev/null", GIT_TERMINAL_PROMPT="0", GIT_OPTIONAL_LOCKS="0",
77
78
  GIT_CONFIG_COUNT="1", GIT_CONFIG_KEY_0="credential.helper", GIT_CONFIG_VALUE_0="",
78
79
  GH_CONFIG_DIR="/home/agent/.config/gh", NPM_CONFIG_CACHE="/cache/npm",
79
80
  PIP_CACHE_DIR="/cache/pip", UV_CACHE_DIR="/cache/uv",
81
+ RTK_DB_PATH="/cache/rtk/history.db",
80
82
  # Cache users have different mount paths; their GC cannot identify each other's browsers.
81
83
  PLAYWRIGHT_BROWSERS_PATH="/cache/playwright", PLAYWRIGHT_SKIP_BROWSER_GC="1", CI="1")
82
84
  return env
@@ -85,7 +87,8 @@ def clean_env(source: dict) -> dict:
85
87
  def sandbox_command(command: list[str], tree: Path, *, network: bool, writable: bool = True,
86
88
  source: dict | None = None, auth_home: str = "", provider: str = "", observation_home: str = "") -> tuple[list[str], dict]:
87
89
  source = os.environ if source is None else source
88
- binary = shutil.which(command[0], path=source.get("PATH"))
90
+ env = clean_env(source)
91
+ binary = shutil.which(command[0], path=env["PATH"])
89
92
  bwrap = shutil.which("bwrap", path=source.get("PATH"))
90
93
  if not binary or not bwrap:
91
94
  raise RuntimeError("Workspace isolation requires bubblewrap and " + command[0])
@@ -93,6 +96,7 @@ def sandbox_command(command: list[str], tree: Path, *, network: bool, writable:
93
96
  raise RuntimeError("Workspace must be a directory")
94
97
  tree = tree.resolve()
95
98
  CACHE.mkdir(parents=True, exist_ok=True)
99
+ (CACHE / "rtk").mkdir(exist_ok=True)
96
100
  # Provider transport uses the same restricted egress, even for read-only tools.
97
101
  online = bool(network or provider)
98
102
  if online:
@@ -137,15 +141,24 @@ def sandbox_command(command: list[str], tree: Path, *, network: bool, writable:
137
141
  or common.is_relative_to(tree) or common.is_relative_to(CACHE.resolve())
138
142
  or Path((gitdir / "gitdir").read_text().strip()).resolve() != marker):
139
143
  raise RuntimeError("Invalid external worktree Git directory")
140
- args += ["--ro-bind", str(marker), str(marker), "--ro-bind", str(common), str(common),
141
- "--tmpfs", str(common / "worktrees"), "--ro-bind", str(gitdir), str(gitdir),
142
- "--ro-bind", "/dev/null", str(common / "config"),
143
- "--tmpfs", str(common / "hooks")]
144
- if (gitdir / "config.worktree").exists():
145
- args += ["--ro-bind", "/dev/null", str(gitdir / "config.worktree")]
144
+ # Use sandbox-owned parents rather than traversing private host homes.
145
+ # Do not expose repository config (remote URLs/helpers/includes), hooks,
146
+ # other worktrees, or the source checkout. Git remains read-only.
147
+ private_git = Path("/home/agent/.git")
148
+ args += ["--ro-bind", str(marker), str(marker), "--dir", str(private_git)]
149
+ # Combine this worktree's HEAD/index with shared history, without any
150
+ # commondir/gitdir pointers back into the host filesystem.
151
+ for origin, names in (
152
+ (gitdir, ["HEAD", "index", *[p.name for p in gitdir.glob("sharedindex.*")]]),
153
+ (common, ["objects", "refs", "packed-refs", "shallow", *[p.name for p in common.glob("sharedindex.*")]]),
154
+ ):
155
+ for name in names:
156
+ if (origin / name).exists():
157
+ args += ["--ro-bind", str(origin / name), str(private_git / name)]
158
+ env.update(GIT_DIR=str(private_git), GIT_COMMON_DIR=str(private_git),
159
+ GIT_WORK_TREE=str(tree))
146
160
  elif marker.exists():
147
161
  raise RuntimeError("Expected an isolated git worktree")
148
- env = clean_env(source)
149
162
  if provider == "codex":
150
163
  auth_path = Path(auth_home) / "auth.json"
151
164
  if json.loads(auth_path.read_text()).get('tokens', {}).get('refresh_token') != '':