fix(session): title user /skill invocations from name and args

Session titles ignored /skill:<name> <args> because the invocation is a
custom skill-prompt, not a user turn. Feed the chip or reconstructed
/skill line into first-title generation and replan context, never the
expanded SKILL.md body.
This commit is contained in:
qiyi71w
2026-08-19 00:59:31 +08:00
parent 8500092296
commit ecd800ef8f
6 changed files with 185 additions and 2 deletions
@@ -3,6 +3,7 @@ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
import {
type CustomMessage,
buildReplanTitleContext,
convertToLlm,
INTERRUPTED_THINKING_MESSAGE_TYPE,
replaceLlmImagesWithText,
@@ -281,3 +282,58 @@ describe("replaceLlmImagesWithText", () => {
expect(replaceLlmImagesWithText(converted, "[image omitted]")).toBe(converted);
});
});
describe("buildReplanTitleContext", () => {
it("titles a user skill invocation from skill args, not the expanded skill body", () => {
const skill: CustomMessage<SkillPromptDetails> = {
role: "custom",
customType: SKILL_PROMPT_MESSAGE_TYPE,
content:
"[IMPORTANT: User invoked the \"implement\" skill]\n\nImplement the work described by the user.\n\nUse this skill.",
display: true,
details: {
name: "implement",
path: "/tmp/implement/SKILL.md",
args: "issues/07-manual-llm.md 创建临时工作树实现",
lineCount: 20,
},
attribution: "user",
timestamp: 1,
};
const context = buildReplanTitleContext([skill, settledAssistant("先读 implement 技能、ticket 07")]);
expect(context).toContain("07-manual-llm.md");
expect(context).toContain("ticket 07");
expect(context).not.toContain("Use this skill.");
expect(context).not.toContain("IMPORTANT");
});
it("does not feed an autoloaded skill prompt into title context", () => {
const skill = customMessage(SKILL_PROMPT_MESSAGE_TYPE, "agent");
skill.details = { ...skill.details, args: "issues/07-manual-llm.md" };
expect(buildReplanTitleContext([skill])).toBe("");
});
it("prefers the operator /skill chip over persisted name and args", () => {
const skill: CustomMessage<SkillPromptDetails> = {
role: "custom",
customType: SKILL_PROMPT_MESSAGE_TYPE,
content: "Use this skill.",
display: true,
details: {
name: "implement",
path: "/tmp/implement/SKILL.md",
args: "issues/07-manual-llm.md",
lineCount: 1,
__queueChipText: "/skill:implement issues/08-app-settings.md",
},
attribution: "user",
timestamp: 1,
};
const context = buildReplanTitleContext([skill]);
expect(context).toContain("08-app-settings.md");
expect(context).not.toContain("07-manual-llm.md");
});
});
@@ -0,0 +1,71 @@
import { describe, expect, it } from "bun:test";
import { formatTitleConversationContext } from "../../src/tiny/message-preproc";
import { skillPromptTitleInput, titleTextFromSkillPrompt } from "../../src/session/skill-title-input";
describe("skillPromptTitleInput", () => {
it("uses the operator /skill chip when present", () => {
expect(
skillPromptTitleInput({
name: "implement",
args: "issues/07-manual-llm.md",
queueChipText: "/skill:implement issues/08-app-settings.md",
}),
).toBe("/skill:implement issues/08-app-settings.md");
});
it("reconstructs /skill:name args when the chip was stripped", () => {
expect(skillPromptTitleInput({ name: "implement", args: "issues/07-manual-llm.md 创建临时工作树实现" })).toBe(
"/skill:implement issues/07-manual-llm.md 创建临时工作树实现",
);
});
});
describe("titleTextFromSkillPrompt", () => {
it("reads args from a user skill-prompt and ignores expanded body fields", () => {
const text = titleTextFromSkillPrompt({
role: "custom",
customType: "skill-prompt",
attribution: "user",
details: {
name: "implement",
path: "/tmp/implement/SKILL.md",
args: "issues/07-manual-llm.md",
lineCount: 20,
},
});
expect(text).toBe("/skill:implement issues/07-manual-llm.md");
});
it("ignores autoloaded skill prompts", () => {
expect(
titleTextFromSkillPrompt({
role: "custom",
customType: "skill-prompt",
attribution: "agent",
details: { name: "implement", args: "issues/07-manual-llm.md" },
}),
).toBeUndefined();
});
});
describe("replan title envelope", () => {
it("puts skill args in the user turn and keeps assistant text", () => {
const text = titleTextFromSkillPrompt({
role: "custom",
customType: "skill-prompt",
attribution: "user",
details: {
name: "implement",
args: "issues/07-manual-llm.md 创建临时工作树实现",
},
});
const context = formatTitleConversationContext([
{ role: "user", text },
{ role: "assistant", text: "先读 implement 技能、ticket 07" },
]);
expect(context).toContain("07-manual-llm.md");
expect(context).toContain("ticket 07");
expect(context).not.toContain("SKILL.md");
});
});