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:
+40
-5
@@ -448,7 +448,7 @@ The OpenAPI spec is generated from `server/openapi.js` (`createOpenApiSpec()`),
|
||||
| Method | Path | Description |
|
||||
| ------- | ------------------- | ------------------------------------------------ |
|
||||
| `GET` | `/api/health` | Server health check (`status`, `version`, `timestamp`) |
|
||||
| `GET` | `/api/sessions` | List sessions (`status`, `limit`, `offset`) |
|
||||
| `GET` | `/api/sessions` | List sessions (`status`, `limit`, `offset`; `tool=claude\|codex\|antigravity` filters by CLI before the limit) |
|
||||
| `GET` | `/api/sessions/:id` | Session detail (includes `agents` + `events`) |
|
||||
| `POST` | `/api/sessions` | Create session (idempotent by `id`) |
|
||||
| `PATCH` | `/api/sessions/:id` | Update session |
|
||||
@@ -593,12 +593,37 @@ pipeline's last node, where the pipeline is finished and a later detection is
|
||||
new work — and, the one rule that matters, never counts as evidence and never
|
||||
renders a node as `done` (see `docs/LANES.md#stage-detection`).
|
||||
|
||||
A `codex` lane has no hooks to read and carries no pipeline, so nothing detects
|
||||
or advances its stage. `server/lib/codex-ingest.js` only mirrors its `status`
|
||||
A `codex` lane carries no pipeline, so nothing detects or advances its stage.
|
||||
Codex's hooks are opt-in (`server/lib/codex-hooks.js`, installed from Settings →
|
||||
Codex → AgentHub and trusted by the user inside Codex); a hook posted with
|
||||
`tool: "codex"` reaches only the codex lane at its cwd and creates a
|
||||
`tool = 'codex'` session (see ARCHITECTURE.md, **Codex hooks**). Otherwise
|
||||
`server/lib/codex-ingest.js` only mirrors its `status` — skipping a lane whose
|
||||
hooks are current (an event for its session in the last two minutes) —
|
||||
between `idle` and `running` from the mtime of Codex's own rollout journals
|
||||
under `$CODEX_HOME/sessions/**/rollout-*.jsonl`, polled every
|
||||
`DASHBOARD_CODEX_POLL_MS` ms (default 15000, `0` disables), under the guard hook
|
||||
ingestion uses (no `run_id`, only from `idle`/`running`); it writes nothing else.
|
||||
`server/lib/codex-usage.js` reads the same journals for every Codex session
|
||||
(lane or not) into `sessions` (`tool = 'codex'`) and `token_usage`, tracking
|
||||
each file's progress in `codex_rollouts` (see ARCHITECTURE.md).
|
||||
|
||||
An `antigravity` lane carries no pipeline either.
|
||||
- **Sessions and tokens:** `server/lib/antigravity-usage.js` reads agy's
|
||||
per-conversation SQLite files (`~/.gemini/antigravity-cli/conversations/<id>.db`)
|
||||
every `DASHBOARD_ANTIGRAVITY_POLL_MS` ms (default 15000, `0` disables). They
|
||||
become `sessions` rows (`tool = 'antigravity'`) and `token_usage` rows, and
|
||||
`antigravity_conversations` holds the mtime read for each file.
|
||||
- **Quiet sessions:** agy fires no exit hook, so a session quiet for ten
|
||||
minutes (with hook events) or two (without) is settled to `completed`, its
|
||||
agents too, and sets its lane idle. That lane must be one the hooks left
|
||||
running with no dashboard pane.
|
||||
- **Hooks:** opt-in. `server/lib/antigravity-hooks.js` installs them from
|
||||
Settings → Antigravity: the named hook `agenthub` in
|
||||
`~/.gemini/config/hooks.json`, with PreInvocation, PostToolUse and Stop,
|
||||
never PreToolUse. A hook posted with `tool: "antigravity"` reaches only the
|
||||
antigravity lane at its workspace (see ARCHITECTURE.md, **Antigravity
|
||||
Sessions and Hooks**).
|
||||
|
||||
### Remote Data Sources
|
||||
|
||||
@@ -647,10 +672,20 @@ Because sync runs non-interactively (`ssh -o BatchMode=yes`), the connection mus
|
||||
| Method | Path | Description |
|
||||
| ------ | ------------------------------ | ------------------------------------------------ |
|
||||
| `GET` | `/api/settings/info` | System info, DB stats, hooks status, cache stats. Also powers the Dashboard Health tab (server uptime, memory, CPU, DB record counts, WAL/journal mode, transcript cache hit/miss rates) |
|
||||
| `POST` | `/api/settings/clear-data` | Delete all sessions/agents/events/token usage |
|
||||
| `POST` | `/api/settings/clear-data` | Delete all sessions/agents/events/token usage, and empty `codex_rollouts`/`antigravity_conversations` so those sessions import again |
|
||||
| `POST` | `/api/settings/reimport` | Re-import legacy sessions from `~/.claude/` |
|
||||
| `POST` | `/api/settings/reinstall-hooks`| Reinstall Claude Code hooks |
|
||||
| `POST` | `/api/settings/reset-pricing` | Reset pricing table to defaults |
|
||||
| `POST` | `/api/settings/reset-pricing` | Reset pricing table to defaults, then run the automatic pricing sync at once (waiting up to 3 s). Body `{scope: "claude"}` (the Claude Code tab's **Reset Defaults**) deletes and reseeds only `claude-…` rows; no body resets every row |
|
||||
| `GET` | `/api/settings/claude-home` | `{claude_home, env_owned}` — `env_owned` when the real environment or the checkout's `.env` set `CLAUDE_HOME` (a saved value is overridden at the next start) |
|
||||
| `PUT` | `/api/settings/claude-home` | `{path}` → validate, apply, persist to AgentHub's settings file. 400 `EBADHOME` (is or contains the OS home) / `INVALID_PATH`, 500 `ESAVE`, 403 `EBADORIGIN` (`sameOriginGuard`). Answers `routers: {missing: [...]}` — lanes naming an AI Router profile the new `<CLAUDE_HOME>/agenthub-routers/` store lacks (they start on native) |
|
||||
| `GET` | `/api/settings/codex-home` | `{codex_home, env_owned}` |
|
||||
| `PUT` | `/api/settings/codex-home` | Same checks as `claude-home`; answers `routers: {updated, failed}` after re-writing every codex lane's AI Router layer into the new home (`reapplyCodexLayers`) |
|
||||
| `GET` | `/api/settings/codex-hooks` | Status of AgentHub's hooks in `$CODEX_HOME/hooks.json` (installed, per-event, Codex's trust as read from `config.toml`) |
|
||||
| `POST` | `/api/settings/codex-hooks` | Install or reinstall them (`sameOriginGuard`). 400 `EBADJSON` / `EUNSUPPORTED` / `EFOREIGN` (the forwarder path holds a file that is not AgentHub's) |
|
||||
| `DELETE` | `/api/settings/codex-hooks` | Remove AgentHub's hooks only (`sameOriginGuard`); a foreign forwarder is left alone |
|
||||
| `GET` | `/api/settings/antigravity-hooks` | Status of the named hook `agenthub` in `~/.gemini/config/hooks.json` |
|
||||
| `POST` | `/api/settings/antigravity-hooks` | Install or reinstall it — PreInvocation, PostToolUse, Stop, never PreToolUse (`sameOriginGuard`; same 400 codes) |
|
||||
| `DELETE` | `/api/settings/antigravity-hooks` | Remove the `agenthub` hook only (`sameOriginGuard`) |
|
||||
| `GET` | `/api/settings/export` | Export all data (sessions, agents, events, token_usage, workflows, dashboard_runs, alert_rules, model_pricing) as one versioned JSON attachment |
|
||||
| `POST` | `/api/settings/import` | Restore a bundle from `/export`. Multipart `file`, or JSON `{ path }` (server reads it). Idempotent + non-destructive: sessions already present are skipped whole |
|
||||
| `POST` | `/api/settings/cleanup` | Abandon stale sessions and purge old data |
|
||||
|
||||
Reference in New Issue
Block a user