feat(ai-tools): drop OpenCode and Grok Build

- opencode.json / grok.json leave the built-in catalog; opencode-usage.js,
  its poller, transcript branch and table creation go with it (existing
  tables and session rows stay as history).
- A Grok payload reaching the Claude hook route (Grok runs the Claude hooks
  itself) is dropped instead of recorded as tool = 'grok'.
- quota.js no longer reads Grok or OpenCode Go logins.
- Redone on v2.8.22 from c6b2892 (feat/grok-usage, never merged).
This commit is contained in:
2026-10-23 13:44:24 +07:00
parent 9ea7758abb
commit c0832182f2
31 changed files with 116 additions and 1277 deletions
+12 -23
View File
@@ -1473,12 +1473,13 @@ function antigravityHookInput(hookType, data) {
}
/**
* A Grok Build (`grok`) hook. Grok runs the hooks in `~/.claude/settings.json`
* itself (its Claude compatibility, on by default), so AgentHub's own Claude
* hook reaches here with no `tool` — told apart by the camelCase keys only
* Grok sends beside Claude's snake_case aliases (`hookEventName`,
* `workspaceRoot`). Its `transcript_path` is Grok's file, never a Claude
* transcript, so it is dropped before anything could parse it.
* A Grok Build hook. Grok is not an AgentHub tool, but it runs the hooks in
* `~/.claude/settings.json` itself (its Claude compatibility, on by default),
* so AgentHub's own Claude hook still reaches here from a Grok the user
* installed, with no `tool` — told apart by the camelCase keys only Grok sends
* beside Claude's snake_case aliases (`hookEventName`, `workspaceRoot`) and
* dropped, or it would land as a Claude session whose `transcript_path` is
* Grok's file.
*/
const isGrokPayload = (data) =>
!!data &&
@@ -1486,18 +1487,9 @@ const isGrokPayload = (data) =>
typeof data.hookEventName === "string" &&
typeof data.workspaceRoot === "string";
function grokHookInput(hookType, data) {
if (typeof data.session_id !== "string" || !data.session_id) return null;
const d = { ...data };
delete d.transcript_path;
delete d.agent_transcript_path;
return { hookType, data: d };
}
const HOOK_TOOLS = {
codex: codexHookInput,
antigravity: antigravityHookInput,
grok: grokHookInput,
};
/**
@@ -1564,14 +1556,11 @@ function ingestHook(body, pushSource = null, peer = { kind: "host" }) {
}
const ignored = { status: 200, body: { ok: true, ignored: true } };
// Which CLI sent it: `codex` from Codex's hooks (codex-hooks.js),
// `antigravity` from agy's (antigravity-hooks.js), `grok` for Grok running
// the Claude hooks (by its payload's own keys), anything else is Claude
// Code — an allowlist, never a value stored as sent.
const tool = Object.hasOwn(HOOK_TOOLS, body.tool)
? body.tool
: isGrokPayload(data)
? "grok"
: "claude";
// `antigravity` from agy's (antigravity-hooks.js), anything else is Claude
// Code — an allowlist, never a value stored as sent. Grok running the
// Claude hooks is no AgentHub tool: dropped.
const tool = Object.hasOwn(HOOK_TOOLS, body.tool) ? body.tool : "claude";
if (tool === "claude" && isGrokPayload(data)) return ignored;
if (tool !== "claude") {
if (!data || typeof data !== "object") return ignored;
require("../lib/hook-auth").dropForeignPaths(peer, data);
-69
View File
@@ -843,25 +843,6 @@ router.get("/:id/transcripts", async (req, res) => {
: [];
return res.json({ transcripts: list });
}
// OpenCode: a subagent is a child session of the store, tied to the agent
// row its poller made.
if (session.tool === "opencode") {
const list = [
{ id: "main", name: "Main Agent", type: "main", has_transcript: true, db_agent_id: null },
];
for (const sub of require("../lib/opencode-usage").subagentSessions(req.params.id)) {
list.push({
id: sub.id,
name: sub.name,
type: "subagent",
subagent_type: "task",
has_transcript: true,
db_agent_id: stmts.getAgent.get(sub.rowId) ? sub.rowId : null,
});
}
return res.json({ transcripts: list });
}
const result = [];
// Query database agent list for db_agent_id association
@@ -1101,20 +1082,6 @@ router.get("/:id/transcript", async (req, res) => {
const isCursor = session.tool === "cursor";
// An agy conversation's step log (`transcript_full.jsonl`), found by antigravity-usage.js.
const isAgy = session.tool === "antigravity";
// An OpenCode conversation lives in its SQLite store, not a JSONL file: read
// whole (one session's rows), then paged like the files below.
if (session.tool === "opencode") {
let all = null;
try {
all = require("../lib/opencode-usage").transcriptMessages(
req.params.id,
isSubagentFile ? agentId : null
);
} catch {
all = null;
}
return res.json(pageMessages(all || [], { limit, afterLine, beforeLine, offset }));
}
if (isOmp) {
jsonlPath = require("../lib/omp-usage").journalPath(req.params.id, agentId || "");
} else if (isCursor) {
@@ -1592,42 +1559,6 @@ function agyMessage(entry, num) {
}
}
/**
* The transcript route's paging over a conversation already in memory (an
* OpenCode store): message N is "line" N, so `after`/`before`/`offset` and the
* default latest-`limit` window answer exactly as they do over a file.
*/
function pageMessages(all, { limit, afterLine, beforeLine, offset }) {
const lined = all.map((m, i) => ({ ...m, line: i + 1 }));
let picked;
let hasMore;
if (afterLine !== null) {
const rest = lined.filter((m) => m.line > afterLine);
picked = rest.slice(0, limit);
hasMore = rest.length > limit;
} else if (beforeLine !== null) {
const older = lined.filter((m) => m.line < beforeLine);
picked = older.slice(-limit);
hasMore = older.length > limit;
} else if (offset > 0) {
picked = lined.slice(offset, offset + limit);
hasMore = lined.length > offset + limit;
} else {
picked = lined.slice(-limit);
hasMore = lined.length > limit;
}
const lastLine = picked.length ? picked[picked.length - 1].line : 0;
const firstLine = picked.length ? picked[0].line : 0;
for (const m of picked) delete m.line;
return {
messages: picked,
total: lined.length,
has_more: hasMore,
last_line: lastLine,
first_line: firstLine,
};
}
module.exports = router;
module.exports.sessionStats = sessionStats;
// Exported for unit tests — sender attribution is correctness-critical.