feat: session lifecycle wrapper with manifest capture and session logging
Refactor new/resume/open to use session.Run() which wraps child process launch with pre/post manifest capture and append-only session logging to logs/sessions.log. Bump version to 0.2.0. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/warrenronsiek/ctask/internal/shell"
|
||||
)
|
||||
|
||||
// LaunchOpts configures a session launch.
|
||||
type LaunchOpts struct {
|
||||
WsDir string
|
||||
EnvVars map[string]string
|
||||
Agent string
|
||||
Mode string
|
||||
Slug string
|
||||
Shell bool // true = interactive shell, false = agent
|
||||
}
|
||||
|
||||
// manifestStartPath returns the path to the start manifest file.
|
||||
func manifestStartPath(wsDir string) string {
|
||||
return filepath.Join(wsDir, ".ctask", "manifest-start.json")
|
||||
}
|
||||
|
||||
// Run launches an agent or shell session with pre/post manifest capture and session logging.
|
||||
// Returns the child process error (for exit code propagation).
|
||||
func Run(opts LaunchOpts) error {
|
||||
startTime := time.Now().UTC().Truncate(time.Second)
|
||||
|
||||
// Pre-session: capture start manifest
|
||||
startManifest, err := CaptureManifest(opts.WsDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[ctask] warning: failed to capture start manifest: %v\n", err)
|
||||
// Continue anyway — never block the user
|
||||
}
|
||||
|
||||
if startManifest != nil {
|
||||
mPath := manifestStartPath(opts.WsDir)
|
||||
if err := WriteManifest(mPath, startManifest); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[ctask] warning: failed to write start manifest: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Launch the session
|
||||
var childErr error
|
||||
if opts.Shell {
|
||||
childErr = shell.ExecShell(opts.WsDir, opts.EnvVars, opts.Slug, opts.Mode)
|
||||
} else {
|
||||
// Print banner before agent launch
|
||||
for _, line := range shell.BannerLines(opts.Mode, opts.Slug, opts.WsDir) {
|
||||
fmt.Println(line)
|
||||
}
|
||||
childErr = shell.ExecAgent(opts.Agent, opts.WsDir, opts.EnvVars)
|
||||
}
|
||||
|
||||
// Post-session: capture end manifest and log
|
||||
endTime := time.Now().UTC().Truncate(time.Second)
|
||||
|
||||
if startManifest != nil {
|
||||
if logErr := captureAndLog(opts, startManifest, startTime, endTime); logErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "[ctask] warning: session logging failed: %v\n", logErr)
|
||||
// Keep manifest-start.json for debugging
|
||||
} else {
|
||||
// Clean up manifest-start.json on success
|
||||
os.Remove(manifestStartPath(opts.WsDir))
|
||||
}
|
||||
}
|
||||
|
||||
return childErr
|
||||
}
|
||||
|
||||
// captureAndLog captures the end manifest, diffs, and appends to session log.
|
||||
func captureAndLog(opts LaunchOpts, startManifest *Manifest, startTime, endTime time.Time) error {
|
||||
endManifest, err := CaptureManifest(opts.WsDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("capturing end manifest: %w", err)
|
||||
}
|
||||
|
||||
diff := DiffManifests(startManifest, endManifest)
|
||||
|
||||
info := &SessionInfo{
|
||||
Agent: opts.Agent,
|
||||
Mode: opts.Mode,
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
Diff: diff,
|
||||
}
|
||||
|
||||
// For shell sessions, record "shell" as agent
|
||||
if opts.Shell {
|
||||
info.Agent = "shell"
|
||||
}
|
||||
|
||||
if err := AppendSessionLog(opts.WsDir, info); err != nil {
|
||||
return fmt.Errorf("appending session log: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user