@@ -168,6 +168,7 @@ interface SessionEventProcessingState {
168168 string ,
169169 {
170170 workspaceId : string ;
171+ workspaceAlias ?: string ;
171172 currentResolvedPath : string ;
172173 desiredState : boolean ;
173174 recordingCycleId ?: string ;
@@ -365,10 +366,18 @@ function readDatePart(
365366 return parts . find ( ( part ) => part . type === type ) ?. value ?? "" ;
366367}
367368
368- function formatTimestampHumane (
369+ function readTimestampTemplateParts (
369370 now : Date ,
370371 timeZone : string ,
371- ) : string {
372+ ) : {
373+ YYYY : string ;
374+ YY : string ;
375+ MM : string ;
376+ DD : string ;
377+ HH : string ;
378+ mm : string ;
379+ timestampHumane : string ;
380+ } {
372381 const formatter = new Intl . DateTimeFormat ( "en-CA" , {
373382 year : "numeric" ,
374383 month : "2-digit" ,
@@ -384,7 +393,15 @@ function formatTimestampHumane(
384393 const day = readDatePart ( parts , "day" ) ;
385394 const hour = readDatePart ( parts , "hour" ) ;
386395 const minute = readDatePart ( parts , "minute" ) ;
387- return `${ year } -${ month } -${ day } _${ hour } ${ minute } ` ;
396+ return {
397+ YYYY : year ,
398+ YY : year . slice ( - 2 ) ,
399+ MM : month ,
400+ DD : day ,
401+ HH : hour ,
402+ mm : minute ,
403+ timestampHumane : `${ year } -${ month } -${ day } _${ hour } ${ minute } ` ,
404+ } ;
388405}
389406
390407function readWorkspaceOutputs (
@@ -523,14 +540,21 @@ function renderWorkspaceFilename(
523540 boundarySnapshot ?: ConversationEvent [ ] ;
524541 } = { } ,
525542) : string {
543+ const timestampTokens = readTimestampTemplateParts (
544+ now ,
545+ profile . filenameTemplateTimezone ,
546+ ) ;
526547 const tokens : Record < string , string > = {
527548 provider : sanitizeFilenamePart ( provider ) ,
528549 sessionId : sanitizeFilenamePart ( sessionId ) ,
529550 sessionShortId : sanitizeFilenamePart ( sessionId . slice ( 0 , 8 ) ) ,
530- timestampHumane : formatTimestampHumane (
531- now ,
532- profile . filenameTemplateTimezone ,
533- ) ,
551+ YYYY : timestampTokens . YYYY ,
552+ YY : timestampTokens . YY ,
553+ MM : timestampTokens . MM ,
554+ DD : timestampTokens . DD ,
555+ HH : timestampTokens . HH ,
556+ mm : timestampTokens . mm ,
557+ timestampHumane : timestampTokens . timestampHumane ,
534558 snippetSlug : slugifySnippetForFilename ( resolveFilenameSnippet ( options ) ) ,
535559 } ;
536560 let rendered = profile . filenameTemplate ;
@@ -1240,6 +1264,11 @@ async function applyPersistentControlCommandsForEvent(
12401264 }
12411265 } else if ( command . verb === "capture" ) {
12421266 let targetPath : string ;
1267+ let resolvedBinding :
1268+ | NonNullable < SessionMetadataV1 [ "workspaceOutputs" ] > [ number ] [
1269+ "currentDestination"
1270+ ]
1271+ | undefined ;
12431272 let stateChanged = false ;
12441273 if ( ! command . argument && output ) {
12451274 targetPath = output . currentResolvedPath ;
@@ -1253,14 +1282,15 @@ async function applyPersistentControlCommandsForEvent(
12531282 rawArgument : command . argument ,
12541283 now : now ( ) ,
12551284 } ) ;
1285+ resolvedBinding = resolved . binding ;
12561286 targetPath = await validateDestinationPathForCommand (
12571287 recordingPipeline ,
12581288 provider ,
12591289 providerSessionId ,
12601290 resolved . resolvedPath ,
12611291 "capture" ,
12621292 ) ;
1263- if ( ! command . argument && ! output ) {
1293+ if ( ! output ) {
12641294 output = createWorkspaceOutputState ( {
12651295 profile,
12661296 binding : resolved . binding ,
@@ -1271,8 +1301,26 @@ async function applyPersistentControlCommandsForEvent(
12711301 } ) ;
12721302 readWorkspaceOutputs ( metadata ) . push ( output ) ;
12731303 stateChanged = true ;
1304+ } else if ( output . currentResolvedPath !== targetPath ) {
1305+ closeWorkspaceOutputCycle (
1306+ output ,
1307+ writeCursor ,
1308+ now ( ) . toISOString ( ) ,
1309+ ) ;
1310+ applyWorkspaceProfileSnapshot ( output , profile ) ;
1311+ if ( resolvedBinding ) {
1312+ output . currentDestination = resolvedBinding ;
1313+ }
1314+ output . currentResolvedPath = targetPath ;
1315+ output . writeCursor = writeCursor ;
1316+ output . desiredState = "off" ;
1317+ stateChanged = true ;
12741318 }
12751319 }
1320+ if ( ! output ) {
1321+ throw new Error ( "Workspace output state was not created" ) ;
1322+ }
1323+ applyWorkspaceProfileSnapshot ( output , profile ) ;
12761324 loggedTargetPath = targetPath ;
12771325 const captureEvents = await resolveBoundaryEventsFromTwinStart (
12781326 metadata ,
@@ -1285,7 +1333,7 @@ async function applyPersistentControlCommandsForEvent(
12851333 captureEvents ,
12861334 providerSessionId ,
12871335 ) ;
1288- const currentCycleId = output ? .activeRecordingCycleId ;
1336+ const currentCycleId = output . activeRecordingCycleId ;
12891337 await recordingPipeline . captureSnapshot ( {
12901338 provider,
12911339 sessionId : providerSessionId ,
@@ -1296,6 +1344,15 @@ async function applyPersistentControlCommandsForEvent(
12961344 workspaceIds : [ workspace . workspaceId ] ,
12971345 outputOverrides,
12981346 } ) ;
1347+ let activeCycleId = output . activeRecordingCycleId ;
1348+ if ( ! activeCycleId || output . desiredState !== "on" ) {
1349+ activeCycleId = openWorkspaceOutputCycle (
1350+ output ,
1351+ writeCursor ,
1352+ now ( ) . toISOString ( ) ,
1353+ ) ;
1354+ stateChanged = true ;
1355+ }
12991356 const continuationEvents = buildCommandSeedEvents (
13001357 event ,
13011358 command . line + 1 ,
@@ -1313,12 +1370,14 @@ async function applyPersistentControlCommandsForEvent(
13131370 targetPath,
13141371 events : continuationEvents ,
13151372 title : captureTitle ,
1316- ...( currentCycleId ? { recordingId : currentCycleId } : { } ) ,
1317- recordingCycleIds : currentCycleId ? [ currentCycleId ] : undefined ,
1373+ ...( activeCycleId ? { recordingId : activeCycleId } : { } ) ,
1374+ recordingCycleIds : activeCycleId ? [ activeCycleId ] : undefined ,
13181375 workspaceIds : [ workspace . workspaceId ] ,
13191376 outputOverrides,
13201377 } ) ;
13211378 }
1379+ output . writeCursor = writeCursor ;
1380+ stateChanged = true ;
13221381 metadataChanged = metadataChanged || stateChanged ;
13231382 } else if ( command . verb === "export" ) {
13241383 const resolved = await resolveWorkspaceCommandDestination ( {
@@ -1580,6 +1639,7 @@ async function applyControlCommandsForEvent(
15801639 loggedTargetPath = resolvedDestination ;
15811640 const state = existingState ?? {
15821641 workspaceId : workspace . workspaceId ,
1642+ workspaceAlias : profile . alias ,
15831643 currentResolvedPath : resolvedDestination ,
15841644 desiredState : false ,
15851645 outputOverrides,
@@ -1606,6 +1666,7 @@ async function applyControlCommandsForEvent(
16061666 provider,
16071667 sessionId,
16081668 recordingKey : workspace . workspaceId ,
1669+ workspaceAlias : profile . alias ,
16091670 targetPath : resolvedDestination ,
16101671 seedEvents,
16111672 title : recordingTitle ,
@@ -1616,6 +1677,7 @@ async function applyControlCommandsForEvent(
16161677 state . currentResolvedPath = resolvedDestination ;
16171678 state . desiredState = true ;
16181679 state . recordingCycleId = recordingCycleId ;
1680+ state . workspaceAlias = profile . alias ;
16191681 state . outputOverrides = outputOverrides ;
16201682 sessionEventState . workspaceOutputs . set (
16211683 workspace . workspaceId ,
@@ -1646,15 +1708,39 @@ async function applyControlCommandsForEvent(
16461708 throw new Error ( "Unable to resolve workspace capture destination" ) ;
16471709 }
16481710 loggedTargetPath = resolvedDestination ;
1711+ const state = existingState ?? {
1712+ workspaceId : workspace . workspaceId ,
1713+ workspaceAlias : profile . alias ,
1714+ currentResolvedPath : resolvedDestination ,
1715+ desiredState : false ,
1716+ outputOverrides,
1717+ } ;
1718+ const destinationChanged = state . currentResolvedPath !==
1719+ resolvedDestination ;
1720+ if ( destinationChanged && state . desiredState ) {
1721+ recordingPipeline . stopRecording (
1722+ provider ,
1723+ sessionId ,
1724+ workspace . workspaceId ,
1725+ ) ;
1726+ }
1727+ if ( destinationChanged ) {
1728+ state . currentResolvedPath = resolvedDestination ;
1729+ state . desiredState = false ;
1730+ delete state . recordingCycleId ;
1731+ }
1732+ state . workspaceAlias = profile . alias ;
1733+ state . outputOverrides = outputOverrides ;
1734+ const activeCycleId = state . desiredState && state . recordingCycleId
1735+ ? state . recordingCycleId
1736+ : undefined ;
16491737 await recordingPipeline . captureSnapshot ( {
16501738 provider,
16511739 sessionId,
16521740 targetPath : resolvedDestination ,
16531741 events : boundarySnapshot ,
16541742 title : recordingTitle ,
1655- recordingCycleIds : existingState ?. recordingCycleId
1656- ? [ existingState . recordingCycleId ]
1657- : undefined ,
1743+ recordingCycleIds : activeCycleId ? [ activeCycleId ] : undefined ,
16581744 workspaceIds : [ workspace . workspaceId ] ,
16591745 outputOverrides,
16601746 } ) ;
@@ -1663,36 +1749,37 @@ async function applyControlCommandsForEvent(
16631749 command . line + 1 ,
16641750 boundary . lastLineInSegment ,
16651751 ) ;
1666- if ( continuationEvents . length > 0 ) {
1667- if ( ! recordingPipeline . appendToDestination ) {
1668- throw new Error (
1669- "Recording pipeline does not support appendToDestination" ,
1670- ) ;
1752+ if ( state . desiredState && state . recordingCycleId ) {
1753+ if ( continuationEvents . length > 0 ) {
1754+ await recordingPipeline . appendToActiveRecording ( {
1755+ provider,
1756+ sessionId,
1757+ recordingKey : workspace . workspaceId ,
1758+ events : continuationEvents ,
1759+ title : recordingTitle ,
1760+ recordingCycleIds : [ state . recordingCycleId ] ,
1761+ workspaceIds : [ workspace . workspaceId ] ,
1762+ outputOverrides,
1763+ } ) ;
16711764 }
1672- await recordingPipeline . appendToDestination ( {
1765+ } else {
1766+ const recordingCycleId = crypto . randomUUID ( ) ;
1767+ await recordingPipeline . activateRecording ( {
16731768 provider,
16741769 sessionId,
1770+ recordingKey : workspace . workspaceId ,
1771+ workspaceAlias : profile . alias ,
16751772 targetPath : resolvedDestination ,
1676- events : continuationEvents ,
1773+ seedEvents : continuationEvents ,
16771774 title : recordingTitle ,
1678- ...( existingState ?. recordingCycleId
1679- ? { recordingId : existingState . recordingCycleId }
1680- : { } ) ,
1681- recordingCycleIds : existingState ?. recordingCycleId
1682- ? [ existingState . recordingCycleId ]
1683- : undefined ,
1775+ recordingId : recordingCycleId ,
16841776 workspaceIds : [ workspace . workspaceId ] ,
16851777 outputOverrides,
16861778 } ) ;
1779+ state . desiredState = true ;
1780+ state . recordingCycleId = recordingCycleId ;
16871781 }
1688- if ( ! command . argument && ! existingState ) {
1689- sessionEventState . workspaceOutputs . set ( workspace . workspaceId , {
1690- workspaceId : workspace . workspaceId ,
1691- currentResolvedPath : resolvedDestination ,
1692- desiredState : false ,
1693- outputOverrides,
1694- } ) ;
1695- }
1782+ sessionEventState . workspaceOutputs . set ( workspace . workspaceId , state ) ;
16961783 } else if ( command . verb === "export" ) {
16971784 const resolved = await resolveWorkspaceCommandDestination ( {
16981785 profile,
@@ -1858,6 +1945,7 @@ async function processInChatRecordingUpdates(
18581945 destinationRecordingIds : new Map < string , string > ( ) ,
18591946 workspaceOutputs : new Map < string , {
18601947 workspaceId : string ;
1948+ workspaceAlias ?: string ;
18611949 currentResolvedPath : string ;
18621950 desiredState : boolean ;
18631951 recordingCycleId ?: string ;
@@ -2166,6 +2254,7 @@ function toActiveRecordingsFromMetadata(
21662254 recordingId : output . activeRecordingCycleId ?? output . workspaceId ,
21672255 provider : metadata . provider ,
21682256 sessionId : metadata . providerSessionId ,
2257+ workspaceAlias : output . workspaceAliasSnapshot ,
21692258 outputPath : output . currentResolvedPath ,
21702259 startedAt : readWorkspaceOutputStartedAt ( output ) || metadata . updatedAt ,
21712260 lastWriteAt : metadata . updatedAt ,
@@ -2296,6 +2385,9 @@ function toSessionStatuses(
22962385 ...( recording . recordingId
22972386 ? { recordingShortId : recording . recordingId . slice ( 0 , 8 ) }
22982387 : { } ) ,
2388+ ...( recording . workspaceAlias
2389+ ? { workspaceAlias : recording . workspaceAlias }
2390+ : { } ) ,
22992391 outputPath : recording . outputPath ,
23002392 startedAt : recording . startedAt ,
23012393 lastWriteAt : recording . lastWriteAt ,
0 commit comments