fix(server): restore auto metadata generation

This commit is contained in:
Zi Makki
2026-03-06 17:36:29 +01:00
parent a99edcc0b2
commit 8dd94757d7
3 changed files with 91 additions and 20 deletions

View File

@@ -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 } : {}),

View File

@@ -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<never>((_, 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<AgentStreamEvent, { type: "timeline" }> =>
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 {

View File

@@ -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;