Add dev mock load-test provider

This commit is contained in:
Mohamed Boudra
2026-04-20 00:38:10 +07:00
parent 86b83c3366
commit 04465f124c
12 changed files with 800 additions and 6 deletions
@@ -116,6 +116,16 @@ const OPENCODE_MODES: AgentProviderModeDefinition[] = [
},
];
const MOCK_LOAD_TEST_MODES: AgentProviderModeDefinition[] = [
{
id: "load-test",
label: "Load Test",
description: "Streams repeated markdown, reasoning, and tool calls for app stress testing",
icon: "ShieldOff",
colorTier: "dangerous",
},
];
export const AGENT_PROVIDER_DEFINITIONS: AgentProviderDefinition[] = [
{
id: "claude",
@@ -168,9 +178,23 @@ export const AGENT_PROVIDER_DEFINITIONS: AgentProviderDefinition[] = [
},
];
export const DEV_AGENT_PROVIDER_DEFINITIONS: AgentProviderDefinition[] = [
{
id: "mock",
label: "Mock Load Test",
description:
"Development-only provider that emits synthetic agent traffic for performance tests",
defaultModeId: "load-test",
modes: MOCK_LOAD_TEST_MODES,
},
];
export function getAgentProviderDefinition(
provider: string,
definitions: AgentProviderDefinition[] = AGENT_PROVIDER_DEFINITIONS,
definitions: AgentProviderDefinition[] = [
...AGENT_PROVIDER_DEFINITIONS,
...DEV_AGENT_PROVIDER_DEFINITIONS,
],
): AgentProviderDefinition {
const definition = definitions.find((entry) => entry.id === provider);
if (!definition) {
@@ -332,6 +332,19 @@ describe("buildProviderRegistry", () => {
expect(Object.keys(registry)).toHaveLength(AGENT_PROVIDER_DEFINITIONS.length);
});
test("includes mock provider only for development builds", () => {
expect(buildProviderRegistry(logger).mock).toBeUndefined();
expect(buildProviderRegistry(logger, { isDev: false }).mock).toBeUndefined();
const registry = buildProviderRegistry(logger, { isDev: true });
expect(registry.mock).toMatchObject({
id: "mock",
label: "Mock Load Test",
defaultModeId: "load-test",
});
});
test("built-in override applies command", () => {
buildProviderRegistry(logger, {
providerOverrides: {
@@ -27,9 +27,11 @@ import { CopilotACPAgentClient } from "./providers/copilot-acp-agent.js";
import { GenericACPAgentClient } from "./providers/generic-acp-agent.js";
import { OpenCodeAgentClient, OpenCodeServerManager } from "./providers/opencode-agent.js";
import { PiDirectAgentClient } from "./providers/pi-direct-agent.js";
import { MockLoadTestAgentClient } from "./providers/mock-load-test-agent.js";
import {
AGENT_PROVIDER_DEFINITIONS,
BUILTIN_PROVIDER_IDS,
DEV_AGENT_PROVIDER_DEFINITIONS,
getAgentProviderDefinition,
type AgentProviderDefinition,
} from "./provider-manifest.js";
@@ -48,6 +50,7 @@ export type BuildProviderRegistryOptions = {
runtimeSettings?: AgentProviderRuntimeSettingsMap;
providerOverrides?: Record<string, ProviderOverride>;
workspaceGitService?: Pick<WorkspaceGitService, "resolveRepoRoot">;
isDev?: boolean;
};
type ProviderClientFactory = (
@@ -85,6 +88,7 @@ const PROVIDER_CLIENT_FACTORIES: Record<string, ProviderClientFactory> = {
logger,
runtimeSettings,
}),
mock: (logger) => new MockLoadTestAgentClient(logger),
};
function getProviderClientFactory(provider: string): ProviderClientFactory {
@@ -349,10 +353,15 @@ function buildResolvedBuiltinProviders(
providerOverrides: Record<string, ProviderOverride>,
runtimeSettings: AgentProviderRuntimeSettingsMap | undefined,
options: Pick<BuildProviderRegistryOptions, "workspaceGitService">,
isDev: boolean,
): Map<string, ResolvedProvider> {
const resolvedProviders = new Map<string, ResolvedProvider>();
for (const definition of AGENT_PROVIDER_DEFINITIONS) {
const definitions = isDev
? [...AGENT_PROVIDER_DEFINITIONS, ...DEV_AGENT_PROVIDER_DEFINITIONS]
: AGENT_PROVIDER_DEFINITIONS;
for (const definition of definitions) {
const override = providerOverrides[definition.id];
const factory = getProviderClientFactory(definition.id);
const mergedRuntimeSettings = mergeRuntimeSettings(
@@ -380,7 +389,7 @@ function addDerivedProviders(
providerOverrides: Record<string, ProviderOverride>,
): void {
for (const [providerId, override] of Object.entries(providerOverrides)) {
if (BUILTIN_PROVIDER_IDS.includes(providerId)) {
if (resolvedProviders.has(providerId) || BUILTIN_PROVIDER_IDS.includes(providerId)) {
continue;
}
@@ -448,9 +457,14 @@ export function buildProviderRegistry(
): Record<AgentProvider, ProviderDefinition> {
const runtimeSettings = options?.runtimeSettings;
const providerOverrides = options?.providerOverrides ?? {};
const resolvedProviders = buildResolvedBuiltinProviders(providerOverrides, runtimeSettings, {
workspaceGitService: options?.workspaceGitService,
});
const resolvedProviders = buildResolvedBuiltinProviders(
providerOverrides,
runtimeSettings,
{
workspaceGitService: options?.workspaceGitService,
},
options?.isDev === true,
);
addDerivedProviders(resolvedProviders, providerOverrides);
return Object.fromEntries(
@@ -0,0 +1,176 @@
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test, vi } from "vitest";
import { createTestLogger } from "../../../test-utils/test-logger.js";
import { AgentManager } from "../agent-manager.js";
import type { AgentStreamEvent, AgentTimelineItem } from "../agent-sdk-types.js";
import {
MOCK_LOAD_TEST_DEFAULT_MODEL_ID,
MockLoadTestAgentClient,
} from "./mock-load-test-agent.js";
describe("MockLoadTestAgentClient", () => {
afterEach(() => {
vi.useRealTimers();
});
test("default model is a five minute foreground stream", async () => {
const client = new MockLoadTestAgentClient();
const models = await client.listModels();
expect(models[0]).toMatchObject({
id: MOCK_LOAD_TEST_DEFAULT_MODEL_ID,
isDefault: true,
metadata: {
durationMs: 300_000,
intervalMs: 1000,
},
});
});
test("emits repeated markdown, reasoning, and tool calls during a foreground turn", async () => {
vi.useFakeTimers();
const client = new MockLoadTestAgentClient();
const session = await client.createSession({
provider: "mock",
cwd: process.cwd(),
model: "ten-second-stream",
});
const events: AgentStreamEvent[] = [];
const unsubscribe = session.subscribe((event) => events.push(event));
const resultPromise = session.run("Exercise the app while terminals are busy.");
await vi.advanceTimersByTimeAsync(0);
await vi.advanceTimersByTimeAsync(10_000);
const result = await resultPromise;
unsubscribe();
expect(
events.map((event) => event.type).filter((type) => type === "turn_started"),
).toHaveLength(1);
expect(events.at(-1)).toMatchObject({
type: "turn_completed",
provider: "mock",
});
expect(result).toMatchObject({
sessionId: session.id,
finalText: "Synthetic load test complete",
canceled: false,
});
const timelineItems = events.flatMap((event): AgentTimelineItem[] =>
event.type === "timeline" ? [event.item] : [],
);
expect(timelineItems.find((item) => item.type === "reasoning")).toMatchObject({
type: "reasoning",
text: expect.stringContaining("Thinking chunk 1"),
});
expect(
timelineItems.filter(
(item) =>
item.type === "assistant_message" && item.text.startsWith("## Synthetic Load Cycle 1\n"),
),
).toHaveLength(1);
expect(
timelineItems.filter((item) => item.type === "assistant_message" && item.text.length > 0)
.length,
).toBeGreaterThan(4);
expect(
timelineItems.filter(
(item) =>
item.type === "tool_call" &&
item.name === "edit" &&
item.status === "running" &&
item.detail.type === "edit",
),
).toHaveLength(40);
expect(
timelineItems.filter(
(item) =>
item.type === "tool_call" &&
item.name === "bash" &&
item.status === "completed" &&
item.detail.type === "shell" &&
item.detail.output?.includes("mock load cycle"),
),
).toHaveLength(40);
});
test("interrupt cancels the active foreground turn and stops future chunks", async () => {
vi.useFakeTimers();
const client = new MockLoadTestAgentClient();
const session = await client.createSession({
provider: "mock",
cwd: process.cwd(),
model: "ten-second-stream",
});
const events: AgentStreamEvent[] = [];
const unsubscribe = session.subscribe((event) => events.push(event));
await session.startTurn("Cancel the synthetic stream.");
await vi.advanceTimersByTimeAsync(0);
await session.interrupt();
const eventCountAfterInterrupt = events.length;
await vi.advanceTimersByTimeAsync(10_000);
unsubscribe();
expect(events.at(-1)).toMatchObject({
type: "turn_canceled",
provider: "mock",
reason: "Interrupted",
});
expect(events).toHaveLength(eventCountAfterInterrupt);
});
test("agent manager coalesces adjacent markdown chunks from the mock provider", async () => {
vi.useFakeTimers();
const workdir = mkdtempSync(join(tmpdir(), "paseo-mock-load-test-"));
try {
const client = new MockLoadTestAgentClient();
const manager = new AgentManager({
clients: { mock: client },
idFactory: () => "00000000-0000-4000-8000-000000000001",
logger: createTestLogger(),
});
const agent = await manager.createAgent(
{
provider: "mock",
cwd: workdir,
model: "ten-second-stream",
},
"00000000-0000-4000-8000-000000000001",
);
const resultPromise = manager.runAgent(
agent.id,
"Stress the agent stream while terminal panes are active.",
);
await vi.advanceTimersByTimeAsync(0);
await vi.advanceTimersByTimeAsync(10_000);
await resultPromise;
const timeline = manager.getTimeline(agent.id);
const loadCycleDocuments = timeline.filter(
(item): item is Extract<AgentTimelineItem, { type: "assistant_message" }> =>
item.type === "assistant_message" && item.text.startsWith("## Synthetic Load Cycle "),
);
const editToolCalls = timeline.filter(
(item) => item.type === "tool_call" && item.name === "edit",
);
expect(loadCycleDocuments).toHaveLength(40);
expect(loadCycleDocuments[0]?.text).toContain("This paragraph is intentionally stable");
expect(editToolCalls).toHaveLength(40);
expect(
editToolCalls.every((item) => item.type === "tool_call" && item.status === "completed"),
).toBe(true);
} finally {
rmSync(workdir, { recursive: true, force: true });
}
});
});
@@ -0,0 +1,549 @@
import { randomUUID } from "node:crypto";
import type { Logger } from "pino";
import type {
AgentCapabilityFlags,
AgentClient,
AgentFeature,
AgentLaunchContext,
AgentMode,
AgentModelDefinition,
AgentPersistenceHandle,
AgentPermissionRequest,
AgentPermissionResponse,
AgentPermissionResult,
AgentPromptInput,
AgentProvider,
AgentRunOptions,
AgentRunResult,
AgentRuntimeInfo,
AgentSession,
AgentSessionConfig,
AgentStreamEvent,
AgentTimelineItem,
ListModelsOptions,
ListPersistedAgentsOptions,
PersistedAgentDescriptor,
ToolCallTimelineItem,
} from "../agent-sdk-types.js";
import { getAgentProviderDefinition } from "../provider-manifest.js";
export const MOCK_LOAD_TEST_PROVIDER_ID = "mock";
export const MOCK_LOAD_TEST_DEFAULT_MODEL_ID = "five-minute-stream";
const MOCK_LOAD_TEST_MODE_ID = "load-test";
const MOCK_LOAD_TEST_DURATION_MS = 5 * 60 * 1000;
const MOCK_LOAD_TEST_INTERVAL_MS = 1000;
const CAPABILITIES: AgentCapabilityFlags = {
supportsStreaming: true,
supportsSessionPersistence: true,
supportsDynamicModes: false,
supportsMcpServers: false,
supportsReasoningStream: true,
supportsToolInvocations: true,
};
const MODELS: AgentModelDefinition[] = [
{
provider: MOCK_LOAD_TEST_PROVIDER_ID,
id: MOCK_LOAD_TEST_DEFAULT_MODEL_ID,
label: "Five minute stream",
description: "Repeats synthetic markdown and tool-call timeline traffic for five minutes.",
isDefault: true,
metadata: {
durationMs: MOCK_LOAD_TEST_DURATION_MS,
intervalMs: MOCK_LOAD_TEST_INTERVAL_MS,
},
},
{
provider: MOCK_LOAD_TEST_PROVIDER_ID,
id: "one-minute-stream",
label: "One minute stream",
description: "Shorter synthetic load stream for quick manual checks.",
metadata: {
durationMs: 60_000,
intervalMs: 500,
},
},
{
provider: MOCK_LOAD_TEST_PROVIDER_ID,
id: "ten-second-stream",
label: "Ten second stream",
description: "Fast synthetic load stream for tests and smoke checks.",
metadata: {
durationMs: 10_000,
intervalMs: 250,
},
},
];
type ActiveTurn = {
turnId: string;
prompt: AgentPromptInput;
startedAt: number;
iteration: number;
durationMs: number;
intervalMs: number;
timer: ReturnType<typeof setTimeout> | null;
resolve: (result: AgentRunResult) => void;
completed: Promise<AgentRunResult>;
};
function resolveModelProfile(modelId: string | null | undefined): {
modelId: string;
durationMs: number;
intervalMs: number;
} {
const model = MODELS.find((entry) => entry.id === modelId) ?? MODELS[0]!;
const metadata = model.metadata ?? {};
return {
modelId: model.id,
durationMs:
typeof metadata.durationMs === "number" ? metadata.durationMs : MOCK_LOAD_TEST_DURATION_MS,
intervalMs:
typeof metadata.intervalMs === "number" ? metadata.intervalMs : MOCK_LOAD_TEST_INTERVAL_MS,
};
}
function promptToText(prompt: AgentPromptInput): string {
if (typeof prompt === "string") {
return prompt;
}
return prompt
.flatMap((block) => (block.type === "text" ? [block.text] : []))
.join("\n")
.trim();
}
function buildMarkdownDocument(iteration: number, prompt: AgentPromptInput): string {
const promptPreview = promptToText(prompt).slice(0, 160) || "No prompt text supplied.";
return [
`## Synthetic Load Cycle ${iteration}`,
"",
`Prompt preview: ${promptPreview}`,
"",
"| Signal | Value |",
"| --- | --- |",
`| cycle | ${iteration} |`,
"| stream | markdown + reasoning + tools |",
"",
"```ts",
`const cycle = ${iteration};`,
'const purpose = "exercise app parsing and terminal rendering under load";',
"```",
"",
"This paragraph is intentionally stable so repeated cycles produce predictable UI pressure.",
"",
].join("\n");
}
function splitText(text: string, chunks: number): string[] {
const size = Math.ceil(text.length / chunks);
const parts: string[] = [];
for (let index = 0; index < text.length; index += size) {
parts.push(text.slice(index, index + size));
}
return parts;
}
function createToolCall(input: {
turnId: string;
iteration: number;
name: string;
status: ToolCallTimelineItem["status"];
detail: ToolCallTimelineItem["detail"];
}): ToolCallTimelineItem {
return {
type: "tool_call",
callId: `${input.turnId}:${input.name}:${input.iteration}`,
name: input.name,
status: input.status,
error: null,
detail: input.detail,
};
}
export class MockLoadTestAgentClient implements AgentClient {
readonly provider: AgentProvider = MOCK_LOAD_TEST_PROVIDER_ID;
readonly capabilities = CAPABILITIES;
constructor(private readonly logger?: Logger) {}
async createSession(
config: AgentSessionConfig,
_launchContext?: AgentLaunchContext,
): Promise<AgentSession> {
return new MockLoadTestAgentSession({
config,
sessionId: randomUUID(),
logger: this.logger,
});
}
async resumeSession(
handle: AgentPersistenceHandle,
overrides?: Partial<AgentSessionConfig>,
_launchContext?: AgentLaunchContext,
): Promise<AgentSession> {
const metadata = (handle.metadata ?? {}) as Partial<AgentSessionConfig>;
return new MockLoadTestAgentSession({
config: {
cwd: String(metadata.cwd ?? overrides?.cwd ?? process.cwd()),
...metadata,
...overrides,
provider: MOCK_LOAD_TEST_PROVIDER_ID,
},
sessionId: handle.sessionId,
logger: this.logger,
});
}
async listModels(_options?: ListModelsOptions): Promise<AgentModelDefinition[]> {
return MODELS;
}
async listModes(): Promise<AgentMode[]> {
return getAgentProviderDefinition(MOCK_LOAD_TEST_PROVIDER_ID).modes;
}
async listPersistedAgents(
_options?: ListPersistedAgentsOptions,
): Promise<PersistedAgentDescriptor[]> {
return [];
}
async isAvailable(): Promise<boolean> {
return true;
}
async getDiagnostic(): Promise<{ diagnostic: string }> {
return {
diagnostic: "Mock load-test provider is available in development builds.",
};
}
}
export class MockLoadTestAgentSession implements AgentSession {
readonly provider: AgentProvider = MOCK_LOAD_TEST_PROVIDER_ID;
readonly capabilities = CAPABILITIES;
readonly features: AgentFeature[] = [];
readonly id: string;
private readonly listeners = new Set<(event: AgentStreamEvent) => void>();
private readonly history: AgentStreamEvent[] = [];
private readonly logger?: Logger;
private activeTurn: ActiveTurn | null = null;
private modeId: string | null;
private modelId: string | null;
constructor(options: {
config: AgentSessionConfig;
sessionId: string;
logger?: Logger;
}) {
this.id = options.sessionId;
this.logger = options.logger;
this.modeId = options.config.modeId ?? MOCK_LOAD_TEST_MODE_ID;
this.modelId = options.config.model ?? MOCK_LOAD_TEST_DEFAULT_MODEL_ID;
}
async run(prompt: AgentPromptInput, options?: AgentRunOptions): Promise<AgentRunResult> {
const { turnId } = await this.startTurn(prompt, options);
const turn = this.activeTurn;
if (!turn || turn.turnId !== turnId) {
throw new Error("Mock load-test turn did not start");
}
return turn.completed;
}
async startTurn(
prompt: AgentPromptInput,
_options?: AgentRunOptions,
): Promise<{ turnId: string }> {
if (this.activeTurn) {
throw new Error("Mock load-test provider already has an active turn");
}
const profile = resolveModelProfile(this.modelId);
const turnId = randomUUID();
let resolve!: (result: AgentRunResult) => void;
const completed = new Promise<AgentRunResult>((promiseResolve) => {
resolve = promiseResolve;
});
const turn: ActiveTurn = {
turnId,
prompt,
startedAt: Date.now(),
iteration: 0,
durationMs: profile.durationMs,
intervalMs: profile.intervalMs,
timer: null,
resolve,
completed,
};
this.activeTurn = turn;
this.schedule(turn, 0);
return { turnId };
}
subscribe(callback: (event: AgentStreamEvent) => void): () => void {
this.listeners.add(callback);
return () => {
this.listeners.delete(callback);
};
}
async *streamHistory(): AsyncGenerator<AgentStreamEvent> {
for (const event of this.history) {
yield event;
}
}
async getRuntimeInfo(): Promise<AgentRuntimeInfo> {
return {
provider: this.provider,
sessionId: this.id,
model: this.modelId,
modeId: this.modeId,
};
}
async getAvailableModes(): Promise<AgentMode[]> {
return getAgentProviderDefinition(MOCK_LOAD_TEST_PROVIDER_ID).modes;
}
async getCurrentMode(): Promise<string | null> {
return this.modeId;
}
async setMode(modeId: string): Promise<void> {
this.modeId = modeId;
}
getPendingPermissions(): AgentPermissionRequest[] {
return [];
}
async respondToPermission(
_requestId: string,
_response: AgentPermissionResponse,
): Promise<AgentPermissionResult | void> {
return undefined;
}
describePersistence(): AgentPersistenceHandle | null {
return {
provider: this.provider,
sessionId: this.id,
metadata: {
model: this.modelId,
modeId: this.modeId,
},
};
}
async interrupt(): Promise<void> {
const turn = this.activeTurn;
if (!turn) {
return;
}
this.clearTurnTimer(turn);
this.activeTurn = null;
const event: AgentStreamEvent = {
type: "turn_canceled",
provider: this.provider,
reason: "Interrupted",
turnId: turn.turnId,
};
this.emit(event);
turn.resolve({
sessionId: this.id,
finalText: "",
timeline: [],
canceled: true,
});
}
async close(): Promise<void> {
await this.interrupt();
this.listeners.clear();
}
async setModel(modelId: string | null): Promise<void> {
this.modelId = modelId ?? MOCK_LOAD_TEST_DEFAULT_MODEL_ID;
}
private schedule(turn: ActiveTurn, delayMs: number): void {
turn.timer = setTimeout(() => {
this.tick(turn);
}, delayMs);
turn.timer.unref?.();
}
private tick(turn: ActiveTurn): void {
if (this.activeTurn !== turn) {
return;
}
this.clearTurnTimer(turn);
if (turn.iteration === 0) {
this.emit({
type: "turn_started",
provider: this.provider,
turnId: turn.turnId,
});
}
const elapsedMs = Date.now() - turn.startedAt;
if (elapsedMs >= turn.durationMs) {
this.finishTurn(turn);
return;
}
turn.iteration += 1;
this.emitIteration(turn);
this.schedule(turn, turn.intervalMs);
}
private emitIteration(turn: ActiveTurn): void {
const { turnId, iteration, prompt } = turn;
this.emitTimeline(turnId, {
type: "reasoning",
text: `Thinking chunk ${iteration}: simulate planning pressure before rendering markdown.\n`,
});
for (const chunk of splitText(buildMarkdownDocument(iteration, prompt), 4)) {
this.emitTimeline(turnId, {
type: "assistant_message",
text: chunk,
});
}
const editDetail = {
type: "edit" as const,
filePath: `src/load-test-${iteration}.ts`,
oldString: "before",
newString: "after",
unifiedDiff: [
`diff --git a/src/load-test-${iteration}.ts b/src/load-test-${iteration}.ts`,
"@@",
"-before",
"+after",
].join("\n"),
};
this.emitTimeline(
turnId,
createToolCall({
turnId,
iteration,
name: "edit",
status: "running",
detail: editDetail,
}),
);
this.emitTimeline(
turnId,
createToolCall({
turnId,
iteration,
name: "edit",
status: "completed",
detail: editDetail,
}),
);
const shellDetail = {
type: "shell" as const,
command: `printf 'mock load cycle ${iteration}\\n'`,
cwd: "/tmp/paseo-mock-load",
output: `mock load cycle ${iteration}\n`,
exitCode: 0,
};
this.emitTimeline(
turnId,
createToolCall({
turnId,
iteration,
name: "bash",
status: "running",
detail: shellDetail,
}),
);
this.emitTimeline(
turnId,
createToolCall({
turnId,
iteration,
name: "bash",
status: "completed",
detail: shellDetail,
}),
);
this.emit({
type: "usage_updated",
provider: this.provider,
turnId,
usage: {
inputTokens: iteration * 32,
outputTokens: iteration * 128,
contextWindowUsedTokens: iteration * 160,
contextWindowMaxTokens: 128_000,
},
});
}
private finishTurn(turn: ActiveTurn): void {
this.activeTurn = null;
this.emitTimeline(turn.turnId, {
type: "assistant_message",
text: "## Synthetic load test complete\n\nThe mock provider finished its foreground turn.\n",
});
const usage = {
inputTokens: turn.iteration * 32,
outputTokens: turn.iteration * 128,
contextWindowUsedTokens: turn.iteration * 160,
contextWindowMaxTokens: 128_000,
};
this.emit({
type: "turn_completed",
provider: this.provider,
turnId: turn.turnId,
usage,
});
turn.resolve({
sessionId: this.id,
finalText: "Synthetic load test complete",
usage,
timeline: [],
canceled: false,
});
}
private emitTimeline(turnId: string, item: AgentTimelineItem): void {
this.emit({
type: "timeline",
provider: this.provider,
turnId,
item,
});
}
private emit(event: AgentStreamEvent): void {
this.history.push(event);
for (const listener of this.listeners) {
try {
listener(event);
} catch (error) {
this.logger?.warn({ err: error }, "Mock load-test listener failed");
}
}
}
private clearTurnTimer(turn: ActiveTurn): void {
if (!turn.timer) {
return;
}
clearTimeout(turn.timer);
turn.timer = null;
}
}
+4
View File
@@ -180,6 +180,7 @@ export type PaseoDaemonConfig = {
mcpInjectIntoAgents?: boolean;
staticDir: string;
mcpDebug: boolean;
isDev?: boolean;
agentClients: Partial<Record<AgentProvider, AgentClient>>;
agentStoragePath: string;
relayEnabled?: boolean;
@@ -425,6 +426,7 @@ export async function createPaseoDaemon(
runtimeSettings: config.agentProviderSettings,
providerOverrides: config.providerOverrides,
workspaceGitService,
isDev: config.isDev === true,
}),
...config.agentClients,
},
@@ -435,6 +437,7 @@ export async function createPaseoDaemon(
runtimeSettings: config.agentProviderSettings,
providerOverrides: config.providerOverrides,
workspaceGitService,
isDev: config.isDev === true,
});
const detachAgentStoragePersistence = attachAgentStoragePersistence(
@@ -707,6 +710,7 @@ export async function createPaseoDaemon(
},
config.agentProviderSettings,
config.providerOverrides,
config.isDev === true,
daemonVersion,
(intent) => {
try {
+1
View File
@@ -181,6 +181,7 @@ export function loadConfig(
mcpEnabled,
mcpInjectIntoAgents,
mcpDebug: env.MCP_DEBUG === "1",
isDev: env.NODE_ENV === "development",
agentStoragePath: path.join(paseoHome, "agents"),
staticDir: "public",
agentClients: {},
+5
View File
@@ -526,6 +526,7 @@ export type SessionOptions = {
};
agentProviderRuntimeSettings?: AgentProviderRuntimeSettingsMap;
providerOverrides?: Record<string, ProviderOverride>;
isDev?: boolean;
};
export type SessionLifecycleIntent =
@@ -720,6 +721,7 @@ export class Session {
private readonly getSpeechReadiness?: () => SpeechReadinessSnapshot;
private readonly agentProviderRuntimeSettings: AgentProviderRuntimeSettingsMap | undefined;
private readonly providerOverrides: Record<string, ProviderOverride> | undefined;
private readonly isDev: boolean;
private voiceModeAgentId: string | null = null;
private voiceModeBaseConfig: VoiceModeBaseConfig | null = null;
@@ -761,6 +763,7 @@ export class Session {
dictation,
agentProviderRuntimeSettings,
providerOverrides,
isDev,
} = options;
this.clientId = clientId;
this.appVersion = appVersion ?? null;
@@ -829,11 +832,13 @@ export class Session {
this.getSpeechReadiness = dictation?.getSpeechReadiness;
this.agentProviderRuntimeSettings = agentProviderRuntimeSettings;
this.providerOverrides = providerOverrides;
this.isDev = isDev === true;
this.abortController = new AbortController();
this.providerRegistry = buildProviderRegistry(this.sessionLogger, {
runtimeSettings: this.agentProviderRuntimeSettings,
providerOverrides: this.providerOverrides,
workspaceGitService: this.workspaceGitService,
isDev: this.isDev,
});
// Initialize per-session managers
@@ -86,6 +86,7 @@ function createServer(agentManagerOverrides?: Record<string, unknown>) {
undefined,
undefined,
undefined,
false,
"1.2.3-test",
undefined,
undefined,
@@ -181,6 +181,7 @@ function createServer(options?: { speechReadiness?: SpeechReadinessSnapshot | nu
undefined,
undefined,
undefined,
false,
TEST_DAEMON_VERSION,
undefined,
undefined,
@@ -94,6 +94,7 @@ function createServer(logger: ReturnType<typeof createLogger>) {
undefined,
undefined,
undefined,
false,
"1.2.3-test",
undefined,
undefined,
@@ -319,6 +319,7 @@ export class VoiceAssistantWebSocketServer {
private readonly voiceCallerContexts = new Map<string, VoiceCallerContext>();
private readonly agentProviderRuntimeSettings: AgentProviderRuntimeSettingsMap | undefined;
private readonly providerOverrides: Record<string, ProviderOverride> | undefined;
private readonly isDev: boolean;
private readonly providerSnapshotManager: ProviderSnapshotManager;
private readonly onLifecycleIntent: ((intent: SessionLifecycleIntent) => void) | null;
private readonly onBranchChanged:
@@ -374,6 +375,7 @@ export class VoiceAssistantWebSocketServer {
},
agentProviderRuntimeSettings?: AgentProviderRuntimeSettingsMap,
providerOverrides?: Record<string, ProviderOverride>,
isDev?: boolean,
daemonVersion?: string,
onLifecycleIntent?: (intent: SessionLifecycleIntent) => void,
projectRegistry?: ProjectRegistry,
@@ -432,11 +434,13 @@ export class VoiceAssistantWebSocketServer {
this.dictation = dictation ?? null;
this.agentProviderRuntimeSettings = agentProviderRuntimeSettings;
this.providerOverrides = providerOverrides;
this.isDev = isDev === true;
const providerSnapshotLogger = this.logger.child({ module: "provider-snapshot-manager" });
this.providerSnapshotManager = new ProviderSnapshotManager(
buildProviderRegistry(providerSnapshotLogger, {
runtimeSettings: this.agentProviderRuntimeSettings,
providerOverrides: this.providerOverrides,
isDev: this.isDev,
}),
providerSnapshotLogger,
);
@@ -793,6 +797,7 @@ export class VoiceAssistantWebSocketServer {
: undefined,
agentProviderRuntimeSettings: this.agentProviderRuntimeSettings,
providerOverrides: this.providerOverrides,
isDev: this.isDev,
});
connection = {