feat: add saveSession flag to create scenario runs
- Add optional `saveSession` boolean parameter to CreateScenarioRunDto
- Add `saveSession` column to ScenarioRunEntity
- When saveSession=true and run completes, preserve the browser context as a named explore session instead of closing it
- Session name follows pattern: run-{runId}
- Automatically extracts token and localStorage for session persistence
- Injected SessionService and SessionContextService into ScenarioSchedulerService
- Updated MCP run_scenario tool to accept saveSession parameter
- Updated UI run dialog with checkbox to enable session preservation
- Client API updated to pass saveSession flag
This commit is contained in:
@@ -10,6 +10,8 @@ import { CodeExecutorService } from "../code-executor/code-executor.service";
|
||||
import { traceStorage } from "../common/trace-context";
|
||||
import { TraceLogger } from "../common/trace-logger";
|
||||
import { EnvironmentData, EnvironmentEntity } from "../environment/environment.entity";
|
||||
import { SessionContextService } from "../session/session-context.service";
|
||||
import { SessionService } from "../session/session.service";
|
||||
import { SnippetService } from "../snippet/snippet.service";
|
||||
import { ScenarioRunLogEntity } from "./scenario-run-log.entity";
|
||||
import { ScenarioRunStepEntity } from "./scenario-run-step.entity";
|
||||
@@ -47,6 +49,8 @@ export class ScenarioSchedulerService {
|
||||
private readonly codeExecutor: CodeExecutorService,
|
||||
private readonly scenarioService: ScenarioService,
|
||||
private readonly snippetService: SnippetService,
|
||||
private readonly sessionService: SessionService,
|
||||
private readonly sessionContextService: SessionContextService,
|
||||
) {}
|
||||
|
||||
private persistLog(
|
||||
@@ -150,6 +154,53 @@ export class ScenarioSchedulerService {
|
||||
this.runCredentials.delete(runId);
|
||||
this.runSnippets.delete(runId);
|
||||
this.runEnvironments.delete(runId);
|
||||
await this.maybePreserveSession(runId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the run was created with saveSession=true, promote the run's browser
|
||||
* context to a named explore session instead of closing it.
|
||||
*/
|
||||
private async maybePreserveSession(runId: string): Promise<void> {
|
||||
const run = await this.runRepo.findOneBy({ id: runId });
|
||||
if (!run?.saveSession) {
|
||||
await this.closeBrowserHandle(runId);
|
||||
return;
|
||||
}
|
||||
|
||||
const handle = this.runBrowsers.get(runId);
|
||||
if (!handle) return;
|
||||
|
||||
const sessionName = `run-${runId}`;
|
||||
try {
|
||||
const cookies = await handle.context.cookies();
|
||||
const localStorage = await handle.page
|
||||
.evaluate(() => ({ ...window.localStorage }))
|
||||
.catch((): Record<string, string> => ({}));
|
||||
const token =
|
||||
(localStorage["accessToken"] as string | undefined) ??
|
||||
(localStorage["token"] as string | undefined) ??
|
||||
cookies.find((c) => c.name === "token")?.value ??
|
||||
"";
|
||||
|
||||
await this.sessionService.upsert(sessionName, token, cookies, localStorage);
|
||||
this.sessionContextService.register(
|
||||
sessionName,
|
||||
handle.browser,
|
||||
handle.context,
|
||||
handle.page,
|
||||
);
|
||||
// Remove from runBrowsers so closeBrowserHandle won't close it
|
||||
this.runBrowsers.delete(runId);
|
||||
this.logger.log(
|
||||
`Run #${runId}: browser preserved as session "${sessionName}"`,
|
||||
);
|
||||
} catch (err) {
|
||||
this.logger.warn(
|
||||
`Run #${runId}: failed to preserve session — ${(err as Error).message}`,
|
||||
);
|
||||
await this.closeBrowserHandle(runId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user