@@ -78,6 +78,10 @@ const KanbanCardSchema = z.object({
7878 last_message : z . string ( ) . nullable ( ) . optional ( ) ,
7979 turn_count : z . number ( ) . optional ( ) ,
8080 tokens : z . object ( { total_tokens : z . number ( ) . default ( 0 ) } ) . passthrough ( ) . optional ( ) ,
81+ score_path : z . string ( ) . nullable ( ) . optional ( ) ,
82+ score_summary : z . string ( ) . nullable ( ) . optional ( ) ,
83+ phase_history : z . array ( z . any ( ) ) . optional ( ) ,
84+ conversation : z . array ( z . any ( ) ) . optional ( ) ,
8185 agent_profile : z . any ( ) . optional ( ) ,
8286 agent_profile_id : z . string ( ) . nullable ( ) . optional ( ) ,
8387 agent_name : z . string ( ) . nullable ( ) . optional ( ) ,
@@ -149,6 +153,20 @@ type MusicProfile = {
149153 dynamic ?: string ;
150154 register ?: string ;
151155} ;
156+ type AgentConversationMessage = {
157+ at ?: string ;
158+ from : string ;
159+ to : string ;
160+ stage ?: string ;
161+ kind ?: string ;
162+ message : string ;
163+ } ;
164+ type PhaseHistoryEntry = {
165+ at ?: string ;
166+ phase ?: string ;
167+ status ?: string ;
168+ agent ?: string ;
169+ } ;
152170type IdleMusician = {
153171 id : string ;
154172 profile : AgentProfile ;
@@ -239,6 +257,10 @@ type Card = {
239257 section : string ;
240258 music ?: MusicProfile ;
241259 stagePosition ?: StagePosition ;
260+ scorePath ?: string ;
261+ scoreSummary ?: string ;
262+ phaseHistory : PhaseHistoryEntry [ ] ;
263+ conversation : AgentConversationMessage [ ] ;
242264 intensity : number ;
243265 source : 'live' ;
244266 raw ?: unknown ;
@@ -408,7 +430,7 @@ function normalizeColumn(title: string) {
408430function liveAgentFor ( column : string , status : string ) {
409431 const s = status . toLowerCase ( ) ;
410432 if ( column === 'Retry' || s . includes ( 'retry' ) ) return 'Backoff Agent' ;
411- if ( column === 'Done' || s . includes ( 'complete' ) ) return 'Finisher ' ;
433+ if ( column === 'Done' || s . includes ( 'complete' ) ) return 'Completed Movement ' ;
412434 if ( column === 'Blocked' || s . includes ( 'block' ) ) return 'Guardian' ;
413435 if ( s . includes ( 'refin' ) ) return 'Refiner' ;
414436 if ( column === 'Human Review' || s . includes ( 'review' ) || s . includes ( 'judge' ) ) return 'Reviewer' ;
@@ -536,7 +558,7 @@ function useAgentOrchestra({ enabled, cards, idleMusicians, offline }: { enabled
536558 const startAudio = ( ) => {
537559 if ( cancelled ) return ;
538560 masterRef . current ! . gain . cancelScheduledValues ( ctx . currentTime ) ;
539- masterRef . current ! . gain . linearRampToValueAtTime ( 0.32 , ctx . currentTime + 0.28 ) ;
561+ masterRef . current ! . gain . linearRampToValueAtTime ( 0.48 , ctx . currentTime + 0.28 ) ;
540562 nextTimeRef . current = ctx . currentTime + 0.05 ;
541563 if ( timerRef . current ) window . clearInterval ( timerRef . current ) ;
542564 scheduleAuditionCue ( ctx , masterRef . current ! ) ;
@@ -634,7 +656,7 @@ function scheduleAuditionCue(ctx: AudioContext, out: AudioNode) {
634656 time : now + index * 0.09 ,
635657 freq : midiToFreq ( midi ) ,
636658 duration : 0.22 ,
637- gain : 0.055 ,
659+ gain : 0.14 ,
638660 type : 'sine' ,
639661 filterHz : 6200 ,
640662 pan : 0
@@ -648,7 +670,7 @@ function scheduleConductorVoice(ctx: AudioContext, out: AudioNode, cards: Card[]
648670 if ( ! shouldPlay ( rhythm , step , 0 ) ) return ;
649671
650672 const midi = tuning === 'dissonant' ? 38 : tuning === 'retuning' ? 43 : tuning === 'reviewing' ? 55 : 48 ;
651- const gain = tuning === 'dissonant' ? 0.05 : tuning === 'retuning' ? 0.042 : 0.035 ;
673+ const gain = tuning === 'dissonant' ? 0.075 : tuning === 'retuning' ? 0.062 : 0.052 ;
652674 const type : OscillatorType = tuning === 'dissonant' ? 'sawtooth' : 'triangle' ;
653675 playSynthNote ( ctx , out , {
654676 time,
@@ -700,25 +722,25 @@ function scheduleIdleVoice(ctx: AudioContext, out: AudioNode, musician: IdleMusi
700722 const degree = ( step + index + seed ) % scale . length ;
701723 const midi = 36 + spec . octave * 12 + scale [ degree ] + ( index % 2 === 0 ? 0 : 7 ) ;
702724 const pan = total <= 1 ? 0 : - 0.7 + ( index / ( total - 1 ) ) * 1.4 ;
703- playSynthNote ( ctx , out , { time, freq : midiToFreq ( midi ) , duration : spec . duration * 1.35 , gain : spec . gain * 0.45 , type : spec . type , filterHz : spec . filterHz , pan } ) ;
725+ playSynthNote ( ctx , out , { time, freq : midiToFreq ( midi ) , duration : spec . duration * 1.35 , gain : spec . gain * 0.62 , type : spec . type , filterHz : spec . filterHz , pan } ) ;
704726}
705727
706728type SynthSpec = { type : OscillatorType ; octave : number ; gain : number ; duration : number ; filterHz : number ; rhythm : string } ;
707729
708730function synthSpec ( voice : { column ?: string ; status : string ; agent : string ; instrumentName ?: string ; section ?: string ; music ?: MusicProfile } ) {
709731 const state = `${ voice . column || '' } ${ voice . status } ${ voice . agent } ` . toLowerCase ( ) ;
710732 const instrument = `${ voice . instrumentName || '' } ${ voice . section || '' } ` . toLowerCase ( ) ;
711- let spec : SynthSpec = { type : 'sine' as OscillatorType , octave : 4 , gain : 0.014 , duration : 0.22 , filterHz : 3000 , rhythm : 'sparse' } ;
712- if ( state . includes ( 'block' ) || state . includes ( 'guardian' ) ) spec = { type : 'triangle' as OscillatorType , octave : 2 , gain : 0.026 , duration : 0.7 , filterHz : 420 , rhythm : 'drone' } ;
713- else if ( state . includes ( 'retry' ) || state . includes ( 'backoff' ) ) spec = { type : 'sawtooth' as OscillatorType , octave : 3 , gain : 0.028 , duration : 0.16 , filterHz : 900 , rhythm : 'retry' } ;
714- else if ( instrument . includes ( 'timpani' ) || instrument . includes ( 'percussion' ) || instrument . includes ( 'drum' ) ) spec = { type : 'sawtooth' as OscillatorType , octave : 2 , gain : 0.026 , duration : 0.18 , filterHz : 760 , rhythm : 'retry' } ;
715- else if ( instrument . includes ( 'piano' ) ) spec = { type : 'triangle' as OscillatorType , octave : 4 , gain : 0.024 , duration : 0.32 , filterHz : 2400 , rhythm : 'review' } ;
716- else if ( instrument . includes ( 'horn' ) || instrument . includes ( 'trumpet' ) || instrument . includes ( 'brass' ) ) spec = { type : 'triangle' as OscillatorType , octave : 3 , gain : 0.021 , duration : 0.48 , filterHz : 1150 , rhythm : 'sparse' } ;
717- else if ( instrument . includes ( 'clarinet' ) || instrument . includes ( 'oboe' ) || instrument . includes ( 'flute' ) || instrument . includes ( 'woodwind' ) ) spec = { type : 'triangle' as OscillatorType , octave : 4 , gain : 0.017 , duration : 0.3 , filterHz : 2100 , rhythm : 'sparse' } ;
718- else if ( instrument . includes ( 'glockenspiel' ) || instrument . includes ( 'bell' ) ) spec = { type : 'sine' as OscillatorType , octave : 5 , gain : 0.022 , duration : 0.38 , filterHz : 6200 , rhythm : 'cadence' } ;
719- else if ( state . includes ( 'review' ) || state . includes ( 'judge' ) ) spec = { type : 'triangle' as OscillatorType , octave : 4 , gain : 0.024 , duration : 0.32 , filterHz : 2400 , rhythm : 'review' } ;
720- else if ( state . includes ( 'done' ) || state . includes ( 'finish' ) ) spec = { type : 'sine' as OscillatorType , octave : 5 , gain : 0.024 , duration : 0.42 , filterHz : 6000 , rhythm : 'cadence' } ;
721- else if ( state . includes ( 'progress' ) || state . includes ( 'run' ) || state . includes ( 'builder' ) || instrument . includes ( 'violin' ) || instrument . includes ( 'viola' ) || instrument . includes ( 'strings' ) ) spec = { type : 'square' as OscillatorType , octave : 4 , gain : 0.022 , duration : 0.13 , filterHz : 1700 , rhythm : 'arpeggio' } ;
733+ let spec : SynthSpec = { type : 'sine' as OscillatorType , octave : 4 , gain : 0.024 , duration : 0.22 , filterHz : 3000 , rhythm : 'sparse' } ;
734+ if ( state . includes ( 'block' ) || state . includes ( 'guardian' ) ) spec = { type : 'triangle' as OscillatorType , octave : 2 , gain : 0.038 , duration : 0.7 , filterHz : 420 , rhythm : 'drone' } ;
735+ else if ( state . includes ( 'retry' ) || state . includes ( 'backoff' ) ) spec = { type : 'sawtooth' as OscillatorType , octave : 3 , gain : 0.04 , duration : 0.16 , filterHz : 900 , rhythm : 'retry' } ;
736+ else if ( instrument . includes ( 'timpani' ) || instrument . includes ( 'percussion' ) || instrument . includes ( 'drum' ) ) spec = { type : 'sawtooth' as OscillatorType , octave : 2 , gain : 0.044 , duration : 0.18 , filterHz : 760 , rhythm : 'retry' } ;
737+ else if ( instrument . includes ( 'piano' ) ) spec = { type : 'triangle' as OscillatorType , octave : 4 , gain : 0.034 , duration : 0.32 , filterHz : 2400 , rhythm : 'review' } ;
738+ else if ( instrument . includes ( 'horn' ) || instrument . includes ( 'trumpet' ) || instrument . includes ( 'brass' ) ) spec = { type : 'triangle' as OscillatorType , octave : 3 , gain : 0.034 , duration : 0.48 , filterHz : 1150 , rhythm : 'sparse' } ;
739+ else if ( instrument . includes ( 'clarinet' ) || instrument . includes ( 'oboe' ) || instrument . includes ( 'flute' ) || instrument . includes ( 'woodwind' ) ) spec = { type : 'triangle' as OscillatorType , octave : 4 , gain : 0.03 , duration : 0.3 , filterHz : 2100 , rhythm : 'sparse' } ;
740+ else if ( instrument . includes ( 'glockenspiel' ) || instrument . includes ( 'bell' ) ) spec = { type : 'sine' as OscillatorType , octave : 5 , gain : 0.034 , duration : 0.38 , filterHz : 6200 , rhythm : 'cadence' } ;
741+ else if ( state . includes ( 'review' ) || state . includes ( 'judge' ) ) spec = { type : 'triangle' as OscillatorType , octave : 4 , gain : 0.034 , duration : 0.32 , filterHz : 2400 , rhythm : 'review' } ;
742+ else if ( state . includes ( 'done' ) || state . includes ( 'finish' ) ) spec = { type : 'sine' as OscillatorType , octave : 5 , gain : 0.034 , duration : 0.42 , filterHz : 6000 , rhythm : 'cadence' } ;
743+ else if ( state . includes ( 'progress' ) || state . includes ( 'run' ) || state . includes ( 'builder' ) || instrument . includes ( 'violin' ) || instrument . includes ( 'viola' ) || instrument . includes ( 'strings' ) ) spec = { type : 'square' as OscillatorType , octave : 4 , gain : 0.032 , duration : 0.13 , filterHz : 1700 , rhythm : 'arpeggio' } ;
722744 return applyMusicProfile ( spec , voice . music ) ;
723745}
724746
@@ -796,6 +818,8 @@ function makeCardsFromKanban(kanban?: KanbanState): Card[] {
796818 const instrumentName = assignedProfile ?. instrument_name || stringField ( rawRecord , 'instrument_name' ) || instrumentNameFor ( section , agent , status ) ;
797819 const music = musicProfile ( assignedProfile ?. music ) ;
798820 const profileStagePosition = stagePosition ( assignedProfile ?. stage_position ) ;
821+ const conversation = conversationFromCard ( rawRecord ) ;
822+ const phaseHistory = phaseHistoryFromCard ( rawRecord ) ;
799823
800824 return {
801825 id : `${ column . id } -${ backendId } ` ,
@@ -820,6 +844,10 @@ function makeCardsFromKanban(kanban?: KanbanState): Card[] {
820844 section,
821845 music,
822846 stagePosition : profileStagePosition ,
847+ scorePath : stringField ( rawRecord , 'score_path' ) ,
848+ scoreSummary : stringField ( rawRecord , 'score_summary' ) ,
849+ phaseHistory,
850+ conversation,
823851 intensity : intensityFor ( tokens , turns , priority ) ,
824852 source : 'live' as const ,
825853 raw
@@ -899,6 +927,55 @@ function agentProfileFromCard(raw: z.infer<typeof KanbanCardSchema>) {
899927 } ;
900928}
901929
930+ function conversationFromCard ( raw : Record < string , unknown > ) : AgentConversationMessage [ ] {
931+ const messages = raw . conversation ;
932+ if ( ! Array . isArray ( messages ) ) return [ ] ;
933+
934+ return messages
935+ . map < AgentConversationMessage | null > ( item => {
936+ if ( ! item || typeof item !== 'object' || Array . isArray ( item ) ) return null ;
937+ const record = item as Record < string , unknown > ;
938+ const message = stringField ( record , 'message' ) ;
939+ if ( ! message ) return null ;
940+ return {
941+ at : stringField ( record , 'at' ) || undefined ,
942+ from : stringField ( record , 'from' ) || 'Agent' ,
943+ to : stringField ( record , 'to' ) || 'Conductor' ,
944+ stage : stringField ( record , 'stage' ) || undefined ,
945+ kind : stringField ( record , 'kind' ) || undefined ,
946+ message
947+ } ;
948+ } )
949+ . filter ( ( item ) : item is AgentConversationMessage => Boolean ( item ) )
950+ . slice ( )
951+ . reverse ( ) ;
952+ }
953+
954+ function phaseHistoryFromCard ( raw : Record < string , unknown > ) : PhaseHistoryEntry [ ] {
955+ const history = raw . phase_history ;
956+ if ( ! Array . isArray ( history ) ) return [ ] ;
957+
958+ return history
959+ . map < PhaseHistoryEntry | null > ( item => {
960+ if ( ! item || typeof item !== 'object' || Array . isArray ( item ) ) return null ;
961+ const record = item as Record < string , unknown > ;
962+ const phase = stringField ( record , 'phase' ) ;
963+ const profile = record . agent_profile && typeof record . agent_profile === 'object' && ! Array . isArray ( record . agent_profile )
964+ ? record . agent_profile as Record < string , unknown >
965+ : { } ;
966+ if ( ! phase ) return null ;
967+ return {
968+ at : stringField ( record , 'at' ) || undefined ,
969+ phase,
970+ status : stringField ( record , 'status' ) || undefined ,
971+ agent : stringField ( profile , 'name' ) || undefined
972+ } ;
973+ } )
974+ . filter ( ( item ) : item is PhaseHistoryEntry => Boolean ( item ) )
975+ . slice ( )
976+ . reverse ( ) ;
977+ }
978+
902979function stringField ( value : Record < string , unknown > , key : string ) {
903980 const field = value [ key ] ;
904981 return typeof field === 'string' && field . trim ( ) ? field : '' ;
@@ -1972,11 +2049,11 @@ function Conductor({ active }: { active: boolean }) {
19722049
19732050function Station ( { station, count } : { station : StageStation ; count : number } ) {
19742051 return (
1975- < button className = { `station station-${ station . kind } ` } style = { { left : `${ station . x } %` , top : `${ station . y } %` } } >
2052+ < div className = { `station station-${ station . kind } ` } style = { { left : `${ station . x } %` , top : `${ station . y } %` } } >
19762053 < span className = "standTop" > < FileText size = { 15 } /> </ span >
19772054 < span className = "stationLabel" > { station . label } </ span >
19782055 < span className = "stationCount" > { count } </ span >
1979- </ button >
2056+ </ div >
19802057 ) ;
19812058}
19822059
@@ -2179,17 +2256,65 @@ function MovementPanel({ card }: { card: Card }) {
21792256 < div > < dt > Agent</ dt > < dd > { card . agent } </ dd > </ div >
21802257 < div > < dt > Backend ID</ dt > < dd > < code > { card . backendId } </ code > </ dd > </ div >
21812258 </ dl >
2259+ { ( card . scoreSummary || card . scorePath ) && (
2260+ < div className = "scoreArtifact" >
2261+ < b > Conductor score</ b >
2262+ { card . scoreSummary && < p > { card . scoreSummary } </ p > }
2263+ { card . scorePath && < code > { card . scorePath } </ code > }
2264+ </ div >
2265+ ) }
2266+ < PhaseTrace entries = { card . phaseHistory } />
2267+ < AgentConversation messages = { card . conversation } activeAgent = { card . agent } />
21822268 < p > { card . message || card . retry || 'Waiting for the next orchestration cue.' } </ p >
21832269 </ >
21842270 ) ;
21852271}
21862272
2273+ function PhaseTrace ( { entries } : { entries : PhaseHistoryEntry [ ] } ) {
2274+ if ( ! entries . length ) return null ;
2275+
2276+ return (
2277+ < div className = "phaseTrace" >
2278+ { entries . slice ( - 8 ) . map ( ( entry , index ) => (
2279+ < span key = { `${ entry . phase } -${ entry . status } -${ entry . at || index } ` } className = { `phaseChip ${ statusClass ( entry . status || entry . phase || '' ) } ` } >
2280+ { entry . phase }
2281+ { entry . agent ? ` · ${ entry . agent } ` : '' }
2282+ </ span >
2283+ ) ) }
2284+ </ div >
2285+ ) ;
2286+ }
2287+
2288+ function AgentConversation ( { messages, activeAgent } : { messages : AgentConversationMessage [ ] ; activeAgent : string } ) {
2289+ if ( ! messages . length ) return < div className = "conversationPanel empty" > < p > No agent messages yet.</ p > </ div > ;
2290+
2291+ return (
2292+ < div className = "conversationPanel" aria-label = "Agent conversation" >
2293+ { messages . slice ( - 12 ) . map ( ( message , index ) => {
2294+ const own = message . from === activeAgent ;
2295+ return (
2296+ < div className = { own ? 'speechRow own' : 'speechRow' } key = { `${ message . at || index } -${ message . from } -${ message . kind || 'message' } ` } >
2297+ < div className = "speechBubble" >
2298+ < div className = "speechMeta" >
2299+ < b > { message . from } </ b >
2300+ < span > { message . to } { message . stage ? ` · ${ message . stage } ` : '' } </ span >
2301+ </ div >
2302+ < p > { message . message } </ p >
2303+ </ div >
2304+ </ div >
2305+ ) ;
2306+ } ) }
2307+ </ div >
2308+ ) ;
2309+ }
2310+
21872311function MovementEvents ( { card, debugPayload } : { card : Card ; debugPayload : string | null } ) {
21882312 const events = [
21892313 [ 'Movement' , card . movement ] ,
21902314 [ 'Status' , card . status ] ,
21912315 [ 'Turns' , String ( card . turns ) ] ,
21922316 [ 'Notes' , card . tokens . toLocaleString ( ) ] ,
2317+ [ 'Score path' , card . scorePath || 'none' ] ,
21932318 [ 'Retry due' , card . retry || 'none' ] ,
21942319 [ 'Last message' , card . message || 'none reported' ]
21952320 ] ;
@@ -2603,7 +2728,7 @@ function WorkflowPanel({
26032728 < div className = "workflowComposer pixelPanel" >
26042729 < div className = "panelHeader" >
26052730 < div >
2606- < p className = "eyebrow" > Compose Score </ p >
2731+ < p className = "eyebrow" > Compose Workflow </ p >
26072732 < h2 > < Workflow size = { 20 } /> WORKFLOW.md Builder</ h2 >
26082733 </ div >
26092734 < span className = { `pill ${ validation ?. valid ? 'success' : validation ?. writable ? 'warning' : 'idle' } ` } > { validation ?. valid ? 'ready to activate' : validation ?. writable ? 'writable draft' : 'draft' } </ span >
0 commit comments