summaryrefslogtreecommitdiff
path: root/internal/executor/channel.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/executor/channel.go')
-rw-r--r--internal/executor/channel.go59
1 files changed, 43 insertions, 16 deletions
diff --git a/internal/executor/channel.go b/internal/executor/channel.go
index 76df94f..541694b 100644
--- a/internal/executor/channel.go
+++ b/internal/executor/channel.go
@@ -4,10 +4,10 @@ import (
"context"
"encoding/json"
"errors"
+ "sync"
"time"
"github.com/thepeterstone/claudomator/internal/event"
- "github.com/thepeterstone/claudomator/internal/storage"
"github.com/thepeterstone/claudomator/internal/task"
"github.com/google/uuid"
)
@@ -50,37 +50,64 @@ type channelStore interface {
CreateEvent(e *event.Event) error
}
-// storeChannel is the default AgentChannel backed by storage. Summary reports
-// are buffered onto the execution record so the pool persists them once (with
-// its extract/synthesize fallbacks); other signals are written immediately.
+// storeChannel is the default AgentChannel backed by storage. Summary and
+// question signals are buffered here under a mutex (they may arrive on an MCP
+// handler goroutine mid-run); the pool flushes the summary to the execution and
+// the runner reads the pending question to build a BlockedError after the
+// subprocess exits. SpawnSubtask/RecordProgress write immediately.
type storeChannel struct {
store channelStore
taskID string
- exec *storage.Execution
+
+ mu sync.Mutex
+ summary string
+ summarySet bool
+ question string
+ blocked bool
}
var _ AgentChannel = (*storeChannel)(nil)
-func newStoreChannel(store channelStore, taskID string, exec *storage.Execution) *storeChannel {
- return &storeChannel{store: store, taskID: taskID, exec: exec}
+func newStoreChannel(store channelStore, taskID string) *storeChannel {
+ return &storeChannel{store: store, taskID: taskID}
}
-// AskUser on the default (file-transport) channel cannot deliver an answer
-// in-session, so it always reports the run as blocked. Question persistence is
-// owned by the pool's BlockedError handling.
-func (c *storeChannel) AskUser(_ context.Context, _ string) (string, error) {
+// AskUser buffers the question and flags the run as blocked. The default
+// transport cannot deliver an answer in-session, so it returns ErrAgentBlocked;
+// the runner converts the buffered question into a BlockedError after exit, and
+// question persistence is owned by the pool's BlockedError handling.
+func (c *storeChannel) AskUser(_ context.Context, questionJSON string) (string, error) {
+ c.mu.Lock()
+ c.question = questionJSON
+ c.blocked = true
+ c.mu.Unlock()
return "", ErrAgentBlocked
}
-// ReportSummary buffers the summary onto the execution record. The pool persists
-// it (preferring this value over its extract/synthesize fallbacks).
+// ReportSummary buffers the summary; the pool flushes it onto the execution
+// after the run (preferring it over its extract/synthesize fallbacks).
func (c *storeChannel) ReportSummary(_ context.Context, summary string) error {
- if c.exec != nil {
- c.exec.Summary = summary
- }
+ c.mu.Lock()
+ c.summary = summary
+ c.summarySet = true
+ c.mu.Unlock()
return nil
}
+// ReportedSummary returns the buffered summary if report_summary was called.
+func (c *storeChannel) ReportedSummary() (string, bool) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ return c.summary, c.summarySet
+}
+
+// PendingQuestion returns the buffered ask_user question if the run is blocked.
+func (c *storeChannel) PendingQuestion() (string, bool) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ return c.question, c.blocked
+}
+
func (c *storeChannel) SpawnSubtask(_ context.Context, spec SubtaskSpec) (string, error) {
now := time.Now().UTC()
child := &task.Task{