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:
@@ -94,6 +94,63 @@ describe("REST surface", () => {
|
||||
expect(wrong.status).toBe(403);
|
||||
});
|
||||
|
||||
it("POST /v1/rename transfers pending messages and exposes the new name", async () => {
|
||||
// alice sends to bob-old.
|
||||
await call("POST", "/v1/send", {
|
||||
headers: { "X-Mailbox": "alice" },
|
||||
body: { to: "bob-old", body: "hi old bob" },
|
||||
});
|
||||
|
||||
const rename = await call("POST", "/v1/rename", {
|
||||
headers: { "X-Mailbox": "bob-old" },
|
||||
body: { to: "bob-new" },
|
||||
});
|
||||
expect(rename.status).toBe(200);
|
||||
expect(rename.body).toMatchObject({
|
||||
from: "bob-old",
|
||||
to: "bob-new",
|
||||
messagesTransferred: 1,
|
||||
});
|
||||
|
||||
// Peek under new name shows the pending msg; old name is empty.
|
||||
const peekNew = await call("GET", "/v1/peek?name=bob-new");
|
||||
expect(peekNew.body).toMatchObject({ pending: 1 });
|
||||
const peekOld = await call("GET", "/v1/peek?name=bob-old");
|
||||
expect(peekOld.body).toMatchObject({ pending: 0 });
|
||||
|
||||
// check-inbox under new name pulls the message.
|
||||
const check = await call("POST", "/v1/check-inbox?name=bob-new", {
|
||||
headers: { "X-Mailbox": "bob-new" },
|
||||
});
|
||||
const arr = check.body as Array<{ from: string; body: string }>;
|
||||
expect(arr).toHaveLength(1);
|
||||
expect(arr[0]!.body).toBe("hi old bob");
|
||||
});
|
||||
|
||||
it("POST /v1/rename returns 409 when target name is taken", async () => {
|
||||
await call("POST", "/v1/send", {
|
||||
headers: { "X-Mailbox": "alice" },
|
||||
body: { to: "bob", body: "x" },
|
||||
});
|
||||
// 'taken' already exists thanks to upsert on X-Mailbox.
|
||||
const r = await call("POST", "/v1/rename", {
|
||||
headers: { "X-Mailbox": "bob" },
|
||||
body: { to: "alice" },
|
||||
});
|
||||
expect(r.status).toBe(409);
|
||||
expect(r.body).toMatchObject({ reason: "target-exists" });
|
||||
});
|
||||
|
||||
it("POST /v1/rename requires X-Mailbox and body.to", async () => {
|
||||
const missingHeader = await call("POST", "/v1/rename", { body: { to: "x" } });
|
||||
expect(missingHeader.status).toBe(400);
|
||||
const missingTo = await call("POST", "/v1/rename", {
|
||||
headers: { "X-Mailbox": "alice" },
|
||||
body: {},
|
||||
});
|
||||
expect(missingTo.status).toBe(400);
|
||||
});
|
||||
|
||||
it("/v1/list and /v1/peek are anonymous", async () => {
|
||||
await call("POST", "/v1/send", {
|
||||
headers: { "X-Mailbox": "alice" },
|
||||
|
||||
Reference in New Issue
Block a user