diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts index 084ccf831eec..af1fa2ac671c 100644 --- a/packages/opencode/src/lsp/client.ts +++ b/packages/opencode/src/lsp/client.ts @@ -76,6 +76,15 @@ export namespace LSPClient { uri: pathToFileURL(input.root).href, }, ]) + let alive = true + connection.onClose(() => { + l.info("connection closed") + alive = false + }) + connection.onError((err) => { + l.error("connection error", { error: err }) + }) + connection.listen() l.info("sending initialize") @@ -141,6 +150,9 @@ export namespace LSPClient { get serverID() { return input.serverID }, + get alive() { + return alive + }, get connection() { return connection }, diff --git a/packages/opencode/src/lsp/index.ts b/packages/opencode/src/lsp/index.ts index 6ea7554c0968..2d8d3f923a58 100644 --- a/packages/opencode/src/lsp/index.ts +++ b/packages/opencode/src/lsp/index.ts @@ -275,18 +275,42 @@ export namespace LSP { return false } + async function removeClient(s: Awaited>, client: LSPClient.Info) { + const idx = s.clients.indexOf(client) + if (idx !== -1) s.clients.splice(idx, 1) + s.broken.add(client.root + client.serverID) + client.shutdown().catch(() => {}) + log.info("removed dead LSP client", { + serverID: client.serverID, + root: client.root, + }) + } + export async function touchFile(input: string, waitForDiagnostics?: boolean) { log.info("touching file", { file: input }) + const s = await state() const clients = await getClients(input) await Promise.all( clients.map(async (client) => { - const wait = waitForDiagnostics ? client.waitForDiagnostics({ path: input }) : Promise.resolve() - await client.notify.open({ path: input }) - return wait + if (!client.alive) { + await removeClient(s, client) + return + } + try { + const wait = waitForDiagnostics ? client.waitForDiagnostics({ path: input }) : Promise.resolve() + await client.notify.open({ path: input }) + return wait + } catch (err: any) { + log.error("failed to touch file", { err, file: input }) + if ( + err?.message?.includes("Connection is closed") || + err?.message?.includes("connection is disposed") + ) { + await removeClient(s, client) + } + } }), - ).catch((err) => { - log.error("failed to touch file", { err, file: input }) - }) + ) } export async function diagnostics() { diff --git a/packages/opencode/src/tool/write.txt b/packages/opencode/src/tool/write.txt index 063cbb1f0387..8a849e8ba0cf 100644 --- a/packages/opencode/src/tool/write.txt +++ b/packages/opencode/src/tool/write.txt @@ -3,6 +3,7 @@ Writes a file to the local filesystem. Usage: - This tool will overwrite the existing file if there is one at the provided path. - If this is an existing file, you MUST use the Read tool first to read the file's contents. This tool will fail if you did not read the file first. -- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required. +- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required. Use the Edit tool instead of Write when only a portion of the file changed. +- When creating or writing multiple files, avoid writing more than 5 files in rapid succession. Each write triggers formatting, LSP, and snapshot operations that need time to complete. - NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User. - Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.