1package agent
2
3import (
4 "context"
5 "fmt"
6 "strings"
7 "sync"
8
9 "github.com/bnema/zut/packages/agent/extensions"
10 "github.com/bnema/zut/packages/agent/scheduler"
11 "github.com/bnema/zut/packages/core"
12 "github.com/bnema/zut/packages/provider"
13)
14
15// runScheduledSession reconstructs an inactive session in the current zut
16// process and appends the resulting turn to its existing transcript. The
17// caller serializes this with session transitions before invoking it.
18func runScheduledSession(ctx context.Context, task scheduler.Task, args Args, base Resolved, extMgr *extensions.Manager, prepareRegistry func(core.Registry) core.Registry) error {
19 path, err := core.FindManagedSessionByID(ctx, ZutHome(), task.SessionID)
20 if err != nil {
21 return fmt.Errorf("find scheduled session %q: %w", task.SessionID, err)
22 }
23 if path == "" {
24 return fmt.Errorf("scheduled session %q no longer exists", task.SessionID)
25 }
26 sess, messages, err := core.OpenSession(path)
27 if err != nil {
28 return fmt.Errorf("open scheduled session: %w", err)
29 }
30 defer sess.Close()
31
32 providerName := strings.TrimSpace(sess.Meta.Provider)
33 if providerName == "" {
34 providerName = base.Provider
35 }
36 model := strings.TrimSpace(sess.Meta.Model)
37 if model == "" {
38 model = base.Model
39 }
40 ag, _, _, err := buildNonInteractiveSessionAgentWithRegistry(ctx, args, base, extMgr, providerName, model, prepareRegistry)
41 if err != nil {
42 return fmt.Errorf("build scheduled session agent: %w", err)
43 }
44 ag.SetMessages(trimMessagesForResume(messages, 100))
45 if cumulative, lastTurn, usageErr := core.SessionUsageDetail(path); usageErr == nil {
46 ag.SeedCost(cumulative)
47 ag.SeedLastTurnUsage(lastTurn)
48 }
49 ag.SetSessionTimeContext(sess.Meta.Started, sess.Meta.Timezone, sess.Meta.TimezoneOffset)
50 var appendMu sync.Mutex
51 var appendErr error
52 recordAppendError := func(err error) {
53 if err == nil {
54 return
55 }
56 appendMu.Lock()
57 defer appendMu.Unlock()
58 if appendErr == nil {
59 appendErr = err
60 }
61 }
62 ag.OnMessageAppended = func(message provider.Message) {
63 recordAppendError(sess.AppendMessage(message))
64 }
65 ag.OnUsage = func(cumulative provider.Usage) {
66 recordAppendError(sess.AppendUsage(cumulative, cumulative))
67 }
68 ag.OnTranscriptCompacted = func(messages []provider.Message) {
69 recordAppendError(sess.AppendCompaction(messages))
70 }
71 if err := ag.Prompt(ctx, task.Message, nil, func(core.AgentEvent) {}); err != nil {
72 return err
73 }
74 appendMu.Lock()
75 err = appendErr
76 appendMu.Unlock()
77 if err != nil {
78 return fmt.Errorf("append scheduled transcript: %w", err)
79 }
80 if err := sess.Flush(); err != nil {
81 return fmt.Errorf("flush scheduled session: %w", err)
82 }
83 return nil
84}