feat(coding-agent): add configurable title system prompt for sessions

- add discovery of `TITLE_SYSTEM.md` and pass it through interactive startup context
- route custom title prompts to online and local tiny title generators via protocol
- update session-title docs and changelog with override behavior
- add tests for prompt discovery, forwarding, and fallback to bundled title prompts
This commit is contained in:
Adryel Dearo
2026-06-09 14:13:20 -03:00
committed by can1357
parent 96defff9a5
commit ecbc2a3c15
15 changed files with 232 additions and 19 deletions
@@ -71,6 +71,49 @@ describe("title generator", () => {
});
});
it("uses the bundled default prompt when no title prompt file is resolved", async () => {
const model = getModelOrThrow("claude-sonnet-4-5");
const completeSimpleMock = vi.spyOn(ai, "completeSimple").mockResolvedValue({
stopReason: "stop",
content: [{ type: "toolCall", id: "call-title", name: "set_title", arguments: { title: "Default Prompt" } }],
} as never);
await generateSessionTitle("Investigate the resolver", createRegistry(model), createSettings(model));
const request = completeSimpleMock.mock.calls[0]?.[1] as { systemPrompt?: string[] } | undefined;
expect(request?.systemPrompt).toHaveLength(1);
expect(request?.systemPrompt?.[0]).toContain("Generate a concise, sentence-case title");
});
it("uses the resolved TITLE_SYSTEM.md prompt for online title generation", async () => {
const model = getModelOrThrow("claude-sonnet-4-5");
const customPrompt = "Generate lowercase colon-delimited session names.";
const completeSimpleMock = vi.spyOn(ai, "completeSimple").mockResolvedValue({
stopReason: "stop",
content: [{ type: "toolCall", id: "call-title", name: "set_title", arguments: { title: "fix:resolver" } }],
} as never);
await generateSessionTitle(
"Investigate the resolver",
createRegistry(model),
createSettings(model),
undefined,
undefined,
undefined,
customPrompt,
);
const request = completeSimpleMock.mock.calls[0]?.[1] as
| { systemPrompt?: string[]; tools?: Array<{ name?: string }> }
| undefined;
const options = completeSimpleMock.mock.calls[0]?.[2] as
| { toolChoice?: { type?: string; name?: string } }
| undefined;
expect(request?.systemPrompt).toEqual([customPrompt]);
expect(request?.tools?.[0]?.name).toBe("set_title");
expect(options?.toolChoice).toEqual({ type: "tool", name: "set_title" });
});
it("falls back to text content when no set_title tool call is returned", async () => {
const model = getModelOrThrow("claude-sonnet-4-5");
vi.spyOn(ai, "completeSimple").mockResolvedValue({