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:
+20
-45
@@ -273,55 +273,31 @@ function findSubagentTranscriptPath(sessionId, agentId, runId = null) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update CLAUDE_HOME at runtime. Updates process.env so getClaudeHome()
|
||||
* immediately returns the new value, and persists to .env file.
|
||||
* Returns the resolved absolute path.
|
||||
* Update CLAUDE_HOME at runtime: `process.env` changes at once and the value
|
||||
* persists to the settings file (`settings-env.js` — outside the repo, which a
|
||||
* plugin update replaces, and outside CLAUDE_HOME). The path passes the shared
|
||||
* `validateHomeDir` guard, which refuses anything that is or contains the
|
||||
* user's home (`EBADHOME`).
|
||||
*
|
||||
* The data dir is derived from CLAUDE_HOME, so the first change pins the one
|
||||
* in use as DASHBOARD_DATA_DIR (process + settings file) unless something
|
||||
* already sets it: otherwise the next start would open an EMPTY database under
|
||||
* the new home, and the plugin bootstrap and CLI — which never read the
|
||||
* settings file — would disagree with the server about where its runtime is.
|
||||
* The discovery file (`.agenthub.json`, server-info.js) is NOT pinned: it
|
||||
* follows CLAUDE_HOME, so hooks and the CLI running under the default home
|
||||
* find a dashboard on a non-default port only if they share its CLAUDE_HOME.
|
||||
*
|
||||
* @returns {string} the resolved absolute path.
|
||||
*/
|
||||
function setClaudeHome(newPath) {
|
||||
const resolved = newPath.replace(/^~(?=\/)/, os.homedir());
|
||||
if (!path.isAbsolute(resolved)) {
|
||||
throw new Error("CLAUDE_HOME must be an absolute path");
|
||||
}
|
||||
if (!fs.existsSync(resolved)) {
|
||||
throw new Error(`Directory does not exist: ${resolved}`);
|
||||
}
|
||||
const stat = fs.statSync(resolved);
|
||||
if (!stat.isDirectory()) {
|
||||
throw new Error(`Not a directory: ${resolved}`);
|
||||
}
|
||||
process.env.CLAUDE_HOME = resolved;
|
||||
writeEnvFile("CLAUDE_HOME", resolved);
|
||||
const { validateHomeDir, applySetting } = require("./settings-env");
|
||||
const resolved = validateHomeDir("CLAUDE_HOME", newPath);
|
||||
if (!process.env.DASHBOARD_DATA_DIR) applySetting("DASHBOARD_DATA_DIR", getDataDir());
|
||||
applySetting("CLAUDE_HOME", resolved);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write or update a key=value line in the .env file.
|
||||
* Creates the file if it doesn't exist.
|
||||
*/
|
||||
function writeEnvFile(key, value) {
|
||||
const envPath = path.resolve(__dirname, "..", "..", ".env");
|
||||
let lines = [];
|
||||
if (fs.existsSync(envPath)) {
|
||||
lines = fs.readFileSync(envPath, "utf8").split("\n");
|
||||
}
|
||||
let found = false;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const trimmed = lines[i].trim();
|
||||
if (trimmed.startsWith(`${key}=`)) {
|
||||
lines[i] = `${key}=${value}`;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
lines.push(`${key}=${value}`);
|
||||
}
|
||||
// Write atomically: write to temp file then rename to prevent corruption
|
||||
const tempPath = envPath + ".tmp";
|
||||
fs.writeFileSync(tempPath, lines.join("\n") + "\n", "utf8");
|
||||
fs.renameSync(tempPath, envPath);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getClaudeHome,
|
||||
getProjectsDir,
|
||||
@@ -337,5 +313,4 @@ module.exports = {
|
||||
findTranscriptPath,
|
||||
findSubagentTranscriptPath,
|
||||
setClaudeHome,
|
||||
writeEnvFile,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user