mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Fix imported agent title hydration
This commit is contained in:
@@ -12,9 +12,11 @@ import type {
|
||||
AgentClient,
|
||||
AgentMode,
|
||||
AgentModelDefinition,
|
||||
AgentTimelineItem,
|
||||
ListModesOptions,
|
||||
ListModelsOptions,
|
||||
} from "./agent/agent-sdk-types.js";
|
||||
import type { ManagedAgent } from "./agent/agent-manager.js";
|
||||
import type { ProviderDefinition } from "./agent/provider-registry.js";
|
||||
import { ProviderSnapshotManager } from "./agent/provider-snapshot-manager.js";
|
||||
import type { SessionOptions } from "./session.js";
|
||||
@@ -27,6 +29,7 @@ interface SessionHandlerInternals {
|
||||
handleCheckoutPullRequest(params: unknown): Promise<unknown>;
|
||||
handleCheckoutPushRequest(params: unknown): Promise<unknown>;
|
||||
handleCheckoutStatusRequest(params: unknown): Promise<unknown>;
|
||||
handleImportAgentRequest(params: unknown): Promise<unknown>;
|
||||
describeWorkspaceRecord(...args: unknown[]): Promise<WorkspaceDescriptorPayload>;
|
||||
describeWorkspaceRecordWithGitData(...args: unknown[]): Promise<WorkspaceDescriptorPayload>;
|
||||
handleValidateBranchRequest(params: unknown): Promise<unknown>;
|
||||
@@ -63,6 +66,10 @@ const agentResponseMocks = vi.hoisted(() => ({
|
||||
generateStructuredAgentResponseWithFallback: vi.fn(),
|
||||
}));
|
||||
|
||||
const agentMetadataMocks = vi.hoisted(() => ({
|
||||
scheduleAgentMetadataGeneration: vi.fn(),
|
||||
}));
|
||||
|
||||
const spawnMocks = vi.hoisted(() => ({
|
||||
execCommand: vi.fn(),
|
||||
spawnWorkspaceScript: vi.fn(),
|
||||
@@ -172,6 +179,14 @@ vi.mock("./agent/agent-response-loop.js", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./agent/agent-metadata-generator.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./agent/agent-metadata-generator.js")>();
|
||||
return {
|
||||
...actual,
|
||||
scheduleAgentMetadataGeneration: agentMetadataMocks.scheduleAgentMetadataGeneration,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./worktree-bootstrap.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./worktree-bootstrap.js")>();
|
||||
return {
|
||||
@@ -570,6 +585,87 @@ afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("session agent import", () => {
|
||||
test("sets a provisional title and schedules auto-title generation from the first hydrated user message", async () => {
|
||||
const messages: unknown[] = [];
|
||||
const cwd = "/tmp/imported-agent";
|
||||
const timeline: AgentTimelineItem[] = [
|
||||
{ type: "user_message", text: "Investigate flaky checkout status\n\ninclude logs" },
|
||||
{ type: "assistant_message", text: "I will inspect the checkout flow." },
|
||||
];
|
||||
const snapshot = {
|
||||
id: "00000000-0000-4000-8000-000000000632",
|
||||
provider: "codex",
|
||||
cwd,
|
||||
capabilities: TEST_CAPABILITIES,
|
||||
config: { provider: "codex", cwd },
|
||||
createdAt: new Date("2026-04-30T00:00:00.000Z"),
|
||||
updatedAt: new Date("2026-04-30T00:00:00.000Z"),
|
||||
availableModes: [],
|
||||
currentModeId: null,
|
||||
pendingPermissions: new Map(),
|
||||
bufferedPermissionResolutions: new Map(),
|
||||
inFlightPermissionResponses: new Set(),
|
||||
pendingReplacement: false,
|
||||
persistence: {
|
||||
provider: "codex",
|
||||
sessionId: "thread-imported",
|
||||
nativeHandle: "thread-imported",
|
||||
metadata: { provider: "codex", cwd },
|
||||
},
|
||||
historyPrimed: true,
|
||||
lastUserMessageAt: null,
|
||||
attention: { requiresAttention: false },
|
||||
foregroundTurnWaiters: new Set(),
|
||||
finalizedForegroundTurnIds: new Set(),
|
||||
unsubscribeSession: null,
|
||||
internal: false,
|
||||
labels: {},
|
||||
lifecycle: "closed",
|
||||
session: null,
|
||||
activeForegroundTurnId: null,
|
||||
} satisfies ManagedAgent;
|
||||
const agentManager = {
|
||||
listAgents: vi.fn(() => []),
|
||||
subscribe: vi.fn(() => () => {}),
|
||||
findPersistedAgent: vi.fn().mockResolvedValue(null),
|
||||
resumeAgentFromPersistence: vi.fn().mockResolvedValue(snapshot),
|
||||
hydrateTimelineFromProvider: vi.fn().mockResolvedValue(undefined),
|
||||
getTimeline: vi.fn().mockReturnValue(timeline),
|
||||
setTitle: vi.fn().mockResolvedValue(undefined),
|
||||
notifyAgentState: vi.fn(),
|
||||
};
|
||||
const agentStorage = {
|
||||
list: vi.fn().mockResolvedValue([]),
|
||||
get: vi.fn().mockResolvedValue(null),
|
||||
};
|
||||
const session = createSessionForTest({ messages });
|
||||
Object.assign(session, { agentManager, agentStorage });
|
||||
|
||||
await asSessionInternals(session).handleImportAgentRequest({
|
||||
type: "import_agent_request",
|
||||
provider: "codex",
|
||||
sessionId: "thread-imported",
|
||||
cwd,
|
||||
requestId: "import-thread",
|
||||
});
|
||||
|
||||
expect(agentManager.setTitle).toHaveBeenCalledWith(
|
||||
snapshot.id,
|
||||
"Investigate flaky checkout status",
|
||||
);
|
||||
expect(agentMetadataMocks.scheduleAgentMetadataGeneration).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
agentManager,
|
||||
agentId: snapshot.id,
|
||||
cwd,
|
||||
initialPrompt: "Investigate flaky checkout status\n\ninclude logs",
|
||||
explicitTitle: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("session PR status payload normalization", () => {
|
||||
test("includes repository identity fields on the wire", () => {
|
||||
const payload = normalizeCheckoutPrStatusPayload({
|
||||
|
||||
@@ -133,6 +133,7 @@ import type {
|
||||
AgentRunOptions,
|
||||
AgentSessionConfig,
|
||||
AgentStreamEvent,
|
||||
AgentTimelineItem,
|
||||
ProviderSnapshotEntry,
|
||||
} from "./agent/agent-sdk-types.js";
|
||||
import type { StoredAgentRecord } from "./agent/agent-storage.js";
|
||||
@@ -417,6 +418,19 @@ export function resolveCreateAgentTitles(options: {
|
||||
};
|
||||
}
|
||||
|
||||
function getFirstUserMessageText(timeline: readonly AgentTimelineItem[]): string | null {
|
||||
for (const item of timeline) {
|
||||
if (item.type !== "user_message") {
|
||||
continue;
|
||||
}
|
||||
const text = item.text.trim();
|
||||
if (text) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseFetchWorkspacesCursorSort(raw: unknown[]): FetchWorkspacesRequestSort[] {
|
||||
const cursorSort: FetchWorkspacesRequestSort[] = [];
|
||||
for (const item of raw) {
|
||||
@@ -3329,6 +3343,7 @@ export class Session {
|
||||
);
|
||||
await unarchiveAgentState(this.agentStorage, this.agentManager, snapshot.id);
|
||||
await this.agentManager.hydrateTimelineFromProvider(snapshot.id);
|
||||
await this.applyImportedAgentTitle(snapshot);
|
||||
await this.forwardAgentUpdate(snapshot);
|
||||
const timelineSize = this.agentManager.getTimeline(snapshot.id).length;
|
||||
const agentPayload = await this.buildAgentPayload(snapshot);
|
||||
@@ -3365,6 +3380,31 @@ export class Session {
|
||||
}
|
||||
}
|
||||
|
||||
private async applyImportedAgentTitle(snapshot: ManagedAgent): Promise<void> {
|
||||
const initialPrompt = getFirstUserMessageText(this.agentManager.getTimeline(snapshot.id));
|
||||
if (!initialPrompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { explicitTitle, provisionalTitle } = resolveCreateAgentTitles({
|
||||
configTitle: snapshot.config.title,
|
||||
initialPrompt,
|
||||
});
|
||||
if (!explicitTitle && provisionalTitle) {
|
||||
await this.agentManager.setTitle(snapshot.id, provisionalTitle);
|
||||
}
|
||||
|
||||
scheduleAgentMetadataGeneration({
|
||||
agentManager: this.agentManager,
|
||||
agentId: snapshot.id,
|
||||
cwd: snapshot.cwd,
|
||||
initialPrompt,
|
||||
explicitTitle,
|
||||
paseoHome: this.paseoHome,
|
||||
logger: this.sessionLogger,
|
||||
});
|
||||
}
|
||||
|
||||
private async handleRefreshAgentRequest(
|
||||
msg: Extract<SessionInboundMessage, { type: "refresh_agent_request" }>,
|
||||
): Promise<void> {
|
||||
|
||||
@@ -418,7 +418,7 @@ class FakeAgentSession implements AgentSession {
|
||||
|
||||
private async emitSlashCommandTurn(slashCommand: {
|
||||
commandName: string;
|
||||
args: string;
|
||||
args?: string;
|
||||
}): Promise<void> {
|
||||
const threadStarted: AgentStreamEvent = {
|
||||
type: "thread_started",
|
||||
|
||||
Reference in New Issue
Block a user