Implement unified Claude Tasks system with single multi-action tool (#1356)

* chore: pin bun-types to 1.3.6

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* chore: exclude test files and script from tsconfig

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* refactor: remove sisyphus-swarm feature

Remove mailbox types and swarm config schema. Update docs.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* refactor: remove legacy sisyphus-tasks feature

Remove old storage and types implementation, replaced by claude-tasks.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* feat(claude-tasks): add task schema and storage utilities

- Task schema with Zod validation (pending, in_progress, completed, deleted)
- Storage utilities: getTaskDir, readJsonSafe, writeJsonAtomic, acquireLock
- Atomic writes with temp file + rename
- File-based locking with 30s stale threshold

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* feat(tools/task): add task object schemas

Add Zod schemas for task CRUD operations input validation.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* feat(tools): add TaskCreate tool

Create new tasks with sequential ID generation and lock-based concurrency.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* feat(tools): add TaskGet tool

Retrieve task by ID with null-safe handling.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* feat(tools): add TaskUpdate tool with claim validation

Update tasks with status transitions and owner claim validation.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* feat(tools): add TaskList tool and exports

- TaskList for summary view of all tasks
- Export all claude-tasks tool factories from index

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* feat(hooks): add task-reminder hook

Remind agents to use task tools after 10 turns without task operations.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* feat(config): add disabled_tools setting and tasks-todowrite-disabler hook

- Add disabled_tools config option to disable specific tools by name
- Register tasks-todowrite-disabler hook name in schema

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* feat(config-handler): add task_* and teammate tool permissions

Grant task_* and teammate permissions to atlas, sisyphus, prometheus, and sisyphus-junior agents.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* feat(delegate-task): add execute option for task execution

Add optional execute field with task_id and task_dir for task-based delegation.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* fix(truncator): add type guard for non-string outputs

Prevent crashes when output is not a string by adding typeof checks.

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* chore: export config types and update task-resume-info

- Export SisyphusConfig and SisyphusTasksConfig types
- Add task_tool to TARGET_TOOLS list

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* refactor(storage): remove team namespace, use flat task directory

* feat(task): implement unified task tool with all 5 actions

* fix(hooks): update task-reminder to track unified task tool

* refactor(tools): register unified task tool, remove 4 separate tools

* chore(cleanup): remove old 4-tool task implementation

* refactor(config): use new_task_system_enabled as top-level flag

- Add new_task_system_enabled to OhMyOpenCodeConfigSchema
- Remove enabled from SisyphusTasksConfigSchema (keep storage_path, claude_code_compat)
- Update index.ts to gate on new_task_system_enabled
- Update plugin-config.ts default for config initialization
- Update test configs in task.test.ts and storage.test.ts

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: resolve typecheck and test failures

- Add explicit ToolDefinition return type to createTask function
- Fix planDemoteConfig to use 'subagent' mode instead of 'all'

---------

Co-authored-by: justsisyphus <justsisyphus@users.noreply.github.com>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-02-01 22:42:28 +09:00
committed by GitHub
parent 491df05b63
commit 8d29a1c5c7
32 changed files with 2070 additions and 684 deletions
+125
View File
@@ -0,0 +1,125 @@
import { describe, test, expect, beforeEach } from "bun:test"
import { createTaskReminderHook } from "./index"
import type { PluginInput } from "@opencode-ai/plugin"
const mockCtx = {} as PluginInput
describe("TaskReminderHook", () => {
let hook: ReturnType<typeof createTaskReminderHook>
beforeEach(() => {
hook = createTaskReminderHook(mockCtx)
})
test("does not inject reminder before 10 turns", async () => {
//#given
const sessionID = "test-session"
const output = { output: "Result" }
//#when
for (let i = 0; i < 9; i++) {
await hook["tool.execute.after"]?.(
{ tool: "bash", sessionID, callID: `call-${i}` },
output
)
}
//#then
expect(output.output).not.toContain("task tools haven't been used")
})
test("injects reminder after 10 turns without task tool usage", async () => {
//#given
const sessionID = "test-session"
const output = { output: "Result" }
//#when
for (let i = 0; i < 10; i++) {
await hook["tool.execute.after"]?.(
{ tool: "bash", sessionID, callID: `call-${i}` },
output
)
}
//#then
expect(output.output).toContain("task tools haven't been used")
})
test("resets counter when task tool is used", async () => {
//#given
const sessionID = "test-session"
const output = { output: "Result" }
//#when
for (let i = 0; i < 5; i++) {
await hook["tool.execute.after"]?.(
{ tool: "bash", sessionID, callID: `call-${i}` },
output
)
}
await hook["tool.execute.after"]?.(
{ tool: "task", sessionID, callID: "call-task" },
output
)
for (let i = 0; i < 9; i++) {
await hook["tool.execute.after"]?.(
{ tool: "bash", sessionID, callID: `call-after-${i}` },
output
)
}
//#then
expect(output.output).not.toContain("task tools haven't been used")
})
test("resets counter after injecting reminder", async () => {
//#given
const sessionID = "test-session"
const output1 = { output: "Result 1" }
const output2 = { output: "Result 2" }
//#when
for (let i = 0; i < 10; i++) {
await hook["tool.execute.after"]?.(
{ tool: "bash", sessionID, callID: `call-1-${i}` },
output1
)
}
for (let i = 0; i < 9; i++) {
await hook["tool.execute.after"]?.(
{ tool: "bash", sessionID, callID: `call-2-${i}` },
output2
)
}
//#then
expect(output1.output).toContain("task tools haven't been used")
expect(output2.output).not.toContain("task tools haven't been used")
})
test("tracks separate counters per session", async () => {
//#given
const session1 = "session-1"
const session2 = "session-2"
const output1 = { output: "Result 1" }
const output2 = { output: "Result 2" }
//#when
for (let i = 0; i < 10; i++) {
await hook["tool.execute.after"]?.(
{ tool: "bash", sessionID: session1, callID: `call-${i}` },
output1
)
}
for (let i = 0; i < 5; i++) {
await hook["tool.execute.after"]?.(
{ tool: "bash", sessionID: session2, callID: `call-${i}` },
output2
)
}
//#then
expect(output1.output).toContain("task tools haven't been used")
expect(output2.output).not.toContain("task tools haven't been used")
})
})
+45
View File
@@ -0,0 +1,45 @@
import type { PluginInput } from "@opencode-ai/plugin"
const TASK_TOOLS = new Set(["task"])
const TURN_THRESHOLD = 10
const REMINDER_MESSAGE = `
The task tools haven't been used recently. If you're working on tasks that would benefit from tracking progress, consider using TaskCreate to add new tasks and TaskUpdate to update task status (set to in_progress when starting, completed when done).`
interface ToolExecuteInput {
tool: string
sessionID: string
callID: string
}
interface ToolExecuteOutput {
output: string
}
export function createTaskReminderHook(_ctx: PluginInput) {
const sessionCounters = new Map<string, number>()
const toolExecuteAfter = async (input: ToolExecuteInput, output: ToolExecuteOutput) => {
const { tool, sessionID } = input
const toolLower = tool.toLowerCase()
if (TASK_TOOLS.has(toolLower)) {
sessionCounters.set(sessionID, 0)
return
}
const currentCount = sessionCounters.get(sessionID) ?? 0
const newCount = currentCount + 1
if (newCount >= TURN_THRESHOLD) {
output.output += REMINDER_MESSAGE
sessionCounters.set(sessionID, 0)
} else {
sessionCounters.set(sessionID, newCount)
}
}
return {
"tool.execute.after": toolExecuteAfter,
}
}