refactor: break SessionPrompt/TaskTool cycle via ctx injection (#21948)

This commit is contained in:
Kit Langton
2026-04-10 19:36:13 -04:00
committed by GitHub
parent d72ddd71fa
commit d9d5a0615e
3 changed files with 47 additions and 67 deletions

View File

@@ -6,10 +6,10 @@ import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
import { Instance } from "../../src/project/instance"
import { Session } from "../../src/session"
import { MessageV2 } from "../../src/session/message-v2"
import { SessionPrompt } from "../../src/session/prompt"
import type { SessionPrompt } from "../../src/session/prompt"
import { MessageID, PartID } from "../../src/session/schema"
import { ModelID, ProviderID } from "../../src/provider/schema"
import { TaskTool } from "../../src/tool/task"
import { TaskTool, type TaskPromptOps } from "../../src/tool/task"
import { ToolRegistry } from "../../src/tool/registry"
import { provideTmpdirInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
@@ -62,6 +62,17 @@ const seed = Effect.fn("TaskToolTest.seed")(function* (title = "Pinned") {
return { chat, assistant }
})
function stubOps(opts?: { onPrompt?: (input: SessionPrompt.PromptInput) => void; text?: string }): TaskPromptOps {
return {
cancel() {},
resolvePromptParts: async (template) => [{ type: "text", text: template }],
prompt: async (input) => {
opts?.onPrompt?.(input)
return reply(input, opts?.text ?? "done")
},
}
}
function reply(input: Parameters<typeof SessionPrompt.prompt>[0], text: string): MessageV2.WithParts {
const id = MessageID.ascending()
return {
@@ -180,21 +191,8 @@ describe("tool.task", () => {
const child = yield* sessions.create({ parentID: chat.id, title: "Existing child" })
const tool = yield* TaskTool
const def = yield* Effect.promise(() => tool.init())
const resolve = SessionPrompt.resolvePromptParts
const prompt = SessionPrompt.prompt
let seen: Parameters<typeof SessionPrompt.prompt>[0] | undefined
SessionPrompt.resolvePromptParts = async (template) => [{ type: "text", text: template }]
SessionPrompt.prompt = async (input) => {
seen = input
return reply(input, "resumed")
}
yield* Effect.addFinalizer(() =>
Effect.sync(() => {
SessionPrompt.resolvePromptParts = resolve
SessionPrompt.prompt = prompt
}),
)
let seen: SessionPrompt.PromptInput | undefined
const promptOps = stubOps({ text: "resumed", onPrompt: (input) => (seen = input) })
const result = yield* Effect.promise(() =>
def.execute(
@@ -209,6 +207,7 @@ describe("tool.task", () => {
messageID: assistant.id,
agent: "build",
abort: new AbortController().signal,
extra: { promptOps },
messages: [],
metadata() {},
ask: async () => {},
@@ -232,20 +231,10 @@ describe("tool.task", () => {
const { chat, assistant } = yield* seed()
const tool = yield* TaskTool
const def = yield* Effect.promise(() => tool.init())
const resolve = SessionPrompt.resolvePromptParts
const prompt = SessionPrompt.prompt
const calls: unknown[] = []
const promptOps = stubOps()
SessionPrompt.resolvePromptParts = async (template) => [{ type: "text", text: template }]
SessionPrompt.prompt = async (input) => reply(input, "done")
yield* Effect.addFinalizer(() =>
Effect.sync(() => {
SessionPrompt.resolvePromptParts = resolve
SessionPrompt.prompt = prompt
}),
)
const exec = (extra?: { bypassAgentCheck?: boolean }) =>
const exec = (extra?: Record<string, any>) =>
Effect.promise(() =>
def.execute(
{
@@ -258,7 +247,7 @@ describe("tool.task", () => {
messageID: assistant.id,
agent: "build",
abort: new AbortController().signal,
extra,
extra: { promptOps, ...extra },
messages: [],
metadata() {},
ask: async (input) => {
@@ -292,21 +281,8 @@ describe("tool.task", () => {
const { chat, assistant } = yield* seed()
const tool = yield* TaskTool
const def = yield* Effect.promise(() => tool.init())
const resolve = SessionPrompt.resolvePromptParts
const prompt = SessionPrompt.prompt
let seen: Parameters<typeof SessionPrompt.prompt>[0] | undefined
SessionPrompt.resolvePromptParts = async (template) => [{ type: "text", text: template }]
SessionPrompt.prompt = async (input) => {
seen = input
return reply(input, "created")
}
yield* Effect.addFinalizer(() =>
Effect.sync(() => {
SessionPrompt.resolvePromptParts = resolve
SessionPrompt.prompt = prompt
}),
)
let seen: SessionPrompt.PromptInput | undefined
const promptOps = stubOps({ text: "created", onPrompt: (input) => (seen = input) })
const result = yield* Effect.promise(() =>
def.execute(
@@ -321,6 +297,7 @@ describe("tool.task", () => {
messageID: assistant.id,
agent: "build",
abort: new AbortController().signal,
extra: { promptOps },
messages: [],
metadata() {},
ask: async () => {},
@@ -346,21 +323,8 @@ describe("tool.task", () => {
const { chat, assistant } = yield* seed()
const tool = yield* TaskTool
const def = yield* Effect.promise(() => tool.init())
const resolve = SessionPrompt.resolvePromptParts
const prompt = SessionPrompt.prompt
let seen: Parameters<typeof SessionPrompt.prompt>[0] | undefined
SessionPrompt.resolvePromptParts = async (template) => [{ type: "text", text: template }]
SessionPrompt.prompt = async (input) => {
seen = input
return reply(input, "done")
}
yield* Effect.addFinalizer(() =>
Effect.sync(() => {
SessionPrompt.resolvePromptParts = resolve
SessionPrompt.prompt = prompt
}),
)
let seen: SessionPrompt.PromptInput | undefined
const promptOps = stubOps({ onPrompt: (input) => (seen = input) })
const result = yield* Effect.promise(() =>
def.execute(
@@ -374,6 +338,7 @@ describe("tool.task", () => {
messageID: assistant.id,
agent: "build",
abort: new AbortController().signal,
extra: { promptOps },
messages: [],
metadata() {},
ask: async () => {},