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:
+42
-22
@@ -20,28 +20,19 @@ for (const k of Object.keys(process.env)) {
|
||||
// real editor setting is anything else and stays.
|
||||
if (process.env.GIT_EDITOR === "true") delete process.env.GIT_EDITOR;
|
||||
|
||||
// Load .env file (simple key=value, no external dependency needed)
|
||||
(function loadDotEnv() {
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
const envPath = require("path").resolve(__dirname, "..", ".env");
|
||||
if (!fs.existsSync(envPath)) return;
|
||||
for (const line of fs.readFileSync(envPath, "utf8").split("\n")) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith("#")) continue;
|
||||
const eqIdx = trimmed.indexOf("=");
|
||||
if (eqIdx === -1) continue;
|
||||
const key = trimmed.slice(0, eqIdx).trim();
|
||||
let val = trimmed.slice(eqIdx + 1).trim();
|
||||
// Strip surrounding quotes (single or double)
|
||||
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
||||
val = val.slice(1, -1);
|
||||
}
|
||||
if (!process.env[key]) {
|
||||
process.env[key] = val.replace(/^~(?=\/)/, os.homedir());
|
||||
}
|
||||
}
|
||||
})();
|
||||
// Load the repo `.env` (simple key=value), then persisted settings. The real
|
||||
// environment always wins, then a checkout's own `.env`; a dashboard-saved
|
||||
// value (CLAUDE_HOME, CODEX_HOME, the pinned DASHBOARD_DATA_DIR) lives in the
|
||||
// settings file, outside the plugin cache an update replaces — see
|
||||
// server/lib/settings-env.js. Only when this
|
||||
// file IS the process: the test suite requires it dozens of times, and a
|
||||
// developer's real settings file pins DASHBOARD_DATA_DIR at their live database.
|
||||
if (require.main === module) {
|
||||
const settingsEnv = require("./lib/settings-env");
|
||||
settingsEnv.loadEnvFiles({
|
||||
owned: [require("path").resolve(__dirname, "..", ".env")],
|
||||
});
|
||||
}
|
||||
|
||||
// Carry a pre-rebrand `~/.claude/agent-dashboard` over to `~/.claude/agenthub`.
|
||||
//
|
||||
@@ -513,6 +504,35 @@ function startBackgroundServices() {
|
||||
} catch (err) {
|
||||
console.warn("codex poll failed to start:", err.message);
|
||||
}
|
||||
// Codex sessions and token usage, from the same journals (codex-usage.js):
|
||||
// a first pass over every rollout in the background, then the same beat.
|
||||
try {
|
||||
require("./lib/codex-usage").startCodexUsagePoll(broadcast);
|
||||
} catch (err) {
|
||||
console.warn("codex usage poll failed to start:", err.message);
|
||||
}
|
||||
// Antigravity CLI sessions and token usage, from agy's conversation files.
|
||||
try {
|
||||
const { broadcastLane } = require("./routes/lanes");
|
||||
require("./lib/antigravity-usage").startAntigravityUsagePoll(broadcast, broadcastLane);
|
||||
} catch (err) {
|
||||
console.warn("antigravity usage poll failed to start:", err.message);
|
||||
}
|
||||
// Codex/Antigravity hooks the user installed forward through a script whose
|
||||
// content names this build; point it here (the command in the tool's
|
||||
// hooks.json, which Codex trusts, stays the same).
|
||||
for (const [name, mod] of [
|
||||
["codex-hooks", "./lib/codex-hooks"],
|
||||
["antigravity-hooks", "./lib/antigravity-hooks"],
|
||||
]) {
|
||||
try {
|
||||
const refreshed = require(mod).refreshScript();
|
||||
if (refreshed === "written") console.log(`[${name}] forwarder updated to this build`);
|
||||
if (refreshed === "failed") console.warn(`[${name}] could not update the forwarder`);
|
||||
} catch (err) {
|
||||
console.warn(`${name} forwarder refresh failed:`, err.message);
|
||||
}
|
||||
}
|
||||
// A codex/antigravity pane that exits fires no hook, so release its lane on
|
||||
// a beat instead (one `tmux has-session` per lane with a run). Off with
|
||||
// DASHBOARD_RUN_HEAL_MS=0.
|
||||
|
||||
Reference in New Issue
Block a user