Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
983 changes: 962 additions & 21 deletions common/config/rush/pnpm-lock.yaml

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion packages/acp-agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
"@agentclientprotocol/sdk": "^0.4.3",
"@ai-sdk/mcp": "0.0.18",
"@ai-sdk/openai": "^2.0.0",
"@fledgling/agent-core": "workspace:*",
"@fledgling/common": "workspace:*",
"@fledgling/context-builder": "workspace:*",
"@fledgling/mcp-workspace": "workspace:*",
"@fledgling/session-log": "workspace:*",
"@fledgling/session-file-system": "workspace:*",
"@fledgling/tools-mcp-node": "workspace:*",
"ai": "^5.0.0"
Comment thread
Copilot marked this conversation as resolved.
},
"devDependencies": {
Expand Down
54 changes: 33 additions & 21 deletions packages/acp-agent/src/agent-prompt-cancellation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";

import type { SessionEvent } from "@fledgling/common";
import { SessionStore } from "@fledgling/session-log";
import { FileSystemSessionManager } from "@fledgling/session-file-system";

import type { FledglingAgent, FledglingAgentDependencies } from "./agent.js";

Expand Down Expand Up @@ -324,14 +324,14 @@ describe("FledglingAgent prompt cancellation", () => {
});

it("replays stored user and assistant messages when loading a session", async () => {
const { agent, sessionId, sessionStore, sessionUpdates, tempDir } = await createTestAgent();
await sessionStore.append({
...sessionStore.createEventBase(sessionId),
const { agent, sessionId, sessionManager, sessionUpdates, tempDir } = await createTestAgent();
await sessionManager.appendEvent({
...sessionManager.createEventBase(sessionId),
type: "message.user",
text: "previous user"
});
await sessionStore.append({
...sessionStore.createEventBase(sessionId),
await sessionManager.appendEvent({
...sessionManager.createEventBase(sessionId),
type: "message.assistant",
text: "previous assistant"
});
Expand All @@ -358,13 +358,17 @@ describe("FledglingAgent prompt cancellation", () => {

it("rejects session creation when injected tool setup fails", async () => {
const { FledglingAgent } = await import("./agent.js");
const sessionStore = await createTempSessionStore();
const sessionStore = await createTempSessionManager();
const agent = new FledglingAgent(createFakeConnection() as never, {
createSessionTools: vi.fn(async () => {
throw new Error("setup failed");
}),
sessionStore: sessionStore.store,
runModelTurn: vi.fn()
toolProvider: {
createSessionTools: vi.fn(async () => {
throw new Error("setup failed");
})
},
sessionManager: sessionStore.manager,
modelTurnRunner: {
runModelTurn: vi.fn()
}
});

await expect(agent.newSession({ cwd: sessionStore.tempDir, mcpServers: [] })).rejects.toThrow("setup failed");
Expand All @@ -374,27 +378,31 @@ describe("FledglingAgent prompt cancellation", () => {
readonly agent: FledglingAgent;
readonly sessionId: string;
readonly sessionFile: string;
readonly sessionStore: SessionStore;
readonly sessionManager: FileSystemSessionManager;
readonly sessionUpdates: FakeSessionUpdate[];
readonly tempDir: string;
readonly streamText: ReturnType<typeof vi.fn>;
}> {
const { store: sessionStore, sessionFile, tempDir: createdTempDir } = await createTempSessionStore();
const { manager: sessionManager, sessionFile, tempDir: createdTempDir } = await createTempSessionManager();
const sessionUpdates: FakeSessionUpdate[] = [];
const streamText = vi.fn();
const { FledglingAgent } = await import("./agent.js");
const agent = new FledglingAgent(createFakeConnection(sessionUpdates) as never, {
createSessionTools: vi.fn(async () => ({ mcpClients: [], tools: {} })),
sessionStore,
runModelTurn: streamText
toolProvider: {
createSessionTools: vi.fn(async () => ({ clients: [], tools: {} }))
},
sessionManager,
modelTurnRunner: {
runModelTurn: streamText
}
} satisfies FledglingAgentDependencies);
const session = await agent.newSession({ cwd: createdTempDir, mcpServers: [] });

return {
agent,
sessionId: session.sessionId,
sessionFile,
sessionStore,
sessionManager,
sessionUpdates,
tempDir: createdTempDir,
streamText
Expand All @@ -418,8 +426,8 @@ function createFakeConnection(sessionUpdates: FakeSessionUpdate[] = []): { sessi
};
}

async function createTempSessionStore(): Promise<{
readonly store: SessionStore;
async function createTempSessionManager(): Promise<{
readonly manager: FileSystemSessionManager;
readonly sessionFile: string;
readonly tempDir: string;
}> {
Expand All @@ -428,7 +436,11 @@ async function createTempSessionStore(): Promise<{
process.env.FLEDGLING_CONFIG = path.join(createdTempDir, "missing-config.json");
const sessionFile = path.join(createdTempDir, "session.jsonl");
process.env.FLEDGLING_SESSION_FILE = sessionFile;
return { store: new SessionStore(createdTempDir, sessionFile), sessionFile, tempDir: createdTempDir };
return {
manager: new FileSystemSessionManager(createdTempDir, sessionFile),
sessionFile,
tempDir: createdTempDir
};
}

function createControlledStream(parts: readonly StreamPart[]): ControlledStream {
Expand Down
45 changes: 27 additions & 18 deletions packages/acp-agent/src/agent-session-lifecycle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import path from "node:path";

import { afterEach, describe, expect, it, vi } from "vitest";

import { FileSystemSessionManager } from "@fledgling/session-file-system";

describe("FledglingAgent session lifecycle", () => {
const originalConfig = process.env.FLEDGLING_CONFIG;
const originalSessionFile = process.env.FLEDGLING_SESSION_FILE;
Expand Down Expand Up @@ -38,26 +40,33 @@ describe("FledglingAgent session lifecycle", () => {
};
const clients = [firstClient, secondClient];

vi.doMock("@ai-sdk/mcp", () => ({
experimental_createMCPClient: vi.fn(async () => {
const client = clients.shift();
if (!client) {
throw new Error("unexpected client request");
}
const { FledglingAgent } = await import("./agent.js");
const agent = new FledglingAgent(
{
sessionUpdate: vi.fn(async () => {})
} as never,
{
sessionManager: new FileSystemSessionManager(tempDir, process.env.FLEDGLING_SESSION_FILE),
toolProvider: {
createSessionTools: vi.fn(async () => {
const client = clients.shift();
if (!client) {
throw new Error("unexpected client request");
}

return client;
})
}));
vi.doMock("@ai-sdk/mcp/mcp-stdio", () => ({
Experimental_StdioMCPTransport: class Experimental_StdioMCPTransport {
public constructor(_options: unknown) {}
return {
clients: [client],
tools: {}
};
})
},
modelTurnRunner: {
runModelTurn: vi.fn(() => ({
fullStream: (async function* () {})()
}))
}
}
}));

const { FledglingAgent } = await import("./agent.js");
const agent = new FledglingAgent({
sessionUpdate: vi.fn(async () => {})
} as never);
);
const mcpServers = [{ name: "workspace", command: "workspace-command", args: [], env: [] }];

const session = await agent.newSession({ cwd: tempDir, mcpServers });
Expand Down
Loading
Loading