mirror of
https://github.com/blamouche/obsidian-any-ai-code.git
synced 2026-07-22 06:53:38 +00:00
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>
110 lines
3.6 KiB
TypeScript
110 lines
3.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
canOpenSession,
|
|
nextSessionLabel,
|
|
resolveRuntimeForAutomation,
|
|
runtimeMatches,
|
|
tabDotClass
|
|
} from "../session-utils";
|
|
import type { CliRuntimeConfig } from "../runtime-utils";
|
|
|
|
const runtimes: CliRuntimeConfig[] = [
|
|
{ id: "claude", name: "Claude", command: "claude" },
|
|
{ id: "codex", name: "Codex", command: "codex" },
|
|
{ id: "abc123", name: "My Gemini", command: "gemini" }
|
|
];
|
|
|
|
describe("runtimeMatches", () => {
|
|
it("matches by id", () => {
|
|
expect(runtimeMatches(runtimes[0], "claude")).toBe(true);
|
|
});
|
|
|
|
it("matches by name case-insensitively", () => {
|
|
expect(runtimeMatches(runtimes[2], "my gemini")).toBe(true);
|
|
});
|
|
|
|
it("returns false on no match or empty target", () => {
|
|
expect(runtimeMatches(runtimes[0], "codex")).toBe(false);
|
|
expect(runtimeMatches(runtimes[0], " ")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("resolveRuntimeForAutomation", () => {
|
|
it("resolves a declared runtime by name", () => {
|
|
expect(resolveRuntimeForAutomation(runtimes, "Codex", "claude")?.id).toBe("codex");
|
|
});
|
|
|
|
it("resolves a declared runtime by id", () => {
|
|
expect(resolveRuntimeForAutomation(runtimes, "abc123", "claude")?.id).toBe("abc123");
|
|
});
|
|
|
|
it("returns null when a declared runtime is unknown", () => {
|
|
expect(resolveRuntimeForAutomation(runtimes, "Nope", "claude")).toBeNull();
|
|
});
|
|
|
|
it("falls back to the default runtime when none is declared", () => {
|
|
expect(resolveRuntimeForAutomation(runtimes, null, "codex")?.id).toBe("codex");
|
|
expect(resolveRuntimeForAutomation(runtimes, "", "abc123")?.id).toBe("abc123");
|
|
});
|
|
|
|
it("falls back to the first runtime when the default id is invalid", () => {
|
|
expect(resolveRuntimeForAutomation(runtimes, null, "missing")?.id).toBe("claude");
|
|
});
|
|
|
|
it("returns null when there are no runtimes", () => {
|
|
expect(resolveRuntimeForAutomation([], null, "claude")).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("nextSessionLabel", () => {
|
|
it("uses the runtime name when free", () => {
|
|
expect(nextSessionLabel([], "Claude")).toBe("Claude");
|
|
});
|
|
|
|
it("disambiguates duplicates", () => {
|
|
expect(nextSessionLabel(["Claude"], "Claude")).toBe("Claude (2)");
|
|
expect(nextSessionLabel(["Claude", "Claude (2)"], "Claude")).toBe("Claude (3)");
|
|
});
|
|
|
|
it("falls back to (Unnamed) for blank names", () => {
|
|
expect(nextSessionLabel([], " ")).toBe("(Unnamed)");
|
|
});
|
|
});
|
|
|
|
describe("canOpenSession", () => {
|
|
it("allows when under the cap", () => {
|
|
expect(canOpenSession(2, 8)).toBe(true);
|
|
});
|
|
|
|
it("blocks at the cap", () => {
|
|
expect(canOpenSession(8, 8)).toBe(false);
|
|
});
|
|
|
|
it("treats 0 or negative as unlimited", () => {
|
|
expect(canOpenSession(100, 0)).toBe(true);
|
|
expect(canOpenSession(100, -1)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("tabDotClass", () => {
|
|
it("is green for a working manual session", () => {
|
|
expect(tabDotClass({ running: true, activity: "working", origin: "manual" })).toBe("is-working");
|
|
});
|
|
|
|
it("is purple for a working automation session", () => {
|
|
expect(tabDotClass({ running: true, activity: "working", origin: "automation" })).toBe(
|
|
"is-automation"
|
|
);
|
|
});
|
|
|
|
it("is gray when idle, regardless of origin", () => {
|
|
expect(tabDotClass({ running: true, activity: "idle", origin: "manual" })).toBe("is-idle");
|
|
expect(tabDotClass({ running: true, activity: "idle", origin: "automation" })).toBe("is-idle");
|
|
});
|
|
|
|
it("is gray when the process has stopped even if last activity was working", () => {
|
|
expect(tabDotClass({ running: false, activity: "working", origin: "automation" })).toBe(
|
|
"is-idle"
|
|
);
|
|
});
|
|
});
|