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