Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/hub/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,9 @@ export async function createHubServer({
if (principalRow === undefined) {
throw new Error(`no principal "${principal.principalId}" to address a mailbox sender as`);
}
return `${principalRow.refId}@${tenantRow.domain}`;
// A person's refId is mixed case; a small model lowercases an address
// before replying, so the canonical stamped form is lowercase.
return `${principalRow.refId}@${tenantRow.domain}`.toLowerCase();
},
deliver: createMailboxDeliver({ app, persistMail: mailboxLookups.persistMail }),
});
Expand Down
5 changes: 4 additions & 1 deletion apps/web/src/agent-deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "@corbits/myra/workflow-ids";

import { ensureAgentHubCredential } from "./agent-hub-credential";
import { personMailAddress } from "./mail-address";
import { resolveExistingOffering } from "./onboarding/provider-connect-step";
import { isValidSlug, slugify } from "@/lib/slug";

Expand Down Expand Up @@ -300,7 +301,9 @@ async function resolveDeployerAddress(
return undefined;
}
const parsed = SessionUserShape(body);
return parsed instanceof type.errors ? undefined : `${parsed.user.id}@${tenantDomain}`;
return parsed instanceof type.errors
? undefined
: personMailAddress(parsed.user.id, tenantDomain);
}

/** Creates a `@corbits/cron` schedule row targeting a deployed agent by its
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/chat/threads-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe("chatTitle", () => {
});

describe("listWorkbenchParticipants", () => {
test("a person's address is their refId at the workbench's own domain, never email or bare refId", async () => {
test("a person's address is their refId lowercased at the workbench's own domain, never email or bare refId", async () => {
globalThis.fetch = ((input: RequestInfo | URL) => {
const path = typeof input === "string" ? input : String(input);
if (path.includes("/principals")) {
Expand Down Expand Up @@ -98,7 +98,7 @@ describe("listWorkbenchParticipants", () => {
id: "prin_1",
kind: "person",
name: "Alice",
address: "Mk9tHH@example.com",
address: "mk9thh@example.com",
});
});
});
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/chat/threads-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { reportError } from "@corbits/error-sink";
import { agentSlugFromSourceAssetName } from "../agent-deploy";
import { listTopLevelRuns } from "../agents-api";
import { MYRA_SOURCE_CONFIG } from "../myra-source";
import { personMailAddress } from "../mail-address";
import { mentionedAgents } from "./mentions";
import { appendRoster } from "./workbench-roster";

Expand Down Expand Up @@ -646,7 +647,7 @@ export async function listWorkbenchParticipants(
id: principal.id,
kind: "person",
name: principal.displayName,
address: `${principal.refId}@${tenantDomain}`,
address: personMailAddress(principal.refId, tenantDomain),
}));
const agents = chatAgents.map((agent): WorkbenchParticipant => ({
id: agent.id,
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/mail-address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// A person's address is their auth user id, which is mixed case. Small models
// lowercase an address before replying, so the canonical form is lowercase
// everywhere a person's address is shown, stamped, or handed to an agent.

export function personMailAddress(refId: string, tenantDomain: string): string {
return `${refId}@${tenantDomain}`.toLowerCase();
}
11 changes: 11 additions & 0 deletions docs/chat-mail-threading.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ at send time to narrow the `to` list to just those agents. The token stays
in the body so the agent sees who was addressed, and the roster block is
still appended in full, so a narrowed message can still be handed on.

## A person's address is lowercase

A person's address is their auth user id at the workbench domain, and that
id is mixed case. Small models lowercase an address before replying, so
`personMailAddress` lowercases it at every point the client derives one —
the roster, the Participants panel, a schedule's body — and the hub stamps
the same lowercase form on a person's outbound mail. Mailbox delivery
matches the local part case-insensitively, so both forms reach the same
inbox: threads written before this keep their mixed-case addresses, new
mail carries the lowercase one, and nothing is migrated.

## Primary-thread root resolution

`readHubSnapshot`'s caller resolves a workbench's primary-thread root from
Expand Down
Loading