fix: event-loop freezes, plugin-install boot crash, tmux attach/scrollbar, Codex transcripts

- GET /api/workflows: one LEAD() window pass in a read worker (read-pool.js,
  workflow-stats.js); idx_events_session restored, idx_events_tool added
- compat-sqlite gains function/pluck/readonly so a plugin install without a
  better-sqlite3 build boots again; antigravity-usage uses db.js's Database
- install-hooks adds no checkout hooks while the agenthub plugin is enabled
  (events were doubled)
- terminal attach runs the live tmux server's own binary; the pane scrollbar
  gets a visible 2-column style, and claude panes start with
  CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1 so tmux keeps history to scroll
- Codex sessions' conversation in GET /api/sessions/:id/transcript
- Codex/Antigravity sessions claimed by their workspace without hooks
- UI-audit follow-ups, docs and OpenAPI
This commit is contained in:
2026-10-05 17:01:05 +07:00
parent 442f133930
commit 198fbb7980
78 changed files with 2716 additions and 873 deletions
+54 -13
View File
@@ -2,7 +2,9 @@
/**
* Installs Claude Code hooks that forward events to the Agent Dashboard.
* Modifies ~/.claude/settings.json to add hook entries.
* Modifies ~/.claude/settings.json to add hook entries — or, while the
* `agenthub` plugin is enabled (it ships the same hooks inline), removes ours
* instead, since two handlers post every event twice.
*
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
@@ -31,6 +33,22 @@ function pluginBootstrapRan() {
}
}
/**
* True when settings.json enables the `agenthub` plugin, whose `plugin.json`
* carries these same eight hooks inline — Claude Code then runs them itself,
* and any hook-handler.js entry in settings.json is a second copy of each.
*
* @param {object} settings parsed settings.json
* @returns {boolean}
*/
function pluginProvidesHooks(settings) {
const ep = settings && settings.enabledPlugins;
if (!ep || typeof ep !== "object") return false;
return Object.entries(ep).some(
([k, v]) => v === true && (k === "agenthub" || k.startsWith("agenthub@"))
);
}
function envFlag(name) {
return ["1", "true", "yes", "on"].includes(String(process.env[name] || "").toLowerCase());
}
@@ -143,17 +161,6 @@ function installHooks(silent = false) {
return false;
}
// The `agenthub` plugin installs the same eight hooks itself. Running both means
// every event is POSTed twice and token/cost figures double — warn loudly
// rather than silently double-count. (`/agenthub-doctor` reports the same state.)
if (!silent && pluginBootstrapRan()) {
console.warn(
"WARNING: the agenthub plugin is installed and already provides these hooks.\n" +
" Installing them again double-counts every event. Uninstall the\n" +
" plugin first, or skip this step. Run /agenthub-doctor to check."
);
}
let settings = {};
if (fs.existsSync(getSettingsPath())) {
try {
@@ -165,6 +172,40 @@ function installHooks(silent = false) {
}
}
// The enabled plugin already runs these hooks: take our copies out instead
// of adding them (every server start used to re-add them and double every
// event). Other hooks stay; the file is backed up before it is written.
if (pluginProvidesHooks(settings)) {
let removed = 0;
for (const [type, entries] of Object.entries(settings.hooks || {})) {
if (!Array.isArray(entries)) continue;
const kept = entries.filter((e) => !isOurEntry(e));
removed += entries.length - kept.length;
if (kept.length) settings.hooks[type] = kept;
else delete settings.hooks[type];
}
if (removed) {
fs.copyFileSync(getSettingsPath(), `${getSettingsPath()}.agenthub-bak`);
fs.writeFileSync(getSettingsPath(), JSON.stringify(settings, null, 2) + "\n", "utf8");
}
if (!silent) {
console.log(
"The agenthub plugin is enabled and provides these hooks — nothing to install." +
(removed ? ` Removed ${removed} duplicate entr${removed === 1 ? "y" : "ies"}.` : "")
);
}
return true;
}
// Bootstrapped once but not enabled now (disabled or uninstalled): the
// plugin's hooks are off, so ours are the only ones — install them.
if (!silent && pluginBootstrapRan()) {
console.warn(
"Note: the agenthub plugin was installed here but is not enabled — installing\n" +
" the checkout's hooks. Re-enabling the plugin removes them again."
);
}
if (!settings.hooks) settings.hooks = {};
let installed = 0;
@@ -206,4 +247,4 @@ if (require.main === module) {
// isOurEntry is also used by scripts/plugin-bootstrap.js to strip hook entries
// a previous checkout install left behind (they would double-count events).
module.exports = { installHooks, isInsideContainer, isOurEntry };
module.exports = { installHooks, isInsideContainer, isOurEntry, pluginProvidesHooks };