feat: per-tool settings, persistent settings file, Codex and Antigravity sessions, tokens and hooks (#40 B, C1, C2, D)
Part B: Model Pricing, Hook Configuration and CLAUDE_HOME move from Settings → Dashboard to the Claude Code tab's AgentHub sub-tab (ClaudeAgentHubSettings.tsx); CODEX_HOME is editable in Codex → AgentHub. A home that is or contains the OS home is refused (EBADHOME). Settings persistence: dashboard-saved settings go to ~/.config/agenthub/settings.env (AGENTHUB_SETTINGS_FILE), outside the plugin cache an update wipes. Load order: real env > the checkout's .env > that file. The first CLAUDE_HOME change pins DASHBOARD_DATA_DIR so the DB stays put. C1: codex-usage.js reads Codex rollout journals into sessions (new column tool) and token_usage, tracked per file in codex_rollouts; forks deduped by total vector, sub-agent threads folded into the root; pricing-sync also prices OpenAI models; PricingSettings with a vendor filter. C2: codex-hooks.js installs AgentHub's hooks into $CODEX_HOME/hooks.json on a click, through a stable forwarder; Codex trust is never written. The hook route creates tool='codex' sessions and folds sub-agent hooks into the root. D: antigravity-usage.js reads agy's per-conversation SQLite files (tokens from ModelUsageStats) into tool='antigravity' sessions; antigravity-hooks.js installs the named hook `agenthub` (PreInvocation, PostToolUse, Stop — never PreToolUse) into ~/.gemini/config/hooks.json; Settings → Antigravity tab; Gemini pricing. Whole-change review fixes: sameOriginGuard on both home PUTs, routers.missing on a CLAUDE_HOME change, reset-pricing scope, GET /api/sessions?tool=, clear-data covering the new tables, Codex child-thread events moving no status, hooks owning status only while current, EFOREIGN for a forwarder that is not ours, agy quiet window and turn rules, AGENTHUB_SETTINGS_FILE honoured from .env. Docs, OpenAPI and the CLAUDE.md marker updated. Server 2141, client 779.
This commit is contained in:
+49
-6
@@ -164,7 +164,7 @@ These checks run unconditionally inside `resetWorktree`/`removeWorktree` themsel
|
||||
- **`clear`** (UI/API action only, no CLI verb) — resets the lane's own bookkeeping (stage, status, gate/CI fields, notes) back to idle, **including the detected columns** (`detected_stage`/`detected_signal`/`detected_at`), since detection is forward-only and a surviving detection would both claim progress the reset tree no longer has and block every later detection. Touches only the `lanes` row; no git operation, no session/event data is deleted.
|
||||
- **`reset`** (managed lanes only) — checks out the base branch, `git reset --hard` to it, then `git clean -fd` (no `-x`, so **gitignored files such as `node_modules` and `.env` survive**; only untracked-but-not-ignored files are deleted), then recreates the feature branch off the base and clears the lane's bookkeeping. Requires `force: true` when the preflight reports unpushed commits.
|
||||
- **`remove`** — **deletes the dashboard's record of the lane and nothing else**, for every lane kind. The working tree stays on disk, its branch stays in the source repo, git's own worktree bookkeeping is untouched, and the lane's database (if any) is left in place; deleting any of them is the user's own call (`git worktree remove`, `git branch -d`, dropping the database). Stopping the lane's run and stack still happens first — those are processes, not data — and the runtime bookkeeping under `LANES_ROOT/.state/lane<slot>/` (pid files, hook logs) is cleared, because it is AgentHub's own and would otherwise be inherited by the next lane to claim that slot. Consequently there is **no unpushed-commits force-gate on `remove`**: nothing it does can discard a commit.
|
||||
- **`purge`** — deletes the lane's own `sessions`/`events`/`token_usage` rows (excluding the lane's currently-bound or any still-active session), for its own `cwd` and subdirectories. No git operation, no worktree change; not gated on unpushed commits.
|
||||
- **`purge`** — deletes the lane's own `sessions`/`events`/`token_usage` rows (excluding the lane's currently-bound or any still-active session), for its own `cwd` and subdirectories, recorded by the lane's own `tool` — purging a Claude lane leaves a Codex lane's sessions at the same directory alone. No git operation, no worktree change; not gated on unpushed commits.
|
||||
|
||||
### The preflight contract
|
||||
|
||||
@@ -1109,13 +1109,28 @@ pipeline map without requiring every skill to be rewritten.
|
||||
The matcher is fed by hooks — `touchLaneFromHook` in `server/routes/hooks.js`,
|
||||
on every hook, live — so it only ever sees `claude` lanes.
|
||||
|
||||
Codex has no hook mechanism at all, so `touchLaneFromHook` is pinned to
|
||||
`"claude"` — a hook must never touch a `codex` lane sharing the same directory.
|
||||
Codex lanes carry no pipeline, so nothing detects or advances their stage.
|
||||
`touchLaneFromHook` resolves the lane by `(cwd, tool)` — a Claude hook never
|
||||
touches a `codex` lane sharing the same directory, nor a Codex hook the
|
||||
`claude` one — and runs detection for `claude` lanes only. Codex lanes carry no
|
||||
pipeline, so nothing detects or advances their stage. Codex's own hooks are
|
||||
opt-in (Settings → Codex → AgentHub → **Codex hooks**; Codex then asks you to
|
||||
trust them): with them a codex lane gets `running`/`idle` as each turn starts
|
||||
and ends, and "Codex needs your permission: …" as its `needs_action` while
|
||||
Codex waits on an approval. A sub-agent thread's tool calls are folded onto
|
||||
the root session but move nothing: they neither clear `needs_action` nor set
|
||||
the lane `running` — see ARCHITECTURE.md, **Codex hooks**.
|
||||
Codex sessions and their token usage are ingested from these same journals by
|
||||
`server/lib/codex-usage.js` (see ARCHITECTURE.md); that never touches a lane.
|
||||
`server/lib/codex-ingest.js` only mirrors a codex lane's `status` between
|
||||
`idle` and `running`: every `DASHBOARD_CODEX_POLL_MS` (default 15000; `0`
|
||||
`idle` and `running`, and skips a lane whose hooks are current (an event for
|
||||
its session in the last two minutes — the hooks already report each turn's
|
||||
start and end): every `DASHBOARD_CODEX_POLL_MS` (default 15000; `0`
|
||||
disables) it lists `$CODEX_HOME/sessions/<YYYY>/<MM>/<DD>/rollout-*.jsonl`
|
||||
(`$CODEX_HOME` defaults to `~/.codex`), reads each thread's `cwd` from the
|
||||
(`$CODEX_HOME` defaults to `~/.codex`; every codex pane is started with the
|
||||
dashboard's own `CODEX_HOME` through tmux `-e`, so the pane writes where this
|
||||
reads; it is editable in Settings → Codex → AgentHub, and a save re-writes each
|
||||
codex lane's AI Router layer into the new home — a pane already running keeps
|
||||
the old one until it restarts), reads each thread's `cwd` from the
|
||||
`session_meta` on the file's first line, and calls the owning lane `running`
|
||||
while that journal's mtime is under two minutes old — and `idle` once it is
|
||||
older **or in the future** (a clock stepped back makes a raw `now - mtime`
|
||||
@@ -1124,6 +1139,34 @@ ingestion uses (only a lane with no `run_id`, and only from `idle`/`running` —
|
||||
never from `provisioning` or `failed`) and writes nothing else: not `stage`,
|
||||
not `notes`, not `slot`/`ports`.
|
||||
|
||||
An `antigravity` lane (Antigravity's `agy` CLI) also carries no pipeline.
|
||||
|
||||
**Antigravity hooks** are opt-in: Settings → Antigravity → **Antigravity
|
||||
hooks**, and agy asks for no trust.
|
||||
- **What they add:** the lane turns `running` when a run starts (a prompt, or a
|
||||
PostToolUse) and `idle` on its Stop. A PreInvocation starts a turn (a `UserPromptSubmit`) when it is the
|
||||
run's first, follows a Stop, has no event before it, lands on a session that
|
||||
is not `active`, or comes more than two minutes after the last event; inside
|
||||
a turn a model call follows a hook within seconds, and is dropped.
|
||||
- **Stop:** one with `fullyIdle: false` (background work still running) is
|
||||
dropped; one carrying a non-empty `error`, or a `terminationReason` matching
|
||||
`error`/`fail`, ends the turn as an error.
|
||||
- **No `needs_action`:** agy has no permission hook, so the lane never gets
|
||||
one.
|
||||
- **A session gone quiet:** agy fires no hook when it exits, nor when a run
|
||||
ends on an API error or a permission refused in headless mode, so
|
||||
`server/lib/antigravity-usage.js` marks the session completed (its agents
|
||||
too, waiting flags cleared) and sets its lane `idle` once it has been quiet
|
||||
for ten minutes when it has hook events — a long tool call writes nothing,
|
||||
and after a Stop agy may still be waiting for the user — or two minutes when
|
||||
it has none. Its activity is the newest of the conversation file (and a
|
||||
non-empty `-wal`), its transcript and its last hook event. This uses the same
|
||||
guard, so it happens only for a lane with no `run_id`, and only from
|
||||
`running`.
|
||||
- **Where sessions come from:** agy's own conversation files,
|
||||
`~/.gemini/antigravity-cli/conversations/<id>.db`, with or without the hooks
|
||||
(see ARCHITECTURE.md, **Antigravity Sessions and Hooks**).
|
||||
|
||||
### The evidence boundary
|
||||
|
||||
**An inferred stage is never evidence and never renders as done.** Green
|
||||
|
||||
Reference in New Issue
Block a user