diff --git a/packages/coding-agent/src/task/label.ts b/packages/coding-agent/src/task/label.ts index e24ea6fb4..cd0f861c0 100644 --- a/packages/coding-agent/src/task/label.ts +++ b/packages/coding-agent/src/task/label.ts @@ -13,8 +13,11 @@ const TASK_LABEL_SYSTEM_PROMPT = prompt.render(taskLabelSystemPrompt); export function labelEchoesHandle(handle: string | undefined, label: string): boolean { if (!handle) return false; if (label.localeCompare(handle, undefined, { sensitivity: "accent" }) === 0) return true; - const suffix = handle.startsWith(`${label}-`) ? handle.slice(label.length + 1) : ""; - return /^\d+$/.test(suffix); + const separator = handle.lastIndexOf("-"); + if (separator <= 0) return false; + const prefix = handle.slice(0, separator); + const suffix = handle.slice(separator + 1); + return /^\d+$/.test(suffix) && prefix.localeCompare(label, undefined, { sensitivity: "accent" }) === 0; } /** Compresses a delegated assignment into a one-sentence UI label via the tiny title model — fired by the executor spawn path because the task wire schema no longer carries a `description`; null on empty input or failure. */ diff --git a/packages/coding-agent/test/subagent-hud-render.test.ts b/packages/coding-agent/test/subagent-hud-render.test.ts index 91db8c220..e2086fc15 100644 --- a/packages/coding-agent/test/subagent-hud-render.test.ts +++ b/packages/coding-agent/test/subagent-hud-render.test.ts @@ -141,6 +141,16 @@ describe("subagent HUD lines", () => { expect(collision).toMatch(/AuthLoader-3.*scout/); expect(collision).not.toContain("AuthLoader-3: AuthLoader"); + const mixedCase = render([ + makeSession({ + id: "AuthLoader-3", + agent: "scout", + description: "authloader", + }), + ]); + expect(mixedCase).toContain("AuthLoader-3"); + expect(mixedCase).not.toContain("AuthLoader-3: authloader"); + const defaultWorker = render([ makeSession({ id: "SchemaMigrator", agent: "task", description: "Migrate users" }), ]); diff --git a/packages/coding-agent/test/task-label.test.ts b/packages/coding-agent/test/task-label.test.ts index 4a1ee4206..17f678eba 100644 --- a/packages/coding-agent/test/task-label.test.ts +++ b/packages/coding-agent/test/task-label.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, it, vi } from "bun:test"; import type { Api, Model } from "@oh-my-pi/pi-ai"; import * as ai from "@oh-my-pi/pi-ai"; import { getBundledModel } from "@oh-my-pi/pi-catalog/models"; -import { generateTaskLabel } from "@oh-my-pi/pi-coding-agent/task/label"; +import { generateTaskLabel, labelEchoesHandle } from "@oh-my-pi/pi-coding-agent/task/label"; function getModelOrThrow(id: string): Model { const model = getBundledModel("anthropic", id); @@ -93,4 +93,11 @@ describe("task label generation", () => { ); expect(labeled).toBe("Sleep then reply done"); }); + + it("treats a case-insensitive Name-N collision as an echoed handle", () => { + expect(labelEchoesHandle("AuthLoader-3", "authloader")).toBe(true); + expect(labelEchoesHandle("AuthLoader-3", "AuthLoader")).toBe(true); + expect(labelEchoesHandle("AuthLoader", "authloader")).toBe(true); + expect(labelEchoesHandle("AuthLoader-3", "Migrate users")).toBe(false); + }); });