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:
@@ -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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user