feat(ai-tools): omp shared + OpenCode/omp sessions, tokens, subagents; Grok hooks; resume by id

- omp-usage.js / opencode-usage.js: poll each CLI's own session store into
  sessions (tool omp/opencode), token_usage per model and subagent agents;
  transcript routes read them (omp journals, OpenCode's opencode.db).
- Grok runs the Claude hooks in ~/.claude/settings.json: its hooks are now
  recognised (hookEventName + workspaceRoot), tagged tool=grok, and their
  transcript_path dropped instead of being parsed as a Claude transcript.
- Catalog `resumeById`: omp/Grok/Cursor `--resume <id>`, OpenCode
  `--session <id>`; session ids may carry `_`. The client offers Resume for
  any tool that resumes by id.
- Kimi Code and GitHub Copilot leave the catalog (their session stores could
  not be verified).
This commit is contained in:
2026-10-19 16:49:59 +07:00
parent 0434ffc750
commit f8d07dab61
33 changed files with 2007 additions and 103 deletions
+15 -7
View File
@@ -295,16 +295,18 @@ function buildCodexArgv({
/**
* Any catalog tool without a hand-written launcher: `[bin, ...args, ...resume?,
* ...bypass?, ...prompt]` (`[flag, model]` right after the fixed args when the
* ...bypass?, ...prompt]` (`...resumeById, <id>` in place of `resume` for a
* named session; `[flag, model]` right after the fixed args when the
* catalog declares a model flag). Every piece but the prompt and the
* validated model comes from the catalog
* file, and the prompt is one argv element — or, for `after-start`, nothing
* here: spawnRun pastes it into the pane once the CLI is up.
*/
function buildCatalogArgv(entry, bin, { model, resumeLast, bypass, initialPrompt }) {
function buildCatalogArgv(entry, bin, { model, resumeSessionId, resumeLast, bypass, initialPrompt }) {
const argv = [bin, ...entry.args];
if (model && entry.model) argv.push(entry.model.flag, model);
if (resumeLast && entry.resume) argv.push(...entry.resume);
if (resumeSessionId && entry.resumeById) argv.push(...entry.resumeById, resumeSessionId);
else if (resumeLast && entry.resume) argv.push(...entry.resume);
if (bypass && entry.bypass) argv.push(...entry.bypass);
const p = entry.prompt;
if (initialPrompt && p.mode === "flag") argv.push(p.flag, promptArg(initialPrompt));
@@ -437,15 +439,21 @@ function spawnRun(args) {
}
if (
resumeSessionId != null &&
(typeof resumeSessionId !== "string" || !/^[A-Za-z0-9-]{8,}$/.test(resumeSessionId))
(typeof resumeSessionId !== "string" || !/^[A-Za-z0-9_-]{8,}$/.test(resumeSessionId))
) {
throw makeErr("EBADSESSION", "resumeSessionId is not a valid session id");
}
// Only the hand-written launchers can resume a NAMED session; a catalog
// tool would silently start fresh under a "resume".
if (resumeSessionId && !entry.launcher) {
// Only the hand-written launchers and a catalog tool that declares
// `resumeById` can resume a NAMED session; any other would silently start
// fresh under a "resume".
if (resumeSessionId && !entry.launcher && !entry.resumeById) {
throw makeErr("EUNSUPPORTED", `${tool} cannot resume a session by id`);
}
// A catalog tool takes the id as an argv element of its own: never one that
// could read as a flag.
if (resumeSessionId && !entry.launcher && !/^[A-Za-z0-9]/.test(resumeSessionId)) {
throw makeErr("EBADSESSION", "resumeSessionId is not a valid session id");
}
if (worktree === true && entry.launcher !== "claude") {
throw makeErr("EUNSUPPORTED", `${tool} cannot start a session in its own worktree`);
}