mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-14 17:23:22 +00:00
refactor: remove todo tool (#35989)
This commit is contained in:
parent
d4155f2906
commit
7feefb697f
250 changed files with 237 additions and 4755 deletions
|
|
@ -116,7 +116,6 @@ it.instance("explore agent denies edit and write", () =>
|
|||
expect(explore?.mode).toBe("subagent")
|
||||
expect(evalPerm(explore, "edit")).toBe("deny")
|
||||
expect(evalPerm(explore, "write")).toBe("deny")
|
||||
expect(evalPerm(explore, "todowrite")).toBe("deny")
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
@ -160,16 +159,6 @@ it.instance(
|
|||
},
|
||||
)
|
||||
|
||||
it.instance("general agent denies todo tools", () =>
|
||||
Effect.gen(function* () {
|
||||
const general = yield* load((svc) => svc.get("general"))
|
||||
expect(general).toBeDefined()
|
||||
expect(general?.mode).toBe("subagent")
|
||||
expect(general?.hidden).toBeUndefined()
|
||||
expect(evalPerm(general, "todowrite")).toBe("deny")
|
||||
}),
|
||||
)
|
||||
|
||||
it.instance("compaction agent denies all permissions", () =>
|
||||
Effect.gen(function* () {
|
||||
const compaction = yield* load((svc) => svc.get("compaction"))
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ describe("extractResponseText", () => {
|
|||
})
|
||||
|
||||
test("returns text even when tool parts follow", () => {
|
||||
const parts = [createTextPart("I'll help with that."), createToolPart("todowrite", "3 todos")]
|
||||
const parts = [createTextPart("I'll help with that."), createToolPart("read", "src/index.ts")]
|
||||
expect(extractResponseText(parts)).toBe("I'll help with that.")
|
||||
})
|
||||
|
||||
|
|
@ -103,8 +103,7 @@ describe("extractResponseText", () => {
|
|||
})
|
||||
|
||||
test("returns null for tool-only response (signals summary needed)", () => {
|
||||
// This is the exact scenario from the bug report - todowrite with no text
|
||||
const parts = [createToolPart("todowrite", "8 todos")]
|
||||
const parts = [createToolPart("read", "src/index.ts")]
|
||||
expect(extractResponseText(parts)).toBeNull()
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -519,8 +519,8 @@ Options:
|
|||
--description what the agent should do [string]
|
||||
--mode agent mode [string] [choices: "all", "primary", "subagent"]
|
||||
--permissions, --tools comma-separated list of permissions to allow (default: all).
|
||||
Available: "bash, read, edit, glob, grep, webfetch, task, todowrite,
|
||||
websearch, lsp, skill" [string]
|
||||
Available: "bash, read, edit, glob, grep, webfetch, task, websearch,
|
||||
lsp, skill" [string]
|
||||
-m, --model model to use in the format of provider/model [string]"
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -321,50 +321,8 @@ test("holds markdown code blocks until final commit and keeps newline ownership"
|
|||
}
|
||||
})
|
||||
|
||||
test("renders todo and question summaries without boilerplate footer copy", async () => {
|
||||
test("renders question summaries without boilerplate footer copy", async () => {
|
||||
const cases = [
|
||||
{
|
||||
title: "# Todos",
|
||||
include: [
|
||||
"[✓] List files under `run/`",
|
||||
"[•] Count functions in each `run/` file",
|
||||
"[ ] Mark each tracking item complete",
|
||||
],
|
||||
exclude: ["Updating", "todos completed"],
|
||||
start: toolCommit({
|
||||
tool: "todowrite",
|
||||
phase: "start",
|
||||
toolState: "running",
|
||||
state: {
|
||||
status: "running",
|
||||
input: {
|
||||
todos: [
|
||||
{ status: "completed", content: "List files under `run/`" },
|
||||
{ status: "in_progress", content: "Count functions in each `run/` file" },
|
||||
{ status: "pending", content: "Mark each tracking item complete" },
|
||||
],
|
||||
},
|
||||
time: { start: 1 },
|
||||
},
|
||||
}),
|
||||
final: toolCommit({
|
||||
tool: "todowrite",
|
||||
phase: "final",
|
||||
toolState: "completed",
|
||||
state: {
|
||||
status: "completed",
|
||||
input: {
|
||||
todos: [
|
||||
{ status: "completed", content: "List files under `run/`" },
|
||||
{ status: "in_progress", content: "Count functions in each `run/` file" },
|
||||
{ status: "pending", content: "Mark each tracking item complete" },
|
||||
],
|
||||
},
|
||||
metadata: {},
|
||||
time: { start: 1, end: 4 },
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "# Questions",
|
||||
include: ["What should I work on in the codebase next?", "Bug fix"],
|
||||
|
|
|
|||
|
|
@ -220,7 +220,6 @@ export default {
|
|||
api.kv.set(options.kv_key, "stored")
|
||||
const kv_after = api.kv.get(options.kv_key, "missing")
|
||||
const diff = api.state.session.diff(options.session_id)
|
||||
const todo = api.state.session.todo(options.session_id)
|
||||
const lsp = api.state.lsp()
|
||||
const mcp = api.state.mcp()
|
||||
const depth_before = api.ui.dialog.depth
|
||||
|
|
@ -263,8 +262,6 @@ export default {
|
|||
kv_ready: api.kv.ready,
|
||||
diff_count: diff.length,
|
||||
diff_file: diff[0]?.file,
|
||||
todo_count: todo.length,
|
||||
todo_first: todo[0]?.content,
|
||||
lsp_count: lsp.length,
|
||||
mcp_count: mcp.length,
|
||||
mcp_first: mcp[0]?.name,
|
||||
|
|
@ -513,10 +510,6 @@ export default {
|
|||
if (sessionID !== "ses_test") return []
|
||||
return [{ file: "src/app.ts", additions: 3, deletions: 1 }]
|
||||
},
|
||||
todo(sessionID) {
|
||||
if (sessionID !== "ses_test") return []
|
||||
return [{ content: "ship it", status: "pending" }]
|
||||
},
|
||||
},
|
||||
lsp() {
|
||||
return [{ id: "ts", root: "/tmp/project", status: "connected" }]
|
||||
|
|
@ -867,8 +860,6 @@ describe("tui.plugin.loader", () => {
|
|||
expect(data.local.kv_ready).toBe(true)
|
||||
expect(data.local.diff_count).toBe(1)
|
||||
expect(data.local.diff_file).toBe("src/app.ts")
|
||||
expect(data.local.todo_count).toBe(1)
|
||||
expect(data.local.todo_first).toBe("ship it")
|
||||
expect(data.local.lsp_count).toBe(1)
|
||||
expect(data.local.mcp_count).toBe(1)
|
||||
expect(data.local.mcp_first).toBe("github")
|
||||
|
|
|
|||
|
|
@ -1307,7 +1307,6 @@ it.instance("permission config preserves user key order", () =>
|
|||
write: "ask",
|
||||
external_directory: "ask",
|
||||
read: "allow",
|
||||
todowrite: "allow",
|
||||
"thoughts_*": "allow",
|
||||
"reasoning_model_*": "allow",
|
||||
"tools_*": "allow",
|
||||
|
|
@ -1322,7 +1321,6 @@ it.instance("permission config preserves user key order", () =>
|
|||
"write",
|
||||
"external_directory",
|
||||
"read",
|
||||
"todowrite",
|
||||
"thoughts_*",
|
||||
"reasoning_model_*",
|
||||
"tools_*",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { describe, expect, test } from "bun:test"
|
|||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||
import { Agent } from "@opencode-ai/schema"
|
||||
import { EventManifest as SchemaEventManifest } from "@opencode-ai/schema/event-manifest"
|
||||
import { Todo } from "@/session/todo"
|
||||
import { EventManifest } from "@/event-manifest"
|
||||
|
||||
describe("public event manifest", () => {
|
||||
|
|
@ -10,10 +9,9 @@ describe("public event manifest", () => {
|
|||
expect(EventManifest.Definitions).toBe(SchemaEventManifest.Definitions)
|
||||
expect(EventManifest.Latest).toBe(SchemaEventManifest.Latest)
|
||||
expect(EventManifest.Durable).toBe(SchemaEventManifest.Durable)
|
||||
expect(Array.from(EventManifest.Latest.keys()).filter((type) => type !== "agent.updated").length).toBe(101)
|
||||
expect(Array.from(EventManifest.Latest.keys()).filter((type) => type !== "agent.updated").length).toBe(108)
|
||||
expect(EventManifest.Latest.get("session.next.step.ended")).toBe(SessionEvent.Step.Ended)
|
||||
expect(EventManifest.Latest.get("agent.updated")).toBe(Agent.Event.Updated)
|
||||
expect(EventManifest.Latest.get("todo.updated")).toBe(Todo.Event.Updated)
|
||||
expect(EventManifest.Latest.has("ide.installed")).toBe(false)
|
||||
expect(EventManifest.Latest.has("server.connected")).toBe(true)
|
||||
expect(EventManifest.Latest.has("global.disposed")).toBe(true)
|
||||
|
|
|
|||
|
|
@ -316,7 +316,6 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
|
|||
count: opts.state?.session?.count ?? (() => 0),
|
||||
get: opts.state?.session?.get ?? (() => undefined),
|
||||
diff: opts.state?.session?.diff ?? (() => []),
|
||||
todo: opts.state?.session?.todo ?? (() => []),
|
||||
messages: opts.state?.session?.messages ?? (() => []),
|
||||
status: opts.state?.session?.status ?? (() => undefined),
|
||||
permission: opts.state?.session?.permission ?? (() => []),
|
||||
|
|
|
|||
|
|
@ -1326,23 +1326,6 @@ const scenarios: Scenario[] = [
|
|||
"children should include seeded child",
|
||||
)
|
||||
}),
|
||||
http.protected
|
||||
.get("/session/{sessionID}/todo", "session.todo")
|
||||
.seeded((ctx) =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* ctx.session({ title: "Todo session" })
|
||||
const todos = [{ content: "cover session todo", status: "pending" as const, priority: "high" as const }]
|
||||
yield* ctx.todos(session.id, todos)
|
||||
return { session, todos }
|
||||
}),
|
||||
)
|
||||
.at((ctx) => ({
|
||||
path: route("/session/{sessionID}/todo", { sessionID: ctx.state.session.id }),
|
||||
headers: ctx.headers(),
|
||||
}))
|
||||
.json(200, (body, ctx) => {
|
||||
check(stable(body) === stable(ctx.state.todos), "todos should match seeded state")
|
||||
}),
|
||||
http.protected
|
||||
.get("/session/{sessionID}/diff", "session.diff")
|
||||
.seeded((ctx) => ctx.session({ title: "Diff session" }))
|
||||
|
|
|
|||
|
|
@ -176,7 +176,6 @@ function withContext<A, E>(
|
|||
}),
|
||||
messages: (sessionID) =>
|
||||
run(modules.Session.Service.use((svc) => svc.messages({ sessionID }).pipe(Effect.orDie))),
|
||||
todos: (sessionID, todos) => run(modules.Todo.Service.use((svc) => svc.update({ sessionID, todos }))),
|
||||
worktree: (input) => run(modules.Worktree.Service.use((svc) => svc.create(input).pipe(Effect.orDie))),
|
||||
worktreeRemove: (directory) =>
|
||||
run(modules.Worktree.Service.use((svc) => svc.remove({ directory })).pipe(Effect.ignore)),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ export type Runtime = {
|
|||
InstanceRef: (typeof import("../../../src/effect/instance-ref"))["InstanceRef"]
|
||||
InstanceStore: (typeof import("../../../src/project/instance-store"))["InstanceStore"]
|
||||
Session: (typeof import("../../../src/session/session"))["Session"]
|
||||
Todo: (typeof import("../../../src/session/todo"))["Todo"]
|
||||
Worktree: (typeof import("../../../src/worktree"))["Worktree"]
|
||||
Project: (typeof import("../../../src/project/project"))["Project"]
|
||||
Tui: typeof import("../../../src/server/shared/tui-control")
|
||||
|
|
@ -26,7 +25,6 @@ export function runtime() {
|
|||
const instanceRef = await import("../../../src/effect/instance-ref")
|
||||
const instanceStore = await import("../../../src/project/instance-store")
|
||||
const session = await import("../../../src/session/session")
|
||||
const todo = await import("../../../src/session/todo")
|
||||
const worktree = await import("../../../src/worktree")
|
||||
const project = await import("../../../src/project/project")
|
||||
const tui = await import("../../../src/server/shared/tui-control")
|
||||
|
|
@ -40,7 +38,6 @@ export function runtime() {
|
|||
InstanceRef: instanceRef.InstanceRef,
|
||||
InstanceStore: instanceStore.InstanceStore,
|
||||
Session: session.Session,
|
||||
Todo: todo.Todo,
|
||||
Worktree: worktree.Worktree,
|
||||
Project: project.Project,
|
||||
Tui: tui,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ export type ScenarioContext = {
|
|||
project: () => Effect.Effect<Project.Info>
|
||||
message: (sessionID: SessionID, input?: { text?: string }) => Effect.Effect<MessageSeed>
|
||||
messages: (sessionID: SessionID) => Effect.Effect<SessionV1.WithParts[]>
|
||||
todos: (sessionID: SessionID, todos: TodoInfo[]) => Effect.Effect<void>
|
||||
worktree: (input?: { name?: string }) => Effect.Effect<Worktree.Info>
|
||||
worktreeRemove: (directory: string) => Effect.Effect<void>
|
||||
llmText: (value: string) => Effect.Effect<void>
|
||||
|
|
@ -119,9 +118,4 @@ export type Result =
|
|||
| { status: "skip"; scenario: TodoScenario }
|
||||
|
||||
export type SessionInfo = { id: SessionID; title: string; parentID?: SessionID }
|
||||
export type TodoInfo = {
|
||||
content: string
|
||||
status: "pending" | "in_progress" | "completed" | "cancelled"
|
||||
priority: "high" | "medium" | "low"
|
||||
}
|
||||
export type MessageSeed = { info: SessionV1.User; part: SessionV1.TextPart }
|
||||
|
|
|
|||
|
|
@ -577,7 +577,6 @@ describe("HttpApi SDK", () => {
|
|||
const roots = yield* capture(() => sdk.session.list({ roots: true, limit: 10 }))
|
||||
const all = yield* capture(() => sdk.session.list({ roots: false, limit: 10 }))
|
||||
const children = yield* capture(() => sdk.session.children({ sessionID: parentID }))
|
||||
const todo = yield* capture(() => sdk.session.todo({ sessionID: parentID }))
|
||||
const status = yield* capture(() => sdk.session.status())
|
||||
const messages = yield* capture(() => sdk.session.messages({ sessionID: parentID }))
|
||||
const missingGet = yield* capture(() => sdk.session.get({ sessionID: "ses_missing" }))
|
||||
|
|
@ -597,7 +596,6 @@ describe("HttpApi SDK", () => {
|
|||
roots,
|
||||
all,
|
||||
children,
|
||||
todo,
|
||||
status,
|
||||
messages,
|
||||
missingGet,
|
||||
|
|
@ -611,7 +609,6 @@ describe("HttpApi SDK", () => {
|
|||
rootTitles: sessionTitles(roots.data),
|
||||
allTitles: sessionTitles(all.data),
|
||||
childCount: array(children.data).length,
|
||||
todoCount: array(todo.data).length,
|
||||
messageCount: array(messages.data).length,
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -272,10 +272,6 @@ describe("session HttpApi", () => {
|
|||
expect(children.status).toBe(404)
|
||||
expect(yield* responseJson(children)).toEqual(missingSessionBody)
|
||||
|
||||
const todo = yield* request(pathFor(SessionPaths.todo, { sessionID: missingSession }), { headers })
|
||||
expect(todo.status).toBe(404)
|
||||
expect(yield* responseJson(todo)).toEqual(missingSessionBody)
|
||||
|
||||
const messages = yield* request(pathFor(SessionPaths.messages, { sessionID: missingSession }), { headers })
|
||||
expect(messages.status).toBe(404)
|
||||
expect(yield* responseJson(messages)).toEqual(missingSessionBody)
|
||||
|
|
@ -344,10 +340,6 @@ describe("session HttpApi", () => {
|
|||
})).map((item) => item.id),
|
||||
).toEqual([child.id])
|
||||
|
||||
expect(
|
||||
yield* requestJson<unknown[]>(pathFor(SessionPaths.todo, { sessionID: parent.id }), { headers }),
|
||||
).toEqual([])
|
||||
|
||||
expect(
|
||||
yield* requestJson<unknown[]>(pathFor(SessionPaths.diff, { sessionID: parent.id }), { headers }),
|
||||
).toEqual([])
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import { Git } from "../../src/git"
|
|||
import { Image } from "../../src/image/image"
|
||||
|
||||
import { Question } from "../../src/question"
|
||||
import { Todo } from "../../src/session/todo"
|
||||
import { Session } from "@/session/session"
|
||||
import { SessionMessageTable } from "@opencode-ai/core/session/sql"
|
||||
import { LLM } from "../../src/session/llm"
|
||||
|
|
@ -191,7 +190,6 @@ const promptRoot = LayerNode.group([
|
|||
Database.node,
|
||||
EventV2Bridge.node,
|
||||
Question.node,
|
||||
Todo.node,
|
||||
ToolRegistry.node,
|
||||
Skill.node,
|
||||
Git.node,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { SessionPrompt } from "../../src/session/prompt"
|
|||
import { SessionRevert } from "../../src/session/revert"
|
||||
import { SessionStatus } from "../../src/session/status"
|
||||
import { SessionSummary } from "../../src/session/summary"
|
||||
import { Todo } from "../../src/session/todo"
|
||||
import { SessionID, MessageID, PartID } from "../../src/session/schema"
|
||||
import { ProjectV2 } from "@opencode-ai/core/project"
|
||||
import { WorkspaceV2 } from "@opencode-ai/core/workspace"
|
||||
|
|
@ -252,15 +251,6 @@ describe("SessionStatus.Info", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("Todo.Info", () => {
|
||||
const decode = decodeUnknown(Todo.Info)
|
||||
|
||||
test("three-field round-trip", () => {
|
||||
const input = Todo.Info.make({ content: "do a thing", status: "pending", priority: "high" })
|
||||
expect(decode(input)).toEqual(input)
|
||||
})
|
||||
})
|
||||
|
||||
describe("SessionPrompt input schemas", () => {
|
||||
test("LoopInput is just sessionID", () => {
|
||||
const decode = decodeUnknown(SessionPrompt.LoopInput)
|
||||
|
|
|
|||
|
|
@ -335,44 +335,6 @@ exports[`tool parameters JSON Schema (wire shape) task 1`] = `
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`tool parameters JSON Schema (wire shape) todo 1`] = `
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {
|
||||
"todos": {
|
||||
"description": "The updated todo list",
|
||||
"items": {
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "Brief description of the task",
|
||||
"type": "string",
|
||||
},
|
||||
"priority": {
|
||||
"description": "Priority level of the task: high, medium, low",
|
||||
"type": "string",
|
||||
},
|
||||
"status": {
|
||||
"description": "Current status of the task: pending, in_progress, completed, cancelled",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"content",
|
||||
"status",
|
||||
"priority",
|
||||
],
|
||||
"type": "object",
|
||||
},
|
||||
"type": "array",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"todos",
|
||||
],
|
||||
"type": "object",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`tool parameters JSON Schema (wire shape) webfetch 1`] = `
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import { Parameters as Read } from "../../src/tool/read"
|
|||
import { Parameters as Shell } from "../../src/tool/shell"
|
||||
import { Parameters as Skill } from "../../src/tool/skill"
|
||||
import { Parameters as Task } from "../../src/tool/task"
|
||||
import { Parameters as Todo } from "../../src/tool/todo"
|
||||
import { Parameters as WebFetch } from "../../src/tool/webfetch"
|
||||
import { Parameters as WebSearch } from "../../src/tool/websearch"
|
||||
import { Parameters as Write } from "../../src/tool/write"
|
||||
|
|
@ -48,7 +47,6 @@ describe("tool parameters", () => {
|
|||
test("read", () => expect(toJsonSchema(Read)).toMatchSnapshot())
|
||||
test("skill", () => expect(toJsonSchema(Skill)).toMatchSnapshot())
|
||||
test("task", () => expect(toJsonSchema(Task)).toMatchSnapshot())
|
||||
test("todo", () => expect(toJsonSchema(Todo)).toMatchSnapshot())
|
||||
test("webfetch", () => expect(toJsonSchema(WebFetch)).toMatchSnapshot())
|
||||
test("websearch", () => expect(toJsonSchema(WebSearch)).toMatchSnapshot())
|
||||
test("write", () => expect(toJsonSchema(Write)).toMatchSnapshot())
|
||||
|
|
@ -248,18 +246,6 @@ describe("tool parameters", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("todo", () => {
|
||||
test("accepts todos array", () => {
|
||||
const parsed = parse(Todo, {
|
||||
todos: [{ id: "t1", content: "do x", status: "pending", priority: "medium" }],
|
||||
})
|
||||
expect(parsed.todos.length).toBe(1)
|
||||
})
|
||||
test("rejects missing todos", () => {
|
||||
expect(accepts(Todo, {})).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("webfetch", () => {
|
||||
test("defaults omitted format to markdown", () => {
|
||||
expect(parse(WebFetch, { url: "https://example.com" })).toEqual({
|
||||
|
|
|
|||
|
|
@ -388,7 +388,7 @@ describe("tool.task", () => {
|
|||
)
|
||||
|
||||
it.instance(
|
||||
"execute shapes child permissions for task, todowrite, and primary tools",
|
||||
"execute shapes child permissions for task and primary tools",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const sessions = yield* Session.Service
|
||||
|
|
@ -420,11 +420,6 @@ describe("tool.task", () => {
|
|||
expect(child.parentID).toBe(chat.id)
|
||||
expect(child.agent).toBe("reviewer")
|
||||
expect(child.permission).toEqual([
|
||||
{
|
||||
permission: "todowrite",
|
||||
pattern: "*",
|
||||
action: "deny",
|
||||
},
|
||||
{
|
||||
permission: "bash",
|
||||
pattern: "*",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue