feat(features): add TaskToastManager for consolidated task notifications

- Create task-toast-manager feature with singleton pattern

- Show running task list (newest first) when new task starts

- Track queued tasks status from ConcurrencyManager

- Integrate with BackgroundManager and sisyphus-task tool

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode) assistance
This commit is contained in:
YeonGyu-Kim
2026-01-06 13:40:27 +09:00
parent 110dbd665a
commit 9eafe6b5f9
9 changed files with 371 additions and 48 deletions
+25 -2
View File
@@ -1,5 +1,8 @@
import type { PluginInput } from "@opencode-ai/plugin"
import { existsSync, readdirSync } from "node:fs"
import { join } from "node:path"
import { HOOK_NAME, PROMETHEUS_AGENTS, ALLOWED_EXTENSIONS, ALLOWED_PATH_PREFIX, BLOCKED_TOOLS, PLANNING_CONSULT_WARNING } from "./constants"
import { findNearestMessageWithFields, MESSAGE_STORAGE } from "../../features/hook-message-injector"
import { log } from "../../shared/logger"
export * from "./constants"
@@ -10,15 +13,35 @@ function isAllowedFile(filePath: string): boolean {
return hasAllowedExtension && isInAllowedPath
}
function getMessageDir(sessionID: string): string | null {
if (!existsSync(MESSAGE_STORAGE)) return null
const directPath = join(MESSAGE_STORAGE, sessionID)
if (existsSync(directPath)) return directPath
for (const dir of readdirSync(MESSAGE_STORAGE)) {
const sessionPath = join(MESSAGE_STORAGE, dir, sessionID)
if (existsSync(sessionPath)) return sessionPath
}
return null
}
const TASK_TOOLS = ["sisyphus_task", "task", "call_omo_agent"]
function getAgentFromSession(sessionID: string): string | undefined {
const messageDir = getMessageDir(sessionID)
if (!messageDir) return undefined
return findNearestMessageWithFields(messageDir)?.agent
}
export function createPrometheusMdOnlyHook(_ctx: PluginInput) {
return {
"tool.execute.before": async (
input: { tool: string; sessionID: string; callID: string; agent?: string },
input: { tool: string; sessionID: string; callID: string },
output: { args: Record<string, unknown>; message?: string }
): Promise<void> => {
const agentName = input.agent
const agentName = getAgentFromSession(input.sessionID)
if (!agentName || !PROMETHEUS_AGENTS.includes(agentName)) {
return
+60 -8
View File
@@ -14,6 +14,37 @@ import type { BackgroundManager } from "../../features/background-agent"
export const HOOK_NAME = "sisyphus-orchestrator"
const ALLOWED_PATH_PREFIX = ".sisyphus/"
const WRITE_EDIT_TOOLS = ["Write", "Edit", "write", "edit"]
const DIRECT_WORK_REMINDER = `
---
[SYSTEM REMINDER - DELEGATION REQUIRED]
You just performed direct file modifications outside \`.sisyphus/\`.
**Sisyphus is an ORCHESTRATOR, not an IMPLEMENTER.**
As an orchestrator, you should:
- **DELEGATE** implementation work to subagents via \`sisyphus_task\`
- **VERIFY** the work done by subagents
- **COORDINATE** multiple tasks and ensure completion
You should NOT:
- Write code directly (except for \`.sisyphus/\` files like plans and notepads)
- Make direct file edits outside \`.sisyphus/\`
- Implement features yourself
**If you need to make changes:**
1. Use \`sisyphus_task\` to delegate to an appropriate subagent
2. Provide clear instructions in the prompt
3. Verify the subagent's work after completion
---
`
const BOULDER_CONTINUATION_PROMPT = `[SYSTEM REMINDER - BOULDER CONTINUATION]
You have an active work plan with incomplete tasks. Continue working.
@@ -208,12 +239,20 @@ interface ToolExecuteInput {
agent?: string
}
interface ToolExecuteOutput {
interface ToolExecuteAfterInput {
tool: string
sessionID?: string
callID?: string
}
interface ToolExecuteAfterOutput {
title: string
output: string
metadata: unknown
metadata: Record<string, unknown>
}
type ToolExecuteOutput = ToolExecuteAfterOutput
function getMessageDir(sessionID: string): string | null {
if (!existsSync(MESSAGE_STORAGE)) return null
@@ -427,9 +466,26 @@ export function createSisyphusOrchestratorHook(
},
"tool.execute.after": async (
input: ToolExecuteInput,
output: ToolExecuteOutput
input: ToolExecuteAfterInput,
output: ToolExecuteAfterOutput
): Promise<void> => {
if (!isCallerOrchestrator(input.sessionID)) {
return
}
if (WRITE_EDIT_TOOLS.includes(input.tool)) {
const filePath = output.metadata?.filePath as string | undefined
if (filePath && !filePath.includes(ALLOWED_PATH_PREFIX)) {
output.output = (output.output || "") + DIRECT_WORK_REMINDER
log(`[${HOOK_NAME}] Direct work reminder appended`, {
sessionID: input.sessionID,
tool: input.tool,
filePath,
})
}
return
}
if (input.tool !== "sisyphus_task") {
return
}
@@ -440,10 +496,6 @@ export function createSisyphusOrchestratorHook(
if (isBackgroundLaunch) {
return
}
if (!isCallerOrchestrator(input.sessionID)) {
return
}
if (output.output && typeof output.output === "string") {
const gitStats = getGitDiffStats(ctx.directory)