fix(session): land tree navigation on /skill: injection node
navigateTree() treated every custom_message entry as a re-editable user turn, setting the leaf to the injection's parent and dumping the expanded skill body into the editor. A /skill:<name> invocation is persisted as a skill-prompt custom_message, so selecting it in /tree dropped the skill off the active branch. Skip the parent-leaf/editor-prefill path for skill-prompt entries so the leaf lands on the injection node itself. Fixes #5374
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `/tree` navigation onto a `/skill:` injection node landing on the entry before it (dropping the skill off the active branch and prefilling the editor with the expanded skill body); selecting a skill injection now lands the leaf on the injection node ([#5374](https://github.com/can1357/oh-my-pi/issues/5374)).
|
||||
|
||||
## [16.5.0] - 2026-07-13
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
@@ -15993,7 +15993,7 @@ export class AgentSession {
|
||||
// User message: leaf = parent (null if root), text goes to editor
|
||||
newLeafId = targetEntry.parentId;
|
||||
editorText = this.#extractUserMessageText(targetEntry.message.content);
|
||||
} else if (targetEntry.type === "custom_message") {
|
||||
} else if (targetEntry.type === "custom_message" && targetEntry.customType !== SKILL_PROMPT_MESSAGE_TYPE) {
|
||||
// Custom message: leaf = parent (null if root), text goes to editor
|
||||
newLeafId = targetEntry.parentId;
|
||||
editorText =
|
||||
@@ -16004,7 +16004,10 @@ export class AgentSession {
|
||||
.map(c => c.text)
|
||||
.join("");
|
||||
} else {
|
||||
// Non-user message: leaf = selected node
|
||||
// Non-user message (or a user-invoked skill-prompt injection): land the
|
||||
// leaf on the selected node so it stays on the active branch. Skill
|
||||
// prompts are custom_message entries but must not be re-editable — their
|
||||
// content is a large expanded body, not a user turn (issue #5374).
|
||||
newLeafId = targetId;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Regression: `/tree` navigation onto a `/skill:` injection node (issue #5374).
|
||||
*
|
||||
* A user-invoked skill injection is persisted as a `custom_message` entry
|
||||
* (customType `skill-prompt`). Selecting it in the tree must leave the leaf ON
|
||||
* the injection node so the skill stays on the active branch — not on its
|
||||
* parent with the expanded skill body dumped into the editor.
|
||||
*/
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { SKILL_PROMPT_MESSAGE_TYPE } from "@oh-my-pi/pi-coding-agent/session/messages";
|
||||
import { assistantMsg, createTestSession, userMsg } from "./utilities";
|
||||
|
||||
describe("AgentSession tree navigation onto skill injection", () => {
|
||||
it("lands the leaf on the skill injection node and keeps it on the active branch", async () => {
|
||||
const ctx = await createTestSession({ inMemory: true });
|
||||
try {
|
||||
const { session, sessionManager } = ctx;
|
||||
|
||||
// u1 -> skill injection -> a1 -> a2
|
||||
sessionManager.appendMessage(userMsg("hello"));
|
||||
const skillId = sessionManager.appendCustomMessageEntry(
|
||||
SKILL_PROMPT_MESSAGE_TYPE,
|
||||
"<skill>huge expanded skill body</skill>",
|
||||
true,
|
||||
{ name: "some-skill", path: "/skills/some-skill/SKILL.md", lineCount: 1 },
|
||||
"user",
|
||||
);
|
||||
sessionManager.appendMessage(assistantMsg("first reply"));
|
||||
sessionManager.appendMessage(assistantMsg("second reply"));
|
||||
|
||||
const result = await session.navigateTree(skillId);
|
||||
|
||||
expect(result.cancelled).toBe(false);
|
||||
// Leaf must be the skill node itself, not its parent.
|
||||
expect(sessionManager.getLeafId()).toBe(skillId);
|
||||
// The skill injection must remain on the active branch.
|
||||
expect(sessionManager.getBranch().some(e => e.id === skillId)).toBe(true);
|
||||
// The expanded skill body must NOT be dumped into the editor.
|
||||
expect(result.editorText).toBeUndefined();
|
||||
} finally {
|
||||
await ctx.cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user