feat(coding-agent): added streaming text preview with real-time delta updates
- Added streaming text preview during agent specification generation with real-time text delta updates. - Added `onRequestRender` callback to agent dashboard for async UI re-renders on state changes. - Improved system prompt preview rendering with text wrapping and line count indicators. - Enhanced agent creation flow to display generation status hint and reordered spec review before input form.
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Display streaming text preview during agent specification generation to show real-time progress
|
||||
- Added `onRequestRender` callback to agent dashboard for triggering UI updates during async operations
|
||||
- Added agent creation flow (press N in dashboard) to generate custom agents from natural language descriptions
|
||||
- Added ability to save generated agents to project or user scope with automatic identifier and system prompt generation
|
||||
- Added scope toggle (Tab) during agent creation to choose between project-level and user-level agent storage
|
||||
@@ -10,6 +13,12 @@
|
||||
- Added model suggestions in model override editor to help users discover available models
|
||||
- Added success notices to confirm agent creation and model override updates
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated agent creation flow to show review screen before generation completes, improving UX feedback
|
||||
- Changed generation status hint to display "Generating..." while specification is being created
|
||||
- Improved system prompt preview formatting with text wrapping and line truncation indicators
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed interactive-mode editor height to stay bounded and resize-aware, preventing off-screen cursor drift during long prompt/history navigation ([#99](https://github.com/can1357/oh-my-pi/issues/99)).
|
||||
|
||||
@@ -352,8 +352,10 @@ export class AgentDashboard extends Container {
|
||||
#createGenerating = false;
|
||||
#createSpec: GeneratedAgentSpec | null = null;
|
||||
#createError: string | null = null;
|
||||
#createStreamingText = "";
|
||||
|
||||
onClose?: () => void;
|
||||
onRequestRender?: () => void;
|
||||
|
||||
private constructor(
|
||||
private readonly cwd: string,
|
||||
@@ -429,7 +431,7 @@ export class AgentDashboard extends Container {
|
||||
this.#loadError = error instanceof Error ? error.message : String(error);
|
||||
} finally {
|
||||
this.#loading = false;
|
||||
this.#buildLayout();
|
||||
this.#rebuildAndRender();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,6 +574,7 @@ export class AgentDashboard extends Container {
|
||||
this.#createGenerating = false;
|
||||
this.#createSpec = null;
|
||||
this.#createError = null;
|
||||
this.#createStreamingText = "";
|
||||
}
|
||||
|
||||
#toggleCreateScope(): void {
|
||||
@@ -591,6 +594,7 @@ export class AgentDashboard extends Container {
|
||||
this.#createGenerating = true;
|
||||
this.#createError = null;
|
||||
this.#createSpec = null;
|
||||
this.#createStreamingText = "";
|
||||
this.#buildLayout();
|
||||
|
||||
try {
|
||||
@@ -601,7 +605,7 @@ export class AgentDashboard extends Container {
|
||||
this.#createError = error instanceof Error ? error.message : String(error);
|
||||
} finally {
|
||||
this.#createGenerating = false;
|
||||
this.#buildLayout();
|
||||
this.#rebuildAndRender();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -646,6 +650,15 @@ export class AgentDashboard extends Container {
|
||||
promptTemplates: [],
|
||||
slashCommands: [],
|
||||
});
|
||||
const unsubscribe = session.subscribe(event => {
|
||||
if (event.type === "message_update" && "assistantMessageEvent" in event) {
|
||||
const ame = event.assistantMessageEvent;
|
||||
if (ame.type === "text_delta") {
|
||||
this.#createStreamingText += ame.delta;
|
||||
this.#rebuildAndRender();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await session.prompt(userPrompt, { expandPromptTemplates: false });
|
||||
@@ -655,6 +668,7 @@ export class AgentDashboard extends Container {
|
||||
}
|
||||
return parseGeneratedAgentSpec(raw);
|
||||
} finally {
|
||||
unsubscribe();
|
||||
await session.dispose();
|
||||
}
|
||||
}
|
||||
@@ -692,7 +706,7 @@ export class AgentDashboard extends Container {
|
||||
await this.#reloadData();
|
||||
this.#clearCreateFlow();
|
||||
this.#notice = `Created agent ${spec.identifier} at ${shortenPath(filePath)}`;
|
||||
this.#buildLayout();
|
||||
this.#rebuildAndRender();
|
||||
}
|
||||
|
||||
#getModelSuggestions(input: string): string[] {
|
||||
@@ -793,13 +807,33 @@ export class AgentDashboard extends Container {
|
||||
this.addChild(new Spacer(1));
|
||||
this.addChild(new Text(theme.fg("muted", `Scope: ${this.#createScope}`), 0, 0));
|
||||
if (this.#createGenerating) {
|
||||
this.addChild(new Text(theme.fg("muted", "Generating agent specification..."), 0, 0));
|
||||
this.addChild(new Spacer(1));
|
||||
this.addChild(new Text(theme.fg("accent", "Generating agent specification..."), 0, 0));
|
||||
if (this.#createStreamingText) {
|
||||
this.addChild(new Spacer(1));
|
||||
const maxPreview = Math.max(3, this.terminalHeight - 18);
|
||||
const contentWidth = Math.max(20, this.#uiWidth() - 4);
|
||||
const wrappedLines: string[] = [];
|
||||
for (const raw of this.#createStreamingText.split("\n")) {
|
||||
for (const w of wrapTextWithAnsi(replaceTabs(raw), contentWidth)) {
|
||||
wrappedLines.push(w);
|
||||
}
|
||||
}
|
||||
const tail = wrappedLines.slice(-maxPreview);
|
||||
if (wrappedLines.length > maxPreview) {
|
||||
this.addChild(new Text(theme.fg("dim", ` ... ${wrappedLines.length - maxPreview} lines above`), 0, 0));
|
||||
}
|
||||
for (const line of tail) {
|
||||
this.addChild(new Text(theme.fg("dim", ` ${line}`), 0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.#createError) {
|
||||
this.addChild(new Text(theme.fg("error", replaceTabs(this.#createError)), 0, 0));
|
||||
}
|
||||
this.addChild(new Spacer(1));
|
||||
this.addChild(new Text(theme.fg("dim", " Enter: generate Tab: toggle scope Esc: cancel"), 0, 0));
|
||||
const hints = this.#createGenerating ? " Generating..." : " Enter: generate Tab: toggle scope Esc: cancel";
|
||||
this.addChild(new Text(theme.fg("dim", hints), 0, 0));
|
||||
}
|
||||
|
||||
#renderCreateReview(): void {
|
||||
@@ -817,12 +851,21 @@ export class AgentDashboard extends Container {
|
||||
}
|
||||
this.addChild(new Spacer(1));
|
||||
this.addChild(new Text(theme.fg("muted", "systemPrompt preview:"), 0, 0));
|
||||
const previewLines = spec.systemPrompt.split("\n").slice(0, 10);
|
||||
for (const line of previewLines) {
|
||||
this.addChild(new Text(truncateToWidth(replaceTabs(line), this.#uiWidth() - 2), 0, 0));
|
||||
const promptWidth = Math.max(20, this.#uiWidth() - 4);
|
||||
const wrappedPrompt: string[] = [];
|
||||
for (const raw of spec.systemPrompt.split("\n")) {
|
||||
for (const w of wrapTextWithAnsi(replaceTabs(raw), promptWidth)) {
|
||||
wrappedPrompt.push(w);
|
||||
}
|
||||
}
|
||||
if (spec.systemPrompt.split("\n").length > previewLines.length) {
|
||||
this.addChild(new Text(theme.fg("dim", "(truncated)"), 0, 0));
|
||||
const promptPreview = wrappedPrompt.slice(0, 10);
|
||||
for (const line of promptPreview) {
|
||||
this.addChild(new Text(` ${line}`, 0, 0));
|
||||
}
|
||||
if (wrappedPrompt.length > promptPreview.length) {
|
||||
this.addChild(
|
||||
new Text(theme.fg("dim", ` ... ${wrappedPrompt.length - promptPreview.length} more lines`), 0, 0),
|
||||
);
|
||||
}
|
||||
if (this.#createError) {
|
||||
this.addChild(new Spacer(1));
|
||||
@@ -836,6 +879,12 @@ export class AgentDashboard extends Container {
|
||||
return Math.max(40, process.stdout.columns ?? 100);
|
||||
}
|
||||
|
||||
/** Rebuild layout and request a TUI render pass (for use after async state changes). */
|
||||
#rebuildAndRender(): void {
|
||||
this.#buildLayout();
|
||||
this.onRequestRender?.();
|
||||
}
|
||||
|
||||
#buildLayout(): void {
|
||||
this.clear();
|
||||
this.addChild(new DynamicBorder());
|
||||
@@ -854,10 +903,10 @@ export class AgentDashboard extends Container {
|
||||
} else if (this.#loadError) {
|
||||
this.addChild(new Text(theme.fg("error", `Failed to load agents: ${replaceTabs(this.#loadError)}`), 0, 0));
|
||||
this.addChild(new Spacer(1));
|
||||
} else if (this.#createInput || this.#createGenerating) {
|
||||
this.#renderCreateInput();
|
||||
} else if (this.#createSpec) {
|
||||
this.#renderCreateReview();
|
||||
} else if (this.#createInput || this.#createGenerating) {
|
||||
this.#renderCreateInput();
|
||||
} else if (this.#editInput && this.#editingAgentName) {
|
||||
const editingAgent = this.#allAgents.find(agent => agent.name === this.#editingAgentName) ?? null;
|
||||
const draft = this.#editInput.getValue();
|
||||
@@ -974,7 +1023,7 @@ export class AgentDashboard extends Container {
|
||||
if (matchesKey(data, "enter") || matchesKey(data, "return") || data === "\n") {
|
||||
void this.#saveGeneratedAgent().catch(error => {
|
||||
this.#createError = error instanceof Error ? error.message : String(error);
|
||||
this.#buildLayout();
|
||||
this.#rebuildAndRender();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -176,6 +176,9 @@ export class SelectorController {
|
||||
done();
|
||||
this.ctx.ui.requestRender();
|
||||
};
|
||||
dashboard.onRequestRender = () => {
|
||||
this.ctx.ui.requestRender();
|
||||
};
|
||||
return { component: dashboard, focus: dashboard };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,11 +3,8 @@ You are an elite AI agent architect specializing in crafting high-performance ag
|
||||
Important Context: You may have access to project-specific instructions from CLAUDE.md files and other context that may include coding standards, project structure, and custom requirements. Consider this context when creating agents to ensure they align with the project's established patterns and practices.
|
||||
|
||||
When a user describes what they want an agent to do, you will:
|
||||
|
||||
1. Extract Core Intent: Identify the fundamental purpose, key responsibilities, and success criteria for the agent. Look for both explicit requirements and implicit needs. Consider any project-specific context from CLAUDE.md files. For agents that are meant to review code, you should assume that the user is asking to review recently written code and not the whole codebase, unless the user has explicitly instructed you otherwise.
|
||||
|
||||
2. Design Expert Persona: Create a compelling expert identity that embodies deep domain knowledge relevant to the task. The persona should inspire confidence and guide the agent's decision-making approach.
|
||||
|
||||
3. Architect Comprehensive Instructions: Develop a system prompt that:
|
||||
- Establishes clear behavioral boundaries and operational parameters
|
||||
- Provides specific methodologies and best practices for task execution
|
||||
@@ -15,20 +12,17 @@ When a user describes what they want an agent to do, you will:
|
||||
- Incorporates any specific requirements or preferences mentioned by the user
|
||||
- Defines output format expectations when relevant
|
||||
- Aligns with project-specific coding standards and patterns from CLAUDE.md
|
||||
|
||||
4. Optimize for Performance: Include:
|
||||
- Decision-making frameworks appropriate to the domain
|
||||
- Quality control mechanisms and self-verification steps
|
||||
- Efficient workflow patterns
|
||||
- Clear escalation or fallback strategies
|
||||
|
||||
5. Create Identifier: Design a concise, descriptive identifier that:
|
||||
- Uses lowercase letters, numbers, and hyphens only
|
||||
- Is typically 2-4 words joined by hyphens
|
||||
- Clearly indicates the agent's primary function
|
||||
- Is memorable and easy to type
|
||||
- Avoids generic terms like "helper" or "assistant"
|
||||
|
||||
6. Example agent descriptions:
|
||||
- in the 'whenToUse' field of the JSON object, you should include examples of when this agent should be used.
|
||||
- examples should be of the form:
|
||||
@@ -39,17 +33,17 @@ When a user describes what they want an agent to do, you will:
|
||||
<function call omitted for brevity only for this example>
|
||||
<commentary>
|
||||
Since a significant piece of code was written, use the {{TASK_TOOL_NAME}} tool to launch the test-runner agent to run the tests.
|
||||
</commentary>
|
||||
</commentary>
|
||||
assistant: "Now let me use the test-runner agent to run the tests"
|
||||
</example>
|
||||
</example>
|
||||
- <example>
|
||||
Context: User is creating an agent to respond to the word "hello" with a friendly jok.
|
||||
user: "Hello"
|
||||
assistant: "I'm going to use the {{TASK_TOOL_NAME}} tool to launch the greeting-responder agent to respond with a friendly joke"
|
||||
<commentary>
|
||||
Since the user is greeting, use the greeting-responder agent to respond with a friendly joke.
|
||||
</commentary>
|
||||
</example>
|
||||
</commentary>
|
||||
</example>
|
||||
- If the user mentioned or implied that the agent should be used proactively, you should include examples of this.
|
||||
- NOTE: Ensure that in the examples, you are making the assistant use the Agent tool and not simply respond directly to the task.
|
||||
|
||||
@@ -68,4 +62,4 @@ Key principles for your system prompts:
|
||||
- Make the agent proactive in seeking clarification when needed
|
||||
- Build in quality assurance and self-correction mechanisms
|
||||
|
||||
Remember: The agents you create should be autonomous experts capable of handling their designated tasks with minimal additional guidance. Your system prompts are their complete operational manual.
|
||||
Remember: The agents you create should be autonomous experts capable of handling their designated tasks with minimal additional guidance. Your system prompts are their complete operational manual.
|
||||
@@ -3,4 +3,4 @@ Design a custom agent for this request:
|
||||
{{request}}
|
||||
|
||||
Return only the JSON object required by your system instructions.
|
||||
Do not include markdown fences.
|
||||
Do not include markdown fences.
|
||||
Reference in New Issue
Block a user