first commit

This commit is contained in:
2026-08-14 23:41:57 +08:00
commit 086803a8dd
471 changed files with 91938 additions and 0 deletions
+58
View File
@@ -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)))
},
})
}