fix(boulder-state): batch 67 (6 files)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
export { getBoulderFilePath, resolveBoulderPlanPath, resolveBoulderPlanPathForWork } from "./path"
|
||||
export { findPrometheusPlans, getPlanName, getPlanProgress } from "./plan-progress"
|
||||
export { normalizeSessionId } from "./shared"
|
||||
export {
|
||||
getActiveWorks,
|
||||
getBoulderWorks,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { existsSync, readFileSync } from "node:fs"
|
||||
import type { BoulderState, BoulderWorkResumeOption, BoulderWorkState, TaskSessionState } from "../types"
|
||||
import { getBoulderFilePath, resolveBoulderPlanPathForWork } from "./path"
|
||||
import { getPlanProgress } from "./plan-progress"
|
||||
import { buildWorkFromMirror, isValidWorkStatus, parseIsoToMs, projectWorkToMirror, selectMirrorWork } from "./shared"
|
||||
import { buildWorkFromMirror, isValidWorkStatus, normalizeSessionId, parseIsoToMs, projectWorkToMirror, selectMirrorWork } from "./shared"
|
||||
|
||||
export function readBoulderState(directory: string): BoulderState | null {
|
||||
const filePath = getBoulderFilePath(directory)
|
||||
@@ -33,8 +33,9 @@ export function readBoulderState(directory: string): BoulderState | null {
|
||||
}
|
||||
|
||||
function normalizeState(state: Record<string, unknown>): void {
|
||||
normalizeSessionFields(state)
|
||||
|
||||
const sessionIds = Array.isArray(state.session_ids) ? state.session_ids : []
|
||||
state.session_ids = sessionIds
|
||||
|
||||
const sessionOrigins = state.session_origins && typeof state.session_origins === "object" && !Array.isArray(state.session_origins)
|
||||
? (state.session_origins as Record<string, unknown>)
|
||||
@@ -55,6 +56,38 @@ function normalizeState(state: Record<string, unknown>): void {
|
||||
if (!state.task_sessions || typeof state.task_sessions !== "object" || Array.isArray(state.task_sessions)) {
|
||||
state.task_sessions = {}
|
||||
}
|
||||
|
||||
normalizeWorkSessionFields(state.works)
|
||||
}
|
||||
|
||||
function normalizeSessionFields(target: Record<string, unknown>): void {
|
||||
const sessionIds = Array.isArray(target.session_ids)
|
||||
? target.session_ids.filter((sessionId): sessionId is string => typeof sessionId === "string").map((sessionId) => normalizeSessionId(sessionId))
|
||||
: []
|
||||
target.session_ids = sessionIds
|
||||
|
||||
const sessionOrigins = target.session_origins && typeof target.session_origins === "object" && !Array.isArray(target.session_origins)
|
||||
? normalizeSessionOrigins(target.session_origins as Record<string, unknown>)
|
||||
: {}
|
||||
target.session_origins = sessionOrigins
|
||||
}
|
||||
|
||||
function normalizeSessionOrigins(sessionOrigins: Record<string, unknown>): Record<string, unknown> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(sessionOrigins).map(([sessionId, origin]) => [normalizeSessionId(sessionId), origin]),
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeWorkSessionFields(works: unknown): void {
|
||||
if (!works || typeof works !== "object" || Array.isArray(works)) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const work of Object.values(works)) {
|
||||
if (work && typeof work === "object" && !Array.isArray(work)) {
|
||||
normalizeSessionFields(work as Record<string, unknown>)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getBoulderWorks(state: BoulderState): BoulderWorkState[] {
|
||||
@@ -113,15 +146,16 @@ export function getWorkForSession(directory: string, sessionId: string): Boulder
|
||||
return null
|
||||
}
|
||||
|
||||
const normalizedSessionId = normalizeSessionId(sessionId)
|
||||
const works = getBoulderWorks(state)
|
||||
.filter((work) => work.session_ids.includes(sessionId))
|
||||
.filter((work) => work.session_ids.includes(normalizedSessionId))
|
||||
.sort((left, right) => (parseIsoToMs(right.updated_at ?? right.started_at) ?? 0) - (parseIsoToMs(left.updated_at ?? left.started_at) ?? 0))
|
||||
|
||||
if (works.length > 0) {
|
||||
return works[0] ?? null
|
||||
}
|
||||
|
||||
return state.session_ids.includes(sessionId) ? buildWorkFromMirror(state) : null
|
||||
return state.session_ids.includes(normalizedSessionId) ? buildWorkFromMirror(state) : null
|
||||
}
|
||||
|
||||
export function getWorkResumeOptions(directory: string): BoulderWorkResumeOption[] {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { BoulderSessionOrigin, BoulderState, BoulderWorkState } from "../types"
|
||||
import { getBoulderWorks, readBoulderState } from "./read-state"
|
||||
import { nowIsoString, projectWorkToMirror } from "./shared"
|
||||
import { normalizeSessionId, nowIsoString, projectWorkToMirror } from "./shared"
|
||||
import { writeBoulderState } from "./write-state"
|
||||
|
||||
export function appendSessionId(
|
||||
@@ -8,9 +8,10 @@ export function appendSessionId(
|
||||
sessionId: string,
|
||||
origin: "direct" | "appended" = "direct",
|
||||
): BoulderState | null {
|
||||
const normalizedSessionId = normalizeSessionId(sessionId)
|
||||
const activeWorkId = readBoulderState(directory)?.active_work_id
|
||||
if (activeWorkId) {
|
||||
return appendSessionIdForWork(directory, activeWorkId, sessionId, origin)
|
||||
return appendSessionIdForWork(directory, activeWorkId, normalizedSessionId, origin)
|
||||
}
|
||||
|
||||
const state = readBoulderState(directory)
|
||||
@@ -22,15 +23,15 @@ export function appendSessionId(
|
||||
state.session_origins = {}
|
||||
}
|
||||
|
||||
if (!state.session_ids?.includes(sessionId)) {
|
||||
if (!state.session_ids?.includes(normalizedSessionId)) {
|
||||
if (!Array.isArray(state.session_ids)) {
|
||||
state.session_ids = []
|
||||
}
|
||||
|
||||
const originalSessionIds = [...state.session_ids]
|
||||
const originalSessionOrigins = { ...state.session_origins }
|
||||
state.session_ids.push(sessionId)
|
||||
state.session_origins[sessionId] = origin
|
||||
state.session_ids.push(normalizedSessionId)
|
||||
state.session_origins[normalizedSessionId] = origin
|
||||
if (writeBoulderState(directory, state)) {
|
||||
return state
|
||||
}
|
||||
@@ -40,8 +41,8 @@ export function appendSessionId(
|
||||
return null
|
||||
}
|
||||
|
||||
if (!state.session_origins[sessionId]) {
|
||||
state.session_origins[sessionId] = origin
|
||||
if (!state.session_origins[normalizedSessionId]) {
|
||||
state.session_origins[normalizedSessionId] = origin
|
||||
if (!writeBoulderState(directory, state)) {
|
||||
return null
|
||||
}
|
||||
@@ -56,6 +57,7 @@ export function appendSessionIdForWork(
|
||||
sessionId: string,
|
||||
origin: BoulderSessionOrigin = "direct",
|
||||
): BoulderState | null {
|
||||
const normalizedSessionId = normalizeSessionId(sessionId)
|
||||
const state = readBoulderState(directory)
|
||||
if (!state) {
|
||||
return null
|
||||
@@ -69,10 +71,10 @@ export function appendSessionIdForWork(
|
||||
|
||||
const updatedWork: BoulderWorkState = {
|
||||
...targetWork,
|
||||
session_ids: targetWork.session_ids.includes(sessionId)
|
||||
session_ids: targetWork.session_ids.includes(normalizedSessionId)
|
||||
? [...targetWork.session_ids]
|
||||
: [...targetWork.session_ids, sessionId],
|
||||
session_origins: { ...(targetWork.session_origins ?? {}), [sessionId]: origin },
|
||||
: [...targetWork.session_ids, normalizedSessionId],
|
||||
session_origins: { ...(targetWork.session_origins ?? {}), [normalizedSessionId]: origin },
|
||||
updated_at: nowIsoString(),
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,18 @@ import type { BoulderState, BoulderWorkState, BoulderWorkStatus } from "../types
|
||||
|
||||
export const RESERVED_KEYS = new Set(["__proto__", "prototype", "constructor"])
|
||||
|
||||
type SessionPlatform = "codex" | "opencode"
|
||||
|
||||
const SESSION_ID_PREFIX_PATTERN = /^(codex|opencode):/
|
||||
|
||||
export function normalizeSessionId(sessionId: string, platform: SessionPlatform = "opencode"): string {
|
||||
if (SESSION_ID_PREFIX_PATTERN.test(sessionId)) {
|
||||
return sessionId
|
||||
}
|
||||
|
||||
return `${platform}:${sessionId}`
|
||||
}
|
||||
|
||||
export function nowIsoString(): string {
|
||||
return new Date().toISOString()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { BoulderState, BoulderWorkState, TaskSessionState } from "../types"
|
||||
import { getBoulderWorks, readBoulderState } from "./read-state"
|
||||
import { getElapsedMs, nowIsoString, projectWorkToMirror, RESERVED_KEYS } from "./shared"
|
||||
import { getElapsedMs, normalizeSessionId, nowIsoString, projectWorkToMirror, RESERVED_KEYS } from "./shared"
|
||||
import { writeBoulderState } from "./write-state"
|
||||
|
||||
export function upsertTaskSessionState(
|
||||
@@ -24,12 +24,13 @@ export function upsertTaskSessionState(
|
||||
return null
|
||||
}
|
||||
|
||||
const normalizedSessionId = normalizeSessionId(input.sessionId)
|
||||
const taskSessions = state.task_sessions ?? {}
|
||||
taskSessions[input.taskKey] = {
|
||||
task_key: input.taskKey,
|
||||
task_label: input.taskLabel,
|
||||
task_title: input.taskTitle,
|
||||
session_id: input.sessionId,
|
||||
session_id: normalizedSessionId,
|
||||
...(input.agent !== undefined ? { agent: input.agent } : {}),
|
||||
...(input.category !== undefined ? { category: input.category } : {}),
|
||||
updated_at: nowIsoString(),
|
||||
@@ -66,12 +67,13 @@ export function upsertTaskSessionStateForWork(
|
||||
return null
|
||||
}
|
||||
|
||||
const normalizedSessionId = normalizeSessionId(input.sessionId)
|
||||
const previousTaskSession = targetWork.task_sessions?.[input.taskKey]
|
||||
const nextTaskSession: TaskSessionState = {
|
||||
task_key: input.taskKey,
|
||||
task_label: input.taskLabel,
|
||||
task_title: input.taskTitle,
|
||||
session_id: input.sessionId,
|
||||
session_id: normalizedSessionId,
|
||||
...(input.agent !== undefined ? { agent: input.agent } : {}),
|
||||
...(input.category !== undefined ? { category: input.category } : {}),
|
||||
...(previousTaskSession?.started_at !== undefined ? { started_at: previousTaskSession.started_at } : {}),
|
||||
@@ -116,7 +118,10 @@ export function startTaskTimer(
|
||||
startedAt?: string
|
||||
},
|
||||
): BoulderState | null {
|
||||
const nextState = upsertTaskSessionStateForWork(directory, workId, input)
|
||||
const nextState = upsertTaskSessionStateForWork(directory, workId, {
|
||||
...input,
|
||||
sessionId: normalizeSessionId(input.sessionId),
|
||||
})
|
||||
if (!nextState) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { BoulderState, BoulderWorkState } from "../types"
|
||||
import { getBoulderFilePath } from "./path"
|
||||
import { getPlanName } from "./plan-progress"
|
||||
import { getBoulderWorks, readBoulderState } from "./read-state"
|
||||
import { getElapsedMs, nowIsoString, projectWorkToMirror } from "./shared"
|
||||
import { getElapsedMs, normalizeSessionId, nowIsoString, projectWorkToMirror } from "./shared"
|
||||
|
||||
export function writeBoulderState(directory: string, state: BoulderState): boolean {
|
||||
const filePath = getBoulderFilePath(directory)
|
||||
@@ -67,6 +67,7 @@ export function generateWorkId(planName: string): string {
|
||||
|
||||
export function createBoulderState(planPath: string, sessionId: string, agent?: string, worktreePath?: string): BoulderState {
|
||||
const startedAt = nowIsoString()
|
||||
const normalizedSessionId = normalizeSessionId(sessionId)
|
||||
const workId = generateWorkId(getPlanName(planPath))
|
||||
const work: BoulderWorkState = {
|
||||
work_id: workId,
|
||||
@@ -75,8 +76,8 @@ export function createBoulderState(planPath: string, sessionId: string, agent?:
|
||||
status: "active",
|
||||
started_at: startedAt,
|
||||
updated_at: startedAt,
|
||||
session_ids: [sessionId],
|
||||
session_origins: { [sessionId]: "direct" },
|
||||
session_ids: [normalizedSessionId],
|
||||
session_origins: { [normalizedSessionId]: "direct" },
|
||||
...(agent !== undefined ? { agent } : {}),
|
||||
...(worktreePath !== undefined ? { worktree_path: worktreePath } : {}),
|
||||
task_sessions: {},
|
||||
@@ -90,8 +91,8 @@ export function createBoulderState(planPath: string, sessionId: string, agent?:
|
||||
started_at: startedAt,
|
||||
status: "active",
|
||||
updated_at: startedAt,
|
||||
session_ids: [sessionId],
|
||||
session_origins: { [sessionId]: "direct" },
|
||||
session_ids: [normalizedSessionId],
|
||||
session_origins: { [normalizedSessionId]: "direct" },
|
||||
plan_name: getPlanName(planPath),
|
||||
task_sessions: {},
|
||||
...(agent !== undefined ? { agent } : {}),
|
||||
@@ -132,6 +133,7 @@ export function addBoulderWork(
|
||||
|
||||
const workId = generateWorkId(getPlanName(input.planPath))
|
||||
const startedAt = input.startedAt ?? nowIsoString()
|
||||
const normalizedSessionId = normalizeSessionId(input.sessionId)
|
||||
const nextWork: BoulderWorkState = {
|
||||
work_id: workId,
|
||||
active_plan: input.planPath,
|
||||
@@ -139,8 +141,8 @@ export function addBoulderWork(
|
||||
status: "active",
|
||||
started_at: startedAt,
|
||||
updated_at: startedAt,
|
||||
session_ids: [input.sessionId],
|
||||
session_origins: { [input.sessionId]: "direct" },
|
||||
session_ids: [normalizedSessionId],
|
||||
session_origins: { [normalizedSessionId]: "direct" },
|
||||
...(input.agent !== undefined ? { agent: input.agent } : {}),
|
||||
...(input.worktreePath !== undefined ? { worktree_path: input.worktreePath } : {}),
|
||||
task_sessions: {},
|
||||
|
||||
Reference in New Issue
Block a user