feat(start-work): use getWorkResumeOptions for multi-work resume selection

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-05-11 13:40:57 +09:00
parent 29b44fffd0
commit d6f4199cab
2 changed files with 302 additions and 7 deletions
@@ -0,0 +1,171 @@
/// <reference types="bun-types" />
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test"
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
import { randomUUID } from "node:crypto"
import { join } from "node:path"
import { tmpdir } from "node:os"
import { buildStartWorkContextInfo } from "./context-info-builder"
import {
addBoulderWork,
createBoulderState,
getBoulderFilePath,
getWorkByPlanName,
readBoulderState,
writeBoulderState,
} from "../../features/boulder-state"
import * as boulderState from "../../features/boulder-state"
describe("buildStartWorkContextInfo", () => {
let testDirectory = ""
function createPluginInput() {
return {
directory: testDirectory,
} as never
}
function writePlan(planName: string, content: string): string {
const plansDirectory = join(testDirectory, ".sisyphus", "plans")
mkdirSync(plansDirectory, { recursive: true })
const planPath = join(plansDirectory, `${planName}.md`)
writeFileSync(planPath, content)
return planPath
}
function readExistingState() {
return readBoulderState(testDirectory)
}
beforeEach(() => {
testDirectory = join(tmpdir(), `context-info-builder-${randomUUID()}`)
mkdirSync(testDirectory, { recursive: true })
})
afterEach(() => {
if (existsSync(testDirectory)) {
rmSync(testDirectory, { recursive: true, force: true })
}
})
test("lists multiple active works and asks agent to choose resume vs new when no explicit plan", () => {
// given
const clearSpy = spyOn(boulderState, "clearBoulderState")
const planAPath = writePlan("plan-alpha", "## TODOs\n- [ ] 1. Alpha")
const planBPath = writePlan("plan-beta", "## TODOs\n- [ ] 1. Beta")
const initialState = createBoulderState(planAPath, "session-a", "atlas", "/tmp/worktree-a")
writeBoulderState(testDirectory, initialState)
addBoulderWork(testDirectory, {
planPath: planBPath,
sessionId: "session-b",
agent: "atlas",
worktreePath: "/tmp/worktree-b",
})
// when
const contextInfo = buildStartWorkContextInfo({
ctx: createPluginInput(),
explicitPlanName: null,
existingState: readExistingState(),
sessionId: "session-current",
timestamp: "2026-05-11T00:00:00.000Z",
activeAgent: "atlas",
worktreePath: undefined,
worktreeBlock: "",
})
// then
expect(contextInfo).toContain("plan-alpha")
expect(contextInfo).toContain("plan-beta")
expect(contextInfo).toContain("Use the Question tool")
expect(clearSpy).toHaveBeenCalledTimes(0)
})
test("auto-resumes when exactly one active work exists and no explicit plan", () => {
// given
const clearSpy = spyOn(boulderState, "clearBoulderState")
const planPath = writePlan("single-active-plan", "## TODOs\n- [ ] 1. Single task")
const initialState = createBoulderState(planPath, "session-a", "atlas", "/tmp/worktree-single")
writeBoulderState(testDirectory, initialState)
// when
const contextInfo = buildStartWorkContextInfo({
ctx: createPluginInput(),
explicitPlanName: null,
existingState: readExistingState(),
sessionId: "session-current",
timestamp: "2026-05-11T00:00:00.000Z",
activeAgent: "atlas",
worktreePath: undefined,
worktreeBlock: "",
})
// then
expect(contextInfo).toContain("RESUMING existing work")
expect(contextInfo).toContain("single-active-plan")
expect(contextInfo).not.toContain("Use the Question tool")
expect(clearSpy).toHaveBeenCalledTimes(0)
})
test("explicit plan selects matching work only and never clears boulder state", () => {
// given
const clearSpy = spyOn(boulderState, "clearBoulderState")
const planAPath = writePlan("explicit-plan-a", "## TODOs\n- [ ] 1. A")
const planBPath = writePlan("explicit-plan-b", "## TODOs\n- [ ] 1. B")
const initialState = createBoulderState(planAPath, "session-a", "atlas", "/tmp/worktree-a")
writeBoulderState(testDirectory, initialState)
addBoulderWork(testDirectory, {
planPath: planBPath,
sessionId: "session-b",
agent: "atlas",
worktreePath: "/tmp/worktree-b",
})
// when
const contextInfo = buildStartWorkContextInfo({
ctx: createPluginInput(),
explicitPlanName: "explicit-plan-a",
existingState: readExistingState(),
sessionId: "session-current",
timestamp: "2026-05-11T00:00:00.000Z",
activeAgent: "atlas",
worktreePath: "/tmp/worktree-a",
worktreeBlock: "",
})
// then
expect(contextInfo).toContain("explicit-plan-a")
expect(contextInfo).not.toContain("explicit-plan-b")
expect(clearSpy).toHaveBeenCalledTimes(0)
const selectedWork = getWorkByPlanName(testDirectory, "explicit-plan-a", { worktreePath: "/tmp/worktree-a" })
const nextState = readBoulderState(testDirectory)
expect(selectedWork).not.toBeNull()
expect(nextState?.active_work_id).toBe(selectedWork?.work_id)
})
test("falls back to auto-select latest plan when no works exist", () => {
// given
const clearSpy = spyOn(boulderState, "clearBoulderState")
const coldStartPlanPath = writePlan("cold-start-plan", "## TODOs\n- [ ] 1. Cold start")
// when
const contextInfo = buildStartWorkContextInfo({
ctx: createPluginInput(),
explicitPlanName: null,
existingState: null,
sessionId: "session-current",
timestamp: "2026-05-11T00:00:00.000Z",
activeAgent: "atlas",
worktreePath: undefined,
worktreeBlock: "",
})
// then
expect(contextInfo).toContain("Auto-Selected Plan")
expect(contextInfo).toContain("cold-start-plan")
expect(contextInfo).toContain(coldStartPlanPath)
expect(existsSync(getBoulderFilePath(testDirectory))).toBe(true)
expect(clearSpy).toHaveBeenCalledTimes(0)
})
})
+131 -7
View File
@@ -1,13 +1,17 @@
import { statSync } from "node:fs"
import {
appendSessionId,
clearBoulderState,
addBoulderWork,
createBoulderState,
findPrometheusPlans,
getActiveWorks,
getPlanName,
getPlanProgress,
getWorkByPlanName,
getWorkResumeOptions,
readBoulderState,
resolveBoulderPlanPath,
selectActiveWork,
writeBoulderState,
} from "../../features/boulder-state"
import { log } from "../../shared/logger"
@@ -99,9 +103,73 @@ Ask the user which plan to work on.`
No incomplete plans available. Create a new plan using the Prometheus agent.`
}
function formatElapsedHuman(elapsedMs: number | undefined): string {
if (typeof elapsedMs !== "number" || elapsedMs <= 0) {
return "running"
}
const totalSeconds = Math.floor(elapsedMs / 1000)
const seconds = totalSeconds % 60
const totalMinutes = Math.floor(totalSeconds / 60)
const minutes = totalMinutes % 60
const hours = Math.floor(totalMinutes / 60)
if (hours > 0) {
return `${hours}h ${minutes}m ${seconds}s`
}
if (minutes > 0) {
return `${minutes}m ${seconds}s`
}
return `${seconds}s`
}
function buildMultipleActiveWorksContext(params: {
resumeOptions: ReturnType<typeof getWorkResumeOptions>
sessionId: string
timestamp: string
}): string {
const { resumeOptions, sessionId, timestamp } = params
const optionList = resumeOptions
.map((option, index) => `${index + 1}. ${option.plan_name} - ${option.progress.completed}/${option.progress.total} (${option.progress.total === 0 ? 0 : Math.floor((option.progress.completed / option.progress.total) * 100)}%) - elapsed: ${formatElapsedHuman(option.elapsed_ms)} - worktree: ${option.worktree_path ?? "current directory"} - sessions: ${option.session_count}`)
.join("\n")
return `
<system-reminder>
## Multiple Active Works Found
Current Time: ${timestamp}
Session ID: ${sessionId}
${optionList}
Use the Question tool to ask the user which plan to resume.
- If the user chooses one option, run /start-work {plan-name} for that plan.
- If the user chooses to start a new plan, proceed with cold-start auto-selection flow.
</system-reminder>`
}
function createNewWorkOrInitialize(params: {
directory: string
planPath: string
sessionId: string
activeAgent: string
worktreePath: string | undefined
}): void {
const { directory, planPath, sessionId, activeAgent, worktreePath } = params
const created = addBoulderWork(directory, {
planPath,
sessionId,
agent: activeAgent,
worktreePath,
})
if (!created) {
const initializedState = createBoulderState(planPath, sessionId, activeAgent, worktreePath)
writeBoulderState(directory, initializedState)
}
}
function buildExplicitPlanContext(params: {
explicitPlanName: string
existingState: ReturnType<typeof readBoulderState>
sessionId: string
timestamp: string
activeAgent: string
@@ -109,9 +177,24 @@ function buildExplicitPlanContext(params: {
worktreeBlock: string
directory: string
}): string {
const { explicitPlanName, existingState, sessionId, timestamp, activeAgent, worktreePath, worktreeBlock, directory } = params
const { explicitPlanName, sessionId, timestamp, activeAgent, worktreePath, worktreeBlock, directory } = params
log(`[${HOOK_NAME}] Explicit plan name requested: ${explicitPlanName}`, { sessionID: sessionId })
const matchedWork = getWorkByPlanName(directory, explicitPlanName, { worktreePath })
if (matchedWork) {
const selectedState = selectActiveWork(directory, matchedWork.work_id)
if (selectedState) {
return buildExistingSessionContext({
existingState: selectedState,
sessionId,
activeAgent,
worktreePath,
worktreeBlock,
directory,
})
}
}
const allPlans = findPrometheusPlans(directory)
const matchedPlan = findPlanByName(allPlans, explicitPlanName)
if (!matchedPlan) {
@@ -127,9 +210,13 @@ function buildExplicitPlanContext(params: {
All ${progress.total} tasks are done. Create a new plan using the Prometheus agent.`
}
if (existingState) {
clearBoulderState(directory)
}
createNewWorkOrInitialize({
directory,
planPath: matchedPlan,
sessionId,
activeAgent,
worktreePath,
})
return buildAutoSelectedPlanContext({
planPath: matchedPlan,
@@ -287,11 +374,48 @@ export function buildStartWorkContextInfo(params: {
}): string {
const { ctx, explicitPlanName, existingState, sessionId, timestamp, activeAgent, worktreePath, worktreeBlock } = params
const resumeOptions = getWorkResumeOptions(ctx.directory)
.filter((option) => option.status === "active" || option.status === "paused")
if (!explicitPlanName && resumeOptions.length > 1) {
return buildMultipleActiveWorksContext({
resumeOptions,
sessionId,
timestamp,
})
}
if (!explicitPlanName && resumeOptions.length === 1) {
const onlyOption = resumeOptions[0]
const selectedState = selectActiveWork(ctx.directory, onlyOption.work_id)
if (selectedState) {
return buildExistingSessionContext({
existingState: selectedState,
sessionId,
activeAgent,
worktreePath,
worktreeBlock,
directory: ctx.directory,
})
}
}
if (!explicitPlanName && resumeOptions.length === 0 && getActiveWorks(ctx.directory).length === 0) {
return buildPlanDiscoveryContext({
contextInfo: "",
sessionId,
timestamp,
activeAgent,
worktreePath,
worktreeBlock,
directory: ctx.directory,
})
}
let contextInfo = ""
if (explicitPlanName) {
contextInfo = buildExplicitPlanContext({
explicitPlanName,
existingState,
sessionId,
timestamp,
activeAgent,