Files
oh-my-pi/packages/coding-agent/src/modes/components/skill-message.ts
T
can1357 7b510ee6e7 feat(coding-agent): added skill message component and custom message support to AgentSession
- Added SkillMessageComponent to render /skill command messages as compact skill entries with expandable/collapsible UI showing skill metadata and optional full prompt content.
- Added promptCustomMessage() public method to AgentSession to support custom message types with metadata and details properties.
- Added SKILL_PROMPT_MESSAGE_TYPE constant and SkillPromptDetails interface to support skill-specific message handling.
- Enhanced patch tool documentation with guidance on structured block editing, error recovery, and best practices for maintaining code integrity.
- Refactored message handling in AgentSession by extracting _promptWithMessage() private method to consolidate common logic between prompt() and promptCustomMessage().
2026-01-28 14:22:01 +01:00

93 lines
2.6 KiB
TypeScript

import type { TextContent } from "@oh-my-pi/pi-ai";
import type { Component } from "@oh-my-pi/pi-tui";
import { Box, Container, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
import type { CustomMessage, SkillPromptDetails } from "../../session/messages";
export class SkillMessageComponent extends Container {
private message: CustomMessage<SkillPromptDetails>;
private box: Box;
private contentComponent?: Component;
private _expanded = false;
constructor(message: CustomMessage<SkillPromptDetails>) {
super();
this.message = message;
this.addChild(new Spacer(1));
this.box = new Box(1, 1, t => theme.bg("customMessageBg", t));
this.rebuild();
}
setExpanded(expanded: boolean): void {
if (this._expanded !== expanded) {
this._expanded = expanded;
this.rebuild();
}
}
override invalidate(): void {
super.invalidate();
this.rebuild();
}
private rebuild(): void {
if (this.contentComponent) {
this.removeChild(this.contentComponent);
this.contentComponent = undefined;
}
this.removeChild(this.box);
this.addChild(this.box);
this.box.clear();
const label = theme.fg("customMessageLabel", theme.bold("[skill]"));
this.box.addChild(new Text(label, 0, 0));
this.box.addChild(new Spacer(1));
const details = this.message.details;
const args = details?.args?.trim();
const infoLines = [
`Skill: ${details?.name ?? "unknown"}`,
args ? `Args: ${args}` : undefined,
details?.path ? `Path: ${details.path}` : undefined,
typeof details?.lineCount === "number" ? `Prompt: ${details.lineCount} lines` : undefined,
].filter((line): line is string => Boolean(line));
this.box.addChild(
new Markdown(infoLines.join("\n"), 0, 0, getMarkdownTheme(), {
color: (value: string) => theme.fg("customMessageText", value),
}),
);
if (!this._expanded) {
return;
}
const text = this.extractText();
if (!text) {
return;
}
this.box.addChild(new Spacer(1));
const promptHeader = theme.fg("customMessageLabel", theme.bold("Prompt"));
this.box.addChild(new Text(promptHeader, 0, 0));
this.box.addChild(new Spacer(1));
this.contentComponent = new Markdown(text, 0, 0, getMarkdownTheme(), {
color: (value: string) => theme.fg("customMessageText", value),
});
this.box.addChild(this.contentComponent);
}
private extractText(): string {
if (typeof this.message.content === "string") {
return this.message.content;
}
return this.message.content
.filter((c): c is TextContent => c.type === "text")
.map(c => c.text)
.join("\n");
}
}