first commit
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
// This file wires the startup background trigger for /dream memory
|
||||
// consolidation (US-008, FR-4/FR-17): when an interactive REPL session starts,
|
||||
// if [dream].enabled and a consolidation is due (per dream.State.Due), the dream
|
||||
// subprocess is spawned in the BACKGROUND so it never delays the first user
|
||||
// response, and a non-intrusive one-line summary is printed on completion.
|
||||
//
|
||||
// The decision + goroutine live in dream.Scheduler; this file only supplies the
|
||||
// CLI-side spawn seam (reusing spawnDream from dream_repl.go) and the one-line
|
||||
// notice renderer (RenderReportLine). The subprocess's O_EXCL lock enforces
|
||||
// single-instance, so a second trigger just yields a skipped child (silent).
|
||||
package repl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/smallnest/pigo/internal/cli/ui"
|
||||
"github.com/smallnest/pigo/internal/dream"
|
||||
)
|
||||
|
||||
// dreamStartupScheduler owns the startup auto-trigger decision. It is stateless
|
||||
// (dream.Scheduler is a zero-size type), so a package value is enough; tests
|
||||
// exercise this path through the spawnDream seam rather than replacing it.
|
||||
var dreamStartupScheduler dream.Scheduler
|
||||
|
||||
// maybeStartBackgroundDream launches an auto-consolidation in the background at
|
||||
// interactive session startup when dream is enabled and due. It never blocks:
|
||||
// the due check is a single state.json read and any spawn runs in a goroutine,
|
||||
// so the first prompt is served immediately (SPEC FR-4 / §8.2). The completion
|
||||
// notice is a single dim line via RenderReportLine; a skipped, no-op, or failed
|
||||
// run prints nothing (SPEC §6.1).
|
||||
//
|
||||
// out is written to from the background goroutine, so callers must pass a writer
|
||||
// safe for a late async line (the REPL uses os.Stdout, where a one-line notice
|
||||
// simply appears in scrollback). It is a no-op when memoryRoot is empty (dream
|
||||
// state has nowhere to live) or dream is disabled/not due.
|
||||
func maybeStartBackgroundDream(out io.Writer, memoryRoot, projectDir string, cfg dream.Config) bool {
|
||||
if memoryRoot == "" {
|
||||
return false
|
||||
}
|
||||
return dreamStartupScheduler.MaybeRunBackground(context.Background(), dream.BackgroundDeps{
|
||||
MemoryRoot: memoryRoot,
|
||||
ProjectDir: projectDir,
|
||||
Config: cfg,
|
||||
Spawn: func(ctx context.Context, dir string) (dream.Report, error) {
|
||||
// Bound the background run like a manual /dream (SPEC §6.3): a hung
|
||||
// LLM-backed pass is killed rather than leaking a goroutine forever.
|
||||
ctx, cancel := context.WithTimeout(ctx, dreamRunTimeout)
|
||||
defer cancel()
|
||||
res, err := spawnDream(ctx, dir, false)
|
||||
return res.report, err
|
||||
},
|
||||
OnReport: func(r dream.Report) {
|
||||
fmt.Fprintln(out, ui.Colorize(ui.Enabled(), ui.Dim, RenderReportLine(r)))
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user