summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/opencode/src/file/watcher.ts3
-rw-r--r--packages/opencode/src/index.ts10
-rw-r--r--packages/opencode/src/lsp/index.ts4
-rw-r--r--packages/opencode/src/mcp/index.ts4
-rw-r--r--packages/opencode/src/project/bootstrap.ts2
-rw-r--r--packages/opencode/src/project/state.ts50
-rw-r--r--packages/opencode/src/session/prompt.ts49
7 files changed, 51 insertions, 71 deletions
diff --git a/packages/opencode/src/file/watcher.ts b/packages/opencode/src/file/watcher.ts
index d5985b582..7d190c60b 100644
--- a/packages/opencode/src/file/watcher.ts
+++ b/packages/opencode/src/file/watcher.ts
@@ -63,8 +63,7 @@ export namespace FileWatcher {
return { sub }
},
async (state) => {
- if (!state.sub) return
- await state.sub?.unsubscribe()
+ state.sub?.unsubscribe()
},
)
diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts
index 45ccd3cad..a0cce76ad 100644
--- a/packages/opencode/src/index.ts
+++ b/packages/opencode/src/index.ts
@@ -22,6 +22,8 @@ import { AttachCommand } from "./cli/cmd/attach"
import { AcpCommand } from "./cli/cmd/acp"
import { EOL } from "os"
+const cancel = new AbortController()
+
process.on("unhandledRejection", (e) => {
Log.Default.error("rejection", {
e: e instanceof Error ? e.message : e,
@@ -133,10 +135,6 @@ try {
console.error(e)
}
process.exitCode = 1
-} finally {
- // Some subprocesses don't react properly to SIGTERM and similar signals.
- // Most notably, some docker-container-based MCP servers don't handle such signals unless
- // run using `docker run --init`.
- // Explicitly exit to avoid any hanging subprocesses.
- process.exit();
}
+
+cancel.abort()
diff --git a/packages/opencode/src/lsp/index.ts b/packages/opencode/src/lsp/index.ts
index 72a9cae21..d533815fe 100644
--- a/packages/opencode/src/lsp/index.ts
+++ b/packages/opencode/src/lsp/index.ts
@@ -101,7 +101,9 @@ export namespace LSP {
}
},
async (state) => {
- await Promise.all(state.clients.map((client) => client.shutdown()))
+ for (const client of state.clients) {
+ await client.shutdown()
+ }
},
)
diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts
index b0e72b53a..fa3513bb7 100644
--- a/packages/opencode/src/mcp/index.ts
+++ b/packages/opencode/src/mcp/index.ts
@@ -145,7 +145,9 @@ export namespace MCP {
}
},
async (state) => {
- await Promise.all(Object.values(state.clients).map((client) => client.close()))
+ for (const client of Object.values(state.clients)) {
+ client.close()
+ }
},
)
diff --git a/packages/opencode/src/project/bootstrap.ts b/packages/opencode/src/project/bootstrap.ts
index 45e85fd24..c7805aa7a 100644
--- a/packages/opencode/src/project/bootstrap.ts
+++ b/packages/opencode/src/project/bootstrap.ts
@@ -11,7 +11,7 @@ export async function InstanceBootstrap() {
await Plugin.init()
Share.init()
Format.init()
- await LSP.init()
+ LSP.init()
FileWatcher.init()
File.init()
}
diff --git a/packages/opencode/src/project/state.ts b/packages/opencode/src/project/state.ts
index 6377833eb..2ffef3b39 100644
--- a/packages/opencode/src/project/state.ts
+++ b/packages/opencode/src/project/state.ts
@@ -1,26 +1,23 @@
-import { Log } from "@/util/log"
-
export namespace State {
interface Entry {
state: any
dispose?: (state: any) => Promise<void>
}
- const log = Log.create({ service: "state" })
- const recordsByKey = new Map<string, Map<any, Entry>>()
+ const entries = new Map<string, Map<any, Entry>>()
export function create<S>(root: () => string, init: () => S, dispose?: (state: Awaited<S>) => Promise<void>) {
return () => {
const key = root()
- let entries = recordsByKey.get(key)
- if (!entries) {
- entries = new Map<string, Entry>()
- recordsByKey.set(key, entries)
+ let collection = entries.get(key)
+ if (!collection) {
+ collection = new Map<string, Entry>()
+ entries.set(key, collection)
}
- const exists = entries.get(init)
+ const exists = collection.get(init)
if (exists) return exists.state as S
const state = init()
- entries.set(init, {
+ collection.set(init, {
state,
dispose,
})
@@ -29,38 +26,9 @@ export namespace State {
}
export async function dispose(key: string) {
- const entries = recordsByKey.get(key)
- if (!entries) return
-
- log.info("waiting for state disposal to complete", { key })
-
- let disposalFinished = false
-
- setTimeout(() => {
- if (!disposalFinished) {
- log.warn(
- "state disposal is taking an unusually long time - if it does not complete in a reasonable time, please report this as a bug",
- { key },
- )
- }
- }, 10000).unref()
-
- const tasks: Promise<void>[] = []
- for (const entry of entries.values()) {
+ for (const [_, entry] of entries.get(key)?.entries() ?? []) {
if (!entry.dispose) continue
-
- const task = Promise.resolve(entry.state)
- .then((state) => entry.dispose!(state))
- .catch((error) => {
- log.error("Error while disposing state:", { error, key })
- })
-
- tasks.push(task)
+ await entry.dispose(await entry.state)
}
-
- await Promise.all(tasks)
-
- disposalFinished = true
- log.info("state disposal completed", { key })
}
}
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index 184f4af85..7018978e2 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -74,22 +74,13 @@ export namespace SessionPrompt {
callback: (input: MessageV2.WithParts) => void
}[]
>()
- const pending = new Set<Promise<void>>()
-
- const track = (promise: Promise<void>) => {
- pending.add(promise)
- promise.finally(() => pending.delete(promise))
- }
return {
queued,
- pending,
- track,
}
},
async (current) => {
current.queued.clear()
- await Promise.allSettled([...current.pending])
},
)
@@ -200,6 +191,28 @@ export namespace SessionPrompt {
processor,
})
+ // const permUnsub = (() => {
+ // const handled = new Set<string>()
+ // const options = [
+ // { optionId: "allow_once", kind: "allow_once", name: "Allow once" },
+ // { optionId: "allow_always", kind: "allow_always", name: "Always allow" },
+ // { optionId: "reject_once", kind: "reject_once", name: "Reject" },
+ // ]
+ // return Bus.subscribe(Permission.Event.Updated, async (event) => {
+ // const info = event.properties
+ // if (info.sessionID !== input.sessionID) return
+ // if (handled.has(info.id)) return
+ // handled.add(info.id)
+ // const toolCallId = info.callID ?? info.id
+ // const metadata = info.metadata ?? {}
+ // // TODO: emit permission event to bus for ACP to handle
+ // Permission.respond({ sessionID: info.sessionID, permissionID: info.id, response: "reject" })
+ // })
+ // })()
+ // await using _permSub = defer(() => {
+ // permUnsub?.()
+ // })
+
const params = await Plugin.trigger(
"chat.params",
{
@@ -234,15 +247,13 @@ export namespace SessionPrompt {
step++
await processor.next(msgs.findLast((m) => m.info.role === "user")?.info.id!)
if (step === 1) {
- state().track(
- ensureTitle({
- session,
- history: msgs,
- message: userMsg,
- providerID: model.providerID,
- modelID: model.info.id,
- }),
- )
+ ensureTitle({
+ session,
+ history: msgs,
+ message: userMsg,
+ providerID: model.providerID,
+ modelID: model.info.id,
+ })
SessionSummary.summarize({
sessionID: input.sessionID,
messageID: userMsg.info.id,
@@ -1719,7 +1730,7 @@ export namespace SessionPrompt {
thinkingBudget: 0,
}
}
- await generateText({
+ generateText({
maxOutputTokens: small.info.reasoning ? 1500 : 20,
providerOptions: ProviderTransform.providerOptions(small.npm, small.providerID, options),
messages: [