diff --git a/packages/app/src/screens/agent/draft-agent-screen.tsx b/packages/app/src/screens/agent/draft-agent-screen.tsx index 9c6427650..cbb5b6857 100644 --- a/packages/app/src/screens/agent/draft-agent-screen.tsx +++ b/packages/app/src/screens/agent/draft-agent-screen.tsx @@ -53,7 +53,6 @@ import { normalizeAgentSnapshot } from '@/utils/agent-snapshots' import { useDraftAgentCreateFlow } from '@/hooks/use-draft-agent-create-flow' const EMPTY_PENDING_PERMISSIONS = new Map() -const MAX_INITIAL_AGENT_TITLE_CHARS = 60 const DRAFT_CAPABILITIES: AgentCapabilityFlags = { supportsStreaming: true, supportsSessionPersistence: false, @@ -98,22 +97,6 @@ function getValidMode(provider: AgentProvider | undefined, value: string | undef return modes.some((mode) => mode.id === value) ? value : undefined } -function deriveInitialAgentTitle(prompt: string): string | null { - const firstContentLine = prompt - .split(/\r?\n/) - .map((line) => line.trim()) - .find((line) => line.length > 0) - if (!firstContentLine) { - return null - } - const normalized = firstContentLine.replace(/\s+/g, " ").trim() - if (!normalized) { - return null - } - const clamped = normalized.slice(0, MAX_INITIAL_AGENT_TITLE_CHARS).trim() - return clamped.length > 0 ? clamped : null -} - type DraftAgentParams = { serverId?: string provider?: string @@ -872,11 +855,9 @@ function DraftAgentScreenContent({ const modeId = modeOptions.length > 0 && selectedMode !== '' ? selectedMode : undefined const trimmedModel = selectedModel.trim() const trimmedThinkingOptionId = selectedThinkingOptionId.trim() - const derivedTitle = deriveInitialAgentTitle(text) const config: AgentSessionConfig = { provider: selectedProvider, cwd: resolvedWorkingDir, - ...(derivedTitle ? { title: derivedTitle } : {}), ...(modeId ? { modeId } : {}), ...(trimmedModel ? { model: trimmedModel } : {}), ...(trimmedThinkingOptionId ? { thinkingOptionId: trimmedThinkingOptionId } : {}), diff --git a/packages/server/src/server/agent/providers/claude-agent.redesign.test.ts b/packages/server/src/server/agent/providers/claude-agent.redesign.test.ts index 2cf173ab9..b8b4dc62c 100644 --- a/packages/server/src/server/agent/providers/claude-agent.redesign.test.ts +++ b/packages/server/src/server/agent/providers/claude-agent.redesign.test.ts @@ -753,6 +753,93 @@ describe("ClaudeAgentSession redesign invariants", () => { await session.close(); }); + test("completes a foreground run when only system metadata arrives before the first assistant message", async () => { + let step = 0; + sdkMocks.query.mockImplementation(() => + createBaseQueryMock( + vi.fn(async () => { + if (step === 0) { + step += 1; + return { + done: false, + value: { + type: "system", + subtype: "init", + session_id: "redesign-metadata-only-session", + permissionMode: "default", + model: "opus", + }, + }; + } + if (step === 1) { + step += 1; + return { + done: false, + value: { + type: "system", + subtype: "hook_response", + session_id: "redesign-metadata-only-session", + hook_name: "SessionStart:Callback", + hook_event: "SessionStart", + stdout: "", + stderr: "", + }, + }; + } + if (step === 2) { + step += 1; + return { + done: false, + value: { + type: "assistant", + message: { content: "assistant output" }, + }, + }; + } + if (step === 3) { + step += 1; + return { + done: false, + value: { + type: "result", + subtype: "success", + usage: buildUsage(), + total_cost_usd: 0, + }, + }; + } + return { done: true, value: undefined }; + }) + ) + ); + + const session = await createSession(); + try { + const events = await Promise.race([ + collectUntilTerminal(session.stream("metadata helper prompt")), + new Promise((_, reject) => { + setTimeout( + () => reject(new Error("Timed out waiting for foreground terminal event")), + 1_000 + ); + }), + ]); + + expect(events.some((event) => event.type === "turn_completed")).toBe(true); + + const assistantText = events + .filter( + (event): event is Extract => + event.type === "timeline" && event.item.type === "assistant_message" + ) + .map((event) => event.item.text) + .join(""); + expect(assistantText).toContain("assistant output"); + } finally { + await session.close(); + } + }); + test("reuses one autonomous run for unbound stream_event bursts with no foreground run", async () => { const session = await createSession(); const internal = session as unknown as { diff --git a/packages/server/src/server/agent/providers/claude-agent.ts b/packages/server/src/server/agent/providers/claude-agent.ts index 12e9cb20f..37d626030 100644 --- a/packages/server/src/server/agent/providers/claude-agent.ts +++ b/packages/server/src/server/agent/providers/claude-agent.ts @@ -2746,7 +2746,10 @@ class ClaudeAgentSession implements AgentSession { if (!foregroundRun || foregroundRun.promptReplaySeen) { return; } - if (message.type === "system" && message.subtype === "init") { + // System metadata (init/hook callbacks/etc.) can precede the first prompt + // replay for a legitimate foreground run. Treating that as churn strands + // one-shot helper runs in autonomous fallback. + if (message.type === "system") { return; } this.preReplayMetadataSeen = true;