From 501c3592be80e1cd4ec7da847716100241e5d99b Mon Sep 17 00:00:00 2001 From: roboomp Date: Tue, 14 Jul 2026 19:27:04 +0000 Subject: [PATCH] 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: 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 --- packages/coding-agent/CHANGELOG.md | 4 ++ .../coding-agent/src/session/agent-session.ts | 7 ++- ...agent-session-tree-skill-injection.test.ts | 44 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 packages/coding-agent/test/agent-session-tree-skill-injection.test.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index da3550531..e6c8675be 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -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 diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index dcc716744..cc3d70363 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -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; } diff --git a/packages/coding-agent/test/agent-session-tree-skill-injection.test.ts b/packages/coding-agent/test/agent-session-tree-skill-injection.test.ts new file mode 100644 index 000000000..dbc9f9ff8 --- /dev/null +++ b/packages/coding-agent/test/agent-session-tree-skill-injection.test.ts @@ -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, + "huge expanded skill body", + 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(); + } + }); +});