test(boulder-state): batch 44 (4 files)

This commit is contained in:
YeonGyu-Kim
2026-05-30 19:12:07 +09:00
parent e11d24d141
commit 1806ff7cee
4 changed files with 196 additions and 26 deletions
@@ -166,5 +166,35 @@ describe("TaskHistory", () => {
expect(result).not.toContain("\n\n")
expect(result).toContain("Line1 Line2 Line3")
})
it("bounds compaction output when many task descriptions are long", () => {
//#given
const history = new TaskHistory()
const longDescription = "Inspect the same lengthy failure context. ".repeat(200)
//#when
for (let i = 0; i < 100; i++) {
history.record("parent-1", {
id: `t${i}`,
agent: "explore",
description: `${longDescription} task ${i}`,
status: "completed",
category: "quick",
sessionID: `ses_child_${i}`,
})
}
const result = history.formatForCompaction("parent-1")
//#then
expect(result).not.toBeNull()
if (result === null) {
throw new Error("Expected compaction history")
}
expect(result.length).toBeLessThanOrEqual(6_000)
expect(result).toContain("older delegated sessions omitted")
expect(result).toContain("[truncated]")
expect(result).toContain("`t99`")
expect(result).not.toContain("`t0`")
})
})
})
+76 -11
View File
@@ -1,6 +1,9 @@
import type { BackgroundTaskStatus } from "./types"
const MAX_ENTRIES_PER_PARENT = 100
const MAX_COMPACTION_ENTRIES = 20
const MAX_COMPACTION_DESCRIPTION_CHARS = 240
const MAX_COMPACTION_TOTAL_CHARS = 6_000
export interface TaskHistoryEntry {
id: string
@@ -62,18 +65,80 @@ export class TaskHistory {
const list = this.getByParentSession(parentSessionID)
if (list.length === 0) return null
const lines = list.map((e) => {
const desc = e.description?.replace(/[\n\r]+/g, " ").trim() ?? ""
const parts = [
`- **${e.agent}**`,
e.category ? `[${e.category}]` : null,
`(${e.status})`,
`: ${desc}`,
e.sessionID ? ` | session: \`${e.sessionID}\`` : null,
]
return parts.filter(Boolean).join("")
})
const recent = list.slice(-MAX_COMPACTION_ENTRIES)
const olderOmittedCount = list.length - recent.length
const lines: string[] = []
if (olderOmittedCount > 0) {
lines.push(`- ${olderOmittedCount} older delegated sessions omitted from compaction summary.`)
}
let budgetOmittedCount = 0
for (let i = recent.length - 1; i >= 0; i--) {
const entry = recent[i]
if (!entry) continue
const line = formatCompactionEntry(entry)
if (!appendWithinBudget(lines, line, MAX_COMPACTION_TOTAL_CHARS)) {
budgetOmittedCount = i + 1
break
}
}
if (budgetOmittedCount > 0) {
appendBudgetSummary(lines, budgetOmittedCount)
}
return lines.join("\n")
}
}
function formatCompactionEntry(entry: TaskHistoryEntry): string {
const description = compactInline(entry.description, MAX_COMPACTION_DESCRIPTION_CHARS)
const parts = [
`- **${compactInline(entry.agent, 80)}**`,
entry.category ? `[${compactInline(entry.category, 60)}]` : "",
`(${entry.status})`,
` task_id: \`${compactInline(entry.id, 120)}\``,
description ? `: ${description}` : "",
entry.sessionID ? ` | session: \`${compactInline(entry.sessionID, 120)}\`` : "",
]
return parts.filter((part) => part.length > 0).join("")
}
function compactInline(value: string, maxChars: number): string {
const normalized = value.replace(/[\n\r]+/g, " ").replace(/\s+/g, " ").replace(/`/g, "'").trim()
if (normalized.length <= maxChars) {
return normalized
}
const suffix = "... [truncated]"
const keepChars = Math.max(0, maxChars - suffix.length)
return `${normalized.slice(0, keepChars).trimEnd()}${suffix}`
}
function appendWithinBudget(lines: string[], line: string, maxChars: number): boolean {
const currentLength = joinedLength(lines)
const separatorLength = lines.length === 0 ? 0 : 1
if (currentLength + separatorLength + line.length > maxChars) {
return false
}
lines.push(line)
return true
}
function appendBudgetSummary(lines: string[], omittedCount: number): void {
const summary = `- ${omittedCount} delegated sessions omitted to stay within compaction budget.`
if (appendWithinBudget(lines, summary, MAX_COMPACTION_TOTAL_CHARS)) {
return
}
while (lines.length > 0 && !appendWithinBudget(lines, summary, MAX_COMPACTION_TOTAL_CHARS)) {
lines.pop()
}
}
function joinedLength(lines: readonly string[]): number {
return lines.reduce((total, line, index) => total + line.length + (index === 0 ? 0 : 1), 0)
}
+89 -15
View File
@@ -20,7 +20,6 @@ import {
getPlanProgress,
getPlanName,
createBoulderState,
findPrometheusPlans,
getTaskSessionState,
resolveBoulderPlanPath,
resolveBoulderPlanPathForWork,
@@ -74,7 +73,7 @@ describe("boulder-state", () => {
expect(writeSucceeded).toBe(true)
expect(roundTripState?.active_plan).toBe(legacyRawState.active_plan)
expect(roundTripState?.started_at).toBe(legacyRawState.started_at)
expect(roundTripState?.session_ids).toEqual(legacyRawState.session_ids)
expect(roundTripState?.session_ids).toEqual(["opencode:legacy-session"])
expect(roundTripState?.plan_name).toBe(legacyRawState.plan_name)
})
@@ -172,7 +171,7 @@ describe("boulder-state", () => {
const result = readBoulderState(TEST_DIR)
// then
expect(result?.session_origins).toEqual({ "session-1": "direct" })
expect(result?.session_origins).toEqual({ "opencode:session-1": "direct" })
})
test("should keep missing origins empty when multiple sessions are tracked", () => {
@@ -207,7 +206,7 @@ describe("boulder-state", () => {
// then
expect(result).not.toBeNull()
expect(result?.active_plan).toBe("/path/to/plan.md")
expect(result?.session_ids).toEqual(["session-1", "session-2"])
expect(result?.session_ids).toEqual(["opencode:session-1", "opencode:session-2"])
expect(result?.plan_name).toBe("my-plan")
})
@@ -267,7 +266,7 @@ describe("boulder-state", () => {
// then
expect(result).not.toBeNull()
expect(result?.session_ids).toEqual(["session-1", "session-2"])
expect(result?.session_ids).toEqual(["opencode:session-1", "opencode:session-2"])
})
test("should not duplicate existing session id", () => {
@@ -285,7 +284,7 @@ describe("boulder-state", () => {
const result = readBoulderState(TEST_DIR)
// then
expect(result?.session_ids).toEqual(["session-1"])
expect(result?.session_ids).toEqual(["opencode:session-1"])
})
test("should return null when no state exists", () => {
@@ -310,7 +309,7 @@ describe("boulder-state", () => {
//#then - should not crash and should contain the new session
expect(result).not.toBeNull()
expect(result!.session_ids).toContain("ses-new")
expect(result!.session_ids).toContain("opencode:ses-new")
})
test("should persist appended session origin when provided", () => {
@@ -328,8 +327,8 @@ describe("boulder-state", () => {
// then
expect(result?.session_origins).toEqual({
"session-1": "direct",
"session-2": "appended",
"opencode:session-1": "direct",
"opencode:session-2": "appended",
})
})
})
@@ -387,7 +386,7 @@ describe("boulder-state", () => {
// then
expect(result).not.toBeNull()
expect(result?.session_id).toBe("ses_task_123")
expect(result?.session_id).toBe("opencode:ses_task_123")
expect(result?.task_title).toBe("Implement auth flow")
expect(result?.agent).toBe("sisyphus-junior")
expect(result?.category).toBe("deep")
@@ -422,7 +421,7 @@ describe("boulder-state", () => {
const result = getTaskSessionState(TEST_DIR, "todo:1")
// then
expect(result?.session_id).toBe("ses_new")
expect(result?.session_id).toBe("opencode:ses_new")
})
})
@@ -542,7 +541,7 @@ describe("boulder-state", () => {
// then
expect(updated).not.toBeNull()
const taskSession = updated?.works?.[workId]?.task_sessions?.["todo:1"]
expect(taskSession?.session_id).toBe("task-session-b")
expect(taskSession?.session_id).toBe("opencode:task-session-b")
expect(taskSession?.started_at).toBe("2026-01-01T00:00:00.000Z")
})
})
@@ -993,7 +992,7 @@ describe("boulder-state", () => {
// then
expect(state.active_plan).toBe(planPath)
expect(state.session_ids).toEqual([sessionId])
expect(state.session_ids).toEqual(["opencode:ses-abc123"])
expect(state.plan_name).toBe("auth-refactor")
expect(state.started_at).toBeDefined()
})
@@ -1010,7 +1009,7 @@ describe("boulder-state", () => {
//#then - state should include the agent field
expect(state.agent).toBe("atlas")
expect(state.active_plan).toBe(planPath)
expect(state.session_ids).toEqual([sessionId])
expect(state.session_ids).toEqual(["opencode:ses-xyz789"])
expect(state.plan_name).toBe("feature")
})
@@ -1023,7 +1022,7 @@ describe("boulder-state", () => {
const state = createBoulderState(planPath, sessionId)
// then
expect(state.session_origins).toEqual({ [sessionId]: "direct" })
expect(state.session_origins).toEqual({ "opencode:ses-origin": "direct" })
})
test("should allow agent to be undefined", () => {
@@ -1080,4 +1079,79 @@ describe("boulder-state", () => {
expect(resolvedPath).toBe(planPath)
})
})
describe("platform-prefixed session ids", () => {
test("#given a fresh state with raw session id #when read back #then opencode prefix is stored", () => {
// given
const planPath = join(TEST_DIR, ".omo", "plans", "raw-session.md")
// when
const state = createBoulderState(planPath, "raw-sess", "atlas", undefined)
writeBoulderState(TEST_DIR, state)
const readBack = readBoulderState(TEST_DIR)
// then
expect(readBack?.session_ids).toEqual(["opencode:raw-sess"])
})
test("#given a fresh state with codex session id #when read back #then codex prefix is preserved", () => {
// given
const planPath = join(TEST_DIR, ".omo", "plans", "codex-session.md")
// when
const state = createBoulderState(planPath, "codex:raw-sess", "atlas", undefined)
writeBoulderState(TEST_DIR, state)
const readBack = readBoulderState(TEST_DIR)
// then
expect(readBack?.session_ids).toEqual(["codex:raw-sess"])
})
test("#given a legacy boulder file with bare session id #when read #then opencode prefix is migrated", () => {
// given
const boulderFile = join(OMO_DIR, "boulder.json")
writeFileSync(boulderFile, JSON.stringify({
active_plan: "/path/to/legacy.md",
started_at: "2026-01-01T00:00:00Z",
session_ids: ["legacy-bare-id"],
plan_name: "legacy",
}))
// when
const state = readBoulderState(TEST_DIR)
// then
expect(state?.session_ids).toEqual(["opencode:legacy-bare-id"])
})
test("#given existing prefixed session #when appending raw session #then appended id receives opencode prefix", () => {
// given
writeBoulderState(TEST_DIR, {
active_plan: "/path/to/plan.md",
started_at: "2026-01-01T00:00:00Z",
session_ids: ["opencode:first"],
plan_name: "plan",
})
// when
appendSessionId(TEST_DIR, "another-raw")
const state = readBoulderState(TEST_DIR)
// then
expect(state?.session_ids).toEqual(["opencode:first", "opencode:another-raw"])
})
test("#given stored work with prefixed session #when looking up by raw id #then matching work is returned", () => {
// given
const planPath = join(TEST_DIR, ".omo", "plans", "lookup.md")
const state = createBoulderState(planPath, "opencode:raw-id", "atlas", undefined)
writeBoulderState(TEST_DIR, state)
// when
const work = getWorkForSession(TEST_DIR, "raw-id")
// then
expect(work?.session_ids).toEqual(["opencode:raw-id"])
})
})
})
+1
View File
@@ -18,6 +18,7 @@ export {
getWorkByPlanName,
getWorkForSession,
getWorkResumeOptions,
normalizeSessionId,
readBoulderState,
resolveBoulderPlanPath,
resolveBoulderPlanPathForWork,