blamouche_obsidian-any-ai-code/session-utils.ts
blamouche 560735b898 Activity-aware tab dot + idle auto-close for automation sessions
The session tab status dot now reflects live CLI activity rather than just
whether the process is alive: green = a manual session's AI is working,
purple = an automation session's AI is working, gray = the CLI is idle
(finished its turn) or stopped. CliSession tracks activity continuously —
output flowing = working, quiet for ~5s = idle — and notifies the view via
onActivityChange to repaint the dot.

Add a new opt-in setting "Auto-close automation sessions when idle": closes
an automation tab once its CLI goes quiet after the prompt ran, even if the
process stays interactive (Claude/Codex). It complements the existing
on-exit close (renamed "...on exit"); both are kept. Idle-close is armed only
after the prompt is sent and driven by real CLI output, so boot and slow
first responses don't close the tab early.

Extract the pure tabDotClass helper to session-utils.ts with tests. Update
README. Bump to 0.2.11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 17:40:03 +02:00

81 lines
2.7 KiB
TypeScript

import type { CliRuntimeConfig } from "./runtime-utils";
/**
* True when `target` (an automation's declared runtime) refers to `runtime`,
* matched either by id or by display name (case-insensitive). Mirrors the rule
* the sidebar view used before sessions existed.
*/
export function runtimeMatches(runtime: CliRuntimeConfig, target: string): boolean {
const wanted = target.trim().toLowerCase();
if (!wanted) {
return false;
}
if (runtime.id.trim().toLowerCase() === wanted) {
return true;
}
return runtime.name.trim().toLowerCase() === wanted;
}
/**
* Resolve which runtime an automation should spawn.
* - If `declared` is set, find the runtime matching it (id or name); returns
* `null` when none matches (the automation is then skipped).
* - If `declared` is empty/null, fall back to the default runtime
* (`defaultId`), then to the first configured runtime.
*/
export function resolveRuntimeForAutomation(
runtimes: CliRuntimeConfig[],
declared: string | null | undefined,
defaultId: string
): CliRuntimeConfig | null {
const wanted = (declared ?? "").trim();
if (wanted) {
return runtimes.find((runtime) => runtimeMatches(runtime, wanted)) ?? null;
}
return runtimes.find((runtime) => runtime.id === defaultId) ?? runtimes[0] ?? null;
}
/**
* Build a tab label for a new session, disambiguating duplicates of the same
* runtime: "Claude", "Claude (2)", "Claude (3)", ...
*/
export function nextSessionLabel(existingLabels: string[], runtimeName: string): string {
const base = runtimeName.trim() || "(Unnamed)";
const taken = new Set(existingLabels);
if (!taken.has(base)) {
return base;
}
let counter = 2;
while (taken.has(`${base} (${counter})`)) {
counter += 1;
}
return `${base} (${counter})`;
}
/**
* Whether another session may be opened given the current count and the
* configured cap. `max <= 0` means unlimited.
*/
export function canOpenSession(currentCount: number, max: number): boolean {
if (!Number.isFinite(max) || max <= 0) {
return true;
}
return currentCount < max;
}
/**
* CSS modifier class for a session tab's status dot, from its activity:
* - `is-working` — a manual session whose CLI is currently producing output (green)
* - `is-automation` — an automation session whose CLI is currently working (purple)
* - `is-idle` — the CLI is quiet/finished, or the process has stopped (gray)
*/
export function tabDotClass(opts: {
running: boolean;
activity: "working" | "idle";
origin: "manual" | "automation";
}): "is-working" | "is-automation" | "is-idle" {
if (opts.running && opts.activity === "working") {
return opts.origin === "automation" ? "is-automation" : "is-working";
}
return "is-idle";
}