Fix @mention resolution for multi-word agent names

MentionTextarea inserted raw agent names (e.g. @Sarah Chen) but the
backend regex captured only the first word (@Sarah), causing mention
resolution to fail silently. Moved canonicalMentionHandle to shared/
so the renderer can produce space-free handles that the backend resolves
correctly.
This commit is contained in:
ZacharyZhang-NY committed 2026-06-30 21:17:43 -04:00
1 parent dfa2df3334
commit 5bb9aea4f9
10 files changed
+17 -12

No files matched your search

@@ -1,6 +1,6 @@
import type { ProviderService } from "./provider-service";
import { AGENT_TOOLS, dispatchAgentTool, type AgentToolContext, type AgentToolDeps } from "./agent-tools";
import { canonicalMentionHandle } from "./mention-handles";
import { canonicalMentionHandle } from "../../shared/mention-handles";
import type { AgentTurnMessage } from "./provider-tool-turn";
const MAX_ITERATIONS = 12;
@@ -6,7 +6,7 @@ import type { ChatService } from "./chat-service";
import { writeDeliverable } from "./deliverable-writer";
import { DocumentService } from "./document-service";
import { recordWorkspaceFile } from "./files/file-object-store";
import { canonicalMentionHandle } from "./mention-handles";
import { canonicalMentionHandle } from "../../shared/mention-handles";
import type { JsonRecord } from "./provider-http";
import type { ToolCall, ToolDefinition } from "./provider-tool-turn";
@@ -1,5 +1,5 @@
import type { DatabaseSync } from "node:sqlite";
import { canonicalMentionHandle } from "./mention-handles";
import { canonicalMentionHandle } from "../../shared/mention-handles";
export interface MentionTarget {
type: "employee" | "agent" | "board" | "org_group";
@@ -8,7 +8,7 @@ import { IdentityAccessService } from "./identity-access-service";
import { OrgGovernanceService } from "./org-governance-service";
import { OperationsService } from "./operations-service";
import { SdlcService } from "./sdlc-service";
import { assertUsableMentionName } from "./mention-handles";
import { assertUsableMentionName } from "../../shared/mention-handles";
/**
* Seeds a brand-new organization in one transaction's worth of writes: the org
@@ -7,7 +7,7 @@ import { ensureAgentHeartbeat, resolveGoalKickoff, type GoalKickoffPlan } from "
import { PolicyService } from "./policy-service";
import { IdentityAccessService } from "./identity-access-service";
import { OrgGovernanceService } from "./org-governance-service";
import { assertUsableMentionName } from "./mention-handles";
import { assertUsableMentionName } from "../../shared/mention-handles";
import { seedOrganization } from "./organization-seed";
export class OrganizationService {
@@ -1,4 +1,4 @@
import { canonicalMentionHandle } from "./mention-handles";
import { canonicalMentionHandle } from "../../shared/mention-handles";
export interface TaskExecutionTaskRow {
id: string;
@@ -1,17 +1,19 @@
import { useRef, useState, type MutableRefObject } from "react";
import { Avatar } from "../../shared/ui";
import type { AgentRecord, EmployeeRecord } from "../../../../shared/domain";
import { canonicalMentionHandle } from "../../../../shared/mention-handles";
interface MentionCandidate {
handle: string;
displayName: string;
label: string;
bot: boolean;
}
function buildCandidates(agents: AgentRecord[], employees: EmployeeRecord[]): MentionCandidate[] {
return [
...agents.map((a) => ({ handle: a.name, label: `${a.name} · ${a.role}`, bot: true })),
...employees.map((e) => ({ handle: e.displayName, label: `${e.displayName} · ${e.role}`, bot: false })),
...agents.map((a) => ({ handle: canonicalMentionHandle(a.name), displayName: a.name, label: `${a.name} · ${a.role}`, bot: true })),
...employees.map((e) => ({ handle: canonicalMentionHandle(e.displayName), displayName: e.displayName, label: `${e.displayName} · ${e.role}`, bot: false })),
];
}
@@ -41,7 +43,10 @@ export function MentionTextarea({
const ref = textareaRef ?? innerRef;
const candidates = buildCandidates(agents, employees);
const filtered = query !== null
? candidates.filter((c) => c.handle.toLowerCase().includes(query.toLowerCase()))
? candidates.filter((c) => {
const q = query.toLowerCase();
return c.handle.includes(q) || c.displayName.toLowerCase().includes(q);
})
: [];
const close = () => setQuery(null);
@@ -138,7 +143,7 @@ export function MentionTextarea({
textAlign: "left",
}}
>
<Avatar name={c.handle} size="sm" bot={c.bot} />
<Avatar name={c.displayName} size="sm" bot={c.bot} />
<span style={{ fontWeight: 500 }}>{c.label}</span>
</button>
))}
@@ -9,7 +9,7 @@ import { providerAdapters } from "../../src/main/services/provider-adapters";
import { TaskExecutionService } from "../../src/main/services/task-execution-service";
import { SnapshotService } from "../../src/main/services/snapshot-service";
import { claimNextQueuedRun } from "../../src/main/app/task-queue-worker";
import { canonicalMentionHandle } from "../../src/main/services/mention-handles";
import { canonicalMentionHandle } from "../../src/shared/mention-handles";
import type { AgentTurnResult, ProviderToolTurnInput, ToolCall } from "../../src/main/services/provider-tool-turn";
const testVault = {
@@ -7,7 +7,7 @@ import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ChatService } from "../../src/main/services/chat-service";
import { runAgentLoop } from "../../src/main/services/agent-run-loop";
import { canonicalMentionHandle } from "../../src/main/services/mention-handles";
import { canonicalMentionHandle } from "../../src/shared/mention-handles";
import type { AgentToolContext } from "../../src/main/services/agent-tools";
import type { AgentTurnResult, ToolCall } from "../../src/main/services/provider-tool-turn";