mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-15 04:45:11 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78d9a36a16 |
@@ -148,6 +148,23 @@ for (const scenario of [
|
||||
});
|
||||
}
|
||||
|
||||
test("retrying failed agent initialization keeps the plugin metadata set on New workspace", async ({
|
||||
creation,
|
||||
startup,
|
||||
composerMetadata,
|
||||
}) => {
|
||||
await creation.openWorkspaceForm("local");
|
||||
await composerMetadata.set({ "composer-metadata.tag": "kept" });
|
||||
await creation.submitPrompt("Start the tagged task.", "Create");
|
||||
await creation.expectAgentStillStarting();
|
||||
await startup.fail();
|
||||
await creation.expectStartupFailure();
|
||||
await creation.expectOneCreatedWorkspace();
|
||||
await creation.submitPrompt("Start the tagged task.");
|
||||
await creation.expectAgentCount(1);
|
||||
await creation.expectAgentMetadata({ "composer-metadata.tag": "kept" });
|
||||
});
|
||||
|
||||
test("an edited agent draft starts after its first start failed", async ({
|
||||
creation,
|
||||
startup,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { writeFile, rm, rename } from "node:fs/promises";
|
||||
import { mkdtemp, writeFile, rm, rename } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { copyPluginExample } from "./helpers/plugin-fixture";
|
||||
import type { Page } from "@playwright/test";
|
||||
import { copyPluginExample, pluginRequirements } from "./helpers/plugin-fixture";
|
||||
import { connectNewWorkspaceDaemonClient } from "./helpers/new-workspace";
|
||||
import { test as base } from "./fixtures";
|
||||
import {
|
||||
@@ -14,6 +16,7 @@ import { delayBrowserAgentCreatedStatus } from "./helpers/new-workspace";
|
||||
export const test = base.extend<{
|
||||
startup: { release(): Promise<void>; fail(): Promise<void> };
|
||||
promptRejection: void;
|
||||
composerMetadata: { set(patch: Record<string, string>): Promise<void> };
|
||||
creation: Awaited<ReturnType<typeof createCreationScenario>>;
|
||||
promptRetry: ReturnType<typeof createPromptRetryScenario>;
|
||||
agentRetries: Awaited<ReturnType<typeof retryNextAgentCreation>>;
|
||||
@@ -98,6 +101,39 @@ export default function contribute(server) {
|
||||
await client.close();
|
||||
}
|
||||
},
|
||||
composerMetadata: async ({ page }, provide) => {
|
||||
const client = await connectNewWorkspaceDaemonClient();
|
||||
const previous = await client.getDaemonConfig();
|
||||
const directory = await mkdtemp(path.join(tmpdir(), "paseo-composer-metadata-"));
|
||||
try {
|
||||
await writeFile(
|
||||
path.join(directory, "paseo-plugin.json"),
|
||||
JSON.stringify({ id: "composer-metadata", requirements: pluginRequirements }),
|
||||
);
|
||||
await writeFile(
|
||||
path.join(directory, "index.client.tsx"),
|
||||
`import { useComposer } from "@getpaseo/plugin/client";
|
||||
|
||||
function Holder() {
|
||||
globalThis.metadataComposer = useComposer();
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function contribute(client) {
|
||||
client.addComposerItem({ id: "holder", Component: Holder });
|
||||
return () => {};
|
||||
}`,
|
||||
);
|
||||
await client.patchDaemonConfig({ pluginsEnabled: true });
|
||||
await client.installDirectoryPlugin(directory);
|
||||
await provide({ set: (patch) => setComposerMetadata(page, patch) });
|
||||
} finally {
|
||||
await client.removePlugin("composer-metadata");
|
||||
await client.patchDaemonConfig({ pluginsEnabled: previous.config.pluginsEnabled });
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
await client.close();
|
||||
}
|
||||
},
|
||||
creation: async ({ page }, provide) => {
|
||||
const creation = await createCreationScenario(page);
|
||||
try {
|
||||
@@ -126,3 +162,15 @@ export default function contribute(server) {
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
type MetadataComposerGlobal = typeof globalThis & {
|
||||
metadataComposer?: { setMetadata(patch: Record<string, string>): Promise<void> };
|
||||
};
|
||||
|
||||
async function setComposerMetadata(page: Page, patch: Record<string, string>) {
|
||||
await page.waitForFunction(() => (globalThis as MetadataComposerGlobal).metadataComposer);
|
||||
await page.evaluate(
|
||||
(value) => (globalThis as MetadataComposerGlobal).metadataComposer!.setMetadata(value),
|
||||
patch,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -258,6 +258,16 @@ export async function createCreationScenario(page: Page) {
|
||||
.filter(({ agent }) => agent.workspaceId === workspaceId)
|
||||
.map(({ agent }) => agent.id);
|
||||
},
|
||||
async expectAgentMetadata(metadata: Record<string, unknown>) {
|
||||
await expect
|
||||
.poll(
|
||||
async () =>
|
||||
(await project.client.fetchAgents()).entries.find(
|
||||
({ agent }) => agent.workspaceId === workspaceId,
|
||||
)?.agent.metadata,
|
||||
)
|
||||
.toEqual(metadata);
|
||||
},
|
||||
async expectAgentTitle(title: string) {
|
||||
await expect
|
||||
.poll(
|
||||
|
||||
@@ -131,6 +131,7 @@ export interface SeedDaemonClient {
|
||||
currentModeId: string | null;
|
||||
status: string;
|
||||
title?: string | null;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
}>;
|
||||
}>;
|
||||
|
||||
@@ -121,7 +121,10 @@ import {
|
||||
type ListTerminalsPayload,
|
||||
upsertCreatedTerminalPayload,
|
||||
} from "./workspace/terminals/state";
|
||||
import { captureWorkspaceDraftCleanup } from "./new-workspace/background-handoff";
|
||||
import {
|
||||
captureWorkspaceDraftCleanup,
|
||||
handOffDraftMetadata,
|
||||
} from "./new-workspace/background-handoff";
|
||||
import { useNewWorkspaceScreenPresence } from "./new-workspace/screen-presence";
|
||||
|
||||
function useIsNewWorkspaceDraftHandoffActive(input: {
|
||||
@@ -635,6 +638,7 @@ function submitWorkspaceDraft(input: SubmitDraftInput): SubmitOutcome {
|
||||
allowEmptyText: true,
|
||||
agentCreation: input.agentCreation,
|
||||
});
|
||||
handOffDraftMetadata({ serverId, draftId, metadata: composerState.metadata });
|
||||
clearDraft("sent");
|
||||
navigateToWorkspace({
|
||||
serverId,
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
import { useWorkspaceAttachmentsStore } from "@/attachments/workspace-attachments-store";
|
||||
import { buildDraftStoreKey } from "@/stores/draft-keys";
|
||||
import { useDraftStore } from "@/stores/draft-store";
|
||||
import type { JsonValue } from "@getpaseo/protocol/agent-types";
|
||||
import { useWorkspaceDraftSubmissionStore } from "@/stores/workspace-draft-submission-store";
|
||||
|
||||
/**
|
||||
* The ready workspace's draft tab owns the draft once New workspace hands it off, including a
|
||||
* retry after the first agent start fails. Its metadata moves with it, like its text and setup.
|
||||
*/
|
||||
export function handOffDraftMetadata(input: {
|
||||
serverId: string;
|
||||
draftId: string;
|
||||
metadata: Readonly<Record<string, JsonValue>>;
|
||||
}): void {
|
||||
if (Object.keys(input.metadata).length === 0) return;
|
||||
useDraftStore.getState().patchDraftMetadata({
|
||||
draftKey: buildDraftStoreKey({
|
||||
serverId: input.serverId,
|
||||
agentId: input.draftId,
|
||||
draftId: input.draftId,
|
||||
}),
|
||||
patch: { ...input.metadata },
|
||||
});
|
||||
}
|
||||
|
||||
interface WorkspaceDraftCleanupInput {
|
||||
draftId?: string;
|
||||
draftKey: string;
|
||||
|
||||
Reference in New Issue
Block a user