From 7718aec891bfd69d2cb44a49f352f21b13d186b7 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Wed, 12 Nov 2025 21:56:21 -0500 Subject: [PATCH] sync --- .opencode/command/commit.md | 1 + .opencode/opencode.json | 4 +- packages/opencode/src/server/server.ts | 1 - packages/opencode/src/session/compaction.ts | 2 - packages/opencode/src/session/lock.ts | 97 ------------ packages/opencode/src/session/prompt.ts | 156 ++++---------------- packages/opencode/src/session/revert.ts | 13 +- packages/opencode/src/tool/task.ts | 3 +- 8 files changed, 31 insertions(+), 246 deletions(-) delete mode 100644 packages/opencode/src/session/lock.ts diff --git a/.opencode/command/commit.md b/.opencode/command/commit.md index 2e3d759b6..9626f172c 100644 --- a/.opencode/command/commit.md +++ b/.opencode/command/commit.md @@ -1,5 +1,6 @@ --- description: Git commit and push +subtask: true --- commit and push diff --git a/.opencode/opencode.json b/.opencode/opencode.json index b2923f269..2ec720efb 100644 --- a/.opencode/opencode.json +++ b/.opencode/opencode.json @@ -3,9 +3,7 @@ "plugin": ["opencode-openai-codex-auth"], "provider": { "opencode": { - "options": { - "baseURL": "http://localhost:8080" - } + "options": {} } } } diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 211d72860..0ba6c9c39 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -27,7 +27,6 @@ import { Global } from "../global" import { ProjectRoute } from "./project" import { ToolRegistry } from "../tool/registry" import { zodToJsonSchema } from "zod-to-json-schema" -import { SessionLock } from "../session/lock" import { SessionPrompt } from "../session/prompt" import { SessionCompaction } from "../session/compaction" import { SessionRevert } from "../session/revert" diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index ff988845d..3b36f0329 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -13,7 +13,6 @@ import { SessionPrompt } from "./prompt" import { Flag } from "../flag/flag" import { Token } from "../util/token" import { Log } from "../util/log" -import { SessionLock } from "./lock" import { ProviderTransform } from "@/provider/transform" import { SessionRetry } from "./retry" import { Config } from "@/config/config" @@ -88,7 +87,6 @@ export namespace SessionCompaction { } export async function run(input: { sessionID: string; providerID: string; modelID: string; signal?: AbortSignal }) { - if (!input.signal) SessionLock.assertUnlocked(input.sessionID) await using lock = input.signal === undefined ? SessionLock.acquire({ sessionID: input.sessionID }) : undefined const signal = input.signal ?? lock!.signal diff --git a/packages/opencode/src/session/lock.ts b/packages/opencode/src/session/lock.ts deleted file mode 100644 index 22eb8187c..000000000 --- a/packages/opencode/src/session/lock.ts +++ /dev/null @@ -1,97 +0,0 @@ -import z from "zod" -import { Instance } from "../project/instance" -import { Log } from "../util/log" -import { NamedError } from "../util/error" - -export namespace SessionLock { - const log = Log.create({ service: "session.lock" }) - - export const LockedError = NamedError.create( - "SessionLockedError", - z.object({ - sessionID: z.string(), - message: z.string(), - }), - ) - - type LockState = { - controller: AbortController - created: number - } - - const state = Instance.state( - () => { - const locks = new Map() - return { - locks, - } - }, - async (current) => { - for (const [sessionID, lock] of current.locks) { - log.info("force abort", { sessionID }) - lock.controller.abort() - } - current.locks.clear() - }, - ) - - function get(sessionID: string) { - return state().locks.get(sessionID) - } - - function unset(input: { sessionID: string; controller: AbortController }) { - const lock = get(input.sessionID) - if (!lock) return false - if (lock.controller !== input.controller) return false - state().locks.delete(input.sessionID) - return true - } - - export function acquire(input: { sessionID: string }) { - const lock = get(input.sessionID) - if (lock) { - throw new LockedError({ - sessionID: input.sessionID, - message: `Session ${input.sessionID} is locked`, - }) - } - const controller = new AbortController() - state().locks.set(input.sessionID, { - controller, - created: Date.now(), - }) - log.info("locked", { sessionID: input.sessionID }) - return { - signal: controller.signal, - abort() { - controller.abort() - unset({ sessionID: input.sessionID, controller }) - }, - async [Symbol.dispose]() { - const removed = unset({ sessionID: input.sessionID, controller }) - if (removed) { - log.info("unlocked", { sessionID: input.sessionID }) - } - }, - } - } - - export function abort(sessionID: string) { - const lock = get(sessionID) - if (!lock) return false - log.info("abort", { sessionID }) - lock.controller.abort() - state().locks.delete(sessionID) - return true - } - - export function isLocked(sessionID: string) { - return get(sessionID) !== undefined - } - - export function assertUnlocked(sessionID: string) { - const lock = get(sessionID) - if (!lock) return - throw new LockedError({ sessionID, message: `Session ${sessionID} is locked` }) - } -} diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index f50b9df43..e1fc63e8b 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -49,14 +49,12 @@ import { $, fileURLToPath } from "bun" import { ConfigMarkdown } from "../config/markdown" import { SessionSummary } from "./summary" import { NamedError } from "@/util/error" -import { SessionLock } from "./lock" import { fn } from "@/util/fn" import { SessionRetry } from "./retry" export namespace SessionPrompt { const log = Log.create({ service: "session.prompt" }) export const OUTPUT_TOKEN_MAX = 32_000 - const MAX_RETRIES = 10 const DOOM_LOOP_THRESHOLD = 3 export const Status = z @@ -132,6 +130,19 @@ export namespace SessionPrompt { return state().status } + export function getStatus(sessionID: string) { + return ( + state().status[sessionID] ?? { + type: "idle", + } + ) + } + + export function assertNotBusy(sessionID: string) { + const status = getStatus(sessionID) + if (status?.type !== "idle") throw new Session.BusyError(sessionID) + } + export const setStatus = fn(z.object({ sessionID: Identifier.schema("session"), status: Status }), (input) => { Bus.publish(Event.Status, { sessionID: input.sessionID, status: input.status }) if (input.status.type === "idle") { @@ -1387,7 +1398,6 @@ export namespace SessionPrompt { }) export type ShellInput = z.infer export async function shell(input: ShellInput) { - using abort = SessionLock.acquire({ sessionID: input.sessionID }) const session = await Session.get(input.sessionID) if (session.revert) { SessionRevert.cleanup(session) @@ -1502,7 +1512,6 @@ export namespace SessionPrompt { const proc = spawn(shell, args, { cwd: Instance.directory, - signal: abort.signal, detached: true, stdio: ["ignore", "pipe", "pipe"], env: { @@ -1511,11 +1520,6 @@ export namespace SessionPrompt { }, }) - abort.signal.addEventListener("abort", () => { - if (!proc.pid) return - process.kill(-proc.pid) - }) - let output = "" proc.stdout?.on("data", (chunk) => { @@ -1646,132 +1650,22 @@ export namespace SessionPrompt { })() const agent = await Agent.get(agentName) - let result: MessageV2.WithParts if ((agent.mode === "subagent" && command.subtask !== false) || command.subtask === true) { - using abort = SessionLock.acquire({ sessionID: input.sessionID }) - - const userMsg: MessageV2.User = { - id: Identifier.ascending("message"), - sessionID: input.sessionID, - time: { - created: Date.now(), - }, - role: "user", - agent: agentName, - model: { - providerID: model.providerID, - modelID: model.modelID, - }, - } - await Session.updateMessage(userMsg) - const userPart: MessageV2.Part = { - type: "text", - id: Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - text: "The following tool was executed by the user", - synthetic: true, - } - await Session.updatePart(userPart) - - const assistantMsg: MessageV2.Assistant = { - id: Identifier.ascending("message"), - sessionID: input.sessionID, - parentID: userMsg.id, - mode: agentName, - cost: 0, - path: { - cwd: Instance.directory, - root: Instance.worktree, - }, - time: { - created: Date.now(), - }, - role: "assistant", - tokens: { - input: 0, - output: 0, - reasoning: 0, - cache: { read: 0, write: 0 }, - }, - modelID: model.modelID, - providerID: model.providerID, - } - await Session.updateMessage(assistantMsg) - - const args = { - description: "Consulting " + agent.name, - subagent_type: agent.name, - prompt: template, - } - const toolPart: MessageV2.ToolPart = { - type: "tool", - id: Identifier.ascending("part"), - messageID: assistantMsg.id, - sessionID: input.sessionID, - tool: "task", - callID: ulid(), - state: { - status: "running", - time: { - start: Date.now(), - }, - input: { - description: args.description, - subagent_type: args.subagent_type, - // truncate prompt to preserve context - prompt: args.prompt.length > 100 ? args.prompt.substring(0, 97) + "..." : args.prompt, - }, - }, - } - await Session.updatePart(toolPart) - - const taskResult = await TaskTool.init().then((t) => - t.execute(args, { - sessionID: input.sessionID, - abort: abort.signal, - agent: agent.name, - messageID: assistantMsg.id, - extra: {}, - metadata: async (metadata) => { - if (toolPart.state.status === "running") { - toolPart.state.metadata = metadata.metadata - toolPart.state.title = metadata.title - await Session.updatePart(toolPart) - } - }, - }), - ) - - assistantMsg.time.completed = Date.now() - await Session.updateMessage(assistantMsg) - if (toolPart.state.status === "running") { - toolPart.state = { - status: "completed", - time: { - ...toolPart.state.time, - end: Date.now(), - }, - input: toolPart.state.input, - title: "", - metadata: taskResult.metadata, - output: taskResult.output, - } - await Session.updatePart(toolPart) - } - - result = { info: assistantMsg, parts: [toolPart] } - } else { - result = (await prompt({ - sessionID: input.sessionID, - messageID: input.messageID, - model, - agent: agentName, - parts, - })) as MessageV2.WithParts + parts.push({ + type: "agent", + name: agent.name, + }) } + const result = (await prompt({ + sessionID: input.sessionID, + messageID: input.messageID, + model, + agent: agentName, + parts, + })) as MessageV2.WithParts + Bus.publish(Command.Event.Executed, { name: input.command, sessionID: input.sessionID, diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index dbf81edc7..35c7b9a60 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -7,7 +7,7 @@ import { Log } from "../util/log" import { splitWhen } from "remeda" import { Storage } from "../storage/storage" import { Bus } from "../bus" -import { SessionLock } from "./lock" +import { SessionPrompt } from "./prompt" export namespace SessionRevert { const log = Log.create({ service: "session.revert" }) @@ -20,11 +20,7 @@ export namespace SessionRevert { export type RevertInput = z.infer export async function revert(input: RevertInput) { - SessionLock.assertUnlocked(input.sessionID) - using _ = SessionLock.acquire({ - sessionID: input.sessionID, - }) - + SessionPrompt.assertNotBusy(input.sessionID) const all = await Session.messages({ sessionID: input.sessionID }) let lastUser: MessageV2.User | undefined const session = await Session.get(input.sessionID) @@ -70,10 +66,7 @@ export namespace SessionRevert { export async function unrevert(input: { sessionID: string }) { log.info("unreverting", input) - SessionLock.assertUnlocked(input.sessionID) - using _ = SessionLock.acquire({ - sessionID: input.sessionID, - }) + SessionPrompt.assertNotBusy(input.sessionID) const session = await Session.get(input.sessionID) if (!session.revert) return session if (session.revert.snapshot) await Snapshot.restore(session.revert.snapshot) diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index a5369d335..d127fba38 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -6,7 +6,6 @@ import { Bus } from "../bus" import { MessageV2 } from "../session/message-v2" import { Identifier } from "../id/id" import { Agent } from "../agent/agent" -import { SessionLock } from "../session/lock" import { SessionPrompt } from "../session/prompt" export const TaskTool = Tool.define("task", async () => { @@ -63,7 +62,7 @@ export const TaskTool = Tool.define("task", async () => { } ctx.abort.addEventListener("abort", () => { - SessionLock.abort(session.id) + SessionPrompt.cancel(session.id) }) const promptParts = await SessionPrompt.resolvePromptParts(params.prompt) const result = await SessionPrompt.prompt({