summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-03-21 19:55:07 -0400
committerGitHub <[email protected]>2026-03-21 23:55:07 +0000
commit10a3d6c54e403ec68c0ef150c2f109462199df23 (patch)
tree02c967860d14f6f338a61999ba65a18e20c41bc9
parent832b8e252e8e7d0a2d321e0291f468716a26577c (diff)
downloadopencode-10a3d6c54e403ec68c0ef150c2f109462199df23.tar.gz
opencode-10a3d6c54e403ec68c0ef150c2f109462199df23.zip
effectify SessionStatus service (#18565)
-rw-r--r--packages/opencode/specs/effect-migration.md1
-rw-r--r--packages/opencode/src/server/routes/session.ts4
-rw-r--r--packages/opencode/src/session/processor.ts6
-rw-r--r--packages/opencode/src/session/prompt.ts10
-rw-r--r--packages/opencode/src/session/status.ts76
5 files changed, 60 insertions, 37 deletions
diff --git a/packages/opencode/specs/effect-migration.md b/packages/opencode/specs/effect-migration.md
index 80c906fcc..2a5b289ca 100644
--- a/packages/opencode/specs/effect-migration.md
+++ b/packages/opencode/specs/effect-migration.md
@@ -123,6 +123,7 @@ Fully migrated (single namespace, InstanceState where needed, flattened facade):
- [x] `Truncate` — `tool/truncate.ts`
- [x] `Vcs` — `project/vcs.ts`
- [x] `Discovery` — `skill/discovery.ts`
+- [x] `SessionStatus`
Still open and likely worth migrating:
diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts
index e399636ad..abc820c2a 100644
--- a/packages/opencode/src/server/routes/session.ts
+++ b/packages/opencode/src/server/routes/session.ts
@@ -88,8 +88,8 @@ export const SessionRoutes = lazy(() =>
},
}),
async (c) => {
- const result = SessionStatus.list()
- return c.json(result)
+ const result = await SessionStatus.list()
+ return c.json(Object.fromEntries(result))
},
)
.get(
diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts
index c3a572f5b..ccb09e71a 100644
--- a/packages/opencode/src/session/processor.ts
+++ b/packages/opencode/src/session/processor.ts
@@ -57,7 +57,7 @@ export namespace SessionProcessor {
input.abort.throwIfAborted()
switch (value.type) {
case "start":
- SessionStatus.set(input.sessionID, { type: "busy" })
+ await SessionStatus.set(input.sessionID, { type: "busy" })
break
case "reasoning-start":
@@ -368,7 +368,7 @@ export namespace SessionProcessor {
if (retry !== undefined) {
attempt++
const delay = SessionRetry.delay(attempt, error.name === "APIError" ? error : undefined)
- SessionStatus.set(input.sessionID, {
+ await SessionStatus.set(input.sessionID, {
type: "retry",
attempt,
message: retry,
@@ -382,7 +382,7 @@ export namespace SessionProcessor {
sessionID: input.assistantMessage.sessionID,
error: input.assistantMessage.error,
})
- SessionStatus.set(input.sessionID, { type: "idle" })
+ await SessionStatus.set(input.sessionID, { type: "idle" })
}
}
if (snapshot) {
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index 5625c571c..8a1081748 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -257,17 +257,17 @@ export namespace SessionPrompt {
return s[sessionID].abort.signal
}
- export function cancel(sessionID: SessionID) {
+ export async function cancel(sessionID: SessionID) {
log.info("cancel", { sessionID })
const s = state()
const match = s[sessionID]
if (!match) {
- SessionStatus.set(sessionID, { type: "idle" })
+ await SessionStatus.set(sessionID, { type: "idle" })
return
}
match.abort.abort()
delete s[sessionID]
- SessionStatus.set(sessionID, { type: "idle" })
+ await SessionStatus.set(sessionID, { type: "idle" })
return
}
@@ -286,7 +286,7 @@ export namespace SessionPrompt {
})
}
- using _ = defer(() => cancel(sessionID))
+ await using _ = defer(() => cancel(sessionID))
// Structured output state
// Note: On session resumption, state is reset but outputFormat is preserved
@@ -296,7 +296,7 @@ export namespace SessionPrompt {
let step = 0
const session = await Session.get(sessionID)
while (true) {
- SessionStatus.set(sessionID, { type: "busy" })
+ await SessionStatus.set(sessionID, { type: "busy" })
log.info("loop", { step, sessionID })
if (abort.aborted) break
let msgs = await MessageV2.filterCompacted(MessageV2.stream(sessionID))
diff --git a/packages/opencode/src/session/status.ts b/packages/opencode/src/session/status.ts
index 57e793985..462d5ded4 100644
--- a/packages/opencode/src/session/status.ts
+++ b/packages/opencode/src/session/status.ts
@@ -1,7 +1,9 @@
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
-import { Instance } from "@/project/instance"
+import { InstanceState } from "@/effect/instance-state"
+import { makeRunPromise } from "@/effect/run-service"
import { SessionID } from "./schema"
+import { Effect, Layer, ServiceMap } from "effect"
import z from "zod"
export namespace SessionStatus {
@@ -42,36 +44,56 @@ export namespace SessionStatus {
),
}
- const state = Instance.state(() => {
- const data: Record<string, Info> = {}
- return data
- })
+ export interface Interface {
+ readonly get: (sessionID: SessionID) => Effect.Effect<Info>
+ readonly list: () => Effect.Effect<Map<SessionID, Info>>
+ readonly set: (sessionID: SessionID, status: Info) => Effect.Effect<void>
+ }
+
+ export class Service extends ServiceMap.Service<Service, Interface>()("@opencode/SessionStatus") {}
+
+ export const layer = Layer.effect(
+ Service,
+ Effect.gen(function* () {
+ const state = yield* InstanceState.make(
+ Effect.fn("SessionStatus.state")(() => Effect.succeed(new Map<SessionID, Info>())),
+ )
+
+ const get = Effect.fn("SessionStatus.get")(function* (sessionID: SessionID) {
+ const data = yield* InstanceState.get(state)
+ return data.get(sessionID) ?? { type: "idle" as const }
+ })
+
+ const list = Effect.fn("SessionStatus.list")(function* () {
+ return new Map(yield* InstanceState.get(state))
+ })
- export function get(sessionID: SessionID) {
- return (
- state()[sessionID] ?? {
- type: "idle",
- }
- )
+ const set = Effect.fn("SessionStatus.set")(function* (sessionID: SessionID, status: Info) {
+ const data = yield* InstanceState.get(state)
+ yield* Effect.promise(() => Bus.publish(Event.Status, { sessionID, status }))
+ if (status.type === "idle") {
+ yield* Effect.promise(() => Bus.publish(Event.Idle, { sessionID }))
+ data.delete(sessionID)
+ return
+ }
+ data.set(sessionID, status)
+ })
+
+ return Service.of({ get, list, set })
+ }),
+ )
+
+ const runPromise = makeRunPromise(Service, layer)
+
+ export async function get(sessionID: SessionID) {
+ return runPromise((svc) => svc.get(sessionID))
}
- export function list() {
- return state()
+ export async function list() {
+ return runPromise((svc) => svc.list())
}
- export function set(sessionID: SessionID, status: Info) {
- Bus.publish(Event.Status, {
- sessionID,
- status,
- })
- if (status.type === "idle") {
- // deprecated
- Bus.publish(Event.Idle, {
- sessionID,
- })
- delete state()[sessionID]
- return
- }
- state()[sessionID] = status
+ export async function set(sessionID: SessionID, status: Info) {
+ return runPromise((svc) => svc.set(sessionID, status))
}
}