refactor(effect): extract session run state service (#21744)

This commit is contained in:
Kit Langton
2026-04-09 16:03:40 -04:00
committed by GitHub
parent 3199383eef
commit 10441efad1
9 changed files with 184 additions and 111 deletions

View File

@@ -5,6 +5,7 @@ import { Session } from "../../src/session"
import { ModelID, ProviderID } from "../../src/provider/schema"
import { MessageID, PartID, type SessionID } from "../../src/session/schema"
import { SessionPrompt } from "../../src/session/prompt"
import { SessionRunState } from "../../src/session/run-state"
import { Log } from "../../src/util/log"
import { tmpdir } from "../fixture/fixture"
@@ -64,7 +65,7 @@ describe("session action routes", () => {
fn: async () => {
const session = await Session.create({})
const msg = await user(session.id, "hello")
const busy = spyOn(SessionPrompt, "assertNotBusy").mockRejectedValue(new Session.BusyError(session.id))
const busy = spyOn(SessionRunState, "assertNotBusy").mockRejectedValue(new Session.BusyError(session.id))
const remove = spyOn(Session, "removeMessage").mockResolvedValue(msg.id)
const app = Server.Default().app

View File

@@ -25,6 +25,7 @@ import { SessionCompaction } from "../../src/session/compaction"
import { Instruction } from "../../src/session/instruction"
import { SessionProcessor } from "../../src/session/processor"
import { SessionPrompt } from "../../src/session/prompt"
import { SessionRunState } from "../../src/session/run-state"
import { MessageID, PartID, SessionID } from "../../src/session/schema"
import { SessionStatus } from "../../src/session/status"
import { Shell } from "../../src/shell/shell"
@@ -143,6 +144,7 @@ const filetime = Layer.succeed(
)
const status = SessionStatus.layer.pipe(Layer.provideMerge(Bus.layer))
const run = SessionRunState.layer.pipe(Layer.provide(status))
const infra = Layer.mergeAll(NodeFileSystem.layer, CrossSpawnSpawner.defaultLayer)
function makeHttp() {
const deps = Layer.mergeAll(
@@ -174,6 +176,7 @@ function makeHttp() {
return Layer.mergeAll(
TestLLMServer.layer,
SessionPrompt.layer.pipe(
Layer.provideMerge(run),
Layer.provideMerge(compact),
Layer.provideMerge(proc),
Layer.provideMerge(registry),
@@ -300,9 +303,10 @@ const addSubtask = (sessionID: SessionID, messageID: MessageID, model = ref) =>
const boot = Effect.fn("test.boot")(function* (input?: { title?: string }) {
const prompt = yield* SessionPrompt.Service
const run = yield* SessionRunState.Service
const sessions = yield* Session.Service
const chat = yield* sessions.create(input ?? { title: "Pinned" })
return { prompt, sessions, chat }
return { prompt, run, sessions, chat }
})
// Loop semantics
@@ -800,7 +804,7 @@ it.live("concurrent loop callers get same result", () =>
provideTmpdirInstance(
(dir) =>
Effect.gen(function* () {
const { prompt, chat } = yield* boot()
const { prompt, run, chat } = yield* boot()
yield* seed(chat.id, { finish: "stop" })
const [a, b] = yield* Effect.all([prompt.loop({ sessionID: chat.id }), prompt.loop({ sessionID: chat.id })], {
@@ -809,7 +813,7 @@ it.live("concurrent loop callers get same result", () =>
expect(a.info.id).toBe(b.info.id)
expect(a.info.role).toBe("assistant")
yield* prompt.assertNotBusy(chat.id)
yield* run.assertNotBusy(chat.id)
}),
{ git: true },
),
@@ -913,6 +917,7 @@ it.live(
provideTmpdirServer(
Effect.fnUntraced(function* ({ llm }) {
const prompt = yield* SessionPrompt.Service
const run = yield* SessionRunState.Service
const sessions = yield* Session.Service
yield* llm.hang
@@ -922,7 +927,7 @@ it.live(
const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild)
yield* llm.wait(1)
const exit = yield* prompt.assertNotBusy(chat.id).pipe(Effect.exit)
const exit = yield* run.assertNotBusy(chat.id).pipe(Effect.exit)
expect(Exit.isFailure(exit)).toBe(true)
if (Exit.isFailure(exit)) {
expect(Cause.squash(exit.cause)).toBeInstanceOf(Session.BusyError)
@@ -940,11 +945,11 @@ it.live("assertNotBusy succeeds when idle", () =>
provideTmpdirInstance(
(dir) =>
Effect.gen(function* () {
const prompt = yield* SessionPrompt.Service
const run = yield* SessionRunState.Service
const sessions = yield* Session.Service
const chat = yield* sessions.create({})
const exit = yield* prompt.assertNotBusy(chat.id).pipe(Effect.exit)
const exit = yield* run.assertNotBusy(chat.id).pipe(Effect.exit)
expect(Exit.isSuccess(exit)).toBe(true)
}),
{ git: true },
@@ -985,7 +990,7 @@ unix("shell captures stdout and stderr in completed tool output", () =>
provideTmpdirInstance(
(dir) =>
Effect.gen(function* () {
const { prompt, chat } = yield* boot()
const { prompt, run, chat } = yield* boot()
const result = yield* prompt.shell({
sessionID: chat.id,
agent: "build",
@@ -1000,7 +1005,7 @@ unix("shell captures stdout and stderr in completed tool output", () =>
expect(tool.state.output).toContain("err")
expect(tool.state.metadata.output).toContain("out")
expect(tool.state.metadata.output).toContain("err")
yield* prompt.assertNotBusy(chat.id)
yield* run.assertNotBusy(chat.id)
}),
{ git: true, config: cfg },
),
@@ -1010,7 +1015,7 @@ unix("shell completes a fast command on the preferred shell", () =>
provideTmpdirInstance(
(dir) =>
Effect.gen(function* () {
const { prompt, chat } = yield* boot()
const { prompt, run, chat } = yield* boot()
const result = yield* prompt.shell({
sessionID: chat.id,
agent: "build",
@@ -1024,7 +1029,7 @@ unix("shell completes a fast command on the preferred shell", () =>
expect(tool.state.input.command).toBe("pwd")
expect(tool.state.output).toContain(dir)
expect(tool.state.metadata.output).toContain(dir)
yield* prompt.assertNotBusy(chat.id)
yield* run.assertNotBusy(chat.id)
}),
{ git: true, config: cfg },
),
@@ -1034,7 +1039,7 @@ unix("shell lists files from the project directory", () =>
provideTmpdirInstance(
(dir) =>
Effect.gen(function* () {
const { prompt, chat } = yield* boot()
const { prompt, run, chat } = yield* boot()
yield* Effect.promise(() => Bun.write(path.join(dir, "README.md"), "# e2e\n"))
const result = yield* prompt.shell({
@@ -1050,7 +1055,7 @@ unix("shell lists files from the project directory", () =>
expect(tool.state.input.command).toBe("command ls")
expect(tool.state.output).toContain("README.md")
expect(tool.state.metadata.output).toContain("README.md")
yield* prompt.assertNotBusy(chat.id)
yield* run.assertNotBusy(chat.id)
}),
{ git: true, config: cfg },
),
@@ -1060,7 +1065,7 @@ unix("shell captures stderr from a failing command", () =>
provideTmpdirInstance(
(dir) =>
Effect.gen(function* () {
const { prompt, chat } = yield* boot()
const { prompt, run, chat } = yield* boot()
const result = yield* prompt.shell({
sessionID: chat.id,
agent: "build",
@@ -1073,7 +1078,7 @@ unix("shell captures stderr from a failing command", () =>
expect(tool.state.output).toContain("not found")
expect(tool.state.metadata.output).toContain("not found")
yield* prompt.assertNotBusy(chat.id)
yield* run.assertNotBusy(chat.id)
}),
{ git: true, config: cfg },
),
@@ -1198,7 +1203,7 @@ unix(
provideTmpdirInstance(
(dir) =>
Effect.gen(function* () {
const { prompt, chat } = yield* boot()
const { prompt, run, chat } = yield* boot()
const sh = yield* prompt
.shell({ sessionID: chat.id, agent: "build", command: "sleep 30" })
@@ -1209,7 +1214,7 @@ unix(
const status = yield* SessionStatus.Service
expect((yield* status.get(chat.id)).type).toBe("idle")
const busy = yield* prompt.assertNotBusy(chat.id).pipe(Effect.exit)
const busy = yield* run.assertNotBusy(chat.id).pipe(Effect.exit)
expect(Exit.isSuccess(busy)).toBe(true)
const exit = yield* Fiber.await(sh)

View File

@@ -43,6 +43,7 @@ import { Todo } from "../../src/session/todo"
import { SessionCompaction } from "../../src/session/compaction"
import { Instruction } from "../../src/session/instruction"
import { SessionProcessor } from "../../src/session/processor"
import { SessionRunState } from "../../src/session/run-state"
import { SessionStatus } from "../../src/session/status"
import { Shell } from "../../src/shell/shell"
import { Snapshot } from "../../src/snapshot"
@@ -107,6 +108,7 @@ const filetime = Layer.succeed(
)
const status = SessionStatus.layer.pipe(Layer.provideMerge(Bus.layer))
const run = SessionRunState.layer.pipe(Layer.provide(status))
const infra = Layer.mergeAll(NodeFileSystem.layer, CrossSpawnSpawner.defaultLayer)
function makeHttp() {
@@ -139,6 +141,7 @@ function makeHttp() {
return Layer.mergeAll(
TestLLMServer.layer,
SessionPrompt.layer.pipe(
Layer.provideMerge(run),
Layer.provideMerge(compact),
Layer.provideMerge(proc),
Layer.provideMerge(registry),