| |
| |
| |
| |
| |
| |
|
|
| import type { |
| ApprovalHandler, |
| ApprovalRequest, |
| Event, |
| JsonObject, |
| PermissionMode, |
| PromptInput, |
| QuestionHandler, |
| QuestionRequest, |
| Session, |
| SessionSummary, |
| } from "@moonshot-ai/kimi-code-sdk"; |
| import { describe, expect, it } from "vitest"; |
|
|
| import { Events } from "../shared/bridge"; |
| import type { LegacyApprovalFlags } from "../src/runtime/legacy-approval"; |
| import { SessionRuntime } from "../src/runtime/session-runtime"; |
|
|
| interface BroadcastRecord { |
| readonly event: string; |
| readonly data: unknown; |
| readonly webviewId?: string; |
| } |
|
|
| interface BaselineRecord { |
| readonly session: Pick<SessionSummary, "id" | "workDir" | "metadata">; |
| readonly filePath: string; |
| readonly webviewIds: readonly string[]; |
| } |
|
|
| interface FakeSessionBoundary { |
| readonly session: Session; |
| readonly promptInputs: Array<string | PromptInput>; |
| readonly steerInputs: Array<string | PromptInput>; |
| readonly handlerInstallations: { approval: number; question: number }; |
| readonly metadataUpdates: JsonObject[]; |
| readonly setPermissions: PermissionMode[]; |
| readonly subscriptionCount: () => number; |
| readonly cancelCount: () => number; |
| readonly cancelCompactionCount: () => number; |
| readonly closeCount: () => number; |
| emit(event: Event): void; |
| rejectNextPrompt(error: Error): void; |
| rejectNextMetadataUpdate(error: Error): void; |
| requestApproval(request: ApprovalRequest): Promise<Awaited<ReturnType<ApprovalHandler>>>; |
| requestQuestion(request: QuestionRequest): Promise<Awaited<ReturnType<QuestionHandler>>>; |
| } |
|
|
| const DEFAULT_LEGACY_APPROVAL: LegacyApprovalFlags = { yolo: false, afk: false }; |
|
|
| function createFakeSession(): FakeSessionBoundary { |
| const listeners = new Set<(event: Event) => void>(); |
| const promptInputs: Array<string | PromptInput> = []; |
| const steerInputs: Array<string | PromptInput> = []; |
| const handlerInstallations = { approval: 0, question: 0 }; |
| const metadataUpdates: JsonObject[] = []; |
| const setPermissions: PermissionMode[] = []; |
| let approvalHandler: ApprovalHandler | undefined; |
| let questionHandler: QuestionHandler | undefined; |
| let nextPromptError: Error | undefined; |
| let nextMetadataError: Error | undefined; |
| let subscriptions = 0; |
| let cancellations = 0; |
| let compactionCancellations = 0; |
| let closes = 0; |
| let permission: PermissionMode = "manual"; |
|
|
| const summary: SessionSummary = { |
| id: "session-1", |
| workDir: "/workspace", |
| sessionDir: "/home/sessions/session-1", |
| createdAt: 1, |
| updatedAt: 2, |
| metadata: { source: "vscode-test" }, |
| }; |
|
|
| const session = { |
| id: summary.id, |
| workDir: summary.workDir, |
| summary, |
| setApprovalHandler(handler: ApprovalHandler | undefined) { |
| approvalHandler = handler; |
| if (handler !== undefined) handlerInstallations.approval += 1; |
| }, |
| setQuestionHandler(handler: QuestionHandler | undefined) { |
| questionHandler = handler; |
| if (handler !== undefined) handlerInstallations.question += 1; |
| }, |
| onEvent(listener: (event: Event) => void) { |
| subscriptions += 1; |
| listeners.add(listener); |
| return () => listeners.delete(listener); |
| }, |
| async prompt(input: string | PromptInput) { |
| promptInputs.push(input); |
| if (nextPromptError !== undefined) { |
| const error = nextPromptError; |
| nextPromptError = undefined; |
| throw error; |
| } |
| }, |
| async steer(input: string | PromptInput) { |
| steerInputs.push(input); |
| }, |
| async cancel() { |
| cancellations += 1; |
| }, |
| async cancelCompaction() { |
| compactionCancellations += 1; |
| }, |
| async getStatus() { |
| return { |
| thinkingEffort: "off", |
| permission, |
| planMode: false, |
| contextTokens: 0, |
| maxContextTokens: 128_000, |
| contextUsage: 0, |
| }; |
| }, |
| async setPermission(mode: PermissionMode) { |
| permission = mode; |
| setPermissions.push(mode); |
| }, |
| async updateMetadata(patch: JsonObject) { |
| if (nextMetadataError !== undefined) { |
| const error = nextMetadataError; |
| nextMetadataError = undefined; |
| throw error; |
| } |
| metadataUpdates.push(patch); |
| }, |
| async close() { |
| closes += 1; |
| }, |
| } as unknown as Session; |
|
|
| return { |
| session, |
| promptInputs, |
| steerInputs, |
| handlerInstallations, |
| metadataUpdates, |
| setPermissions, |
| subscriptionCount: () => subscriptions, |
| cancelCount: () => cancellations, |
| cancelCompactionCount: () => compactionCancellations, |
| closeCount: () => closes, |
| emit(event) { |
| for (const listener of listeners) listener(event); |
| }, |
| rejectNextPrompt(error) { |
| nextPromptError = error; |
| }, |
| rejectNextMetadataUpdate(error) { |
| nextMetadataError = error; |
| }, |
| async requestApproval(request) { |
| if (approvalHandler === undefined) throw new Error("Approval handler is unavailable"); |
| return approvalHandler(request); |
| }, |
| async requestQuestion(request) { |
| if (questionHandler === undefined) throw new Error("Question handler is unavailable"); |
| return questionHandler(request); |
| }, |
| }; |
| } |
|
|
| function createRuntime(legacyApproval = DEFAULT_LEGACY_APPROVAL) { |
| const sdk = createFakeSession(); |
| const broadcasts: BroadcastRecord[] = []; |
| const baselines: BaselineRecord[] = []; |
| const runtime = new SessionRuntime({ |
| session: sdk.session, |
| legacyApproval, |
| broadcast: (event, data, webviewId) => broadcasts.push({ event, data, webviewId }), |
| captureBaseline: (session, filePath, webviewIds) => { |
| baselines.push({ session, filePath, webviewIds }); |
| }, |
| log: () => undefined, |
| }); |
| runtime.subscribe("view-1"); |
| return { runtime, sdk, broadcasts, baselines }; |
| } |
|
|
| function streamData(records: readonly BroadcastRecord[]): unknown[] { |
| return records.filter((record) => record.event === Events.StreamEvent).map((record) => record.data); |
| } |
|
|
| function turnStarted(): Event { |
| return { |
| type: "turn.started", |
| sessionId: "session-1", |
| agentId: "main", |
| turnId: 7, |
| origin: { kind: "user" }, |
| }; |
| } |
|
|
| function turnEnded( |
| reason: "completed" | "cancelled" | "failed", |
| error?: Extract<Event, { type: "turn.ended" }>["error"], |
| ): Event { |
| return { |
| type: "turn.ended", |
| sessionId: "session-1", |
| agentId: "main", |
| turnId: 7, |
| reason, |
| error, |
| }; |
| } |
|
|
| describe("session runtime (adapts one SDK session for subscribed Webviews)", () => { |
| it("announces the current context usage to a subscribed Webview", async () => { |
| const { runtime, broadcasts } = createRuntime(); |
|
|
| await runtime.announceStatus("view-1"); |
|
|
| expect(streamData(broadcasts)).toContainEqual({ |
| type: "StatusUpdate", |
| payload: { |
| thinking_effort: "off", |
| plan_mode: false, |
| context_usage: 0, |
| }, |
| _sessionId: "session-1", |
| }); |
| }); |
|
|
| it("renders a host-only command without making it a forkable core turn", () => { |
| const { runtime, broadcasts } = createRuntime(); |
|
|
| runtime.beginHostAction("/clear"); |
| runtime.emitHostText("The context has been cleared."); |
| runtime.completeHostAction(); |
|
|
| expect(streamData(broadcasts)).toEqual([ |
| { |
| type: "TurnBegin", |
| payload: { user_input: "/clear", forkable: false }, |
| _sessionId: "session-1", |
| }, |
| { type: "StepBegin", payload: { n: 1 }, _sessionId: "session-1" }, |
| { |
| type: "ContentPart", |
| payload: { type: "text", text: "The context has been cleared." }, |
| _sessionId: "session-1", |
| }, |
| { |
| type: "stream_complete", |
| result: { status: "finished" }, |
| _sessionId: "session-1", |
| }, |
| ]); |
| }); |
|
|
| it("cancels a long-running host action and ignores its late completion", async () => { |
| const { runtime, sdk, broadcasts } = createRuntime(); |
| const actionId = runtime.beginHostAction("/init"); |
|
|
| await runtime.cancel(); |
| runtime.emitHostText("AGENTS.md has been generated.", actionId); |
| runtime.completeHostAction("finished", actionId); |
|
|
| expect(sdk.cancelCount()).toBe(1); |
| expect(sdk.cancelCompactionCount()).toBe(1); |
| expect(streamData(broadcasts)).toContainEqual({ |
| type: "stream_complete", |
| result: { status: "cancelled" }, |
| _sessionId: "session-1", |
| }); |
| expect(JSON.stringify(streamData(broadcasts))).not.toContain("has been generated"); |
| }); |
|
|
| it("does not let a cancelled action finish a newer host command", async () => { |
| const { runtime, broadcasts } = createRuntime(); |
| const initAction = runtime.beginHostAction("/init"); |
| await runtime.cancel(); |
| const clearAction = runtime.beginHostAction("/clear"); |
|
|
| runtime.emitHostText("late init result", initAction); |
| runtime.completeHostAction("finished", initAction); |
|
|
| expect(runtime.isBusy).toBe(true); |
| runtime.emitHostText("The context has been cleared.", clearAction); |
| runtime.completeHostAction("finished", clearAction); |
| expect(runtime.isBusy).toBe(false); |
| expect(JSON.stringify(streamData(broadcasts))).not.toContain("late init result"); |
| }); |
|
|
| it("waits for the cancelled turn to settle before running an exclusive operation", async () => { |
| const { runtime, sdk } = createRuntime(); |
| const prompt = runtime.prompt("keep working"); |
| sdk.emit(turnStarted()); |
| let operationStarted = false; |
|
|
| const operation = runtime.runExclusiveAfterCancelling(async () => { |
| operationStarted = true; |
| return "forked"; |
| }); |
| await Promise.resolve(); |
| await Promise.resolve(); |
|
|
| expect(sdk.cancelCount()).toBe(1); |
| expect(operationStarted).toBe(false); |
| expect(runtime.isBusy).toBe(true); |
|
|
| sdk.emit(turnEnded("cancelled")); |
|
|
| await expect(prompt).resolves.toEqual({ status: "cancelled" }); |
| await expect(operation).resolves.toBe("forked"); |
| expect(operationStarted).toBe(true); |
| expect(runtime.isBusy).toBe(false); |
| }); |
|
|
| it("keeps a public turn action attached to its original slash input", async () => { |
| const { runtime, sdk, broadcasts } = createRuntime(); |
|
|
| const result = runtime.runTurnAction("/skill:review carefully", async () => { |
| sdk.emit(turnStarted()); |
| sdk.emit(turnEnded("completed")); |
| }); |
|
|
| await expect(result).resolves.toEqual({ status: "finished" }); |
| expect(streamData(broadcasts)).toContainEqual({ |
| type: "TurnBegin", |
| payload: { user_input: "/skill:review carefully" }, |
| _sessionId: "session-1", |
| }); |
| }); |
|
|
| it("converts legacy media keys when a prompt crosses the SDK boundary", async () => { |
| const { runtime, sdk } = createRuntime(); |
| const completion = runtime.prompt([ |
| { type: "text", text: "Describe these files" }, |
| { type: "image_url", image_url: { url: "data:image/png;base64,AA", id: "image-1" } }, |
| { type: "video_url", video_url: { url: "file:///workspace/demo.mp4", id: "video-1" } }, |
| ]); |
|
|
| sdk.emit(turnStarted()); |
| sdk.emit(turnEnded("completed")); |
| await completion; |
|
|
| expect(sdk.promptInputs).toEqual([ |
| [ |
| { type: "text", text: "Describe these files" }, |
| { type: "image_url", imageUrl: { url: "data:image/png;base64,AA", id: "image-1" } }, |
| { type: "video_url", videoUrl: { url: "file:///workspace/demo.mp4", id: "video-1" } }, |
| ], |
| ]); |
| }); |
|
|
| it("broadcasts assistant text when the SDK streams a text delta", () => { |
| const { sdk, broadcasts } = createRuntime(); |
|
|
| sdk.emit({ |
| type: "assistant.delta", |
| sessionId: "session-1", |
| agentId: "main", |
| turnId: 7, |
| delta: "Implemented", |
| }); |
|
|
| expect(streamData(broadcasts)).toContainEqual({ |
| type: "ContentPart", |
| payload: { type: "text", text: "Implemented" }, |
| _sessionId: "session-1", |
| }); |
| }); |
|
|
| it("broadcasts model thinking when the SDK streams a thinking delta", () => { |
| const { sdk, broadcasts } = createRuntime(); |
|
|
| sdk.emit({ |
| type: "thinking.delta", |
| sessionId: "session-1", |
| agentId: "main", |
| turnId: 7, |
| delta: "Checking the edge case", |
| }); |
|
|
| expect(streamData(broadcasts)).toContainEqual({ |
| type: "ContentPart", |
| payload: { type: "think", think: "Checking the edge case" }, |
| _sessionId: "session-1", |
| }); |
| }); |
|
|
| it("broadcasts a legacy tool call when an SDK tool starts", () => { |
| const { sdk, broadcasts } = createRuntime(); |
|
|
| sdk.emit({ |
| type: "tool.call.started", |
| sessionId: "session-1", |
| agentId: "main", |
| turnId: 7, |
| toolCallId: "tool-1", |
| name: "Read", |
| args: { path: "src/index.ts" }, |
| }); |
|
|
| expect(streamData(broadcasts)).toContainEqual({ |
| type: "ToolCall", |
| payload: { |
| type: "function", |
| id: "tool-1", |
| function: { name: "ReadFile", arguments: '{"path":"src/index.ts"}' }, |
| }, |
| _sessionId: "session-1", |
| }); |
| }); |
|
|
| it.each([ |
| ["completed", "finished"], |
| ["cancelled", "cancelled"], |
| ] as const)( |
| "emits one stream completion when a turn ends as %s", |
| async (reason, expectedStatus) => { |
| const { runtime, sdk, broadcasts } = createRuntime(); |
| const completion = runtime.prompt("hello"); |
| sdk.emit(turnStarted()); |
|
|
| sdk.emit(turnEnded(reason)); |
| sdk.emit(turnEnded(reason)); |
|
|
| await expect(completion).resolves.toEqual({ status: expectedStatus }); |
| expect( |
| streamData(broadcasts).filter( |
| (event) => |
| typeof event === "object" && |
| event !== null && |
| "type" in event && |
| event.type === "stream_complete", |
| ), |
| ).toEqual([ |
| { |
| type: "stream_complete", |
| result: { status: expectedStatus }, |
| _sessionId: "session-1", |
| }, |
| ]); |
| }, |
| ); |
|
|
| it("emits one error when a failed turn terminal is repeated", async () => { |
| const { runtime, sdk, broadcasts } = createRuntime(); |
| const completion = runtime.prompt("hello"); |
| const error = { |
| code: "provider.api_error" as const, |
| message: "Provider rejected the request", |
| retryable: true, |
| }; |
| sdk.emit(turnStarted()); |
|
|
| sdk.emit(turnEnded("failed", error)); |
| sdk.emit(turnEnded("failed", error)); |
|
|
| await expect(completion).resolves.toEqual({ status: "failed" }); |
| expect( |
| streamData(broadcasts).filter( |
| (event) => |
| typeof event === "object" && event !== null && "type" in event && event.type === "error", |
| ), |
| ).toHaveLength(1); |
| }); |
|
|
| it("suppresses the trailing SDK error when a failed terminal already reported the same error", async () => { |
| const { runtime, sdk, broadcasts } = createRuntime(); |
| const completion = runtime.prompt("hello"); |
| const error = { |
| code: "provider.api_error" as const, |
| message: "Provider rejected the request", |
| retryable: true, |
| }; |
| sdk.emit(turnStarted()); |
|
|
| sdk.emit(turnEnded("failed", error)); |
| sdk.emit({ |
| type: "error", |
| sessionId: "session-1", |
| agentId: "main", |
| ...error, |
| }); |
|
|
| await completion; |
| expect( |
| streamData(broadcasts).filter( |
| (event) => |
| typeof event === "object" && event !== null && "type" in event && event.type === "error", |
| ), |
| ).toHaveLength(1); |
| }); |
|
|
| it("reports a preflight error when SDK prompt setup throws before turn start", async () => { |
| const { runtime, sdk, broadcasts } = createRuntime(); |
| sdk.rejectNextPrompt(new Error("Unable to initialize provider")); |
|
|
| await expect(runtime.prompt("hello")).resolves.toEqual({ status: "failed" }); |
|
|
| expect(streamData(broadcasts)).toContainEqual({ |
| type: "error", |
| code: "internal", |
| message: "Internal error occurred.", |
| detail: "Unable to initialize provider", |
| phase: "preflight", |
| _sessionId: "session-1", |
| }); |
| }); |
|
|
| it("requests SDK cancellation when the active response is stopped", async () => { |
| const { runtime, sdk } = createRuntime(); |
| void runtime.prompt("hello"); |
|
|
| await runtime.cancel(); |
|
|
| expect(sdk.cancelCount()).toBe(1); |
| }); |
|
|
| it("still reaches the SDK cancel when the host lost track of active work", async () => { |
| const { runtime, sdk } = createRuntime(); |
|
|
| await runtime.cancel(); |
|
|
| expect(sdk.cancelCount()).toBe(1); |
| }); |
|
|
| it("converts legacy media keys when steering an active response", async () => { |
| const { runtime, sdk } = createRuntime(); |
| void runtime.prompt("hello"); |
|
|
| await runtime.steer([ |
| { type: "text", text: "Use this instead" }, |
| { type: "image_url", image_url: { url: "file:///workspace/new.png" } }, |
| ]); |
|
|
| expect(sdk.steerInputs).toEqual([ |
| [ |
| { type: "text", text: "Use this instead" }, |
| { type: "image_url", imageUrl: { url: "file:///workspace/new.png" } }, |
| ], |
| ]); |
| }); |
|
|
| it("echoes a successful steer into the subscribed Webview", async () => { |
| const { runtime, broadcasts } = createRuntime(); |
| runtime.subscribe("view-a"); |
|
|
| await runtime.steer("Use this instead"); |
|
|
| expect(streamData(broadcasts)).toContainEqual({ |
| type: "SteerInput", |
| payload: { user_input: "Use this instead" }, |
| _sessionId: "session-1", |
| }); |
| }); |
|
|
| it.each([ |
| ["approve", { decision: "approved" }], |
| ["approve_for_session", { decision: "approved", scope: "session" }], |
| ["reject", { decision: "rejected" }], |
| ] as const)("resolves SDK approval when the Webview responds with %s", async (response, expected) => { |
| const { runtime, sdk, broadcasts } = createRuntime(); |
| const pending = sdk.requestApproval({ |
| toolCallId: "tool-1", |
| toolName: "Bash", |
| action: "Run command", |
| display: { kind: "command", command: "pnpm test" }, |
| }); |
| const request = streamData(broadcasts).find( |
| (event) => |
| typeof event === "object" && |
| event !== null && |
| "type" in event && |
| event.type === "ApprovalRequest", |
| ) as { payload: { id: string } }; |
|
|
| expect(runtime.respondApproval(request.payload.id, response)).toBe(true); |
| await expect(pending).resolves.toEqual(expected); |
| }); |
|
|
| it("forwards SDK approval requests to the Webview in legacy yolo mode", async () => { |
| const { runtime, sdk, broadcasts } = createRuntime({ yolo: true, afk: false }); |
| const pending = sdk.requestApproval({ |
| toolCallId: "tool-yolo", |
| toolName: "Bash", |
| action: "Run command", |
| display: { kind: "command", command: "pnpm test" }, |
| }); |
| const request = streamData(broadcasts).find( |
| (event) => |
| typeof event === "object" && |
| event !== null && |
| "type" in event && |
| event.type === "ApprovalRequest", |
| ) as { payload: { id: string } }; |
|
|
| expect(runtime.respondApproval(request.payload.id, "approve")).toBe(true); |
| await expect(pending).resolves.toEqual({ decision: "approved" }); |
| }); |
|
|
| it("restores core permission when a legacy flag cannot be persisted", async () => { |
| const { runtime, sdk } = createRuntime(); |
| sdk.rejectNextMetadataUpdate(new Error("state is read-only")); |
|
|
| await expect(runtime.toggleLegacyApproval("afk")).rejects.toThrow("state is read-only"); |
|
|
| expect(sdk.setPermissions).toEqual(["auto", "manual"]); |
| expect(runtime.legacyApprovalFlags).toEqual({ yolo: false, afk: false }); |
| }); |
|
|
| it("resolves an SDK question when the Webview submits answers", async () => { |
| const { runtime, sdk, broadcasts } = createRuntime(); |
| const pending = sdk.requestQuestion({ |
| toolCallId: "question-1", |
| questions: [ |
| { |
| question: "Choose a target", |
| header: "Target", |
| options: [{ label: "Tests", description: "Run focused tests" }], |
| multiSelect: false, |
| }, |
| ], |
| }); |
| const request = streamData(broadcasts).find( |
| (event) => |
| typeof event === "object" && |
| event !== null && |
| "type" in event && |
| event.type === "QuestionRequest", |
| ) as { payload: { id: string } }; |
|
|
| expect(runtime.respondQuestion(request.payload.id, { "Choose a target": "Tests" })).toBe(true); |
| await expect(pending).resolves.toEqual({ answers: { "Choose a target": "Tests" } }); |
| }); |
|
|
| it("keeps SDK questions interactive in legacy yolo mode", async () => { |
| const { runtime, sdk, broadcasts } = createRuntime({ yolo: true, afk: false }); |
| const pending = sdk.requestQuestion({ |
| toolCallId: "question-yolo", |
| questions: [ |
| { |
| question: "Continue?", |
| options: [{ label: "Yes" }], |
| multiSelect: false, |
| }, |
| ], |
| }); |
| const request = streamData(broadcasts).find( |
| (event) => |
| typeof event === "object" && |
| event !== null && |
| "type" in event && |
| event.type === "QuestionRequest", |
| ) as { payload: { id: string } }; |
|
|
| expect(runtime.respondQuestion(request.payload.id, { "Continue?": "Yes" })).toBe(true); |
| await expect(pending).resolves.toEqual({ answers: { "Continue?": "Yes" } }); |
| }); |
|
|
| it("cancels a pending SDK approval when the session closes", async () => { |
| const { runtime, sdk } = createRuntime(); |
| const pending = sdk.requestApproval({ |
| toolCallId: "tool-1", |
| toolName: "Bash", |
| action: "Run command", |
| display: { kind: "command", command: "pnpm test" }, |
| }); |
|
|
| await runtime.close(); |
|
|
| await expect(pending).resolves.toEqual({ |
| decision: "cancelled", |
| feedback: "Session closed", |
| }); |
| }); |
|
|
| it("cancels a pending SDK question when the session closes", async () => { |
| const { runtime, sdk } = createRuntime(); |
| const pending = sdk.requestQuestion({ |
| questions: [ |
| { question: "Continue?", options: [{ label: "Yes" }], multiSelect: false }, |
| ], |
| }); |
|
|
| await runtime.close(); |
|
|
| await expect(pending).resolves.toBeNull(); |
| }); |
|
|
| it("fans out one SDK subscription to every Webview attached to the session", () => { |
| const { runtime, sdk, broadcasts } = createRuntime(); |
| runtime.subscribe("view-2"); |
|
|
| sdk.emit({ |
| type: "assistant.delta", |
| sessionId: "session-1", |
| agentId: "main", |
| turnId: 7, |
| delta: "Shared update", |
| }); |
|
|
| expect(sdk.subscriptionCount()).toBe(1); |
| expect(sdk.handlerInstallations).toEqual({ approval: 1, question: 1 }); |
| expect(broadcasts.map((record) => record.webviewId)).toEqual(["view-1", "view-2"]); |
| }); |
|
|
| it.each(["Write", "Edit"] as const)( |
| "captures the original file when %s starts with a path", |
| (name) => { |
| const { sdk, baselines } = createRuntime(); |
|
|
| sdk.emit({ |
| type: "tool.call.started", |
| sessionId: "session-1", |
| agentId: "main", |
| turnId: 7, |
| toolCallId: "tool-1", |
| name, |
| args: { path: "src/index.ts" }, |
| }); |
|
|
| expect(baselines).toEqual([ |
| { |
| session: { |
| id: "session-1", |
| workDir: "/workspace", |
| metadata: { source: "vscode-test" }, |
| }, |
| filePath: "src/index.ts", |
| webviewIds: ["view-1"], |
| }, |
| ]); |
| }, |
| ); |
|
|
| it.each([ |
| ["Read", { path: "src/index.ts" }], |
| ["Write", {}], |
| ["Edit", { path: "" }], |
| ] as const)("does not capture a baseline when %s receives non-write input %#", (name, args) => { |
| const { sdk, baselines } = createRuntime(); |
|
|
| sdk.emit({ |
| type: "tool.call.started", |
| sessionId: "session-1", |
| agentId: "main", |
| turnId: 7, |
| toolCallId: "tool-1", |
| name, |
| args, |
| }); |
|
|
| expect(baselines).toEqual([]); |
| }); |
| }); |
|
|