fix(run): preserve active boulder continuations
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { getPlanProgress, readBoulderState, resolveBoulderPlanPath } from "../../features/boulder-state"
|
import { getPlanProgress, normalizeSessionId, readBoulderState, resolveBoulderPlanPath } from "../../features/boulder-state"
|
||||||
import { getSessionAgent } from "../../features/claude-code-session-state"
|
import { getSessionAgent } from "../../features/claude-code-session-state"
|
||||||
import {
|
import {
|
||||||
getActiveContinuationMarkerReason,
|
getActiveContinuationMarkerReason,
|
||||||
@@ -51,19 +51,25 @@ async function hasActiveBoulderContinuation(
|
|||||||
if (progress.isComplete) return false
|
if (progress.isComplete) return false
|
||||||
if (!client) return false
|
if (!client) return false
|
||||||
|
|
||||||
const isTrackedSession = boulder.session_ids.includes(sessionID)
|
const normalizedSessionID = normalizeSessionId(sessionID)
|
||||||
const sessionOrigin = boulder.session_origins?.[sessionID]
|
const normalizedTrackedSessionIDs = boulder.session_ids.map((trackedSessionID) => normalizeSessionId(trackedSessionID))
|
||||||
if (!isTrackedSession) {
|
if (!normalizedTrackedSessionIDs.includes(normalizedSessionID)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTrackedDescendant = await isTrackedDescendantSession(client, sessionID, boulder.session_ids)
|
const sessionOrigin = boulder.session_origins?.[sessionID] ?? boulder.session_origins?.[normalizedSessionID]
|
||||||
|
if (sessionOrigin === "direct") {
|
||||||
if (isTrackedSession && sessionOrigin === "direct") {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTrackedSession && sessionOrigin !== "direct" && !isTrackedDescendant) {
|
const trackedAncestorSessionIDs = normalizedTrackedSessionIDs
|
||||||
|
.filter((trackedSessionID) => trackedSessionID !== normalizedSessionID)
|
||||||
|
if (trackedAncestorSessionIDs.length === 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTrackedDescendant = await isTrackedDescendantSession(client, sessionID, trackedAncestorSessionIDs)
|
||||||
|
if (!isTrackedDescendant) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,23 +88,22 @@ async function hasActiveBoulderContinuation(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return isTrackedSession || isTrackedDescendant
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isTrackedDescendantSession(
|
async function isTrackedDescendantSession(
|
||||||
client: RunContext["client"],
|
client: RunContext["client"],
|
||||||
sessionID: string,
|
sessionID: string,
|
||||||
trackedSessionIDs: string[],
|
trackedAncestorSessionIDs: string[],
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const ancestorSessionIDs = trackedSessionIDs.filter((trackedSessionID) => trackedSessionID !== sessionID)
|
if (trackedAncestorSessionIDs.length === 0) {
|
||||||
if (ancestorSessionIDs.length === 0) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return isSessionInBoulderLineage({
|
return isSessionInBoulderLineage({
|
||||||
client,
|
client,
|
||||||
sessionID,
|
sessionID,
|
||||||
boulderSessionIDs: ancestorSessionIDs,
|
boulderSessionIDs: trackedAncestorSessionIDs,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,48 @@ function getLastAgentFromMessageDir(messageDir: string): string | null {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getLastAgentFromSessionMessages(
|
||||||
|
sessionID: string,
|
||||||
|
client: SessionMessagesClient,
|
||||||
|
deps: SessionLastAgentDeps,
|
||||||
|
): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const response = await client.session.messages({ path: { id: sessionID } })
|
||||||
|
const messages = deps.normalizeSDKResponse(response, [] as Array<{
|
||||||
|
id?: string
|
||||||
|
info?: { agent?: string; time?: { created?: number } }
|
||||||
|
parts?: Array<{ type?: string }>
|
||||||
|
}>, {
|
||||||
|
preferResponseOnMissingData: true,
|
||||||
|
}).sort((left, right) => {
|
||||||
|
const leftTime = (left as { info?: { time?: { created?: number } } }).info?.time?.created ?? Number.NEGATIVE_INFINITY
|
||||||
|
const rightTime = (right as { info?: { time?: { created?: number } } }).info?.time?.created ?? Number.NEGATIVE_INFINITY
|
||||||
|
if (leftTime !== rightTime) {
|
||||||
|
return rightTime - leftTime
|
||||||
|
}
|
||||||
|
|
||||||
|
const leftId = typeof left.id === "string" ? left.id : ""
|
||||||
|
const rightId = typeof right.id === "string" ? right.id : ""
|
||||||
|
return rightId.localeCompare(leftId)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const message of messages) {
|
||||||
|
if (deps.isCompactionMessage(message)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const agent = message.info?.agent
|
||||||
|
if (typeof agent === "string") {
|
||||||
|
return agent.toLowerCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export async function getLastAgentFromSession(
|
export async function getLastAgentFromSession(
|
||||||
sessionID: string,
|
sessionID: string,
|
||||||
client?: SessionMessagesClient,
|
client?: SessionMessagesClient,
|
||||||
@@ -75,44 +117,13 @@ export async function getLastAgentFromSession(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (resolvedDeps.isSqliteBackend() && client) {
|
if (resolvedDeps.isSqliteBackend() && client) {
|
||||||
try {
|
return getLastAgentFromSessionMessages(sessionID, client, resolvedDeps)
|
||||||
const response = await client.session.messages({ path: { id: sessionID } })
|
|
||||||
const messages = resolvedDeps.normalizeSDKResponse(response, [] as Array<{
|
|
||||||
id?: string
|
|
||||||
info?: { agent?: string; time?: { created?: number } }
|
|
||||||
parts?: Array<{ type?: string }>
|
|
||||||
}>, {
|
|
||||||
preferResponseOnMissingData: true,
|
|
||||||
}).sort((left, right) => {
|
|
||||||
const leftTime = (left as { info?: { time?: { created?: number } } }).info?.time?.created ?? Number.NEGATIVE_INFINITY
|
|
||||||
const rightTime = (right as { info?: { time?: { created?: number } } }).info?.time?.created ?? Number.NEGATIVE_INFINITY
|
|
||||||
if (leftTime !== rightTime) {
|
|
||||||
return rightTime - leftTime
|
|
||||||
}
|
|
||||||
|
|
||||||
const leftId = typeof left.id === "string" ? left.id : ""
|
|
||||||
const rightId = typeof right.id === "string" ? right.id : ""
|
|
||||||
return rightId.localeCompare(leftId)
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const message of messages) {
|
|
||||||
if (resolvedDeps.isCompactionMessage(message)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const agent = message.info?.agent
|
|
||||||
if (typeof agent === "string") {
|
|
||||||
return agent.toLowerCase()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageDir = resolvedDeps.getMessageDir(sessionID)
|
const messageDir = resolvedDeps.getMessageDir(sessionID)
|
||||||
|
if (!messageDir && client) {
|
||||||
|
return getLastAgentFromSessionMessages(sessionID, client, resolvedDeps)
|
||||||
|
}
|
||||||
if (!messageDir) return null
|
if (!messageDir) return null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -149,5 +160,9 @@ export async function getLastAgentFromSession(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client) {
|
||||||
|
return getLastAgentFromSessionMessages(sessionID, client, resolvedDeps)
|
||||||
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user