@@ -11,6 +11,7 @@ import (
1111 "path/filepath"
1212 "sort"
1313 "strings"
14+ "sync"
1415 "time"
1516
1617 "ccgo/internal/api/anthropic"
@@ -264,6 +265,18 @@ func runDaemon(ctx context.Context, state *bootstrap.State, options daemonOption
264265 }
265266 endpoint := ""
266267 var daemonServer * daemonpkg.Server
268+ var tickMu sync.Mutex
269+ writeHeartbeat := func (now time.Time ) error {
270+ return daemonpkg .WriteState (statePath , daemonpkg .BuildState (runner .SessionID , runner .WorkingDirectory , daemonpkg .RuntimeRunning , os .Getpid (), endpoint , now , nil ))
271+ }
272+ runTick := func (now time.Time ) (contracts.ToolResult , error ) {
273+ tickMu .Lock ()
274+ defer tickMu .Unlock ()
275+ if err := writeHeartbeat (now ); err != nil {
276+ return contracts.ToolResult {}, err
277+ }
278+ return runDaemonDueSchedules (ctx , runner , now )
279+ }
267280 if ! options .Once {
268281 daemonServer , err = daemonpkg .StartServer (daemonpkg.ServerOptions {
269282 StateFunc : func () daemonpkg.State {
@@ -273,6 +286,13 @@ func runDaemon(ctx context.Context, state *bootstrap.State, options daemonOption
273286 }
274287 return state
275288 },
289+ TickFunc : func (context.Context ) daemonpkg.TickResponse {
290+ result , err := runTick (time .Now ().UTC ())
291+ if err != nil {
292+ return daemonpkg.TickResponse {OK : false , Error : err .Error ()}
293+ }
294+ return daemonTickResponse (result )
295+ },
276296 })
277297 if err != nil {
278298 fmt .Fprintf (stderr , "ccgo daemon: %v\n " , err )
@@ -285,16 +305,7 @@ func runDaemon(ctx context.Context, state *bootstrap.State, options daemonOption
285305 }()
286306 endpoint = daemonServer .Endpoint ()
287307 }
288- writeHeartbeat := func (now time.Time ) error {
289- return daemonpkg .WriteState (statePath , daemonpkg .BuildState (runner .SessionID , runner .WorkingDirectory , daemonpkg .RuntimeRunning , os .Getpid (), endpoint , now , nil ))
290- }
291- runTick := func (now time.Time ) error {
292- if err := writeHeartbeat (now ); err != nil {
293- return err
294- }
295- return runDaemonDueSchedules (ctx , runner , now )
296- }
297- if err := runTick (time .Now ().UTC ()); err != nil {
308+ if _ , err := runTick (time .Now ().UTC ()); err != nil {
298309 fmt .Fprintf (stderr , "ccgo daemon: %v\n " , err )
299310 return 1
300311 }
@@ -307,27 +318,63 @@ func runDaemon(ctx context.Context, state *bootstrap.State, options daemonOption
307318 for {
308319 select {
309320 case <- ctx .Done ():
321+ tickMu .Lock ()
310322 _ = daemonpkg .WriteState (statePath , daemonpkg .BuildState (runner .SessionID , runner .WorkingDirectory , daemonpkg .RuntimeDisabled , os .Getpid (), endpoint , time .Now ().UTC (), ctx .Err ()))
323+ tickMu .Unlock ()
311324 return 0
312325 case now := <- ticker .C :
313- if err := runTick (now .UTC ()); err != nil {
326+ if _ , err := runTick (now .UTC ()); err != nil {
314327 fmt .Fprintf (stderr , "ccgo daemon: %v\n " , err )
315328 return 1
316329 }
317330 }
318331 }
319332}
320333
321- func runDaemonDueSchedules (ctx context.Context , runner conversation.Runner , now time.Time ) error {
322- _ , err := tasktools .RunDueSchedules (tool.Context {
334+ func runDaemonDueSchedules (ctx context.Context , runner conversation.Runner , now time.Time ) (contracts. ToolResult , error ) {
335+ return tasktools .RunDueSchedules (tool.Context {
323336 Context : ctx ,
324337 WorkingDirectory : runner .WorkingDirectory ,
325338 SessionID : runner .SessionID ,
326339 Metadata : map [string ]any {
327340 tool .MetadataSessionPathKey : runner .SessionPath ,
328341 },
329342 }, "" , now , tool .NopProgressSink ())
330- return err
343+ }
344+
345+ func daemonTickResponse (result contracts.ToolResult ) daemonpkg.TickResponse {
346+ structured := result .StructuredContent
347+ return daemonpkg.TickResponse {
348+ OK : true ,
349+ CheckedAt : stringMapValue (structured , "checked_at" ),
350+ TriggeredCount : intMapValue (structured , "triggered_count" ),
351+ ErrorCount : intMapValue (structured , "error_count" ),
352+ Structured : structured ,
353+ }
354+ }
355+
356+ func stringMapValue (values map [string ]any , key string ) string {
357+ if values == nil {
358+ return ""
359+ }
360+ value , _ := values [key ].(string )
361+ return value
362+ }
363+
364+ func intMapValue (values map [string ]any , key string ) int {
365+ if values == nil {
366+ return 0
367+ }
368+ switch value := values [key ].(type ) {
369+ case int :
370+ return value
371+ case int64 :
372+ return int (value )
373+ case float64 :
374+ return int (value )
375+ default :
376+ return 0
377+ }
331378}
332379
333380func handleChromeNativeHostMessage (raw json.RawMessage ) map [string ]any {
0 commit comments