diff --git a/ui/src/components/ArtifactsPanel.tsx b/ui/src/components/ArtifactsPanel.tsx
index ef634f0c..69c1e2bd 100644
--- a/ui/src/components/ArtifactsPanel.tsx
+++ b/ui/src/components/ArtifactsPanel.tsx
@@ -300,16 +300,16 @@ function DocumentViewer({
This document needs your review.
-
-
- Approve
-
- {
+ {
onReject?.();
onBack();
}}>
-
- Request Changes
+
+ Reject
+
+
+
+ Approve
diff --git a/ui/src/components/CEOChatPanel.tsx b/ui/src/components/CEOChatPanel.tsx
index 3a9ad9fe..133a2c48 100644
--- a/ui/src/components/CEOChatPanel.tsx
+++ b/ui/src/components/CEOChatPanel.tsx
@@ -42,6 +42,60 @@ interface CEOChatPanelProps {
onOpenArtifact?: (key: string, title: string) => void;
}
+/**
+ * Clean agent message content — strip system init JSON, code blocks with
+ * raw config/tool dumps, and other non-conversational output.
+ */
+function cleanAgentMessage(body: string): string {
+ let cleaned = body;
+
+ // Remove markdown links
+ cleaned = cleaned.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
+
+ // Remove lines that look like raw JSON objects (system init, config dumps)
+ cleaned = cleaned.replace(/^\s*\{["\w].*["\w]\}\s*$/gm, "");
+
+ // Remove code blocks containing JSON or system data
+ cleaned = cleaned.replace(/```(?:json|plaintext|text)?\s*\n?\{[\s\S]*?\}\s*\n?```/g, "");
+
+ // Remove lines that are clearly system output (tool lists, session IDs, etc.)
+ cleaned = cleaned.replace(/^.*"(?:type|subtype|session_id|tools|mcp_servers|model|permissionMode|slash_commands|agents)".*$/gm, "");
+
+ // Remove excessive blank lines
+ cleaned = cleaned.replace(/\n{3,}/g, "\n\n");
+
+ return cleaned.trim();
+}
+
+/**
+ * Check if a streaming chunk looks like system/init output rather than
+ * conversational text. Used to filter relay streaming.
+ */
+function isSystemChunk(text: string): boolean {
+ // JSON-like content
+ if (/^\s*\{/.test(text) && /"type"\s*:/.test(text)) return true;
+ // Tool/permission dumps
+ if (/"tools"\s*:\s*\[/.test(text)) return true;
+ if (/"mcp_servers"\s*:\s*\[/.test(text)) return true;
+ if (/"session_id"\s*:/.test(text)) return true;
+ return false;
+}
+
+/**
+ * Detect if a user message is asking the CEO to create a plan/hire.
+ */
+function isAskingForPlan(message: string): boolean {
+ const planPatterns = [
+ /\b(hiring|team|org)\s*(plan|strategy)\b/i,
+ /\b(build|create|draft|start|write)\s*(a\s+)?(hiring|team|the)\s*(plan)?\b/i,
+ /\bget started\b/i,
+ /\bhire\b.*\b(team|agents?|roles?)\b/i,
+ /\blet'?s\s+(build|start|go|do it)\b/i,
+ /\bready to\s+(hire|build|plan)\b/i,
+ ];
+ return planPatterns.some((p) => p.test(message));
+}
+
/** Animated paperclip SVG thinking indicator */
function PaperclipThinking({ className }: { className?: string }) {
return (
@@ -297,6 +351,23 @@ export function CEOChatPanel({
setInput("");
setOptimisticTyping(true);
+ // If user is asking for a plan, create a draft artifact immediately
+ if (isAskingForPlan(trimmed)) {
+ issuesApi.createWorkProduct(taskId, {
+ type: "document",
+ title: "Hiring Plan",
+ provider: "paperclip",
+ status: "draft",
+ reviewState: "none",
+ isPrimary: true,
+ summary: "Your CEO is drafting a hiring plan...",
+ }).then(() => {
+ queryClient.invalidateQueries({
+ queryKey: queryKeys.issues.workProducts(taskId),
+ });
+ }).catch(() => { /* may already exist */ });
+ }
+
const latestId = comments?.[comments.length - 1]?.id ?? null;
setIgnoreBeforeCommentId(latestId);
setDetectedPlanCommentId(null);
@@ -340,7 +411,7 @@ export function CEOChatPanel({
if (!line.startsWith("data: ")) continue;
try {
const event = JSON.parse(line.slice(6));
- if (event.type === "chunk") {
+ if (event.type === "chunk" && !isSystemChunk(event.text)) {
setStreamingText((prev) => prev + event.text);
} else if (event.type === "done") {
setStreamingText("");
@@ -576,6 +647,9 @@ export function CEOChatPanel({
{comments?.map((comment) => {
const isAgent = Boolean(comment.authorAgentId);
const isPlan = detectedPlanCommentId === comment.id;
+ // Hide comments that are entirely system output
+ const displayBody = isAgent ? cleanAgentMessage(comment.body) : comment.body;
+ if (isAgent && !displayBody) return null;
return (
-
- {isAgent
- ? comment.body.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
- : comment.body}
-
+ {displayBody}
diff --git a/ui/src/components/OnboardingWizard.tsx b/ui/src/components/OnboardingWizard.tsx
index 27421ba1..91492fd5 100644
--- a/ui/src/components/OnboardingWizard.tsx
+++ b/ui/src/components/OnboardingWizard.tsx
@@ -1110,10 +1110,11 @@ Follow this structure for every role in the plan.`,
-
Define your mission
+
{!missionPath ? "Name your company" : "Define your mission"}
- Your mission drives everything — your CEO, your hires,
- and the work your company will do.
+ {!missionPath
+ ? "What will your company be called?"
+ : "Your mission drives everything — your CEO, your hires, and the work your company will do."}
@@ -1133,12 +1134,12 @@ Follow this structure for every role in the plan.`,
placeholder="Acme Corp"
value={companyName}
onChange={(e) => setCompanyName(e.target.value)}
- autoFocus
+ autoFocus={!missionPath}
/>
- {/* Mission path selector */}
- {!missionPath && (
+ {/* Mission path selector — only shows after company name is entered */}
+ {!missionPath && companyName.trim() && (
How would you like to define your mission?
diff --git a/ui/src/pages/Chat.tsx b/ui/src/pages/Chat.tsx
index ea294bd9..9aa3937f 100644
--- a/ui/src/pages/Chat.tsx
+++ b/ui/src/pages/Chat.tsx
@@ -246,7 +246,7 @@ export function Chat() {
}
return (
-