feat(naming)!: auto-derive mailbox name from project + runtime rename
Mailbox names are now built as <project>-<session-short>, where <project> is the sanitized git-repo basename (or cwd basename) — no more env-var prefix step. Sessions can re-tag themselves at runtime via the new mcp__mailbox__rename tool (POST /v1/rename), which transfers all pending messages to the new name in a single transaction. Peers using the old name re-discover via list_mailboxes. BREAKING: \$CLAUDE_MAILBOX_NAME is no longer read. Existing setups that relied on the env-var prefix should remove it from .claude/settings.json; the prefix now comes from the working directory automatically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import { describe, it, expect, afterEach, beforeEach } from "vitest";
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { MailboxStore } from "../src/db.js";
|
||||
import { MailboxStore, RenameError } from "../src/db.js";
|
||||
|
||||
let dir: string;
|
||||
let dbPath: string;
|
||||
@@ -75,6 +75,93 @@ describe("send / peek / check round-trip", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("rename", () => {
|
||||
it("renames a mailbox and transfers undelivered messages", () => {
|
||||
const store = new MailboxStore(dbPath);
|
||||
try {
|
||||
store.send("alice", "bob-old", "hi");
|
||||
store.send("alice", "bob-old", "again");
|
||||
|
||||
const r = store.rename("bob-old", "bob-new");
|
||||
expect(r.from).toBe("bob-old");
|
||||
expect(r.to).toBe("bob-new");
|
||||
expect(r.messagesTransferred).toBe(2);
|
||||
|
||||
// Old name is gone.
|
||||
const list = store.listMailboxes().map((m) => m.name);
|
||||
expect(list).toContain("bob-new");
|
||||
expect(list).not.toContain("bob-old");
|
||||
|
||||
// Messages still pending under the new name.
|
||||
const peek = store.peek("bob-new");
|
||||
expect(peek.pending).toBe(2);
|
||||
|
||||
// checkInbox under the new name yields the original bodies and the original from.
|
||||
const pulled = store.checkInbox("bob-new");
|
||||
expect(pulled.map((m) => m.body)).toEqual(["hi", "again"]);
|
||||
} finally {
|
||||
store.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("also rewrites the from-side when the renamed mailbox was a sender", () => {
|
||||
const store = new MailboxStore(dbPath);
|
||||
try {
|
||||
store.send("sender-old", "bob", "msg-1");
|
||||
store.rename("sender-old", "sender-new");
|
||||
const pulled = store.checkInbox("bob");
|
||||
expect(pulled).toHaveLength(1);
|
||||
expect(pulled[0]!.from_mailbox).toBe("sender-new");
|
||||
} finally {
|
||||
store.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("treats rename-to-same-name as a no-op touch", () => {
|
||||
const store = new MailboxStore(dbPath);
|
||||
try {
|
||||
store.upsertMailbox("alice");
|
||||
const r = store.rename("alice", "alice");
|
||||
expect(r.messagesTransferred).toBe(0);
|
||||
expect(store.listMailboxes().map((m) => m.name)).toEqual(["alice"]);
|
||||
} finally {
|
||||
store.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects when target already exists", () => {
|
||||
const store = new MailboxStore(dbPath);
|
||||
try {
|
||||
store.upsertMailbox("alice");
|
||||
store.upsertMailbox("bob");
|
||||
expect(() => store.rename("alice", "bob")).toThrow(RenameError);
|
||||
try {
|
||||
store.rename("alice", "bob");
|
||||
} catch (e) {
|
||||
expect((e as RenameError).reason).toBe("target-exists");
|
||||
}
|
||||
// Source still present after the failed attempt.
|
||||
expect(store.listMailboxes().map((m) => m.name)).toEqual(["alice", "bob"]);
|
||||
} finally {
|
||||
store.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects when source is missing", () => {
|
||||
const store = new MailboxStore(dbPath);
|
||||
try {
|
||||
try {
|
||||
store.rename("nope", "fresh");
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(RenameError);
|
||||
expect((e as RenameError).reason).toBe("source-missing");
|
||||
}
|
||||
} finally {
|
||||
store.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("listMailboxes", () => {
|
||||
it("returns mailboxes alphabetically with pendingForYou for the caller", () => {
|
||||
const store = new MailboxStore(dbPath);
|
||||
|
||||
Reference in New Issue
Block a user