@@ -46,18 +46,19 @@ const MAX_SEED_PROMPT = 16000;
4646/** The template quick-chat reuses-or-creates its workspace from. */
4747const QUICK_CHAT_TEMPLATE = 'chat' ;
4848
49+ const MONTH_ABBR = [ 'jan' , 'feb' , 'mar' , 'apr' , 'may' , 'jun' , 'jul' , 'aug' , 'sep' , 'oct' , 'nov' , 'dec' ] as const ;
50+
4951/**
50- * A valid, unused tag for the one chat workspace quick-chat creates on first
51- * use (`chat`, then `chat-2`, … on collision). Quick-chat reuses this workspace
52- * thereafter, so this only runs once until the user deletes it.
52+ * Tag for TODAY's chat workspace — `chat-<mon><day>` (e.g. `chat-jun15`).
53+ * Quick-chat is one-workspace-per-DAY: today's conversations are sessions inside
54+ * today's workspace. The format mirrors the frontend's `defaultTagFor`
55+ * (`<template>-<month><day>`, en-US short month lowercased) so a quick-chat-
56+ * created daily workspace is byte-identical to one created from the form on the
57+ * same day — the two converge on the same workspace instead of duplicating.
5358 */
54- function freshChatTag ( svc : WorkspaceService ) : string {
55- if ( ! svc . registry . hasTag ( QUICK_CHAT_TEMPLATE ) ) return QUICK_CHAT_TEMPLATE ;
56- for ( let i = 2 ; i < 1000 ; i ++ ) {
57- const t = `${ QUICK_CHAT_TEMPLATE } -${ i } ` ;
58- if ( ! svc . registry . hasTag ( t ) ) return t ;
59- }
60- return `${ QUICK_CHAT_TEMPLATE } -${ randomUUID ( ) . slice ( 0 , 8 ) } ` ;
59+ function todayChatTag ( ) : string {
60+ const now = new Date ( ) ;
61+ return `${ QUICK_CHAT_TEMPLATE } -${ MONTH_ABBR [ now . getMonth ( ) ] } ${ now . getDate ( ) } ` ;
6162}
6263
6364/**
@@ -187,33 +188,41 @@ export function createWorkspaceRoutes(svc: WorkspaceService): Hono {
187188 }
188189 }
189190
190- // Serializes quick-chat's find-or-create so two concurrent FIRST-TIME launches
191- // don't both bootstrap a `chat` workspace — the loser's `registry.add` would
192- // throw on the duplicate tag and leak an orphaned bootstrap dir. In-process
193- // chain (quick-chat is low-frequency, single-process); the `.catch` keeps a
194- // failed run from poisoning the gate forever.
191+ // TODAY's chat workspace, by its daily tag (`chat-jun15`). A workspace someone
192+ // happened to tag `chat-jun15` with a non-chat template doesn't count — the
193+ // daily bucket is a chat-template workspace.
194+ const findTodaysChat = ( ) : WorkspaceMeta | undefined => {
195+ const tag = todayChatTag ( ) ;
196+ return svc . registry . list ( ) . find ( ( w ) => w . template === QUICK_CHAT_TEMPLATE && w . tag === tag ) ;
197+ } ;
198+
199+ // Serializes quick-chat's find-or-create so two concurrent FIRST-OF-DAY
200+ // launches don't both bootstrap today's workspace — the loser's `registry.add`
201+ // would throw on the duplicate tag and leak an orphaned bootstrap dir.
202+ // In-process chain (quick-chat is low-frequency, single-process); the `.catch`
203+ // keeps a failed run from poisoning the gate forever.
195204 let chatWsGate : Promise < unknown > = Promise . resolve ( ) ;
196205
197206 const findOrCreateChatWorkspace = async ( ) : Promise <
198207 { ok : true ; meta : WorkspaceMeta } | { ok : false ; status : number ; body : { error : string ; message ?: string } }
199208 > => {
200- const existing = svc . registry . list ( ) . find ( ( w ) => w . template === QUICK_CHAT_TEMPLATE ) ;
209+ const existing = findTodaysChat ( ) ;
201210 if ( existing ) return { ok : true , meta : existing } ;
202211 let created : Awaited < ReturnType < typeof svc . creator . create > > ;
203212 try {
204- created = await svc . creator . create ( freshChatTag ( svc ) , QUICK_CHAT_TEMPLATE ) ;
213+ created = await svc . creator . create ( todayChatTag ( ) , QUICK_CHAT_TEMPLATE ) ;
205214 } catch ( err ) {
206- // e.g. a concurrent create committed the tag between freshChatTag() and
207- // registry.add(). Re-find — the winner's workspace now exists.
208- const after = svc . registry . list ( ) . find ( ( w ) => w . template === QUICK_CHAT_TEMPLATE ) ;
215+ // e.g. a concurrent create committed today's tag first. Re-find — the
216+ // winner's workspace now exists.
217+ const after = findTodaysChat ( ) ;
209218 if ( after ) return { ok : true , meta : after } ;
210219 launcherLogger . error ( 'quick_chat.create_threw' , { err } ) ;
211220 return { ok : false , status : 500 , body : { error : 'create_failed' , message : ( err as Error ) . message } } ;
212221 }
213222 if ( ! created . ok ) {
214- // tag_in_use means someone else created a chat workspace — re-find and reuse .
223+ // tag_in_use means today's workspace was created concurrently — re-find it .
215224 if ( created . code === 'tag_in_use' ) {
216- const after = svc . registry . list ( ) . find ( ( w ) => w . template === QUICK_CHAT_TEMPLATE ) ;
225+ const after = findTodaysChat ( ) ;
217226 if ( after ) return { ok : true , meta : after } ;
218227 }
219228 const status =
@@ -478,8 +487,8 @@ export function createWorkspaceRoutes(svc: WorkspaceService): Hono {
478487 } ) ;
479488
480489 // Quick-chat launch — the "type a message → you're in" front door, decoupled
481- // from the multi-step create-workspace UI. Reuses (or, on first use, creates)
482- // the chat-template workspace , then spawns ONE fresh interactive session
490+ // from the multi-step create-workspace UI. Enters TODAY's chat workspace
491+ // (creating it on the day's first use) , then spawns a fresh interactive session
483492 // seeded with the user's first message. One POST returns both the workspace
484493 // and the live session, so the client can drop the user straight into the TUI.
485494 // Body: { prompt: string; agent?: string }
@@ -499,12 +508,13 @@ export function createWorkspaceRoutes(svc: WorkspaceService): Hono {
499508 return c . json ( { error : 'bad_request' , message : ( err as Error ) . message } , 400 ) ;
500509 }
501510
502- // Reuse the most-recent chat-template workspace; create one only if none
503- // exists. Create is heavy (bash + git + skill injection), so we never make a
504- // fresh workspace per message — the codebase's "heavy create once, cheap
505- // spawn many" pattern (sessions, not workspaces, carry conversations). The
506- // find-or-create runs through `chatWsGate` so concurrent first launches
507- // don't double-bootstrap.
511+ // One chat workspace per DAY: enter today's if it exists, else create it.
512+ // Each send is a new SESSION inside today's workspace (conversations =
513+ // sessions, resumable from the chat sidebar) — closer to a traditional
514+ // chatbot while staying aligned with the Workspace/Session model. Create is
515+ // heavy (bash + git + skill injection) but happens at most once per day. The
516+ // find-or-create runs through `chatWsGate` so concurrent first-of-day
517+ // launches don't double-bootstrap.
508518 const run = chatWsGate . catch ( ( ) => undefined ) . then ( ( ) => findOrCreateChatWorkspace ( ) ) ;
509519 chatWsGate = run ;
510520 const target = await run ;
0 commit comments