11import path from "path"
22import { Bus } from "@/bus"
3+ import { BusEvent } from "@/bus/bus-event"
4+ import { Agent } from "@/agent/agent"
5+ import { Command } from "@/command"
6+ import { Config } from "@/config/config"
37import { FileWatcher } from "@/file/watcher"
48import { Flag } from "@/flag/flag"
9+ import { SessionStatus } from "@/session/status"
10+ import { Skill } from "@/skill"
511import { Log } from "@/util/log"
612import { Instance } from "./instance"
13+ import z from "zod"
714
815export namespace HotReload {
916 const log = Log . create ( { service : "project.hotreload" } )
1017
18+ export const Event = {
19+ Applied : BusEvent . define (
20+ "opencode.hotreload.applied" ,
21+ z . object ( {
22+ file : z . string ( ) ,
23+ event : z . enum ( [ "add" , "change" , "unlink" ] ) ,
24+ } ) ,
25+ ) ,
26+ }
27+
1128 const watched = new Set ( [
1229 "agent" ,
1330 "agents" ,
@@ -99,45 +116,80 @@ export namespace HotReload {
99116 let timer : ReturnType < typeof setTimeout > | undefined
100117 let busy = false
101118 let last = 0
119+ let queued = false
102120 let latest :
103121 | {
104122 file : string
105123 event : "add" | "change" | "unlink"
106124 }
107125 | undefined
108126
109- const flush = ( ) => {
127+ const active = ( ) =>
128+ Object . values ( SessionStatus . list ( ) ) . filter ( ( status ) => status . type === "busy" || status . type === "retry" ) . length
129+
130+ const reload = async ( ) => {
131+ await Config . reset ( )
132+ await Skill . reset ( )
133+ await Agent . reset ( )
134+ await Command . reset ( )
135+ }
136+
137+ const flush = ( reason : "timer" | "session" ) => {
110138 timer = undefined
111139 if ( busy ) return
112140
141+ const hit = latest
142+ if ( ! hit ) return
143+
144+ const sessions = active ( )
145+ if ( sessions > 0 ) {
146+ if ( ! queued ) {
147+ log . info ( "hot reload queued" , {
148+ file : hit . file ,
149+ event : hit . event ,
150+ sessions,
151+ } )
152+ }
153+ queued = true
154+ return
155+ }
156+
113157 const now = Date . now ( )
114158 const wait = cooldown - ( now - last )
115159 if ( wait > 0 ) {
116- timer = setTimeout ( flush , wait )
160+ timer = setTimeout ( ( ) => flush ( reason ) , wait )
117161 return
118162 }
119163
120- const hit = latest
121- if ( ! hit ) return
122-
123164 busy = true
165+ queued = false
166+ latest = undefined
124167 last = now
125- log . info ( "hot reload triggered" , { file : hit . file , event : hit . event } )
126- void Instance . dispose ( )
168+ log . info ( "hot reload triggered" , { file : hit . file , event : hit . event , reason } )
169+ void reload ( )
170+ . then ( ( ) =>
171+ Bus . publish ( Event . Applied , {
172+ file : hit . file ,
173+ event : hit . event ,
174+ } ) ,
175+ )
127176 . catch ( ( error ) => {
128177 log . error ( "hot reload failed" , { error, file : hit . file , event : hit . event } )
129178 } )
130179 . finally ( ( ) => {
131180 busy = false
181+ if ( ! latest ) return
182+ if ( timer ) clearTimeout ( timer )
183+ timer = setTimeout ( ( ) => flush ( "timer" ) , debounce )
132184 } )
133185 }
134186
135187 const schedule = ( ) => {
136188 if ( timer ) clearTimeout ( timer )
137- timer = setTimeout ( flush , debounce )
189+ timer = setTimeout ( ( ) => flush ( "timer" ) , debounce )
138190 }
139191
140- const unsub = Bus . subscribe ( FileWatcher . Event . Updated , ( event ) => {
192+ const unsubFile = Bus . subscribe ( FileWatcher . Event . Updated , ( event ) => {
141193 const rel = classify ( Instance . directory , event . properties . file )
142194 if ( ! rel ) return
143195 latest = {
@@ -147,9 +199,16 @@ export namespace HotReload {
147199 schedule ( )
148200 } )
149201
202+ const unsubSession = Bus . subscribe ( SessionStatus . Event . Status , ( ) => {
203+ if ( ! queued ) return
204+ if ( timer ) return
205+ timer = setTimeout ( ( ) => flush ( "session" ) , 0 )
206+ } )
207+
150208 log . info ( "hot reload enabled" , { debounce, cooldown } )
151209 return {
152- unsub,
210+ unsubFile,
211+ unsubSession,
153212 clear ( ) {
154213 if ( ! timer ) return
155214 clearTimeout ( timer )
@@ -158,7 +217,8 @@ export namespace HotReload {
158217 }
159218 } ,
160219 async ( entry ) => {
161- entry . unsub ?.( )
220+ entry . unsubFile ?.( )
221+ entry . unsubSession ?.( )
162222 entry . clear ?.( )
163223 } ,
164224 )
0 commit comments