From 246e0dca8043e3a5b96328bfe70b564cf1fe2083 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Mon, 11 May 2026 13:24:28 +0900 Subject: [PATCH] feat(boulder-state): add BoulderWorkState and timing fields to types Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/features/boulder-state/types.test.ts | 78 ++++++++++++++++++++++++ src/features/boulder-state/types.ts | 46 ++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 src/features/boulder-state/types.test.ts diff --git a/src/features/boulder-state/types.test.ts b/src/features/boulder-state/types.test.ts new file mode 100644 index 000000000..15d2ea10c --- /dev/null +++ b/src/features/boulder-state/types.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, test } from "bun:test" +import type { + BoulderSessionOrigin, + BoulderState, + BoulderTaskStatus, + BoulderWorkResumeOption, + BoulderWorkState, + BoulderWorkStatus, + PlanProgress, + TaskSessionState, +} from "./types" + +describe("boulder-state types", () => { + test("keeps legacy BoulderState assignable while allowing v2 fields", () => { + // given + const legacyState: BoulderState = { + active_plan: "/tmp/plan.md", + started_at: "2026-01-01T00:00:00.000Z", + session_ids: ["ses_1"], + plan_name: "plan", + } + + // when + const hasLegacyShape = legacyState.active_plan.length > 0 + + // then + expect(hasLegacyShape).toBe(true) + }) + + test("supports multi-work and timer fields", () => { + // given + const taskStatus: BoulderTaskStatus = "running" + const workStatus: BoulderWorkStatus = "active" + const origin: BoulderSessionOrigin = "direct" + + const taskSession: TaskSessionState = { + task_key: "todo:1", + task_label: "1", + task_title: "Do work", + session_id: "ses_task", + started_at: "2026-01-01T00:00:00.000Z", + ended_at: "2026-01-01T00:00:01.000Z", + elapsed_ms: 1000, + status: taskStatus, + updated_at: "2026-01-01T00:00:01.000Z", + } + + const work: BoulderWorkState = { + work_id: "plan-abc12345", + active_plan: "/tmp/plan.md", + plan_name: "plan", + status: workStatus, + started_at: "2026-01-01T00:00:00.000Z", + session_ids: ["ses_1"], + session_origins: { ses_1: origin }, + task_sessions: { "todo:1": taskSession }, + } + + const progress: PlanProgress = { total: 2, completed: 1, isComplete: false } + const resumeOption: BoulderWorkResumeOption = { + work_id: work.work_id, + plan_name: work.plan_name, + active_plan: work.active_plan, + status: "paused", + started_at: work.started_at, + updated_at: "2026-01-01T00:00:02.000Z", + session_count: 1, + progress, + is_current_mirror: false, + } + + // when + const combined = { taskSession, work, resumeOption } + + // then + expect(combined.resumeOption.progress.total).toBe(2) + }) +}) diff --git a/src/features/boulder-state/types.ts b/src/features/boulder-state/types.ts index f41bc1bf8..15ac41ab5 100644 --- a/src/features/boulder-state/types.ts +++ b/src/features/boulder-state/types.ts @@ -6,10 +6,17 @@ */ export interface BoulderState { + schema_version?: 2 + active_work_id?: string + works?: Record /** Absolute path to the active plan file */ active_plan: string /** ISO timestamp when work started */ started_at: string + ended_at?: string + elapsed_ms?: number + status?: BoulderWorkStatus + updated_at?: string /** Session IDs that have worked on this plan */ session_ids: string[] session_origins?: Record @@ -23,6 +30,26 @@ export interface BoulderState { task_sessions?: Record } +export type BoulderSessionOrigin = "direct" | "appended" +export type BoulderWorkStatus = "active" | "completed" | "paused" | "abandoned" +export type BoulderTaskStatus = "running" | "completed" | "cancelled" + +export interface BoulderWorkState { + work_id: string + active_plan: string + plan_name: string + status?: BoulderWorkStatus + started_at: string + ended_at?: string + elapsed_ms?: number + updated_at?: string + session_ids: string[] + session_origins?: Record + agent?: string + worktree_path?: string + task_sessions?: Record +} + export interface PlanProgress { /** Total number of checkboxes */ total: number @@ -45,10 +72,29 @@ export interface TaskSessionState { agent?: string /** Category associated with the task session, when known */ category?: string + started_at?: string + ended_at?: string + elapsed_ms?: number + status?: BoulderTaskStatus /** Last update timestamp */ updated_at: string } +export interface BoulderWorkResumeOption { + work_id: string + plan_name: string + active_plan: string + worktree_path?: string + status: BoulderWorkStatus + started_at: string + updated_at: string + ended_at?: string + elapsed_ms?: number + session_count: number + progress: PlanProgress + is_current_mirror: boolean +} + export interface TopLevelTaskRef { /** Stable identifier for the current top-level plan task */ key: string