refactor: remove todo tool (#35989)
This commit is contained in:
parent
d4155f2906
commit
7feefb697f
250 changed files with 237 additions and 4755 deletions
File diff suppressed because one or more lines are too long
|
|
@ -526,7 +526,6 @@ describe("LocationServiceMap", () => {
|
|||
"shell",
|
||||
"skill",
|
||||
"subagent",
|
||||
"todowrite",
|
||||
"webfetch",
|
||||
"websearch",
|
||||
"write",
|
||||
|
|
@ -559,7 +558,6 @@ describe("LocationServiceMap", () => {
|
|||
"shell",
|
||||
"skill",
|
||||
"subagent",
|
||||
"todowrite",
|
||||
"webfetch",
|
||||
"websearch",
|
||||
"write",
|
||||
|
|
@ -577,7 +575,6 @@ describe("LocationServiceMap", () => {
|
|||
"shell",
|
||||
"skill",
|
||||
"subagent",
|
||||
"todowrite",
|
||||
"webfetch",
|
||||
"websearch",
|
||||
"write",
|
||||
|
|
|
|||
|
|
@ -182,12 +182,12 @@ describe("PermissionV2", () => {
|
|||
const agents = yield* AgentV2.Service
|
||||
yield* agents.transform((editor) =>
|
||||
editor.update(AgentV2.ID.make("build"), (agent) => {
|
||||
agent.permissions = [{ action: "todowrite", resource: "*", effect: "allow" }]
|
||||
agent.permissions = [{ action: "custom", resource: "*", effect: "allow" }]
|
||||
}),
|
||||
)
|
||||
|
||||
const service = yield* PermissionV2.Service
|
||||
expect(yield* service.ask(assertion({ action: "todowrite", resources: ["*"] }))).toEqual({
|
||||
expect(yield* service.ask(assertion({ action: "custom", resources: ["*"] }))).toEqual({
|
||||
id: PermissionV2.ID.create("per_test"),
|
||||
effect: "allow",
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,94 +0,0 @@
|
|||
import { describe, expect } from "bun:test"
|
||||
import { asc } from "drizzle-orm"
|
||||
import { Effect } from "effect"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { Project } from "@opencode-ai/core/project"
|
||||
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { SessionTable, TodoTable } from "@opencode-ai/core/session/sql"
|
||||
import { SessionTodo } from "@opencode-ai/core/session/todo"
|
||||
import { testEffect } from "./lib/effect"
|
||||
|
||||
const it = testEffect(AppNodeBuilder.build(LayerNode.group([Database.node, EventV2.node, SessionTodo.node])))
|
||||
const sessionID = SessionV2.ID.make("ses_todo_test")
|
||||
|
||||
const setup = Effect.gen(function* () {
|
||||
const { db } = yield* Database.Service
|
||||
yield* db
|
||||
.insert(ProjectTable)
|
||||
.values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
yield* db
|
||||
.insert(SessionTable)
|
||||
.values({
|
||||
id: sessionID,
|
||||
project_id: Project.ID.global,
|
||||
slug: "todo",
|
||||
directory: "/project",
|
||||
title: "todo",
|
||||
version: "test",
|
||||
})
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
})
|
||||
|
||||
describe("SessionTodo", () => {
|
||||
it.effect("replaces persisted todos in order and publishes updates", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const { db } = yield* Database.Service
|
||||
const events = yield* EventV2.Service
|
||||
const todos = yield* SessionTodo.Service
|
||||
const published = new Array<EventV2.Payload>()
|
||||
const unsubscribe = yield* events.listen((event) =>
|
||||
Effect.sync(() => {
|
||||
if (event.type === SessionTodo.Event.Updated.type) published.push(event)
|
||||
}),
|
||||
)
|
||||
yield* Effect.addFinalizer(() => unsubscribe)
|
||||
|
||||
yield* todos.update({
|
||||
sessionID,
|
||||
todos: [
|
||||
{ content: "second", status: "pending", priority: "low" },
|
||||
{ content: "first", status: "in_progress", priority: "high" },
|
||||
],
|
||||
})
|
||||
expect(yield* todos.get(sessionID)).toEqual([
|
||||
{ content: "second", status: "pending", priority: "low" },
|
||||
{ content: "first", status: "in_progress", priority: "high" },
|
||||
])
|
||||
expect(
|
||||
(yield* db.select().from(TodoTable).orderBy(asc(TodoTable.position)).all().pipe(Effect.orDie)).map((row) => ({
|
||||
content: row.content,
|
||||
position: row.position,
|
||||
})),
|
||||
).toEqual([
|
||||
{ content: "second", position: 0 },
|
||||
{ content: "first", position: 1 },
|
||||
])
|
||||
|
||||
yield* todos.update({ sessionID, todos: [{ content: "replacement", status: "completed", priority: "medium" }] })
|
||||
expect(yield* todos.get(sessionID)).toEqual([{ content: "replacement", status: "completed", priority: "medium" }])
|
||||
|
||||
yield* todos.update({ sessionID, todos: [] })
|
||||
expect(yield* todos.get(sessionID)).toEqual([])
|
||||
expect(published.map((event) => event.data)).toEqual([
|
||||
{
|
||||
sessionID,
|
||||
todos: [
|
||||
{ content: "second", status: "pending", priority: "low" },
|
||||
{ content: "first", status: "in_progress", priority: "high" },
|
||||
],
|
||||
},
|
||||
{ sessionID, todos: [{ content: "replacement", status: "completed", priority: "medium" }] },
|
||||
{ sessionID, todos: [] },
|
||||
])
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
@ -24,7 +24,6 @@ import { LLM } from "@opencode-ai/schema/llm"
|
|||
import { Permission } from "@opencode-ai/schema/permission"
|
||||
import { Pty } from "@opencode-ai/schema/pty"
|
||||
import { Reference } from "@opencode-ai/schema/reference"
|
||||
import { SessionTodo } from "@opencode-ai/schema/session-todo"
|
||||
import { Skill } from "@opencode-ai/schema/skill"
|
||||
import { AbsolutePath, DateTimeUtcFromMillis, optional, statics } from "@opencode-ai/schema/schema"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
|
|
@ -46,7 +45,6 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||
coreReference,
|
||||
coreSessionInput,
|
||||
coreSessionMessage,
|
||||
coreSessionTodo,
|
||||
coreSkill,
|
||||
coreV2Schema,
|
||||
coreSchema,
|
||||
|
|
@ -67,7 +65,6 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||
import("@opencode-ai/core/reference"),
|
||||
import("@opencode-ai/core/session/input"),
|
||||
import("@opencode-ai/core/session/message"),
|
||||
import("@opencode-ai/core/session/todo"),
|
||||
import("@opencode-ai/core/skill"),
|
||||
import("@opencode-ai/core/v2-schema"),
|
||||
import("@opencode-ai/core/schema"),
|
||||
|
|
@ -160,8 +157,6 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||
[coreSessionMessage.Assistant, SessionMessage.Assistant],
|
||||
[coreSessionMessage.Compaction, SessionMessage.Compaction],
|
||||
[coreSessionMessage.Info, SessionMessage.Info],
|
||||
[coreSessionTodo.Info, SessionTodo.Info],
|
||||
[coreSessionTodo.Event, SessionTodo.Event],
|
||||
[coreSkill.DirectorySource, Skill.DirectorySource],
|
||||
[coreSkill.UrlSource, Skill.UrlSource],
|
||||
[coreSkill.EmbeddedSource, Skill.EmbeddedSource],
|
||||
|
|
|
|||
|
|
@ -1,142 +0,0 @@
|
|||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||
import { Project } from "@opencode-ai/core/project"
|
||||
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { SessionTable } from "@opencode-ai/core/session/sql"
|
||||
import { SessionTodo } from "@opencode-ai/core/session/todo"
|
||||
import { TodoWriteTool } from "@opencode-ai/core/tool/todowrite"
|
||||
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||
import { testEffect } from "./lib/effect"
|
||||
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||
import { toolIdentity, executeTool, registerToolPlugin, settleTool, toolDefinitions } from "./lib/tool"
|
||||
|
||||
const todoWriteToolNode = makeLocationNode({
|
||||
name: "test/todowrite-tool-plugin",
|
||||
layer: Layer.effectDiscard(registerToolPlugin(TodoWriteTool.Plugin)),
|
||||
deps: [ToolRegistry.toolsNode, PermissionV2.node, SessionTodo.node],
|
||||
})
|
||||
|
||||
const sessionID = SessionV2.ID.make("ses_todowrite_tool_test")
|
||||
const assertions: PermissionV2.AssertInput[] = []
|
||||
let deny = false
|
||||
|
||||
const permission = Layer.succeed(
|
||||
PermissionV2.Service,
|
||||
PermissionV2.Service.of({
|
||||
assert: (input) =>
|
||||
Effect.sync(() => assertions.push(input)).pipe(
|
||||
Effect.andThen(
|
||||
deny
|
||||
? Effect.fail(
|
||||
new PermissionV2.BlockedError({
|
||||
rules: [],
|
||||
permission: input.action,
|
||||
resources: input.resources,
|
||||
}),
|
||||
)
|
||||
: Effect.void,
|
||||
),
|
||||
),
|
||||
ask: () => Effect.die("unused"),
|
||||
reply: () => Effect.die("unused"),
|
||||
get: () => Effect.die("unused"),
|
||||
forSession: () => Effect.die("unused"),
|
||||
list: () => Effect.die("unused"),
|
||||
}),
|
||||
)
|
||||
const it = testEffect(
|
||||
AppNodeBuilder.build(
|
||||
LayerNode.group([
|
||||
Database.node,
|
||||
EventV2.node,
|
||||
SessionTodo.node,
|
||||
ToolRegistry.node,
|
||||
ToolRegistry.toolsNode,
|
||||
todoWriteToolNode,
|
||||
]),
|
||||
[
|
||||
[PermissionV2.node, permission],
|
||||
[ToolOutputStore.node, ToolOutputStore.nodeWithoutConfig],
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
const setup = Effect.gen(function* () {
|
||||
assertions.length = 0
|
||||
deny = false
|
||||
const { db } = yield* Database.Service
|
||||
yield* db
|
||||
.insert(ProjectTable)
|
||||
.values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
yield* db
|
||||
.insert(SessionTable)
|
||||
.values({
|
||||
id: sessionID,
|
||||
project_id: Project.ID.global,
|
||||
slug: "todowrite",
|
||||
directory: "/project",
|
||||
title: "todowrite",
|
||||
version: "test",
|
||||
})
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
})
|
||||
|
||||
const call = (todos: ReadonlyArray<SessionTodo.Info>, id = "call-todowrite") => ({
|
||||
sessionID,
|
||||
...toolIdentity,
|
||||
call: { type: "tool-call" as const, id, name: TodoWriteTool.name, input: { todos } },
|
||||
})
|
||||
|
||||
describe("TodoWriteTool", () => {
|
||||
it.effect("registers, approves the wildcard resource, persists todos, and returns typed output", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const registry = yield* ToolRegistry.Service
|
||||
const service = yield* SessionTodo.Service
|
||||
const todoList: ReadonlyArray<SessionTodo.Info> = [
|
||||
{ content: "Implement slice", status: "in_progress", priority: "high" },
|
||||
]
|
||||
|
||||
expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual([TodoWriteTool.name])
|
||||
expect(yield* settleTool(registry, call(todoList))).toEqual({
|
||||
result: { type: "text", value: JSON.stringify(todoList, null, 2) },
|
||||
output: {
|
||||
structured: { todos: todoList },
|
||||
content: [{ type: "text", text: JSON.stringify(todoList, null, 2) }],
|
||||
},
|
||||
})
|
||||
expect(assertions).toMatchObject([{ sessionID, action: "todowrite", resources: ["*"], save: ["*"] }])
|
||||
expect(yield* service.get(sessionID)).toEqual(todoList)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("does not update persisted todos when permission is denied", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const registry = yield* ToolRegistry.Service
|
||||
const service = yield* SessionTodo.Service
|
||||
yield* service.update({ sessionID, todos: [{ content: "keep", status: "pending", priority: "low" }] })
|
||||
deny = true
|
||||
|
||||
expect(
|
||||
yield* executeTool(registry, call([{ content: "blocked", status: "completed", priority: "high" }])),
|
||||
).toEqual({
|
||||
type: "error",
|
||||
value: "Unable to update todos",
|
||||
})
|
||||
expect(yield* service.get(sessionID)).toEqual([{ content: "keep", status: "pending", priority: "low" }])
|
||||
expect(assertions).toMatchObject([{ sessionID, action: "todowrite", resources: ["*"], save: ["*"] }])
|
||||
}),
|
||||
)
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue