mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-03 08:46:15 -04:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26d3f2f1e5 | |||
| bb84f71338 |
@@ -565,7 +565,7 @@
|
||||
"dependencies": {
|
||||
"@actions/core": "1.11.1",
|
||||
"@actions/github": "6.0.1",
|
||||
"@agentclientprotocol/sdk": "0.21.0",
|
||||
"@agentclientprotocol/sdk": "1.2.1",
|
||||
"@ai-sdk/alibaba": "1.0.17",
|
||||
"@ai-sdk/amazon-bedrock": "4.0.112",
|
||||
"@ai-sdk/anthropic": "3.0.82",
|
||||
@@ -1167,7 +1167,7 @@
|
||||
|
||||
"@adobe/css-tools": ["@adobe/css-tools@4.5.0", "", {}, "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q=="],
|
||||
|
||||
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.21.0", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-ONj+Q8qOdNQp5XbH5jnMwzT9IKZJsSN0p0lkceS4GtUtNOPVLpNzSS8gqQdGMKfBvA0ESbkL8BTaSN1Rc9miEw=="],
|
||||
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@1.2.1", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-jwYUdOQR7tc+Zfch53VL4JJyUNK/46q03uUTYb+PjECsmnNl94XFXOfYLJ8RBpMNidXd1rpOAVgb0vqD98xImA=="],
|
||||
|
||||
"@ai-sdk/alibaba": ["@ai-sdk/alibaba@1.0.17", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZbE+U5bWz2JBc5DERLowx5+TKbjGBE93LqKZAWvuEn7HOSQMraxFMZuc0ST335QZJAyfBOzh7m1mPQ+y7EaaoA=="],
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
"dependencies": {
|
||||
"@actions/core": "1.11.1",
|
||||
"@actions/github": "6.0.1",
|
||||
"@agentclientprotocol/sdk": "0.21.0",
|
||||
"@agentclientprotocol/sdk": "1.2.1",
|
||||
"@ai-sdk/alibaba": "1.0.17",
|
||||
"@ai-sdk/amazon-bedrock": "4.0.112",
|
||||
"@ai-sdk/anthropic": "3.0.82",
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
type AuthenticateRequest,
|
||||
type CancelNotification,
|
||||
type CloseSessionRequest,
|
||||
type DeleteSessionRequest,
|
||||
type ForkSessionRequest,
|
||||
type InitializeRequest,
|
||||
type ListSessionsRequest,
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
type PromptRequest,
|
||||
type ResumeSessionRequest,
|
||||
type SetSessionConfigOptionRequest,
|
||||
type SetSessionModelRequest,
|
||||
type SetSessionModeRequest,
|
||||
} from "@agentclientprotocol/sdk"
|
||||
import { Effect } from "effect"
|
||||
@@ -52,6 +52,10 @@ export class Agent implements ACPAgent {
|
||||
return run(this.service.listSessions(params))
|
||||
}
|
||||
|
||||
deleteSession(params: DeleteSessionRequest) {
|
||||
return run(this.service.deleteSession(params))
|
||||
}
|
||||
|
||||
resumeSession(params: ResumeSessionRequest) {
|
||||
return run(this.service.resumeSession(params))
|
||||
}
|
||||
@@ -72,10 +76,6 @@ export class Agent implements ACPAgent {
|
||||
return run(this.service.setSessionMode(params))
|
||||
}
|
||||
|
||||
unstable_setSessionModel(params: SetSessionModelRequest) {
|
||||
return run(this.service.setSessionModel(params))
|
||||
}
|
||||
|
||||
prompt(params: PromptRequest) {
|
||||
return run(this.service.prompt(params))
|
||||
}
|
||||
|
||||
@@ -30,7 +30,12 @@ type GlobalEventStream = {
|
||||
stream: AsyncIterable<GlobalEventEnvelope>
|
||||
}
|
||||
|
||||
export function start(input: { sdk: OpencodeClient; connection: Connection; session: ACPSession.Interface }) {
|
||||
export function start(input: {
|
||||
sdk: OpencodeClient
|
||||
connection: Connection
|
||||
session: ACPSession.Interface
|
||||
capabilities: ACPPermission.Capabilities
|
||||
}) {
|
||||
const subscription = new Subscription(input)
|
||||
subscription.start()
|
||||
return subscription
|
||||
@@ -48,6 +53,7 @@ export class Subscription {
|
||||
sdk: OpencodeClient
|
||||
connection: Connection
|
||||
session: ACPSession.Interface
|
||||
capabilities: ACPPermission.Capabilities
|
||||
},
|
||||
) {
|
||||
this.permission = new ACPPermission.Handler(input)
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Effect } from "effect"
|
||||
type PermissionEvent = Extract<Event, { type: "permission.asked" }>
|
||||
type Reply = "once" | "always" | "reject"
|
||||
type Connection = Partial<Pick<AgentSideConnection, "requestPermission" | "writeTextFile">>
|
||||
export type Capabilities = { writeTextFile: boolean }
|
||||
|
||||
const permissionOptions: PermissionOption[] = [
|
||||
{ optionId: "once", kind: "allow_once", name: "Allow once" },
|
||||
@@ -31,6 +32,7 @@ export class Handler {
|
||||
sdk: OpencodeClient
|
||||
connection: Connection
|
||||
session: ACPSession.Interface
|
||||
capabilities: Capabilities
|
||||
},
|
||||
) {}
|
||||
|
||||
@@ -99,7 +101,7 @@ export class Handler {
|
||||
private async writeProposedEdit(sessionId: string, metadata: ToolInput) {
|
||||
const filepath = stringValue(metadata.filepath)
|
||||
const diff = stringValue(metadata.diff)
|
||||
if (!filepath || !diff || !this.input.connection.writeTextFile) return
|
||||
if (!filepath || !diff || !this.input.capabilities.writeTextFile || !this.input.connection.writeTextFile) return
|
||||
|
||||
const content = (await exists(filepath)) ? await readText(filepath) : ""
|
||||
const next = applyPatch(content, diff)
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
type CancelNotification,
|
||||
type CloseSessionRequest,
|
||||
type CloseSessionResponse,
|
||||
type DeleteSessionRequest,
|
||||
type DeleteSessionResponse,
|
||||
type ForkSessionRequest,
|
||||
type ForkSessionResponse,
|
||||
type InitializeRequest,
|
||||
@@ -24,8 +26,6 @@ import {
|
||||
type SessionInfo,
|
||||
type SetSessionConfigOptionRequest,
|
||||
type SetSessionConfigOptionResponse,
|
||||
type SetSessionModelRequest,
|
||||
type SetSessionModelResponse,
|
||||
type SetSessionModeRequest,
|
||||
type SetSessionModeResponse,
|
||||
} from "@agentclientprotocol/sdk"
|
||||
@@ -58,6 +58,7 @@ export type Interface = {
|
||||
readonly newSession: (input: NewSessionRequest) => Effect.Effect<NewSessionResponse, Error>
|
||||
readonly loadSession: (input: LoadSessionRequest) => Effect.Effect<LoadSessionResponse, Error>
|
||||
readonly listSessions: (input: ListSessionsRequest) => Effect.Effect<ListSessionsResponse, Error>
|
||||
readonly deleteSession: (input: DeleteSessionRequest) => Effect.Effect<DeleteSessionResponse, Error>
|
||||
readonly resumeSession: (input: ResumeSessionRequest) => Effect.Effect<ResumeSessionResponse, Error>
|
||||
readonly closeSession: (input: CloseSessionRequest) => Effect.Effect<CloseSessionResponse, Error>
|
||||
readonly forkSession: (input: ForkSessionRequest) => Effect.Effect<ForkSessionResponse, Error>
|
||||
@@ -65,7 +66,6 @@ export type Interface = {
|
||||
input: SetSessionConfigOptionRequest,
|
||||
) => Effect.Effect<SetSessionConfigOptionResponse, Error>
|
||||
readonly setSessionMode: (input: SetSessionModeRequest) => Effect.Effect<SetSessionModeResponse, Error>
|
||||
readonly setSessionModel: (input: SetSessionModelRequest) => Effect.Effect<SetSessionModelResponse, Error>
|
||||
readonly prompt: (input: PromptRequest) => Effect.Effect<PromptResponse, Error>
|
||||
readonly cancel: (input: CancelNotification) => Effect.Effect<void, Error>
|
||||
}
|
||||
@@ -84,13 +84,15 @@ export function make(input: {
|
||||
const directoryService = input.directory ?? makeDirectoryService(input.sdk)
|
||||
const registeredMcp = new Map<string, Set<string>>()
|
||||
const sessionSnapshots = new Map<string, Directory.Snapshot>()
|
||||
const capabilities = { writeTextFile: false }
|
||||
const events = input.connection
|
||||
? ACPEvent.start({ sdk: input.sdk, connection: input.connection, session })
|
||||
? ACPEvent.start({ sdk: input.sdk, connection: input.connection, session, capabilities })
|
||||
: undefined
|
||||
if (events) input.eventSubscription?.(events)
|
||||
|
||||
const initialize = Effect.fn("ACP.initialize")(function* (params: InitializeRequest) {
|
||||
const started = performance.now()
|
||||
capabilities.writeTextFile = params.clientCapabilities?.fs?.writeTextFile === true
|
||||
const authMethod: AuthMethod = {
|
||||
description: "Run `opencode auth login` in the terminal",
|
||||
name: "Login with opencode",
|
||||
@@ -121,6 +123,7 @@ export function make(input: {
|
||||
},
|
||||
sessionCapabilities: {
|
||||
close: {},
|
||||
delete: {},
|
||||
fork: {},
|
||||
list: {},
|
||||
resume: {},
|
||||
@@ -287,6 +290,25 @@ export function make(input: {
|
||||
}
|
||||
})
|
||||
|
||||
const deleteSession = Effect.fn("ACP.deleteSession")(function* (params: DeleteSessionRequest) {
|
||||
const current = yield* session.tryGet(params.sessionId)
|
||||
yield* request(
|
||||
() =>
|
||||
input.sdk.session.delete(
|
||||
{
|
||||
sessionID: params.sessionId,
|
||||
...(current ? { directory: current.cwd } : {}),
|
||||
},
|
||||
{ throwOnError: true },
|
||||
),
|
||||
"session",
|
||||
)
|
||||
yield* session.remove(params.sessionId)
|
||||
registeredMcp.delete(params.sessionId)
|
||||
sessionSnapshots.delete(params.sessionId)
|
||||
return {}
|
||||
})
|
||||
|
||||
const resumeSession = Effect.fn("ACP.resumeSession")(function* (params: ResumeSessionRequest) {
|
||||
const snapshot = yield* directorySnapshot(params.cwd)
|
||||
yield* request(
|
||||
@@ -462,33 +484,18 @@ export function make(input: {
|
||||
return {}
|
||||
})
|
||||
|
||||
const setSessionModel = Effect.fn("ACP.setSessionModel")(function* (params: SetSessionModelRequest) {
|
||||
const current = yield* session.get(params.sessionId)
|
||||
const snapshot = yield* configSnapshot(current)
|
||||
const selected = yield* parseSelectedModel(snapshot, params.modelId)
|
||||
yield* session
|
||||
.setVariant(
|
||||
params.sessionId,
|
||||
Directory.variants(snapshot, selected.model)
|
||||
? (selected.variant ?? selectVariant(snapshot, selected.model))
|
||||
: undefined,
|
||||
)
|
||||
.pipe(Effect.andThen(session.setModel(params.sessionId, selected.model)))
|
||||
return {}
|
||||
})
|
||||
|
||||
return {
|
||||
initialize,
|
||||
authenticate,
|
||||
newSession,
|
||||
loadSession,
|
||||
listSessions,
|
||||
deleteSession,
|
||||
resumeSession,
|
||||
closeSession,
|
||||
forkSession,
|
||||
setSessionConfigOption,
|
||||
setSessionMode,
|
||||
setSessionModel,
|
||||
prompt: Effect.fn("ACP.prompt")(function* (params: PromptRequest) {
|
||||
const current = yield* session.get(params.sessionId)
|
||||
const snapshot = yield* directorySnapshot(current.cwd)
|
||||
@@ -521,7 +528,7 @@ export function make(input: {
|
||||
"session",
|
||||
)
|
||||
yield* sendUsageUpdate(input.usage, input.sdk, input.connection, current.id, current.cwd)
|
||||
return yield* promptResponse(response.info, params.messageId)
|
||||
return yield* promptResponse(response.info)
|
||||
}
|
||||
|
||||
const known = snapshot.availableCommands.find((item) => item.name === command.name)
|
||||
@@ -543,7 +550,7 @@ export function make(input: {
|
||||
"session",
|
||||
)
|
||||
yield* sendUsageUpdate(input.usage, input.sdk, input.connection, current.id, current.cwd)
|
||||
return yield* promptResponse(response.info, params.messageId)
|
||||
return yield* promptResponse(response.info)
|
||||
}
|
||||
|
||||
if (command.name === "compact") {
|
||||
@@ -563,7 +570,7 @@ export function make(input: {
|
||||
}
|
||||
|
||||
yield* sendUsageUpdate(input.usage, input.sdk, input.connection, current.id, current.cwd)
|
||||
return yield* promptResponse(undefined, params.messageId)
|
||||
return yield* promptResponse(undefined)
|
||||
}),
|
||||
cancel,
|
||||
}
|
||||
@@ -813,22 +820,17 @@ function detectSlashCommand(parts: ReturnType<typeof promptContentToParts>) {
|
||||
return { name, args: rest.join(" ").trim() }
|
||||
}
|
||||
|
||||
const promptResponse = Effect.fn("ACP.promptResponse")(function* (
|
||||
info: AssistantInfo,
|
||||
messageId: string | null | undefined,
|
||||
) {
|
||||
const promptResponse = Effect.fn("ACP.promptResponse")(function* (info: AssistantInfo) {
|
||||
if (!info?.error) {
|
||||
return {
|
||||
stopReason: "end_turn" as const,
|
||||
...(info ? { usage: UsageService.buildUsage(info) } : {}),
|
||||
...(messageId ? { userMessageId: messageId } : {}),
|
||||
_meta: {},
|
||||
}
|
||||
}
|
||||
|
||||
const base = {
|
||||
usage: UsageService.buildUsage(info),
|
||||
...(messageId ? { userMessageId: messageId } : {}),
|
||||
_meta: {},
|
||||
}
|
||||
|
||||
@@ -1004,6 +1006,7 @@ function mcpRegistrationKey(name: string, config: ReturnType<typeof mcpConfig>)
|
||||
|
||||
function mcpConfig(server: McpServer) {
|
||||
if ("type" in server) {
|
||||
if (server.type === "acp") throw new Error("MCP-over-ACP is not supported")
|
||||
return {
|
||||
type: "remote" as const,
|
||||
url: server.url,
|
||||
|
||||
@@ -108,7 +108,12 @@ function createHarness(messages: Record<string, SessionMessageResponse> = {}) {
|
||||
},
|
||||
} satisfies Pick<AgentSideConnection, "sessionUpdate">
|
||||
const session = makeSessionService()
|
||||
const subscription = new ACPEvent.Subscription({ sdk, connection, session })
|
||||
const subscription = new ACPEvent.Subscription({
|
||||
sdk,
|
||||
connection,
|
||||
session,
|
||||
capabilities: { writeTextFile: false },
|
||||
})
|
||||
|
||||
return { calls, connection, events, sdk, session, subscription, updates }
|
||||
}
|
||||
|
||||
@@ -46,10 +46,12 @@ function makeSessionService() {
|
||||
function createHarness(
|
||||
requestPermission: (params: RequestPermissionRequest) => Promise<RequestPermissionResponse> = () =>
|
||||
Promise.resolve({ outcome: { outcome: "selected", optionId: "once" } }),
|
||||
writeTextFile = false,
|
||||
) {
|
||||
const replies: PermissionReplyParams[] = []
|
||||
const requests: RequestPermissionRequest[] = []
|
||||
const updates: SessionUpdateParams[] = []
|
||||
const writes: Parameters<AgentSideConnection["writeTextFile"]>[0][] = []
|
||||
const session = makeSessionService()
|
||||
const sdk = {
|
||||
permission: {
|
||||
@@ -71,10 +73,19 @@ function createHarness(
|
||||
updates.push(params)
|
||||
return Promise.resolve()
|
||||
},
|
||||
} satisfies Pick<AgentSideConnection, "requestPermission" | "sessionUpdate">
|
||||
const subscription = new ACPEvent.Subscription({ sdk, connection, session })
|
||||
writeTextFile: (params: Parameters<AgentSideConnection["writeTextFile"]>[0]) => {
|
||||
writes.push(params)
|
||||
return Promise.resolve({})
|
||||
},
|
||||
} satisfies Pick<AgentSideConnection, "requestPermission" | "sessionUpdate" | "writeTextFile">
|
||||
const subscription = new ACPEvent.Subscription({
|
||||
sdk,
|
||||
connection,
|
||||
session,
|
||||
capabilities: { writeTextFile },
|
||||
})
|
||||
|
||||
return { connection, replies, requests, sdk, session, subscription, updates }
|
||||
return { connection, replies, requests, sdk, session, subscription, updates, writes }
|
||||
}
|
||||
|
||||
async function createSession(session: ACPSession.Interface, sessionId: string, cwd = "/workspace") {
|
||||
@@ -242,6 +253,27 @@ describe("acp permissions", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("syncs proposed edits only when the client advertised writeTextFile", async () => {
|
||||
const filepath = await tempFile("sync.ts", "before\n")
|
||||
const metadata = {
|
||||
filepath,
|
||||
diff: createTwoFilesPatch(filepath, filepath, "before\n", "after\n"),
|
||||
}
|
||||
const unsupported = createHarness(undefined, false)
|
||||
const supported = createHarness(undefined, true)
|
||||
await createSession(unsupported.session, "ses_unsupported")
|
||||
await createSession(supported.session, "ses_supported")
|
||||
|
||||
unsupported.subscription.handle(
|
||||
permissionAsked("ses_unsupported", "perm_unsupported", { permission: "edit", metadata }),
|
||||
)
|
||||
supported.subscription.handle(permissionAsked("ses_supported", "perm_supported", { permission: "edit", metadata }))
|
||||
await pollUntil(() => unsupported.replies.length === 1 && supported.replies.length === 1, "edits were not replied")
|
||||
|
||||
expect(unsupported.writes).toEqual([])
|
||||
expect(supported.writes).toEqual([{ sessionId: "ses_supported", path: filepath, content: "after\n" }])
|
||||
})
|
||||
|
||||
it("includes per-file diff blocks and locations for apply_patch permission metadata", async () => {
|
||||
const first = await tempFile("first.ts", "one\n")
|
||||
const second = await tempFile("second.ts", "alpha\n")
|
||||
|
||||
@@ -152,6 +152,7 @@ describe("ACP service sessions", () => {
|
||||
const updates: SessionNotification[] = []
|
||||
const mcpAdds: string[] = []
|
||||
const aborts: string[] = []
|
||||
const deletes: string[] = []
|
||||
const forks: string[] = []
|
||||
const prompts: unknown[] = []
|
||||
const commands: unknown[] = []
|
||||
@@ -196,6 +197,10 @@ describe("ACP service sessions", () => {
|
||||
data: input.directory ? sessions.filter((session) => session.directory === input.directory) : sessions,
|
||||
}),
|
||||
messages: () => Promise.resolve({ data: messages }),
|
||||
delete: (input: { sessionID: string }) => {
|
||||
deletes.push(input.sessionID)
|
||||
return Promise.resolve({ data: true })
|
||||
},
|
||||
prompt:
|
||||
options?.prompt ??
|
||||
((input: unknown) => {
|
||||
@@ -268,6 +273,7 @@ describe("ACP service sessions", () => {
|
||||
updates,
|
||||
mcpAdds,
|
||||
aborts,
|
||||
deletes,
|
||||
forks,
|
||||
prompts,
|
||||
commands,
|
||||
@@ -382,6 +388,16 @@ describe("ACP service sessions", () => {
|
||||
expect(listed.sessions[0]?.cwd).toBe("/workspace")
|
||||
})
|
||||
|
||||
it("deletes sessions from backing and local storage", async () => {
|
||||
const { service, deletes } = makeService()
|
||||
const created = await Effect.runPromise(service.newSession({ cwd: "/workspace", mcpServers: [] }))
|
||||
|
||||
expect(await Effect.runPromise(service.deleteSession({ sessionId: created.sessionId }))).toEqual({})
|
||||
expect(deletes).toEqual([created.sessionId])
|
||||
const listed = await Effect.runPromise(service.listSessions({ cwd: "/workspace" }))
|
||||
expect(listed.sessions.some((item) => item.sessionId === created.sessionId)).toBe(false)
|
||||
})
|
||||
|
||||
it("lists all sessions with next cursor when the first page is full", async () => {
|
||||
const { service } = makeService()
|
||||
const first = await Effect.runPromise(service.listSessions({}))
|
||||
@@ -987,7 +1003,6 @@ describe("ACP service sessions", () => {
|
||||
const result = await Effect.runPromise(
|
||||
service.prompt({
|
||||
sessionId: session.sessionId,
|
||||
messageId: "00000000-0000-4000-8000-000000000001",
|
||||
prompt: [{ type: "text", text: "hello" }],
|
||||
}),
|
||||
)
|
||||
@@ -1012,7 +1027,6 @@ describe("ACP service sessions", () => {
|
||||
cachedWriteTokens: 13,
|
||||
totalTokens: 171,
|
||||
},
|
||||
userMessageId: "00000000-0000-4000-8000-000000000001",
|
||||
_meta: {},
|
||||
})
|
||||
expect(usageUpdates).toEqual([session.sessionId])
|
||||
|
||||
@@ -18,6 +18,7 @@ describe("opencode acp initialize/auth subprocess", () => {
|
||||
expect(initialized.agentCapabilities?.mcpCapabilities?.sse).toBe(true)
|
||||
expect(initialized.agentCapabilities?.loadSession).toBe(true)
|
||||
expect(initialized.agentCapabilities?.sessionCapabilities?.close).toEqual({})
|
||||
expect(initialized.agentCapabilities?.sessionCapabilities?.delete).toEqual({})
|
||||
expect(initialized.agentCapabilities?.sessionCapabilities?.fork).toEqual({})
|
||||
expect(initialized.agentCapabilities?.sessionCapabilities?.list).toEqual({})
|
||||
expect(initialized.agentCapabilities?.sessionCapabilities?.resume).toEqual({})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import type {
|
||||
CloseSessionResponse,
|
||||
DeleteSessionResponse,
|
||||
ListSessionsResponse,
|
||||
LoadSessionResponse,
|
||||
ResumeSessionResponse,
|
||||
@@ -82,6 +83,25 @@ describe("opencode acp lifecycle subprocess", () => {
|
||||
60_000,
|
||||
)
|
||||
|
||||
cliIt.live(
|
||||
"delete capability and delete request",
|
||||
({ home, llm, opencode }) =>
|
||||
Effect.gen(function* () {
|
||||
const acp = yield* createAcpClient(
|
||||
{ opencode },
|
||||
{ OPENCODE_CONFIG_CONTENT: JSON.stringify(verifierConfig(llm.url)) },
|
||||
)
|
||||
const initialized = yield* initialize(acp)
|
||||
expect(initialized.agentCapabilities?.sessionCapabilities?.delete).toEqual({})
|
||||
const session = yield* newSession(acp, home)
|
||||
|
||||
expectOk(yield* acp.request<DeleteSessionResponse>("session/delete", { sessionId: session.sessionId }))
|
||||
const listed = expectOk(yield* acp.request<ListSessionsResponse>("session/list", { cwd: home }))
|
||||
expect(listed.sessions.some((item) => item.sessionId === session.sessionId)).toBe(false)
|
||||
}),
|
||||
60_000,
|
||||
)
|
||||
|
||||
cliIt.live(
|
||||
"resume capability advertisement",
|
||||
({ opencode }) =>
|
||||
|
||||
Reference in New Issue
Block a user