@@ -254,6 +254,111 @@ Body
254254 await server . finished ;
255255} ) ;
256256
257+ Deno . test ( "build files API refreshes deck labels when content changes with same size and mtime" , async ( ) => {
258+ const dir = await Deno . makeTempDir ( ) ;
259+ const modHref = modImportPath ( ) ;
260+
261+ const deckPath = path . join ( dir , "build-files-label-cache.deck.ts" ) ;
262+ await Deno . writeTextFile (
263+ deckPath ,
264+ `
265+ import { defineDeck } from "${ modHref } ";
266+ import { z } from "zod";
267+ export default defineDeck({
268+ inputSchema: z.string().optional(),
269+ outputSchema: z.string().optional(),
270+ modelParams: { model: "dummy-model" },
271+ });
272+ ` ,
273+ ) ;
274+
275+ const provider : ModelProvider = {
276+ chat ( ) {
277+ return Promise . resolve ( {
278+ message : { role : "assistant" , content : "ok" } ,
279+ finishReason : "stop" ,
280+ } ) ;
281+ } ,
282+ } ;
283+
284+ const server = startWebSocketSimulator ( {
285+ deckPath,
286+ modelProvider : provider ,
287+ port : 0 ,
288+ } ) ;
289+ const port = ( server . addr as Deno . NetAddr ) . port ;
290+
291+ const workspaceRes = await fetch (
292+ `http://127.0.0.1:${ port } /api/workspace/new` ,
293+ { method : "POST" } ,
294+ ) ;
295+ assertEquals ( workspaceRes . ok , true ) ;
296+ const workspaceBody = await workspaceRes . json ( ) as { workspaceId ?: string } ;
297+ const workspaceId = workspaceBody . workspaceId ?? "" ;
298+ assert ( workspaceId . length > 0 , "missing workspaceId" ) ;
299+
300+ const firstFilesRes = await fetch (
301+ `http://127.0.0.1:${ port } /api/build/files?workspaceId=${
302+ encodeURIComponent ( workspaceId )
303+ } `,
304+ ) ;
305+ assertEquals ( firstFilesRes . ok , true ) ;
306+ const firstFilesBody = await firstFilesRes . json ( ) as { root ?: string } ;
307+ const root = firstFilesBody . root ?? "" ;
308+ assert ( root . length > 0 , "missing bot root" ) ;
309+
310+ const scenarioDir = path . join ( root , "scenarios" , "scenario_a" ) ;
311+ await Deno . mkdir ( scenarioDir , { recursive : true } ) ;
312+ const promptPath = path . join ( scenarioDir , "PROMPT.md" ) ;
313+ const fixedTime = new Date ( "2025-01-01T00:00:00.000Z" ) ;
314+
315+ const makePrompt = ( label : string ) =>
316+ `+++
317+ label = "${ label } "
318+ +++
319+
320+ Body
321+ ` ;
322+
323+ await Deno . writeTextFile ( promptPath , makePrompt ( "Alpha" ) ) ;
324+ await Deno . utime ( promptPath , fixedTime , fixedTime ) ;
325+
326+ const warmCacheRes = await fetch (
327+ `http://127.0.0.1:${ port } /api/build/files?workspaceId=${
328+ encodeURIComponent ( workspaceId )
329+ } `,
330+ ) ;
331+ assertEquals ( warmCacheRes . ok , true ) ;
332+ const warmCacheBody = await warmCacheRes . json ( ) as {
333+ entries ?: Array < { path ?: string ; label ?: string } > ;
334+ } ;
335+ const warmPrompt = ( warmCacheBody . entries ?? [ ] ) . find ( ( entry ) =>
336+ entry . path === "scenarios/scenario_a/PROMPT.md"
337+ ) ;
338+ assertEquals ( warmPrompt ?. label , "Alpha" ) ;
339+
340+ // Same byte length label replacement + identical mtime to reproduce stale-cache risk.
341+ await Deno . writeTextFile ( promptPath , makePrompt ( "Bravo" ) ) ;
342+ await Deno . utime ( promptPath , fixedTime , fixedTime ) ;
343+
344+ const refreshedRes = await fetch (
345+ `http://127.0.0.1:${ port } /api/build/files?workspaceId=${
346+ encodeURIComponent ( workspaceId )
347+ } `,
348+ ) ;
349+ assertEquals ( refreshedRes . ok , true ) ;
350+ const refreshedBody = await refreshedRes . json ( ) as {
351+ entries ?: Array < { path ?: string ; label ?: string } > ;
352+ } ;
353+ const refreshedPrompt = ( refreshedBody . entries ?? [ ] ) . find ( ( entry ) =>
354+ entry . path === "scenarios/scenario_a/PROMPT.md"
355+ ) ;
356+ assertEquals ( refreshedPrompt ?. label , "Bravo" ) ;
357+
358+ await server . shutdown ( ) ;
359+ await server . finished ;
360+ } ) ;
361+
257362Deno . test ( "simulator exposes schema and defaults" , async ( ) => {
258363 const dir = await Deno . makeTempDir ( ) ;
259364 const modHref = modImportPath ( ) ;
0 commit comments