feat(plugin): per-session mailbox identity + mailbox-update command
The hook now derives a unique mailbox name from the session_id supplied on hook stdin, so two parallel Claude Code sessions in the same project get distinct mailboxes (e.g. `claude-a8b3c1d2`, `claude-d4e5f6a7`) instead of colliding on a shared env value. An optional CLAUDE_MAILBOX_NAME base prefix flavors the names as `<base>-<sid>`. Adds: - `claude-mailbox session-announce` subcommand for the new SessionStart hook, which prints the current session's mailbox name to context - `/claude-mailbox:mailbox-update` slash command for `npm update` + daemon restart - stdin parsing helpers (parseHookStdin, deriveSessionName) with unit tests; the doctor no longer needs a mandatory name prompt
This commit is contained in:
@@ -6,8 +6,11 @@ import {
|
||||
applyInstall,
|
||||
applyUninstall,
|
||||
buildHookCommand,
|
||||
deriveSessionName,
|
||||
formatMessagesForHook,
|
||||
parseHookStdin,
|
||||
readSettings,
|
||||
shortSessionId,
|
||||
writeSettings,
|
||||
} from "../src/hook.js";
|
||||
|
||||
@@ -169,6 +172,73 @@ describe("applyUninstall", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseHookStdin", () => {
|
||||
it("returns null for empty or whitespace input", () => {
|
||||
expect(parseHookStdin(null)).toBeNull();
|
||||
expect(parseHookStdin("")).toBeNull();
|
||||
expect(parseHookStdin(" \n ")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for non-JSON input", () => {
|
||||
expect(parseHookStdin("not json")).toBeNull();
|
||||
expect(parseHookStdin("{")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for JSON primitives (only objects allowed)", () => {
|
||||
expect(parseHookStdin("42")).toBeNull();
|
||||
expect(parseHookStdin("\"foo\"")).toBeNull();
|
||||
expect(parseHookStdin("null")).toBeNull();
|
||||
});
|
||||
|
||||
it("parses a hook payload", () => {
|
||||
const out = parseHookStdin(
|
||||
JSON.stringify({
|
||||
session_id: "abc12345-de67-89f0-1234-567890abcdef",
|
||||
hook_event_name: "UserPromptSubmit",
|
||||
prompt: "hi",
|
||||
}),
|
||||
);
|
||||
expect(out?.session_id).toBe("abc12345-de67-89f0-1234-567890abcdef");
|
||||
expect(out?.hook_event_name).toBe("UserPromptSubmit");
|
||||
});
|
||||
});
|
||||
|
||||
describe("shortSessionId / deriveSessionName", () => {
|
||||
it("takes first 8 hex chars from a UUID", () => {
|
||||
expect(shortSessionId("abc12345-de67-89f0-1234-567890abcdef")).toBe("abc12345");
|
||||
});
|
||||
|
||||
it("normalizes case and ignores hyphens", () => {
|
||||
expect(shortSessionId("ABC12345-DE67-89F0-1234-567890ABCDEF")).toBe("abc12345");
|
||||
});
|
||||
|
||||
it("falls back to a sanitized prefix for non-hex ids", () => {
|
||||
expect(shortSessionId("session-Test123")).toBe("sessiont");
|
||||
});
|
||||
|
||||
it("derives anonymous name when no base", () => {
|
||||
expect(deriveSessionName("abc12345-de67-89f0-1234-567890abcdef")).toBe("claude-abc12345");
|
||||
});
|
||||
|
||||
it("prepends base prefix when given", () => {
|
||||
expect(deriveSessionName("abc12345-de67-89f0-1234-567890abcdef", "backend")).toBe(
|
||||
"backend-abc12345",
|
||||
);
|
||||
});
|
||||
|
||||
it("treats whitespace-only base as no base", () => {
|
||||
expect(deriveSessionName("abc12345-de67-89f0-1234-567890abcdef", " ")).toBe(
|
||||
"claude-abc12345",
|
||||
);
|
||||
});
|
||||
|
||||
it("derives different names for different sessions with the same base", () => {
|
||||
const a = deriveSessionName("aaaa1111-de67-89f0-1234-567890abcdef", "shared");
|
||||
const b = deriveSessionName("bbbb2222-de67-89f0-1234-567890abcdef", "shared");
|
||||
expect(a).not.toBe(b);
|
||||
});
|
||||
});
|
||||
|
||||
describe("readSettings / writeSettings roundtrip", () => {
|
||||
it("survives an install → write → read cycle", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "claude-mailbox-hook-"));
|
||||
|
||||
Reference in New Issue
Block a user