22b4b30dd7
After compaction, message history is truncated and the original agent (e.g. Prometheus) can no longer be resolved from messages. The todo continuation enforcer would then inject a continuation prompt with agent=undefined, causing the host to default to General -- which has write permissions Prometheus should never have. Root cause chain: 1. handler.ts had no session.compacted handler (unlike Atlas) 2. idle-event.ts relied on finding a compaction marker in truncated message history -- the marker disappears after real compaction 3. continuation-injection.ts proceeded when agentName was undefined because the skipAgents check only matched truthy agent names 4. prometheus-md-only/agent-resolution.ts did not filter compaction agent from message history fallback results Fixes: - Add session.compacted handler that sets hasRecentCompaction state flag - Replace fragile history-based compaction detection with state flag - Block continuation injection when agent is unknown post-compaction - Filter compaction agent in Prometheus agent resolution fallback
71 lines
2.6 KiB
TypeScript
71 lines
2.6 KiB
TypeScript
import type { PluginInput } from "@opencode-ai/plugin"
|
|
|
|
import { findNearestMessageWithFields, findFirstMessageWithAgent } from "../../features/hook-message-injector"
|
|
import {
|
|
findFirstMessageWithAgentFromSDK,
|
|
findNearestMessageWithFieldsFromSDK,
|
|
} from "../../features/hook-message-injector"
|
|
import { getSessionAgent } from "../../features/claude-code-session-state"
|
|
import { readBoulderState } from "../../features/boulder-state"
|
|
import { getMessageDir } from "../../shared/opencode-message-dir"
|
|
import { isSqliteBackend } from "../../shared/opencode-storage-detection"
|
|
|
|
type OpencodeClient = PluginInput["client"]
|
|
|
|
function isCompactionAgent(agent: string): boolean {
|
|
return agent.toLowerCase() === "compaction"
|
|
}
|
|
|
|
async function getAgentFromMessageFiles(
|
|
sessionID: string,
|
|
client?: OpencodeClient
|
|
): Promise<string | undefined> {
|
|
if (isSqliteBackend() && client) {
|
|
const firstAgent = await findFirstMessageWithAgentFromSDK(client, sessionID)
|
|
if (firstAgent && !isCompactionAgent(firstAgent)) return firstAgent
|
|
|
|
const nearest = await findNearestMessageWithFieldsFromSDK(client, sessionID)
|
|
if (nearest?.agent && !isCompactionAgent(nearest.agent)) return nearest.agent
|
|
return undefined
|
|
}
|
|
|
|
const messageDir = getMessageDir(sessionID)
|
|
if (!messageDir) return undefined
|
|
const firstAgent = findFirstMessageWithAgent(messageDir)
|
|
if (firstAgent && !isCompactionAgent(firstAgent)) return firstAgent
|
|
const nearestAgent = findNearestMessageWithFields(messageDir)?.agent
|
|
if (nearestAgent && !isCompactionAgent(nearestAgent)) return nearestAgent
|
|
return undefined
|
|
}
|
|
|
|
/**
|
|
* Get the effective agent for the session.
|
|
* Priority order:
|
|
* 1. In-memory session agent (most recent, set by /start-work)
|
|
* 2. Boulder state agent (persisted across restarts, fixes #927)
|
|
* 3. Message files (fallback for sessions without boulder state)
|
|
*
|
|
* This fixes issue #927 where after interruption:
|
|
* - In-memory map is cleared (process restart)
|
|
* - Message files return "prometheus" (oldest message from /plan)
|
|
* - But boulder.json has agent: "atlas" (set by /start-work)
|
|
*/
|
|
export async function getAgentFromSession(
|
|
sessionID: string,
|
|
directory: string,
|
|
client?: OpencodeClient
|
|
): Promise<string | undefined> {
|
|
// Check in-memory first (current session)
|
|
const memoryAgent = getSessionAgent(sessionID)
|
|
if (memoryAgent) return memoryAgent
|
|
|
|
// Check boulder state (persisted across restarts) - fixes #927
|
|
const boulderState = readBoulderState(directory)
|
|
if (boulderState?.session_ids?.includes(sessionID) && boulderState.agent) {
|
|
return boulderState.agent
|
|
}
|
|
|
|
// Fallback to message files
|
|
return await getAgentFromMessageFiles(sessionID, client)
|
|
}
|