diff --git a/AGENTS.md b/AGENTS.md index 8ced591cc..95f2a2b04 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,209 +1,106 @@ -# agents-cli +# agents-cli (monorepo) -CLI for managing AI coding agent versions, config, sessions, and cloud dispatch (Claude, Codex, Gemini, Cursor, OpenCode, OpenClaw, Grok Build, Droid). +A monorepo housing the `agents` CLI and the Factory VS Code extension, plus their +shared libraries and native helpers. Install, configure, run, and dispatch AI +coding agents (Claude, Codex, Gemini, Cursor, OpenCode, OpenClaw, Grok, Droid, …) +from one place. -> Phoenix Labs OSS (Apache-2.0). **NOT part of the Rush brand** — see [§Brand identity](#brand-identity) before touching assets, demos, or website. +> Phoenix Labs OSS (Apache-2.0). **NOT part of the Rush brand** — see +> [§Brand identity](#brand-identity) before touching assets, demos, or website. -## Core design choices (read this first) +**This file is the repo map + repo-wide policy.** Each component has its own +`AGENTS.md` (agent map) and/or `README.md` (usage). Start there for anything +component-specific — this file deliberately stays shallow. -Break these and downstream code drifts silently. +## Repo map -### 1. Three DotAgents repos, resolution is project > user > system - -Resources AND `agents.yaml` resolve in that order. Same-name overrides, everything else unions. - -| Path | Role | Edited by | -|---|---|---| -| `/.agents/` | **Project repo** — project-pinned commands / skills / hooks / rules. | Project maintainers | -| `~/.agents/` | **User repo** — user resources + ALL operational state (versions, shims, sessions, `agents.yaml`, browser). | You / CLI | -| `~/.agents/.system/` | **System repo** — npm-shipped defaults ONLY. | Maintainers (`gh:phnx-labs/.agents-system`) | - -Extra repos register via `agents repo add ` → clone into `~/.agents-/` and participate after the user repo. - -### 2. `AGENTS.md` is the canonical memory file - -`CLAUDE.md`, `GEMINI.md` are symlinks (`ls -la *.md` confirms). **Edit `AGENTS.md` only** — editing the symlink targets directly will be stomped on the next sync. The sync writes the right file name per agent (`OPENCODE.md`, `.cursorrules`, etc.) — see [§Supported harnesses](#supported-harnesses). - -### 3. Capability table gates per-agent writes - -`supports(agent, cap, version?)` in [`apps/cli/src/lib/capabilities.ts`](apps/cli/src/lib/capabilities.ts) is the only place that decides whether an agent+version can receive a resource. Out-of-range versions are **skipped silently** — do not add per-call agent checks elsewhere; route through `supports()`. - -### 4. No fallback logic for legacy layouts - -[`apps/cli/src/lib/migrate.ts`](apps/cli/src/lib/migrate.ts) folds legacy paths ONCE at install time (`runMigration()` writes a `.migrated` sentinel). Downstream code assumes the post-fold layout. "Just-in-case" branches re-introduce drift bugs; the migrator is the single source of truth for legacy handling. - -### 5. Hooks live in a single layered `hooks.yaml` - -System (`~/.agents/.system/hooks.yaml`) + user (`~/.agents/hooks.yaml`) merged, user wins on same name. Per-entry `matches:` predicates (`prompt_contains`, `prompt_matches`, `tool_name`, `tool_args_match`, `cwd_includes`, `project_has`, `git_dirty`) AND together at fire time. Per-entry `enabled: false` disables a system-shipped hook from the user side. - -The `agents:` field in `ManifestHook` is `@deprecated` ([`apps/cli/src/lib/types.ts:176-177`](apps/cli/src/lib/types.ts)) — capability table decides which agents register a hook. Promptcuts are hook data (`hooks/promptcuts.yaml`), not a top-level resource. - -### 6. Multi-agent work → `agents teams` - -DAG-style, boundary contracts, `--watch` supervisor, `--worktree` isolation, optional `--cloud` dispatch. The old `mcp__Swarm__*` surface was folded into teams (`migrateLegacySwarmToTeams()` in `apps/cli/src/lib/migrate.ts`). Don't reach for Swarm — it's gone. - ---- - -## Directory layout - -**System repo (`~/.agents/.system/`)** — npm-shipped, fully tracked. Layout: `commands/ hooks/ hooks.yaml mcp/ permissions/ profiles/ rules/ skills/`. Clone `gh:phnx-labs/.agents-system` to inspect what ships. Nothing else belongs here. - -**User repo (`~/.agents/`):** - -- **Resources** (git-tracked, top-level): `commands/`, `skills/`, `hooks/`, `rules/`, `mcp/`, `permissions/`, `profiles/`, `subagents/`, `plugins/`, `workflows/`, `routines/`, `cli/`, `agents.yaml` -- **Durable runtime** (`~/.agents/.history/`): `versions/`, `sessions/`, `runs/`, `trash/`, `backups/`, `teams/agents/` -- **Regenerable runtime** (`~/.agents/.cache/`): `shims/`, `bin/`, `packages/`, `cloud/`, `drive/`, `logs/`, `helpers/`, `state/`, `companion/`, `.migrated` -- **Browser:** `browser/profiles/` (configs) + `~/.agents/.cache/browser//` (chrome-data, pids) - -**Plugins (`~/.agents/plugins/`) are user-authored, NOT regenerable.** Each plugin = a directory with `.claude-plugin/plugin.json`, optionally `skills/`, `commands/`, `hooks/`, `subagents/`, `.mcp.json`. The CLI never moves `plugins/` into `.cache/`. Treat exactly like `skills/`. - -**No `secrets/` directory anywhere** — bundle metadata lives in macOS Keychain. - -**CLI binaries (`~/.agents/cli/.yaml`)** declare host-level tools (e.g. `gh`, `higgsfield`). `install:` methods tried in order (`npm` / `brew` / `script` / `binary`). Unlike skills/commands/hooks, CLI manifests are NOT copied into per-agent version homes — they install to host PATH. Manage via `agents cli list|install|check|view|add`. `agents repo refresh` reports missing entries and prompts to install. - -See [`docs/00-concepts.md`](apps/cli/docs/00-concepts.md) for the full mental model and resolution semantics. - -## Supported harnesses - -14 harnesses ship support today. The full id list is `AgentId` ([`apps/cli/src/lib/types.ts:10`](apps/cli/src/lib/types.ts)); per-harness config + capabilities live in the `AGENTS` registry ([`apps/cli/src/lib/agents.ts:215-550`](apps/cli/src/lib/agents.ts)) and are gated through `supports()` ([`apps/cli/src/lib/capabilities.ts`](apps/cli/src/lib/capabilities.ts)). - -### Prioritized (first-class) harnesses - -These six are the support-priority set — new features and parity work target them first. When a feature can't reach all 14 at once, it must reach these: - -**Claude Code** (`claude`) · **Codex CLI** (`codex`) · **Kimi CLI** (`kimi`) · **Antigravity CLI** (`antigravity`) · **Grok CLI** (`grok`) · **OpenCode** (`opencode`) - -### Feature support matrix - -Not every harness supports every capability — the registry decides per harness. Snapshot below (★ = prioritized); [`apps/cli/src/lib/agents.ts`](apps/cli/src/lib/agents.ts) is canonical, keep both in sync. - -| Harness | `id` | hooks | mcp | allowlist | skills | commands | plugins | subagents | workflows | modes | -|---|---|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|---| -| ★ Claude Code | `claude` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | plan·edit·auto·skip | -| ★ Codex CLI | `codex` | ≥0.116 | ✓ | — | ✓ | <0.117 | ≥0.128 | — | — | plan·edit·skip | -| ★ Kimi CLI | `kimi` | ✓ | ✓ | ✓ | ✓ | — | ✓ | — | — | plan·edit·auto·skip | -| ★ Antigravity CLI | `antigravity` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | — | edit·skip | -| ★ Grok CLI | `grok` | ✓ | ✓ | ✓ | ✓ | — | ✓ | — | — | plan·edit·skip | -| ★ OpenCode | `opencode` | — | ✓ | — | ✓ | ✓ | — | — | — | plan·edit | -| Gemini † | `gemini` | ≥0.26 | ✓ | — | ✓ | ✓ | — | — | — | plan·edit·skip | -| Cursor | `cursor` | — | ✓ | — | ✓ | ✓ | — | — | — | edit·skip | -| OpenClaw | `openclaw` | ✓ | ✓ | — | ✓ | — | ✓ | ✓ | — | plan·edit·skip | -| Copilot | `copilot` | — | ✓ | — | ✓ | ✓ | ✓ | — | — | plan·edit·auto·skip | -| Amp | `amp` | — | ✓ | — | ✓ | ✓ | — | — | — | plan·edit | -| Kiro | `kiro` | — | ✓ | — | ✓ | ✓ | — | — | — | edit | -| Goose | `goose` | — | ✓ | — | — | — | — | — | — | edit | -| Droid | `droid` | ✓ | ✓ | — | — | ✓ | ✓ | ✓ | — | plan·edit·auto·skip | - -✓ = supported · — = not supported · version cell = supported only within that range (e.g. `≥0.116` needs version `>= 0.116.0`; `<0.117` only below `0.117.0`). Out-of-range cells are **skipped silently** — `supports()` returns false and the resource is never written. - -- `rules` (the memory file) is supported by **all 14** — each writes its own file (see config matrix below). `workflows` is **Claude-only**. `mcp` is universal. -- `allowlist` (granular per-tool permission rules) is limited to `claude`, `antigravity`, `grok`, `kimi`. -- `subagents` is limited to `claude`, `openclaw`, `droid`. -- **† Gemini (`gemini`) is deprecated by Google.** The Gemini CLI was retired for free/Pro/Ultra tiers on June 18, 2026 (announced at Google I/O 2026); **Antigravity CLI (`antigravity`) is the official successor.** agents-cli still manages existing installs but warns on `agents add gemini` / `agents teams add … gemini` (`warnAgentDeprecated` in [`apps/cli/src/lib/agents.ts`](apps/cli/src/lib/agents.ts); marker on the `gemini` registry entry). - -### Config matrix - -| Agent | Commands dir | Memory file | -|-------|--------------|-------------| -| Claude | `commands/` (md) | `CLAUDE.md` → `AGENTS.md` | -| Codex | `prompts/` (md) | `AGENTS.md` (native) | -| Gemini | `commands/` (toml) | `GEMINI.md` → `AGENTS.md` | -| Cursor | `commands/` (md) | `.cursorrules` | -| OpenCode | `commands/` (md) | `OPENCODE.md` | -| Grok | skills + `.grok/` (hooks, plugins, agents, `config.toml`) | `AGENTS.md` + `~/.grok/memory/` | -| Droid | `commands/` (md) + `.factory/` (`settings.json` hooks, `mcp.json`, `droids/`, `plugins/`) | `AGENTS.md` (native) | - -## Source layout - -``` -src/ - index.ts # CLI entry (commander.js) - commands/ # User-facing subcommands (one file per `agents `) - lib/ - state.ts # Path constants; agents.yaml read/write - resources.ts # resolveResource() / listResources() — layered resolution - capabilities.ts # supports() — the per-agent write gate - agents.ts # Per-agent capability table - versions.ts # Install, remove, syncResourcesToVersion - shims.ts # Shim generation, config symlink switching - hooks.ts # hooks.yaml parser + per-agent registrar - hooks/match.ts # `matches:` predicate evaluator - migrate.ts # One-shot idempotent migrations - session/ # Discovery, parsing, rendering (Claude / Codex / Gemini / OpenCode) - terminal/ # Terminal launch engine — tab/split surfaces in iTerm/Ghostty/tmux, local or --host (docs/terminal-engine.md) - cloud/ # Provider registry (Rush / Codex / Factory / Antigravity) - teams/ # `agents teams` orchestration - profiles.ts # Host CLI + endpoint + model bundles -``` - -## Build, test, dev - -```bash -cd apps/cli && bun install && bun run build && bun test -``` - -Tests are `*.test.ts` next to source; integration in `tests/`. Every PR to `main` runs the real suite cheaply on Linux — `test` ([`tests.yml`](.github/workflows/tests.yml)) plus `gitleaks` ([`secret-scan.yml`](.github/workflows/secret-scan.yml)); those two are the required checks. The full cross-platform matrix (ubuntu + macOS + Windows × Node 22/24, [`ci.yml`](.github/workflows/ci.yml)) is cost-gated to `release/**` branches and `v*` tags (plus manual dispatch), so it runs before a release, not on every PR. - -**Local dev build:** `apps/cli/scripts/install.sh --skip-tests` builds the working tree and installs at `$HOME/.local/agents-cli-dev/`, symlinked into `$HOME/.local/bin/agents`. The npm-installed global is never touched. Version stamps as `0.0.0-dev.[-dirty]` so `agents --version` disambiguates which build is on PATH. - -```bash -apps/cli/scripts/install.sh --skip-tests -# Put $HOME/.local/bin ahead of nvm's bin dir on PATH to use the dev build. -# Revert: drop the PATH entry, or `rm -rf ~/.local/agents-cli-dev`. ``` - -**Why two prefixes?** Issue #20-class regressions ship to npm and silently break auto-updaters; side-by-side dev installs let you iterate without risking the working install. - -**Bin entrypoints need `chmod 755`.** [`apps/cli/scripts/build.sh`](apps/cli/scripts/build.sh) chmods every `package.json#bin` entry after `tsc` emits. Newer npm preserves tarball file mode and does NOT auto-chmod — 644 surfaces as `zsh: permission denied: agents`. Do not skip this step. - -## Releasing - -**Releases are cut locally on macOS — there is no CI publish.** Run from a clean, in-sync `main`: - -```bash -apps/cli/scripts/release.sh # dry-run: validates bump, type-checks, builds, tests, previews tarball -apps/cli/scripts/release.sh --apply # opens release/v PR, waits CI, merges, tags, npm publish +apps/ + cli/ @phnx-labs/agents-cli — the `agents`/`ag` CLI (the published npm package) + factory/ Factory — the VS Code extension + its React UI + Electron app (publisher: swarmify, swarm-ext) +native/ + computer-mac/ Swift daemon behind `agents computer` (Accessibility + screen capture) + computer-win/ C#/.NET daemon behind `agents computer` on Windows (UI Automation) +packages/ + session-tracker/ @agents/session-tracker — SessionStart hook that WRITES live-session state + swarmify-mirror/ legacy npm-redirect stub (@companion/agents-cli → @phnx-labs/agents-cli) +docs/ Source-grounded design reference (start: docs/00-concepts.md) +assets/ demo/ website/ Brand, launch demo, landing (repo-root, not shipped in any tarball) ``` -`release.sh` reads the npm token from the `npmjs.com` secrets bundle (`agents secrets`), so no 2FA prompt and no token on disk. - -**Why not CI?** The package bundles `dist/lib/secrets/Agents CLI.app` — a native keychain helper that must be compiled with `swiftc`, codesigned with the Developer ID identity, and notarized (`xcrun notarytool`); see [`apps/cli/scripts/build-keychain-helper.sh`](apps/cli/scripts/build-keychain-helper.sh). `prepack` ([`apps/cli/scripts/verify-keychain-helper.sh`](apps/cli/scripts/verify-keychain-helper.sh)) refuses to pack unless that signed binary matches the sha pinned in `apps/cli/scripts/Agents CLI.app.sha256`. CI runners are Linux and cannot produce it, so publishing stays on a macOS machine that holds the signed helper. The keychain helper itself is rebuilt only when [`apps/cli/src/lib/secrets/keychain-helper.swift`](apps/cli/src/lib/secrets/keychain-helper.swift) changes — rerun `apps/cli/scripts/build-keychain-helper.sh` (needs `APPLE_ID` / `APPLE_APP_SPECIFIC_PASSWORD` / `APPLE_TEAM_ID`) and update the pinned sha. - -**The `@swarmify/agents-cli` shim is intentionally frozen at 1.19.x — do NOT "catch it up."** It is a legacy re-export that has **not been published since v1.20.0**: `release.sh` publishes only `@phnx-labs`, and the shim's "Building… / Tarball preview" lines are vestigial — there is no `npm publish` for it (see [`apps/cli/scripts/release.sh`](apps/cli/scripts/release.sh), the "legacy shim no longer published as of v1.20.0" comment). Bumping `@swarmify` to the current version would un-deprecate a retired package. Leave it at 1.19.1. - -**The macOS menu-bar helper** ([`apps/cli/menubar`](apps/cli/menubar) → `bin/MenubarHelper.app`) ships in the tarball the same way as the keychain app — built into `bin/`, copied to `dist/lib/menubar/` by `build`, gated in `prepack` by [`apps/cli/scripts/verify-menubar-helper.sh`](apps/cli/scripts/verify-menubar-helper.sh) (presence + `codesign --verify`). Unlike the keychain helper it needs **no notarization** (a status item has no Keychain ACL / TCC grant). Keep it a **separate bundle** from the keychain app — process isolation matters: a menu-bar UI crash must never take down the secret broker. Stage a freshly-built `bin/MenubarHelper.app` before any release or the menu bar ships code-only (the 1.20.22 bug the prepack gate now prevents). - -## Conventions - -- `AGENTS.md` is canonical; `CLAUDE.md` / `GEMINI.md` are symlinks. **Edit `AGENTS.md` only.** -- `agents repo push` / `pull` operates on `~/.agents/` only. System updates ride `npm update -g @phnx-labs/agents-cli`. -- Real services only — no mocking. Tests must exercise the actual critical path (see [CLAUDE.md](CLAUDE.md) testing rules). -- **PRs are auto-reviewed by `prix/code-reviewer`** ([`.github/rush.yml`](.github/rush.yml)) — it reviews every PR to `main` and posts its verdict as the **`prix-cloud`** comment. That is the non-author review: rely on it and merge on green, don't spawn a redundant subagent reviewer. Review manually only if `prix-cloud` hasn't posted after CI settles or flags something to dig into. (It's a Rush Cloud app, not a `.github/workflows/` Action.) +| Component | What it is | Read | +|---|---|---| +| [`apps/cli`](apps/cli) | The CLI — version mgmt, config sync, sessions, teams, cloud, browser, computer, secrets | [AGENTS.md](apps/cli/AGENTS.md) · [README.md](apps/cli/README.md) | +| [`apps/factory`](apps/factory) | Factory VS Code extension — spawns agent terminals as tabs, Factory Floor dashboard, dispatch | [AGENTS.md](apps/factory/AGENTS.md) · [README.md](apps/factory/README.md) | +| [`native/computer-mac`](native/computer-mac) | macOS `agents computer` backend (Swift) | [AGENTS.md](native/computer-mac/AGENTS.md) · [README.md](native/computer-mac/README.md) | +| [`native/computer-win`](native/computer-win) | Windows `agents computer` backend (C#/.NET) | [AGENTS.md](native/computer-win/AGENTS.md) · [README.md](native/computer-win/README.md) | +| [`packages/session-tracker`](packages/session-tracker) | Live-session **writer** (SessionStart hook) | [AGENTS.md](packages/session-tracker/AGENTS.md) · [README.md](packages/session-tracker/README.md) | +| [`packages/swarmify-mirror`](packages/swarmify-mirror) | Deprecated npm-redirect stub | [README.md](packages/swarmify-mirror/README.md) | + +**No JS workspaces.** Each package self-installs (`bun install` inside it). There is +deliberately no root `workspaces` field — adding one changed bun's hoisting and broke +`@inquirer/core` resolution under `--frozen-lockfile`. Don't add it back. There are no +cross-package imports except the CLI resolving the native helpers by relative path. + +## Conventions (repo-wide) + +- **`AGENTS.md` is the canonical memory file.** `CLAUDE.md` / `GEMINI.md` are symlinks + to it (`ls -la *.md`). **Edit `AGENTS.md` only** — a symlink target edited directly + gets stomped on the next sync. This holds at the repo root and in every component. +- **Real services only — no mocking.** Tests must exercise the actual critical path. + Test file sits next to source (`read.ts` → `read.test.ts`); integration tests in each + package's `tests/`. +- **PRs are auto-reviewed by `prix/code-reviewer`** ([`.github/rush.yml`](.github/rush.yml)) — + it reviews every PR to `main` and posts its verdict as the **`prix-cloud`** comment. That + is the non-author review: rely on it and merge on green, don't spawn a redundant subagent + reviewer. Review manually only if `prix-cloud` hasn't posted after CI settles or flags + something to dig into. (It's a Rush Cloud app, not a `.github/workflows/` Action.) +- **The default branch is untouchable.** Every change is a git worktree + PR — never + edit or commit on `main`. Worktrees live under `.agents/worktrees//`. +- **VS Code publish identity is frozen.** `apps/factory` publishes as publisher + `swarmify`, name `swarm-ext`, appId `com.swarmify.factory`, productName `Factory`. + Never change these — it would orphan the Marketplace listing. The product is called + **Factory**; the CLI is **agents-cli**. (There is no "Agency.Li" — that was a + dictation artifact.) ## Security -**No sensitive data in any DotAgents repo.** All three repos are designed to be safely version-controlled. - -- Use `agents secrets` — bundle metadata in macOS Keychain, never on disk. -- Browser profile configs reference bundles by name, not raw credentials. -- Accidentally committed a secret? Rotate immediately — git history persists. - -## Detailed design - -[`docs/`](apps/cli/docs/README.md) is source-grounded reference. Start with [`00-concepts.md`](apps/cli/docs/00-concepts.md). Index covers core (concepts, version mgmt, sync, sessions, observability), credentials (profiles, secrets), orchestration (teams, cloud, routines), extensibility (plugins, workflows, subagents, hooks), automation (browser, pty, computer). - ---- +**No sensitive data in any DotAgents repo** — all three (`project` / `user` / `system`) +are designed to be safely version-controlled. Use `agents secrets` (macOS +Keychain-backed, metadata only, never raw credentials on disk). Committed a secret by +accident? Rotate immediately — git history persists. ## Brand identity -`agents-cli` is a Phoenix Labs OSS product (Apache-2.0). **NOT part of the Rush brand.** Phoenix Horizon, Inc. owns several brands; agents-cli sits in the OSS lane, Rush in the consumer-product lane. +`agents-cli` is a Phoenix Labs OSS product (Apache-2.0). **NOT part of the Rush brand.** +Phoenix Horizon, Inc. owns several brands; agents-cli sits in the OSS lane, Rush in the +consumer-product lane. When working on this repo or the sibling `agent-cli-web` landing: -- **No Rush styling** — no gold sheen, cream paper, falcon mark, Cormorant Garamond serif, "Interface for the future" voice. The Rush plugin at `~/.agents/plugins/rush/` is a tool to call, not a brand to import. -- **No `~/Rush/Brand/` writes** for agents-cli renders, screenshots, or videos. Use `~/Phoenix/agents-cli/` or this repo's `assets/` / `demo/out/`. -- **Visual language is terminal-coded** — `#0a0a0a` bg, `#a3e635` lime accent, JetBrains Mono for wordmark + code, Inter for prose. See [`assets/`](assets/), [`demo/src/`](demo/src/), [`website/`](website/). -- **Voice is direct-developer** — verb + artifact, no marketing claims. Closer to a `man` page than a landing pitch. -- **Composer + animator skills (`~/.agents/plugins/rush/skills/`)** can be USED here — but ignore their §Brand voice sections (Rush-only). Override destination to `~/Phoenix/agents-cli/launches/` and use this repo's color/type tokens. +- **No Rush styling** — no gold sheen, cream paper, falcon mark, Cormorant Garamond + serif, "Interface for the future" voice. The Rush plugin at `~/.agents/plugins/rush/` + is a tool to call, not a brand to import. +- **No `~/Rush/Brand/` writes** for agents-cli renders, screenshots, or videos. Use + `~/Phoenix/agents-cli/` or this repo's `assets/` / `demo/out/`. +- **Visual language is terminal-coded** — `#0a0a0a` bg, `#a3e635` lime accent, JetBrains + Mono for wordmark + code, Inter for prose. See [`assets/`](assets/), [`demo/src/`](demo/src/), + [`website/`](website/). +- **Voice is direct-developer** — verb + artifact, no marketing claims. Closer to a `man` + page than a landing pitch. +- **Composer + animator skills** can be USED here — but ignore their §Brand voice + sections (Rush-only). Override destination to `~/Phoenix/agents-cli/launches/` and use + this repo's color/type tokens. + +Note: `apps/factory` (the Factory extension) is a **different product** with its own +`swarmify`/Factory identity — the Phoenix "no Rush brand" rule governs the CLI, assets, +demo, and website; Factory keeps its own brand and publish identity (frozen, above). + +If any agent starts pulling Rush styling, paths, or voice into agents-cli work, stop +and reread this block. + +## Detailed design -If any agent starts pulling Rush styling, paths, or voice into agents-cli work, stop and reread this block. +[`docs/`](docs/README.md) is source-grounded reference. Start with +[`00-concepts.md`](docs/00-concepts.md) for the full mental model and resolution +semantics of the CLI. diff --git a/apps/cli/AGENTS.md b/apps/cli/AGENTS.md new file mode 100644 index 000000000..7c2f73004 --- /dev/null +++ b/apps/cli/AGENTS.md @@ -0,0 +1,215 @@ +# agents-cli (the CLI) + +`@phnx-labs/agents-cli` — the `agents` / `ag` CLI for managing AI coding-agent +versions, config, sessions, and cloud dispatch (Claude, Codex, Gemini, Cursor, +OpenCode, OpenClaw, Grok, Droid, …). + +This is the **internal architecture** map. The user-facing feature tour is +[README.md](README.md) (pin versions, run, sessions, hosts, teams, workflows, +plugins, browser, secrets, routines, pty). This file covers the design choices, +module map, build, and release mechanics the README does not. + +> Phoenix Labs OSS (Apache-2.0). **NOT part of the Rush brand** — brand rules are +> repo-wide, see the root [AGENTS.md](../../AGENTS.md) §Brand identity. + +## Core design choices (read this first) + +Break these and downstream code drifts silently. + +### 1. Three DotAgents repos, resolution is project > user > system + +Resources AND `agents.yaml` resolve in that order. Same-name overrides, everything +else unions. + +| Path | Role | Edited by | +|---|---|---| +| `/.agents/` | **Project repo** — project-pinned commands / skills / hooks / rules. | Project maintainers | +| `~/.agents/` | **User repo** — user resources + ALL operational state (versions, shims, sessions, `agents.yaml`, browser). | You / CLI | +| `~/.agents/.system/` | **System repo** — npm-shipped defaults ONLY. | Maintainers (`gh:phnx-labs/.agents-system`) | + +Extra repos register via `agents repo add ` → clone into `~/.agents-/` +and participate after the user repo. + +### 2. `AGENTS.md` is the canonical memory file + +`CLAUDE.md`, `GEMINI.md` are symlinks. **Edit `AGENTS.md` only** — editing a symlink +target directly gets stomped on the next sync. The sync writes the right file name +per agent (`OPENCODE.md`, `.cursorrules`, etc.). + +### 3. Capability table gates per-agent writes + +`supports(agent, cap, version?)` in [`src/lib/capabilities.ts`](src/lib/capabilities.ts) +is the only place that decides whether an agent+version can receive a resource. +Out-of-range versions are **skipped silently** — do not add per-call agent checks +elsewhere; route through `supports()`. + +### 4. No fallback logic for legacy layouts + +[`src/lib/migrate.ts`](src/lib/migrate.ts) folds legacy paths ONCE at install time +(`runMigration()` writes a `.migrated` sentinel). Downstream code assumes the +post-fold layout. "Just-in-case" branches re-introduce drift bugs; the migrator is +the single source of truth for legacy handling. + +### 5. Hooks live in a single layered `hooks.yaml` + +System + user `hooks.yaml` merged, user wins on same name. Per-entry `matches:` +predicates (`prompt_contains`, `prompt_matches`, `tool_name`, `tool_args_match`, +`cwd_includes`, `project_has`, `git_dirty`) AND together at fire time. Per-entry +`enabled: false` disables a system-shipped hook from the user side. The `agents:` +field in `ManifestHook` is `@deprecated` — the capability table decides which +agents register a hook. + +### 6. Multi-agent work → `agents teams` + +DAG-style, boundary contracts, `--watch` supervisor, `--worktree` isolation, optional +`--cloud` dispatch. The old `mcp__Swarm__*` surface was folded into teams +(`migrateLegacySwarmToTeams()` in `src/lib/migrate.ts`). Don't reach for Swarm — gone. + +## Supported harnesses + +14 harnesses ship support today. The full id list is `AgentId` +([`src/lib/types.ts`](src/lib/types.ts)); per-harness config + capabilities live in the +`AGENTS` registry ([`src/lib/agents.ts`](src/lib/agents.ts)) and are gated through +`supports()`. **Prioritized (first-class):** Claude Code, Codex CLI, Kimi CLI, +Antigravity CLI, Grok CLI, OpenCode — features target these six first. + +| Harness | `id` | hooks | mcp | allowlist | skills | commands | plugins | subagents | workflows | +|---|---|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:| +| ★ Claude Code | `claude` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| ★ Codex CLI | `codex` | ≥0.116 | ✓ | — | ✓ | <0.117 | ≥0.128 | — | — | +| ★ Kimi CLI | `kimi` | ✓ | ✓ | ✓ | ✓ | — | ✓ | — | — | +| ★ Antigravity CLI | `antigravity` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | — | +| ★ Grok CLI | `grok` | ✓ | ✓ | ✓ | ✓ | — | ✓ | — | — | +| ★ OpenCode | `opencode` | — | ✓ | — | ✓ | ✓ | — | — | — | +| Gemini † | `gemini` | ≥0.26 | ✓ | — | ✓ | ✓ | — | — | — | +| Cursor | `cursor` | — | ✓ | — | ✓ | ✓ | — | — | — | +| OpenClaw | `openclaw` | ✓ | ✓ | — | ✓ | — | ✓ | ✓ | — | +| Copilot | `copilot` | — | ✓ | — | ✓ | ✓ | ✓ | — | — | +| Amp | `amp` | — | ✓ | — | ✓ | ✓ | — | — | — | +| Kiro | `kiro` | — | ✓ | — | ✓ | ✓ | — | — | — | +| Goose | `goose` | — | ✓ | — | — | — | — | — | — | +| Droid | `droid` | ✓ | ✓ | — | — | ✓ | ✓ | ✓ | — | + +✓ = supported · — = not · version cell = only within that range (out-of-range = +skipped silently). [`src/lib/agents.ts`](src/lib/agents.ts) is canonical — keep this +snapshot in sync. `workflows` is Claude-only; `mcp` is universal; `allowlist` is +`claude`/`antigravity`/`grok`/`kimi`; `subagents` is `claude`/`openclaw`/`droid`. +**† Gemini is deprecated by Google** (retired June 18 2026); Antigravity is the +successor — the CLI warns on `agents add gemini` (`warnAgentDeprecated`). + +## Source layout + +``` +src/ + index.ts # CLI entry (commander.js) + commands/ # User-facing subcommands (one file per `agents `) + lib/ + state.ts # Path constants; agents.yaml read/write + resources.ts # resolveResource() / listResources() — layered resolution + capabilities.ts # supports() — the per-agent write gate + agents.ts # Per-agent capability table + versions.ts # Install, remove, syncResourcesToVersion + shims.ts # Shim generation, config symlink switching + hooks.ts # hooks.yaml parser + per-agent registrar + hooks/match.ts # `matches:` predicate evaluator + migrate.ts # One-shot idempotent migrations + session/ # `agents sessions` READER — discovery/parse/render of agent transcripts + terminal/ # Terminal launch engine — tab/split in iTerm/Ghostty/tmux, local or --host + cloud/ # Provider registry (Rush / Codex / Factory / Antigravity) + teams/ # `agents teams` orchestration + computer-rpc.ts # `agents computer` client → native/computer-mac (Unix socket) + ssh-tunnel.ts # `agents computer --host` → native/computer-win over ssh -L + menubar/ # Menu-bar helper installer (source in ../menubar) + profiles.ts # Host CLI + endpoint + model bundles +``` + +Note: `src/lib/session/` here is the transcript **reader**. The live-session +**writer** is a separate package, [`packages/session-tracker`](../../packages/session-tracker) +— different data, different consumer; see its AGENTS.md. + +## Bundled native helpers (where the tarball's `.app`s come from) + +Two native helpers ship **inside** this package's npm tarball; two more are +dev-only and live at repo-root `native/`. + +| Helper | Source | Ships in tarball? | Resolver | +|---|---|---|---| +| Keychain broker | `src/lib/secrets/keychain-helper.swift` → `bin/Agents CLI.app` | **Yes** (signed + notarized) | `src/lib/secrets/` | +| Menu-bar helper | [`menubar/`](menubar) (SwiftPM) → `bin/MenubarHelper.app` | **Yes** (signed, no notarization) | `src/lib/menubar/install-menubar.ts` | +| computer-mac | [`../../native/computer-mac`](../../native/computer-mac) | No (built from source) | `src/lib/computer-rpc.ts` | +| computer-win | [`../../native/computer-win`](../../native/computer-win) | No (staged at release) | `src/lib/ssh-tunnel.ts` | + +Path math: compiled resolvers run from `apps/cli/dist/lib/…`. Repo-root `native/` +is **4 hops up** (`../../../../native/…`); the co-located `menubar/` is **3 hops up** +(`../../../menubar/dist/…`) because it moved into `apps/cli` with the CLI. Recompute +depth if you move files — don't blind-replace. + +## Build, test, dev + +```bash +bun install && bun run build && bun test +``` + +Tests are `*.test.ts` next to source; integration in `tests/`. Every PR to `main` +runs the real suite cheaply on Linux — `test` +([`../../.github/workflows/tests.yml`](../../.github/workflows/tests.yml)) plus +`gitleaks`; those two are the required checks. The full cross-platform matrix +(ubuntu + macOS + Windows × Node 22/24, `ci.yml`) is cost-gated to `release/**` +branches and `v*` tags. CI runs from `apps/cli` via `defaults.run.working-directory`. + +**Local dev build:** `scripts/install.sh --skip-tests` builds the working tree and +installs at `$HOME/.local/agents-cli-dev/`, symlinked into `$HOME/.local/bin/agents`. +The npm-installed global is never touched. Version stamps as `0.0.0-dev.[-dirty]`. + +**Bin entrypoints need `chmod 755`.** [`scripts/build.sh`](scripts/build.sh) chmods +every `package.json#bin` entry after `tsc` emits. Newer npm preserves tarball file +mode and does NOT auto-chmod — 644 surfaces as `zsh: permission denied: agents`. + +The `files` allowlist in [`package.json`](package.json) is a **whitelist** — only +`dist/**`, the two signed `.app`s, and the postinstall scripts + README/LICENSE ship. +Nothing from `apps/`, `native/`, or sibling `packages/` can leak into the tarball. + +## Releasing + +**Releases are cut locally on macOS — there is no CI publish.** Run from a clean, +in-sync `main`: + +```bash +scripts/release.sh # dry-run: bump, type-check, build, test, tarball preview +scripts/release.sh --apply # commits chore(release), tags v, npm publish, pushes +``` + +`release.sh` reads the npm token from the `npmjs.com` secrets bundle (`agents +secrets`) — no 2FA prompt, no token on disk. The script's git-scope reads use +`:apps/cli/package.json` (not root) since the package moved under `apps/cli`. + +**Why not CI?** The tarball bundles `dist/lib/secrets/Agents CLI.app` — a native +keychain helper compiled with `swiftc`, codesigned (Developer ID), and notarized +(`xcrun notarytool`). `prepack` ([`scripts/verify-keychain-helper.sh`](scripts/verify-keychain-helper.sh)) +refuses to pack unless that signed binary matches the sha pinned in +`scripts/Agents CLI.app.sha256`. CI runners are Linux and cannot produce it. Rebuild +the helper only when `src/lib/secrets/keychain-helper.swift` changes. + +**Menu-bar helper** ([`menubar/`](menubar) → `bin/MenubarHelper.app`) ships the same +way — built into `bin/`, copied to `dist/lib/menubar/` by `build`, gated in `prepack` +by [`scripts/verify-menubar-helper.sh`](scripts/verify-menubar-helper.sh) (presence + +`codesign --verify`). No notarization (a status item has no Keychain ACL / TCC +grant). Keep it a **separate bundle** from the keychain app — a menu-bar crash must +never take down the secret broker. Stage a freshly-built `bin/MenubarHelper.app` +before any release or the menu bar ships code-only (the 1.20.22 bug the gate prevents). + +**The `@swarmify/agents-cli` shim is frozen at 1.19.x — do NOT "catch it up."** It's a +legacy re-export not published since v1.20.0; `release.sh` publishes only `@phnx-labs`. +Bumping it would un-deprecate a retired package. + +## Conventions + +- Real services only — no mocking. Tests exercise the actual critical path. +- `agents repo push` / `pull` operates on `~/.agents/` only. System updates ride + `npm update -g @phnx-labs/agents-cli`. +- No sensitive data in any DotAgents repo — use `agents secrets` (Keychain-backed). + +## Detailed design + +[`../../docs/`](../../docs/README.md) is source-grounded reference. Start with +[`00-concepts.md`](../../docs/00-concepts.md) for the full mental model. diff --git a/native/computer-mac/AGENTS.md b/native/computer-mac/AGENTS.md new file mode 100644 index 000000000..06b0ee959 --- /dev/null +++ b/native/computer-mac/AGENTS.md @@ -0,0 +1,46 @@ +# computer-helper (macOS) + +macOS backend for `agents computer` — Swift daemon exposing Accessibility (AX) + +ScreenCaptureKit + CoreGraphics event injection over line-delimited JSON-RPC on a +Unix domain socket. Windows sibling: [`native/computer-win`](../computer-win). + +This file is a **map**. [README.md](README.md) is the deep reference (permissions, +peer-auth, `.app`-bundle rationale, protocol) — read it before editing. + +## Layout + +``` +Sources/ComputerHelper/RPC.swift Canonical method list + dispatch +Sources/ComputerHelper/*.swift AX walk, screenshot, event injection, policy, peer-auth +scripts/build.sh Build (debug single-arch / release universal) +Package.swift SwiftPM manifest +``` + +## Build + +```bash +./scripts/build.sh # debug (single-arch) +./scripts/build.sh release # universal arm64 + x86_64 +``` + +Outputs `dist/computer-helper-mac` (bare binary) and `dist/ComputerHelper.app` +(signed bundle, id `com.phnx-labs.computer-helper`). The `.app` form gives a stable +TCC identity that survives across launches — a bare binary inherits TCC identity +from its launcher and loses grants (see README §"Why a .app bundle"). + +## How the CLI reaches it + +`apps/cli/src/lib/computer-rpc.ts` — `resolveHelperExec()` looks for +`native/computer-mac/dist/ComputerHelper.app/Contents/MacOS/ComputerHelper` (dev +checkout, 4 hops up from `apps/cli/dist/lib`) or the bundled npm copy. The daemon +runs under launchd on a Unix socket; the CLI connects locally (no tunnel). + +## Security model (see README for full detail) + +- **Default-deny allow list** — every action checks the target's bundle id against + `Computer()` rules in `~/.agents/permissions/groups/`. `agents computer + reload` pushes changes via SIGHUP. +- **Hard floor** — `com.apple.tccd`, `com.apple.SecurityAgent`, + `com.apple.systempreferences` are denied unconditionally (TCC escalation paths). +- **Peer-auth** — connecting callers are verified by exec path against + `computer-peers.json`; fail-safe-empty denies everything if the policy is missing. diff --git a/native/computer-win/AGENTS.md b/native/computer-win/AGENTS.md new file mode 100644 index 000000000..39952204c --- /dev/null +++ b/native/computer-win/AGENTS.md @@ -0,0 +1,53 @@ +# computer-helper-win + +Windows backend for `agents computer` — C#/.NET 10 daemon, sibling of the Swift +[`native/computer-mac`](../computer-mac). Same JSON-RPC wire protocol, same result +shapes; one TS client (`apps/cli/src/lib/computer-rpc.ts`) drives both. + +This file is a **map**. Read the code (and [README.md](README.md)) for current detail. + +## Layout + +``` +Program.cs Entry point (top-level statements) — TCP listener, auth, RPC loop +Rpc.cs Method dispatch table (19 methods) +Automation.cs UI Automation tree walk + SendInput (click/type/key/scroll/drag) + window focus +Screenshot.cs Graphics.CopyFromScreen over the virtual screen +Apps.cs Process/window enumeration; launch_app via PATH + App Paths registry +ElementCache.cs @eN element handle cache +smoke/smoke.mjs Smoke test (Unicode-typing regression loops for #554/#581) +computer-helper-win.csproj net10.0-windows, WPF + WinForms enabled +``` + +## Build + +```bash +bash ../../apps/cli/scripts/build-win.sh # dotnet publish -r win-x64 --self-contained -p:PublishSingleFile +``` + +Output: `dist/computer-helper-win.exe` (gitignored; staged into the npm tarball at +release). Needs the **.NET 10 SDK**; cross-publishes from macOS/Linux +(`EnableWindowsTargeting`). + +## How the CLI reaches it + +`apps/cli/src/lib/ssh-tunnel.ts` — `resolveWinHelperExe()` looks for +`native/computer-win/dist/computer-helper-win.exe` (dev checkout, 4 hops up from +`apps/cli/dist/lib`) or the bundled npm copy. `setupRemoteHelper()` scp's the exe +to `%LOCALAPPDATA%\agents\`, registers a Task Scheduler task +(`AgentsComputerHelper`, `-AtLogOn`, interactive), and starts it. The CLI connects +over an `ssh -L` tunnel to the daemon's loopback TCP port (`8765`). + +## Key differences from computer-mac (don't assume mac semantics) + +- **Transport is loopback TCP + SSH tunnel**, not a Unix socket. The tunnel is the + sole ingress; an optional `--token-file` shared secret is defense-in-depth. +- **No TCC / no permission model.** Windows UIA needs no per-process grant — + `trust_status` always returns `trusted=true`. There is no allow-list, + `computer-policy.json`, or peer-auth file. Access control = the SSH tunnel. +- **Lifecycle is Task Scheduler, not launchd** — it must run in the interactive + desktop session (not Session 0) for UIA + screen capture to work. +- **Single-file needs native-lib self-extraction** (`IncludeNativeLibrariesForSelfExtract`) + or UIA throws on the first tree walk (#519). +- **`bundle_id` = process image name** (`notepad`), not reverse-DNS. +- **`notify` is pass-through only** — no Windows Toast; Rush intercepts the return. diff --git a/native/computer-win/README.md b/native/computer-win/README.md new file mode 100644 index 000000000..37079b396 --- /dev/null +++ b/native/computer-win/README.md @@ -0,0 +1,110 @@ +# computer-helper-win + +Windows native helper that exposes UI Automation (UIA) + screen capture + +`SendInput` event injection over line-delimited JSON-RPC on a loopback TCP port. + +This is the Windows backend for `agents computer` — the C#/.NET counterpart of the +Swift [`native/computer-mac`](../computer-mac) helper. Both speak the **identical** +wire protocol and return the identical result shapes, so one TypeScript client +(`apps/cli/src/lib/computer-rpc.ts`) drives either platform. + +## Transport: loopback TCP + SSH tunnel + +Unlike the macOS helper (which listens on a local Unix domain socket), the Windows +helper binds **loopback TCP only** (`IPAddress.Loopback`, default port `8765`) and +is never exposed to the network. The agents CLI reaches a remote Windows box over +an `ssh -L` tunnel, so authentication piggybacks on SSH keys. An optional +shared-secret token (`--token-file`, checked as the first RPC frame) is +defense-in-depth on top of that; in practice the CLI currently runs the daemon +token-less and relies on the SSH tunnel as the sole ingress. + +``` +CLI (mac/linux) ──ssh -L 8765:127.0.0.1:8765──▶ computer-helper-win.exe (Windows, loopback) +``` + +## No TCC — no permission dialog + +Windows UI Automation needs **no per-process trust grant** (unlike macOS +Accessibility/TCC). `trust_status` always reports `trusted=true`. There is no +allow-list of targets, no `computer-policy.json`, and no peer-auth file — the +macOS deny-by-default permission model has no Windows equivalent. Access control +here is entirely the SSH tunnel (plus the optional token). + +## Lifecycle: Task Scheduler, not self-managed + +The CLI installs the helper as a **scheduled task** (`AgentsComputerHelper`) with +an `-AtLogOn` trigger running `-LogonType Interactive -RunLevel Highest`. Task +Scheduler owns the process — it survives SSH disconnects and runs inside the live +interactive desktop session, which UI Automation and `Graphics.CopyFromScreen` +both require (they do not work from the non-interactive Session 0). `setupRemoteHelper` +in `apps/cli/src/lib/ssh-tunnel.ts` handles the whole flow: stop any running +instance, `scp` the exe to `%LOCALAPPDATA%\agents\`, verify the byte count, then +`Register-ScheduledTask` + `Start-ScheduledTask`. + +## Build + +```bash +bash ../../apps/cli/scripts/build-win.sh +``` + +Under the hood (needs the **.NET 10 SDK**): + +```bash +dotnet publish computer-helper-win.csproj -c Release -r win-x64 \ + --self-contained true -p:PublishSingleFile=true -o dist +``` + +Output: `dist/computer-helper-win.exe` — a self-contained, single-file exe. +`EnableWindowsTargeting` lets it **cross-publish from a macOS/Linux build host**. +The exe is not committed (`dist/` is gitignored); release scripts stage it into +the npm tarball. + +### Why single-file needs native-lib self-extraction + +WPF/UIAutomation resolves its client through COM/native libraries +(`PresentationNative`, `wpfgfx`, `UIAutomationCore`) that the single-file host does +**not** map from memory — they must exist as real files on disk for +`LoadLibrary`/`CoCreateInstance`. So the csproj sets +`IncludeNativeLibrariesForSelfExtract=true`, extracting them to disk at startup. +Without it, the shipped exe throws on the first UIA tree walk (describe / get-text +/ focus / ax-action) even though a loose-DLL framework-dependent build works. (#519) + +## Protocol + +Newline-delimited JSON-RPC over the TCP stream — byte-identical to the macOS helper. + +``` +in : {"id":N,"method":"...","params":{...}} +out: {"id":N,"result":{...}} | {"id":N,"error":{"code":"...","message":"..."}} +``` + +19 methods, dispatched in `Rpc.cs`: `ping`, `trust_status`, `list_apps`, +`launch_app`, `screenshot`, `describe`, `click`, `type`, `type_text`, `key`, +`set_focus`, `get_text`, `scroll`, `drag`, `right_click`, `focus_window`, +`ax_action`, `wait`, `notify`. Element IDs (`@eN`) and the `element_stale` error +match the mac helper exactly. + +## Layout + +| File | Role | +|---|---| +| `Program.cs` | Entry point (top-level statements); TCP listener, auth handshake, RPC framing loop | +| `Rpc.cs` | Method dispatch table | +| `Automation.cs` | UI Automation tree walk, `SendInput` click/type/key/scroll/drag, window focus | +| `Screenshot.cs` | `Graphics.CopyFromScreen` over `SystemInformation.VirtualScreen` | +| `Apps.cs` | Process/window enumeration; `launch_app` via PATH + App Paths registry | +| `ElementCache.cs` | `@eN` element handle cache | +| `smoke/smoke.mjs` | Smoke test (incl. Unicode-typing regression loops for #554/#581) | +| `computer-helper-win.csproj` | .NET 10 project (`net10.0-windows`, WPF + WinForms) | + +## Non-obvious gotchas + +- **Unicode typing is per-character.** Consecutive `KEYEVENTF_UNICODE` events + sharing `VK_PACKET` could coalesce in a busy receiver's message queue (#554/#581). + The fix issues one atomic `SendInput` per character with a 5ms settle + (`TypeSettleMs`). The smoke test guards this. +- **`bundle_id` is the process image name** (`notepad`, `msedge`), not an + Apple-style reverse-DNS id — Windows has no bundle IDs. `launch_app` resolves + targets via PATH + the App Paths registry. +- **`notify` is pass-through only** — there is no Windows Toast call; the Rush + computer-manager intercepts the return value (mirrors `Notify.swift`). diff --git a/packages/session-tracker/AGENTS.md b/packages/session-tracker/AGENTS.md new file mode 100644 index 000000000..3e4cea421 --- /dev/null +++ b/packages/session-tracker/AGENTS.md @@ -0,0 +1,49 @@ +# @agents/session-tracker + +Polyglot `SessionStart` hook + descendant-pid lookup. The **writer** side of +live-session tracking. See [README.md](README.md) for the full picture. + +This file is a **map**. Read the code for current detail. + +## The one thing to understand first + +There are **two unrelated "session" surfaces** in this repo — do not conflate: + +| | This package | `apps/cli/src/lib/session/` | +|---|---|---| +| Role | **Writes** live-session state | **Reads** agent transcripts | +| Trigger | `SessionStart` hook (`hook.sh`) | `agents sessions` command | +| Data | `~/.agents/.cache/terminals/sessions/.json` | `~/.claude/projects/*.jsonl`, Codex rollouts, … | +| `SessionState` type | `{session_id, agent, cwd, pid, terminal_id, launch_id, ts, method}` | different type (`activity`, `pr`, `worktree`, `ticket`, …) | +| Consumer | VS Code ext (`apps/factory/src/core/liveSession.ts`) | the CLI itself | + +They share the word "session" and nothing else. The CLI does **not** import this +package. + +## Layout + +``` +src/hook.sh Polyglot SessionStart hook — parses each harness's payload, writes the state file +src/index.ts Entry — re-exports all modules + trackSpawn() / getLiveSession() +src/types.ts SessionState, AgentId, DetectionMethod, LookupInput +src/state-file.ts STATE_DIR (canonical path), stateFilePath(), writeStateAtomic(), parseState() +src/writer.ts recordSession() / clearSession() +src/reader.ts descendantPids(), findStateByPid/InTree/ByTerminalId/ByLaunchId, pruneStaleSessionState() +src/install-hook.ts installHookFor(agent) — writes the hook into per-agent native config (idempotent) +src/adapters/ Ground-truth test helpers (claude snapshot/await-new-session) +tests/scenarios/ cold-spawn (50×, ≥99%) + kill-restart (20×, stale-entry regression) +``` + +## Gotchas + +- **`STATE_DIR` is declared in two places** that must agree: `src/state-file.ts` + (`STATE_DIR`) and `src/hook.sh` (`STATE_DIR="$HOME/.agents/.cache/terminals/sessions"`). + Change one → change both. +- **Files are keyed by the agent PID** (`.json`). Lookups from a terminal walk + the descendant-pid tree from the shell pid to find the agent pid. +- **Stale entries** — the spawn-time id goes stale after the user exits + reruns in + the same terminal, or after `/clear`. `pruneStaleSessionState()` + the + kill-restart test guard this; the live id from the hook is preferred over any + cached spawn-time id. +- **`install-hook.ts` is idempotent** — it strips prior `session-tracker/src/hook.sh` + registrations before adding, so re-running never duplicates the hook. diff --git a/packages/session-tracker/README.md b/packages/session-tracker/README.md new file mode 100644 index 000000000..0190bafaf --- /dev/null +++ b/packages/session-tracker/README.md @@ -0,0 +1,81 @@ +# @agents/session-tracker + +> Per-agent SessionStart hook + descendant-pid lookup. One state-file format for +> every coding-agent CLI. + +When a coding agent (Claude, Codex, Cursor, Grok, Gemini, Antigravity, …) starts, +each harness fires its own native `SessionStart` hook with its own payload shape. +This package is a **polyglot hook** that normalizes all of them into a single +state file, plus the lookup helpers to find "which live session is running in this +terminal / under this pid." + +It is the **writer** side of live-session tracking. The reader that consumes these +files is the VS Code extension (`apps/factory/src/core/liveSession.ts`). This is a +**different** surface from the CLI's `agents sessions` command +(`apps/cli/src/lib/session/`), which parses agent transcript logs, not these state +files — don't conflate the two. + +## What it writes, where, and when + +On each agent's `SessionStart` event, `src/hook.sh` drops one JSON file: + +``` +~/.agents/.cache/terminals/sessions/.json +``` + +```jsonc +{ + "session_id": "…", // parsed per-agent (stdin JSON key, or env var) + "agent": "claude", + "cwd": "/path", + "pid": 12345, + "terminal_id": "…", // from $AGENT_TERMINAL_ID, if set + "launch_id": "…", // from $AGENT_LAUNCH_ID, if set + "ts": 1730000000000, + "method": "hook-stdin" // | hook-env | fs-watch | stdout-banner +} +``` + +Writes are atomic (`mktemp` + `mv`). Per-agent payload parsing lives in `hook.sh`: +`claude`/`codex` read `session_id` from stdin JSON; `cursor` tries +`session_id`/`conversation_id`; `grok` reads `$GROK_SESSION_ID` from env; +`gemini`/`antigravity` try several stdin keys. + +## Public API + +```ts +import { trackSpawn, getLiveSession, findStateByPid, descendantPids } from '@agents/session-tracker'; + +// Poll for the state file a just-spawned agent will drop (default 5s timeout). +const res = await trackSpawn({ shellPid, agent, ... }); // → { confidence: 'high' | 'low', ... } + +// Look up the live session for a terminal, by launchId → terminalId → pid tree walk. +const state = await getLiveSession({ launchId, terminalId, shellPid }); +``` + +Also exported: `findStateInTree`, `findStateByTerminalId`, `findStateByLaunchId`, +`pruneStaleSessionState`, plus the writer (`recordSession`, `clearSession`) and +state-file helpers (`serializeState`, `parseState`, `writeStateAtomic`). + +## Installing the hook + +```bash +bun run install-hook claude # register the hook in ~/.claude/settings.json +bun run install-hook claude codex cursor grok +``` + +`install-hook.ts` writes into each harness's native config (Claude +`settings.json`, Codex/Cursor `hooks.json`, Grok `hooks/session-start.json`) and is +**idempotent** — prior registrations of this package's `hook.sh` are stripped first. + +## Build & test + +```bash +bun install +bun run build # tsc → dist/ +bun test # vitest +bun run test:cold-spawn # 50-iteration reliability test (≥99% match, P95 < 1s) +``` + +The `files` allowlist ships `dist/` + `src/hook.sh` (the hook must be a real file +on disk for harnesses to exec it).