import type { Plugin } from "@opencode-ai/plugin" interface WebhookConfig { url: string secret?: string enabled: boolean } export const StatusLightPlugin: Plugin = async ({ project, client, $, directory, worktree }) => { // 默认配置,可以通过环境变量或配置文件覆盖 const config: WebhookConfig = { url: process.env.OPENCODE_WEBHOOK_URL || "http://localhost:8080/api/webhook", secret: process.env.OPENCODE_WEBHOOK_SECRET || "", enabled: process.env.OPENCODE_WEBHOOK_ENABLED !== "false", } console.log(`[StatusLight] Plugin initialized`) console.log(`[StatusLight] Webhook URL: ${config.url}`) console.log(`[StatusLight] Webhook enabled: ${config.enabled}`) // 发送webhook请求 async function sendWebhook(eventType: string, data: any) { if (!config.enabled) { return } try { const payload = { type: eventType, timestamp: new Date().toISOString(), project: project?.name || "unknown", directory, worktree, data, } const headers: Record = { "Content-Type": "application/json", } // 如果配置了密钥,添加签名头 if (config.secret) { const encoder = new TextEncoder() const key = await crypto.subtle.importKey( "raw", encoder.encode(config.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"] ) const signature = await crypto.subtle.sign( "HMAC", key, encoder.encode(JSON.stringify(payload)) ) const signatureArray = Array.from(new Uint8Array(signature)) const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, "0")).join("") headers["X-Webhook-Signature"] = `sha256=${signatureHex}` } const response = await fetch(config.url, { method: "POST", headers, body: JSON.stringify(payload), }) if (!response.ok) { console.error(`[StatusLight] Webhook failed: ${response.status} ${response.statusText}`) } } catch (error) { console.error(`[StatusLight] Webhook error:`, error) } } return { // 工具执行前 "tool.execute.before": async (input, output) => { console.log(`[StatusLight] Tool executing: ${input.tool}`) await sendWebhook("tool.execute.before", { tool: input.tool, args: output.args, status: "running", }) }, // 工具执行后 "tool.execute.after": async (input, output) => { console.log(`[StatusLight] Tool completed: ${input.tool}`) await sendWebhook("tool.execute.after", { tool: input.tool, args: output.args, result: output.result, status: "completed", }) }, // 会话状态变化 "session.status": async (input, output) => { console.log(`[StatusLight] Session status: ${JSON.stringify(input)}`) await sendWebhook("session.status", { status: input, }) }, // 会话空闲 "session.idle": async (input, output) => { console.log(`[StatusLight] Session idle`) await sendWebhook("session.idle", { status: "idle", }) }, // 会话错误 "session.error": async (input, output) => { console.log(`[StatusLight] Session error: ${JSON.stringify(input)}`) await sendWebhook("session.error", { error: input, }) }, // 权限请求 "permission.asked": async (input, output) => { console.log(`[StatusLight] Permission requested: ${JSON.stringify(input)}`) await sendWebhook("permission.asked", { permission: input, }) }, // 消息更新(包含工具状态) "message.part.updated": async (input, output) => { const part = input as any if (part.type === "tool") { console.log(`[StatusLight] Tool part updated: ${part.tool} - ${part.state?.status}`) await sendWebhook("message.part.updated", { part: { type: part.type, tool: part.tool, state: part.state, }, }) } else if (part.type === "reasoning") { console.log(`[StatusLight] Reasoning part updated`) await sendWebhook("message.part.updated", { part: { type: part.type, status: "reasoning", }, }) } }, } }