From 7be9839ca163626646acde4a75d8452000288d4b Mon Sep 17 00:00:00 2001 From: HiranoMasaaki Date: Mon, 19 Jan 2026 09:02:29 +0900 Subject: [PATCH] fix(tui): fix event accumulation across multiple runs in start command When using `perstack start` with interactive tools, events from all runs in a job were not being loaded because `getAllEventContentsForJob` relied on `run-setting.json` files which are not always present. Added `getRunIdsByJobId` function that scans the runs directory directly, ensuring all runs are discovered even when `run-setting.json` is missing. Co-Authored-By: Claude Opus 4.5 --- .changeset/fix-event-accumulation.md | 13 +++++++++++++ apps/perstack/src/lib/run-manager.ts | 7 ++++--- packages/storages/filesystem/src/event.ts | 11 +++++++++++ packages/storages/filesystem/src/index.ts | 2 +- 4 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 .changeset/fix-event-accumulation.md diff --git a/.changeset/fix-event-accumulation.md b/.changeset/fix-event-accumulation.md new file mode 100644 index 00000000..3180f509 --- /dev/null +++ b/.changeset/fix-event-accumulation.md @@ -0,0 +1,13 @@ +--- +"perstack": patch +"@perstack/filesystem-storage": patch +--- + +fix(tui): fix event accumulation across multiple runs in start command + +When using `perstack start` with interactive tools, events from all runs in a job +were not being loaded because `getAllEventContentsForJob` relied on `run-setting.json` +files which are not always present. + +Added `getRunIdsByJobId` function that scans the runs directory directly, ensuring +all runs are discovered even when `run-setting.json` is missing. diff --git a/apps/perstack/src/lib/run-manager.ts b/apps/perstack/src/lib/run-manager.ts index f04da46a..7481e92c 100644 --- a/apps/perstack/src/lib/run-manager.ts +++ b/apps/perstack/src/lib/run-manager.ts @@ -4,6 +4,7 @@ import { checkpointSchema } from "@perstack/core" import { getCheckpointPath, getEventsByRun, + getRunIdsByJobId, getAllJobs as runtimeGetAllJobs, getAllRuns as runtimeGetAllRuns, getCheckpointsByJobId as runtimeGetCheckpointsByJobId, @@ -119,10 +120,10 @@ export function getEventContents(jobId: string, runId: string, maxStepNumber?: n } export function getAllEventContentsForJob(jobId: string, maxStepNumber?: number): RunEvent[] { - const runs = getRunsByJobId(jobId) + const runIds = getRunIdsByJobId(jobId) const allEvents: RunEvent[] = [] - for (const run of runs) { - const events = runtimeGetEventContents(jobId, run.runId, maxStepNumber) + for (const runId of runIds) { + const events = runtimeGetEventContents(jobId, runId, maxStepNumber) allEvents.push(...events) } return allEvents.sort((a, b) => a.timestamp - b.timestamp) diff --git a/packages/storages/filesystem/src/event.ts b/packages/storages/filesystem/src/event.ts index d4af827c..82ccee0c 100644 --- a/packages/storages/filesystem/src/event.ts +++ b/packages/storages/filesystem/src/event.ts @@ -2,6 +2,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs" import { mkdir, writeFile } from "node:fs/promises" import path from "node:path" import type { RunEvent } from "@perstack/core" +import { getJobDir } from "./job.js" import { defaultGetRunDir as getRunDir } from "./run-setting.js" export async function defaultStoreEvent(event: RunEvent): Promise { @@ -53,3 +54,13 @@ export function getEventContents(jobId: string, runId: string, maxStepNumber?: n } return events } + +export function getRunIdsByJobId(jobId: string): string[] { + const runsDir = path.resolve(getJobDir(jobId), "runs") + if (!existsSync(runsDir)) { + return [] + } + return readdirSync(runsDir, { withFileTypes: true }) + .filter((dir) => dir.isDirectory()) + .map((dir) => dir.name) +} diff --git a/packages/storages/filesystem/src/index.ts b/packages/storages/filesystem/src/index.ts index 4cfebbff..0836e672 100644 --- a/packages/storages/filesystem/src/index.ts +++ b/packages/storages/filesystem/src/index.ts @@ -5,7 +5,7 @@ export { getCheckpointPath, getCheckpointsByJobId, } from "./checkpoint.js" -export { defaultStoreEvent, getEventContents, getEventsByRun } from "./event.js" +export { defaultStoreEvent, getEventContents, getEventsByRun, getRunIdsByJobId } from "./event.js" export { FileSystemStorage, type FileSystemStorageConfig } from "./filesystem-storage.js" export { createInitialJob,