fix(plugin): verify event hook compatibility with v1.4.0
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -150,14 +150,21 @@ export async function handleSessionIdle(args: {
|
||||
|
||||
let resolvedInfo: ResolvedMessageInfo | undefined
|
||||
let encounteredCompaction = false
|
||||
let latestMessageWasCompaction = false
|
||||
try {
|
||||
const messageInfoResult = await resolveLatestMessageInfo(ctx, sessionID, prefetchedMessages)
|
||||
resolvedInfo = messageInfoResult.resolvedInfo
|
||||
encounteredCompaction = messageInfoResult.encounteredCompaction
|
||||
latestMessageWasCompaction = messageInfoResult.latestMessageWasCompaction
|
||||
} catch (error) {
|
||||
log(`[${HOOK_NAME}] Failed to fetch messages for agent check`, { sessionID, error: String(error) })
|
||||
}
|
||||
|
||||
if (latestMessageWasCompaction) {
|
||||
log(`[${HOOK_NAME}] Skipped: latest message is a compaction marker`, { sessionID })
|
||||
return
|
||||
}
|
||||
|
||||
const sessionAgent = getSessionAgent(sessionID)
|
||||
if (!resolvedInfo?.agent && sessionAgent) {
|
||||
resolvedInfo = { ...resolvedInfo, agent: sessionAgent }
|
||||
|
||||
@@ -2,7 +2,7 @@ import { log } from "../../shared/logger"
|
||||
import { HOOK_NAME } from "./constants"
|
||||
|
||||
interface MessagePart {
|
||||
type: string
|
||||
type?: string
|
||||
name?: string
|
||||
toolName?: string
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
|
||||
import { normalizeSDKResponse } from "../../shared"
|
||||
import { isCompactionMessage } from "../../shared/compaction-marker"
|
||||
|
||||
import type { MessageInfo, MessageWithInfo, ResolveLatestMessageInfoResult } from "./types"
|
||||
|
||||
@@ -16,10 +17,17 @@ export async function resolveLatestMessageInfo(
|
||||
[] as MessageWithInfo[],
|
||||
)
|
||||
let encounteredCompaction = false
|
||||
let latestMessageWasCompaction = false
|
||||
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const info = messages[i].info
|
||||
if (info?.agent === "compaction") {
|
||||
const message = messages[i]
|
||||
const info = message.info
|
||||
const isCompaction = isCompactionMessage(message)
|
||||
if (i === messages.length - 1) {
|
||||
latestMessageWasCompaction = isCompaction
|
||||
}
|
||||
|
||||
if (isCompaction) {
|
||||
encounteredCompaction = true
|
||||
continue
|
||||
}
|
||||
@@ -31,9 +39,10 @@ export async function resolveLatestMessageInfo(
|
||||
tools: info.tools,
|
||||
},
|
||||
encounteredCompaction,
|
||||
latestMessageWasCompaction,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { resolvedInfo: undefined, encounteredCompaction }
|
||||
return { resolvedInfo: undefined, encounteredCompaction, latestMessageWasCompaction }
|
||||
}
|
||||
|
||||
@@ -1594,8 +1594,8 @@ describe("todo-continuation-enforcer", () => {
|
||||
// when resolving agent info, preventing infinite continuation loops
|
||||
// ============================================================
|
||||
|
||||
test("should skip compaction agent messages when resolving agent info", async () => {
|
||||
// given - session where last message is from compaction agent but previous was Sisyphus
|
||||
test("should skip injection while the latest message is from the compaction agent", async () => {
|
||||
// given - session where the latest activity is still the compaction assistant turn
|
||||
const sessionID = "main-compaction-filter"
|
||||
setMainSession(sessionID)
|
||||
|
||||
@@ -1644,9 +1644,8 @@ describe("todo-continuation-enforcer", () => {
|
||||
await hook.handler({ event: { type: "session.idle", properties: { sessionID } } })
|
||||
await fakeTimers.advanceBy(2500)
|
||||
|
||||
// then - continuation uses Sisyphus (skipped compaction agent)
|
||||
expect(promptCalls.length).toBe(1)
|
||||
expect(promptCalls[0].agent).toBe("sisyphus")
|
||||
// then - no continuation while compaction is still the latest event
|
||||
expect(promptCalls).toHaveLength(0)
|
||||
})
|
||||
|
||||
test("should skip injection when only compaction agent messages exist", async () => {
|
||||
@@ -1702,6 +1701,62 @@ describe("todo-continuation-enforcer", () => {
|
||||
expect(promptCalls).toHaveLength(0)
|
||||
})
|
||||
|
||||
test("should skip compaction marker user messages when resolving agent info", async () => {
|
||||
// given - latest user message is the OpenCode compaction marker, not a real turn
|
||||
const sessionID = "main-compaction-marker-filter"
|
||||
setMainSession(sessionID)
|
||||
|
||||
const mockMessagesWithCompactionMarker = [
|
||||
{ info: { id: "msg-1", role: "assistant", agent: "sisyphus", modelID: "claude-sonnet-4-6", providerID: "anthropic" } },
|
||||
{
|
||||
info: { id: "msg-2", role: "user", agent: "atlas", model: { providerID: "openai", modelID: "gpt-5.4" } },
|
||||
parts: [{ type: "compaction" }],
|
||||
},
|
||||
]
|
||||
|
||||
const mockInput = {
|
||||
client: {
|
||||
session: {
|
||||
todo: async () => ({
|
||||
data: [{ id: "1", content: "Task 1", status: "pending", priority: "high" }],
|
||||
}),
|
||||
messages: async () => ({ data: mockMessagesWithCompactionMarker }),
|
||||
prompt: async (opts: any) => {
|
||||
promptCalls.push({
|
||||
sessionID: opts.path.id,
|
||||
agent: opts.body.agent,
|
||||
model: opts.body.model,
|
||||
text: opts.body.parts[0].text,
|
||||
})
|
||||
return {}
|
||||
},
|
||||
promptAsync: async (opts: any) => {
|
||||
promptCalls.push({
|
||||
sessionID: opts.path.id,
|
||||
agent: opts.body.agent,
|
||||
model: opts.body.model,
|
||||
text: opts.body.parts[0].text,
|
||||
})
|
||||
return {}
|
||||
},
|
||||
},
|
||||
tui: { showToast: async () => ({}) },
|
||||
},
|
||||
directory: "/tmp/test",
|
||||
} as any
|
||||
|
||||
const hook = createTodoContinuationEnforcer(mockInput, {
|
||||
backgroundManager: createMockBackgroundManager(false),
|
||||
})
|
||||
|
||||
// when - session goes idle
|
||||
await hook.handler({ event: { type: "session.idle", properties: { sessionID } } })
|
||||
await fakeTimers.advanceBy(3000)
|
||||
|
||||
// then - no continuation while the compaction marker is the latest event
|
||||
expect(promptCalls).toHaveLength(0)
|
||||
})
|
||||
|
||||
test("should skip injection when prometheus agent is after compaction", async () => {
|
||||
// given - prometheus session that was compacted
|
||||
const sessionID = "main-prometheus-compacted"
|
||||
|
||||
@@ -54,6 +54,7 @@ export interface MessageInfo {
|
||||
|
||||
export interface MessageWithInfo {
|
||||
info?: MessageInfo
|
||||
parts?: Array<{ type?: string }>
|
||||
}
|
||||
|
||||
export interface ResolvedMessageInfo {
|
||||
@@ -65,6 +66,7 @@ export interface ResolvedMessageInfo {
|
||||
export interface ResolveLatestMessageInfoResult {
|
||||
resolvedInfo?: ResolvedMessageInfo
|
||||
encounteredCompaction: boolean
|
||||
latestMessageWasCompaction: boolean
|
||||
}
|
||||
|
||||
export interface ContinuationProgressOptions {
|
||||
|
||||
Reference in New Issue
Block a user