@@ -13,12 +13,12 @@ import { getMidsceneRunSubDir } from '@midscene/shared/common';
1313import type { AgentOpt , IExecutionDump , IGroupedActionDump } from './types' ;
1414import { ExecutionDump } from './types' ;
1515
16- const sessionLockRetryDelayMs = 20 ;
17- const sessionLockTimeoutMs = 30_000 ;
18- const sessionLockStaleMs = 5 * 60_000 ;
19- const sessionLockSleepArray = new Int32Array ( new SharedArrayBuffer ( 4 ) ) ;
16+ const lockRetryDelayMs = 20 ;
17+ const lockTimeoutMs = 30_000 ;
18+ const lockStaleMs = 5 * 60_000 ;
19+ const lockSleepArray = new Int32Array ( new SharedArrayBuffer ( 4 ) ) ;
2020
21- export interface PersistedSession {
21+ export interface ExecutionSession {
2222 sessionId : string ;
2323 platform : string ;
2424 groupName : string ;
@@ -32,7 +32,7 @@ export interface PersistedSession {
3232 reportFilePath ?: string ;
3333}
3434
35- interface EnsureSessionInput {
35+ export interface EnsureExecutionSessionInput {
3636 sessionId : string ;
3737 platform : string ;
3838 groupName ?: string ;
@@ -47,11 +47,11 @@ function defaultGroupName(platform: string, sessionId: string): string {
4747}
4848
4949function normalizeSessionRecord (
50- session : Partial < PersistedSession > & {
50+ session : Partial < ExecutionSession > & {
5151 sessionId : string ;
5252 platform : string ;
5353 } ,
54- ) : PersistedSession {
54+ ) : ExecutionSession {
5555 return {
5656 sessionId : session . sessionId ,
5757 platform : session . platform ,
@@ -82,7 +82,7 @@ function orderedRootExecutionFiles(dir: string): string[] {
8282}
8383
8484function sleepSync ( ms : number ) : void {
85- Atomics . wait ( sessionLockSleepArray , 0 , 0 , ms ) ;
85+ Atomics . wait ( lockSleepArray , 0 , 0 , ms ) ;
8686}
8787
8888function validateSessionId ( sessionId : string ) : void {
@@ -114,10 +114,10 @@ function isAlreadyExistsError(error: unknown): error is NodeJS.ErrnoException {
114114 ) ;
115115}
116116
117- function withSessionLock < T > ( sessionId : string , fn : ( ) => T ) : T {
117+ function withLock < T > ( sessionId : string , fn : ( ) => T ) : T {
118118 const dir = sessionDirPath ( sessionId ) ;
119119 const lockDir = sessionLockDir ( sessionId ) ;
120- const lockDeadline = Date . now ( ) + sessionLockTimeoutMs ;
120+ const lockDeadline = Date . now ( ) + lockTimeoutMs ;
121121
122122 mkdirSync ( dir , { recursive : true } ) ;
123123
@@ -131,7 +131,7 @@ function withSessionLock<T>(sessionId: string, fn: () => T): T {
131131 }
132132
133133 try {
134- if ( Date . now ( ) - statSync ( lockDir ) . mtimeMs > sessionLockStaleMs ) {
134+ if ( Date . now ( ) - statSync ( lockDir ) . mtimeMs > lockStaleMs ) {
135135 rmSync ( lockDir , { recursive : true , force : true } ) ;
136136 continue ;
137137 }
@@ -143,7 +143,7 @@ function withSessionLock<T>(sessionId: string, fn: () => T): T {
143143 throw new Error ( `Timed out waiting for session lock: ${ sessionId } ` ) ;
144144 }
145145
146- sleepSync ( sessionLockRetryDelayMs ) ;
146+ sleepSync ( lockRetryDelayMs ) ;
147147 }
148148 }
149149
@@ -160,61 +160,65 @@ function writeTextFileAtomic(filePath: string, content: string): void {
160160 renameSync ( tempFilePath , filePath ) ;
161161}
162162
163- export const SessionStore = {
163+ /**
164+ * Persists agent execution dumps to the filesystem, grouped by session ID.
165+ * Each agent should own its own instance.
166+ */
167+ export class ExecutionStore {
164168 rootDir ( ) : string {
165169 return getMidsceneRunSubDir ( 'session' ) ;
166- } ,
170+ }
167171
168172 sessionDir ( sessionId : string ) : string {
169173 return sessionDirPath ( sessionId ) ;
170- } ,
174+ }
171175
172176 agentFilePath ( sessionId : string ) : string {
173- return join ( SessionStore . sessionDir ( sessionId ) , 'agent.json' ) ;
174- } ,
177+ return join ( this . sessionDir ( sessionId ) , 'agent.json' ) ;
178+ }
175179
176180 executionBasePath ( sessionId : string , order : number ) : string {
177- return join ( SessionStore . sessionDir ( sessionId ) , `${ order } .json` ) ;
178- } ,
181+ return join ( this . sessionDir ( sessionId ) , `${ order } .json` ) ;
182+ }
179183
180184 reportDir ( sessionId : string ) : string {
181- const dir = join ( SessionStore . sessionDir ( sessionId ) , 'report' ) ;
185+ const dir = join ( this . sessionDir ( sessionId ) , 'report' ) ;
182186 mkdirSync ( dir , { recursive : true } ) ;
183187 return dir ;
184- } ,
188+ }
185189
186- load ( sessionId : string ) : PersistedSession {
187- const filePath = SessionStore . agentFilePath ( sessionId ) ;
190+ load ( sessionId : string ) : ExecutionSession {
191+ const filePath = this . agentFilePath ( sessionId ) ;
188192
189193 if ( ! existsSync ( filePath ) ) {
190194 throw new Error ( `Session not found: ${ sessionId } ` ) ;
191195 }
192196
193197 return normalizeSessionRecord (
194- JSON . parse ( readFileSync ( filePath , 'utf-8' ) ) as PersistedSession ,
198+ JSON . parse ( readFileSync ( filePath , 'utf-8' ) ) as ExecutionSession ,
195199 ) ;
196- } ,
200+ }
197201
198- save ( session : PersistedSession ) : PersistedSession {
199- mkdirSync ( SessionStore . sessionDir ( session . sessionId ) , { recursive : true } ) ;
202+ save ( session : ExecutionSession ) : ExecutionSession {
203+ mkdirSync ( this . sessionDir ( session . sessionId ) , { recursive : true } ) ;
200204 const normalized = normalizeSessionRecord ( session ) ;
201205 writeTextFileAtomic (
202- SessionStore . agentFilePath ( normalized . sessionId ) ,
206+ this . agentFilePath ( normalized . sessionId ) ,
203207 JSON . stringify ( normalized , null , 2 ) ,
204208 ) ;
205209 return normalized ;
206- } ,
210+ }
207211
208- ensureSession ( input : EnsureSessionInput ) : PersistedSession {
209- return withSessionLock ( input . sessionId , ( ) => {
212+ ensureSession ( input : EnsureExecutionSessionInput ) : ExecutionSession {
213+ return withLock ( input . sessionId , ( ) => {
210214 const now = Date . now ( ) ;
211- const filePath = SessionStore . agentFilePath ( input . sessionId ) ;
215+ const filePath = this . agentFilePath ( input . sessionId ) ;
212216
213217 if ( existsSync ( filePath ) ) {
214- const existing = SessionStore . load ( input . sessionId ) ;
218+ const existing = this . load ( input . sessionId ) ;
215219 const mergedModelBriefs = new Set ( existing . modelBriefs ) ;
216220 input . modelBriefs ?. forEach ( ( brief ) => mergedModelBriefs . add ( brief ) ) ;
217- const next : PersistedSession = {
221+ const next : ExecutionSession = {
218222 ...existing ,
219223 platform : input . platform ?? existing . platform ,
220224 groupName :
@@ -228,10 +232,10 @@ export const SessionStore = {
228232 input . deviceType ?? existing . deviceType ?? existing . platform ,
229233 updatedAt : now ,
230234 } ;
231- return SessionStore . save ( next ) ;
235+ return this . save ( next ) ;
232236 }
233237
234- return SessionStore . save ( {
238+ return this . save ( {
235239 sessionId : input . sessionId ,
236240 platform : input . platform ,
237241 groupName :
@@ -245,64 +249,64 @@ export const SessionStore = {
245249 executionCount : 0 ,
246250 } ) ;
247251 } ) ;
248- } ,
252+ }
249253
250254 markReportGenerated (
251255 sessionId : string ,
252256 reportFilePath : string ,
253- ) : PersistedSession {
254- return withSessionLock ( sessionId , ( ) => {
255- const session = SessionStore . load ( sessionId ) ;
256- return SessionStore . save ( {
257+ ) : ExecutionSession {
258+ return withLock ( sessionId , ( ) => {
259+ const session = this . load ( sessionId ) ;
260+ return this . save ( {
257261 ...session ,
258262 reportFilePath,
259263 updatedAt : Date . now ( ) ,
260264 } ) ;
261265 } ) ;
262- } ,
266+ }
263267
264268 appendExecution ( sessionId : string , execution : ExecutionDump ) : number {
265- return withSessionLock ( sessionId , ( ) => {
266- const session = SessionStore . load ( sessionId ) ;
269+ return withLock ( sessionId , ( ) => {
270+ const session = this . load ( sessionId ) ;
267271 const order = session . executionCount + 1 ;
268- const basePath = SessionStore . executionBasePath ( sessionId , order ) ;
272+ const basePath = this . executionBasePath ( sessionId , order ) ;
269273
270274 ExecutionDump . cleanupFiles ( basePath ) ;
271275 execution . serializeToFiles ( basePath ) ;
272- SessionStore . save ( {
276+ this . save ( {
273277 ...session ,
274278 executionCount : order ,
275279 updatedAt : Date . now ( ) ,
276280 } ) ;
277281
278282 return order ;
279283 } ) ;
280- } ,
284+ }
281285
282286 updateExecution (
283287 sessionId : string ,
284288 order : number ,
285289 execution : ExecutionDump ,
286290 ) : void {
287- withSessionLock ( sessionId , ( ) => {
288- const session = SessionStore . load ( sessionId ) ;
289- const basePath = SessionStore . executionBasePath ( sessionId , order ) ;
291+ withLock ( sessionId , ( ) => {
292+ const session = this . load ( sessionId ) ;
293+ const basePath = this . executionBasePath ( sessionId , order ) ;
290294
291295 ExecutionDump . cleanupFiles ( basePath ) ;
292296 execution . serializeToFiles ( basePath ) ;
293- SessionStore . save ( {
297+ this . save ( {
294298 ...session ,
295299 executionCount : Math . max ( session . executionCount , order ) ,
296300 updatedAt : Date . now ( ) ,
297301 } ) ;
298302 } ) ;
299- } ,
303+ }
300304
301- buildSessionDump ( sessionId : string ) : IGroupedActionDump {
302- return withSessionLock ( sessionId , ( ) => {
303- const session = SessionStore . load ( sessionId ) ;
305+ buildGroupedDump ( sessionId : string ) : IGroupedActionDump {
306+ return withLock ( sessionId , ( ) => {
307+ const session = this . load ( sessionId ) ;
304308 const rootExecutionFiles = orderedRootExecutionFiles (
305- SessionStore . sessionDir ( sessionId ) ,
309+ this . sessionDir ( sessionId ) ,
306310 ) ;
307311
308312 if ( ! rootExecutionFiles . length ) {
@@ -311,7 +315,7 @@ export const SessionStore = {
311315
312316 const executions : IExecutionDump [ ] = [ ] ;
313317 for ( const fileName of rootExecutionFiles ) {
314- const basePath = join ( SessionStore . sessionDir ( sessionId ) , fileName ) ;
318+ const basePath = join ( this . sessionDir ( sessionId ) , fileName ) ;
315319 const inlineJson = ExecutionDump . fromFilesAsInlineJson ( basePath ) ;
316320 executions . push ( JSON . parse ( inlineJson ) as IExecutionDump ) ;
317321 }
@@ -325,8 +329,8 @@ export const SessionStore = {
325329 deviceType : session . deviceType ?? session . platform ,
326330 } ;
327331 } ) ;
328- } ,
329- } ;
332+ }
333+ }
330334
331335export function createSessionAgentOptions ( input : {
332336 sessionId ?: string ;
0 commit comments