Files
oh-my-opencode/src/cli/run/continuation-state.ts
T
YeonGyu-Kim 7ac1109611 Merge pull request #3636 from lucasyounger/codex/fix-3629-worktree-plan-path
fix(boulder): resolve continuation progress from worktree plan
2026-05-06 17:15:21 +09:00

115 lines
3.6 KiB
TypeScript

import { getPlanProgress, readBoulderState, resolveBoulderPlanPath } from "../../features/boulder-state"
import { getSessionAgent } from "../../features/claude-code-session-state"
import {
getActiveContinuationMarkerReason,
isContinuationMarkerActive,
readContinuationMarker,
} from "../../features/run-continuation-state"
import { isSessionInBoulderLineage } from "../../hooks/atlas/boulder-session-lineage"
import { getLastAgentFromSession } from "../../hooks/atlas/session-last-agent"
import { getAgentConfigKey } from "../../shared/agent-display-names"
import { readState as readRalphLoopState } from "../../hooks/ralph-loop/storage"
import type { RunContext } from "./types"
export interface ContinuationState {
hasActiveBoulder: boolean
hasActiveRalphLoop: boolean
hasHookMarker: boolean
hasTodoHookMarker: boolean
hasActiveBackgroundTaskMarker: boolean
hasActiveHookMarker: boolean
activeHookMarkerReason: string | null
}
export async function getContinuationState(
directory: string,
sessionID: string,
client?: RunContext["client"],
): Promise<ContinuationState> {
const marker = readContinuationMarker(directory, sessionID)
return {
hasActiveBoulder: await hasActiveBoulderContinuation(directory, sessionID, client),
hasActiveRalphLoop: hasActiveRalphLoopContinuation(directory, sessionID),
hasHookMarker: marker !== null,
hasTodoHookMarker: marker?.sources.todo !== undefined,
hasActiveBackgroundTaskMarker: marker?.sources["background-task"]?.state === "active",
hasActiveHookMarker: isContinuationMarkerActive(marker),
activeHookMarkerReason: getActiveContinuationMarkerReason(marker),
}
}
async function hasActiveBoulderContinuation(
directory: string,
sessionID: string,
client?: RunContext["client"],
): Promise<boolean> {
const boulder = readBoulderState(directory)
if (!boulder) return false
const progress = getPlanProgress(resolveBoulderPlanPath(directory, boulder))
if (progress.isComplete) return false
if (!client) return false
const isTrackedSession = boulder.session_ids.includes(sessionID)
const sessionOrigin = boulder.session_origins?.[sessionID]
if (!isTrackedSession) {
return false
}
const isTrackedDescendant = await isTrackedDescendantSession(client, sessionID, boulder.session_ids)
if (isTrackedSession && sessionOrigin === "direct") {
return true
}
if (isTrackedSession && sessionOrigin !== "direct" && !isTrackedDescendant) {
return false
}
const sessionAgent = await getLastAgentFromSession(sessionID, client)
?? getSessionAgent(sessionID)
if (!sessionAgent) {
return false
}
const requiredAgentKey = getAgentConfigKey(boulder.agent ?? "atlas")
const sessionAgentKey = getAgentConfigKey(sessionAgent)
if (
sessionAgentKey !== requiredAgentKey
&& !(requiredAgentKey === getAgentConfigKey("atlas") && sessionAgentKey === getAgentConfigKey("sisyphus"))
) {
return false
}
return isTrackedSession || isTrackedDescendant
}
async function isTrackedDescendantSession(
client: RunContext["client"],
sessionID: string,
trackedSessionIDs: string[],
): Promise<boolean> {
const ancestorSessionIDs = trackedSessionIDs.filter((trackedSessionID) => trackedSessionID !== sessionID)
if (ancestorSessionIDs.length === 0) {
return false
}
return isSessionInBoulderLineage({
client,
sessionID,
boulderSessionIDs: ancestorSessionIDs,
})
}
function hasActiveRalphLoopContinuation(directory: string, sessionID: string): boolean {
const state = readRalphLoopState(directory)
if (!state || !state.active) return false
if (state.session_id && state.session_id !== sessionID) {
return false
}
return true
}