Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 26 additions & 78 deletions internal/agent/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,13 +266,9 @@ func (flow *Flow) SendMessage(ctx dex.Context, input PendingUserMessage) (*dex.R
if pending != nil {
return &dex.RPCResult[bool]{Output: false}, nil
}
accepted, err := flow.resetInactivityTimerForUserOperation(ctx)
if err != nil {
if err := flow.resetInactivityTimerForUserOperation(ctx); err != nil {
return nil, err
}
if !accepted {
return &dex.RPCResult[bool]{Output: false}, nil
}
if err := queuedUserMessagesChannel.Publish(ctx, input); err != nil {
return nil, err
}
Expand All @@ -295,13 +291,9 @@ func (flow *Flow) AnswerQuestions(ctx dex.Context, input AnswerQuestionsRequest)
if !isValid {
return &dex.RPCResult[bool]{Output: false}, nil
}
accepted, err := flow.resetInactivityTimerForUserOperation(ctx)
if err != nil {
if err := flow.resetInactivityTimerForUserOperation(ctx); err != nil {
return nil, err
}
if !accepted {
return &dex.RPCResult[bool]{Output: false}, nil
}
if err := pendingUserInputAttribute.Delete(ctx); err != nil {
return nil, err
}
Expand Down Expand Up @@ -343,13 +335,9 @@ func (flow *Flow) SteerMessage(ctx dex.Context, input SteerMessageRequest) (*dex
}
shouldDeletePendingToolRecovery = true
}
accepted, err := flow.resetInactivityTimerForUserOperation(ctx)
if err != nil {
if err := flow.resetInactivityTimerForUserOperation(ctx); err != nil {
return nil, err
}
if !accepted {
return &dex.RPCResult[bool]{Output: false}, nil
}
if shouldDeletePendingToolRecovery {
if deleteErr := pendingToolRecoveryAttribute.Delete(ctx); deleteErr != nil {
return nil, deleteErr
Expand Down Expand Up @@ -462,13 +450,9 @@ func (flow *Flow) DeleteQueuedMessage(ctx dex.Context, messageID MessageID) (*de
if !found {
return &dex.RPCResult[bool]{Output: false}, nil
}
accepted, err := flow.resetInactivityTimerForUserOperation(ctx)
if err != nil {
if err := flow.resetInactivityTimerForUserOperation(ctx); err != nil {
return nil, err
}
if !accepted {
return &dex.RPCResult[bool]{Output: false}, nil
}
if err := queuedUserMessagesChannel.Delete(ctx, message.MessageID); err != nil {
return nil, err
}
Expand All @@ -484,13 +468,9 @@ func (flow *Flow) ApproveTool(ctx dex.Context, input ToolApprovalRequest) (*dex.
if pending == nil || pending.CallID != input.CallID {
return &dex.RPCResult[bool]{Output: false}, nil
}
accepted, err := flow.resetInactivityTimerForUserOperation(ctx)
if err != nil {
if err := flow.resetInactivityTimerForUserOperation(ctx); err != nil {
return nil, err
}
if !accepted {
return &dex.RPCResult[bool]{Output: false}, nil
}
if err := pendingApprovalAttribute.Delete(ctx); err != nil {
return nil, err
}
Expand All @@ -515,13 +495,9 @@ func (flow *Flow) ResolveToolRecovery(
if !isValidToolRecoveryResolution(*pending, input) {
return &dex.RPCResult[bool]{Output: false}, nil
}
accepted, err := flow.resetInactivityTimerForUserOperation(ctx)
if err != nil {
if err := flow.resetInactivityTimerForUserOperation(ctx); err != nil {
return nil, err
}
if !accepted {
return &dex.RPCResult[bool]{Output: false}, nil
}
if err := pendingToolRecoveryAttribute.Delete(ctx); err != nil {
return nil, err
}
Expand Down Expand Up @@ -571,13 +547,9 @@ func (flow *Flow) ExecutePlan(ctx dex.Context, input PlanExecutionRequest) (*dex
if !canExecute {
return &dex.RPCResult[bool]{Output: false}, nil
}
accepted, err := flow.resetInactivityTimerForUserOperation(ctx)
if err != nil {
if err := flow.resetInactivityTimerForUserOperation(ctx); err != nil {
return nil, err
}
if !accepted {
return &dex.RPCResult[bool]{Output: false}, nil
}
revision := plan.Revision
state.PendingPlanExecutionRevision = &revision
state.PlanNoProgressAttempts = 0
Expand Down Expand Up @@ -673,44 +645,36 @@ func clearInactivityDeadline(ctx dex.Context) error {
return err
}

func (flow *Flow) resetInactivityTimerForUserOperation(ctx dex.Context) (bool, error) {
func (flow *Flow) resetInactivityTimerForUserOperation(ctx dex.Context) error {
config, err := agentConfigAttribute.Get(ctx)
if err != nil {
return false, err
return err
}
if config.InactivityTimeoutSeconds == 0 {
return true, nil
return nil
}
activityAt := time.Now().UTC()
resetTarget := activityAt.Add(
resetTarget := time.Now().UTC().Add(
time.Duration(config.InactivityTimeoutSeconds) * time.Second,
)
return flow.resetInactivityTimer(ctx, activityAt, resetTarget)
return flow.resetInactivityTimer(ctx, resetTarget)
}

func (flow *Flow) resetInactivityTimer(
ctx dex.Context,
activityAt time.Time,
resetTarget time.Time,
) (bool, error) {
func (flow *Flow) resetInactivityTimer(ctx dex.Context, resetTarget time.Time) error {
deadline, err := inactivityDeadlineAttribute.Get(ctx)
if err != nil {
return false, err
}
if !deadline.After(activityAt.UTC()) {
return false, nil
return err
}
resetTarget = resetTarget.UTC()
if resetTarget.Sub(deadline) <= flow.inactivityResetMinimumExtension() {
return true, nil
return nil
}
if err := inactivityDeadlineAttribute.Set(ctx, resetTarget); err != nil {
return false, err
return err
}
if err := resetInactivityTimerChannel.Publish(ctx, resetTarget); err != nil {
return false, err
return err
}
return true, nil
return nil
}

func (flow *Flow) inactivityResetMinimumExtension() time.Duration {
Expand Down Expand Up @@ -2084,15 +2048,11 @@ var (
},
}
durableWaitStepOptions = &dex.StepOptions{
WaitForMethodTimeout: messageMutationStepOptions.WaitForMethodTimeout,
ExecuteMethodTimeout: messageMutationStepOptions.ExecuteMethodTimeout,
ExecuteLoadAttributeMaps: messageMutationStepOptions.ExecuteLoadAttributeMaps,
WaitForLockAttributes: []dex.AttributeLock{
dex.LockAttribute(inactivityDeadlineAttribute),
},
ExecuteLockAttributes: messageMutationStepOptions.ExecuteLockAttributes,
}
inactivityExpirationStepOptions = &dex.StepOptions{
inactivityTimeoutStepOptions = &dex.StepOptions{
WaitForMethodTimeout: time.Minute,
ExecuteMethodTimeout: time.Minute,
ExecuteDurability: dex.StepDurabilitySync,
Expand Down Expand Up @@ -3721,13 +3681,9 @@ func (step durableWaitStep) WaitFor(ctx dex.Context, _ dex.None) (*dex.Wait, err
}
waitTarget := timer.StartedAt.UTC().Add(time.Duration(timer.DurationSeconds) * time.Second)
resetTarget := waitTarget.Add(time.Duration(config.InactivityTimeoutSeconds) * time.Second)
accepted, resetErr := step.flow.resetInactivityTimer(ctx, ctx.FirstAttemptAt(), resetTarget)
if resetErr != nil {
if resetErr := step.flow.resetInactivityTimer(ctx, resetTarget); resetErr != nil {
return nil, resetErr
}
if !accepted {
return nil, errors.New("durable wait started after inactivity expiration")
}
}
return dex.AnyOf(
dex.Timer(time.Duration(timer.DurationSeconds)*time.Second),
Expand Down Expand Up @@ -3815,7 +3771,7 @@ func (inactivityTimeoutStep) GetStepType() string {
}

func (inactivityTimeoutStep) GetStepOptions() *dex.StepOptions {
return inactivityExpirationStepOptions
return inactivityTimeoutStepOptions
}

func (inactivityTimeoutStep) WaitFor(
Expand Down Expand Up @@ -3851,19 +3807,11 @@ func (step inactivityTimeoutStep) Execute(
if err != nil {
return nil, err
}
latestDeadline := currentDeadline.UTC()
for _, resetTarget := range resetTargets {
if resetTarget.After(latestDeadline) {
latestDeadline = resetTarget.UTC()
}
}
if latestDeadline.After(deadline.UTC()) {
if setErr := inactivityDeadlineAttribute.Set(ctx, latestDeadline); setErr != nil {
return nil, setErr
}
return dex.GoTo(inactivityTimeoutStep{flow: step.flow}, latestDeadline), nil
currentDeadline = currentDeadline.UTC()
if currentDeadline.After(deadline.UTC()) {
return dex.GoTo(inactivityTimeoutStep{flow: step.flow}, currentDeadline), nil
}
if !ctx.HasTimerFired() {
if len(resetTargets) > 0 || !ctx.HasTimerFired() {
return nil, errors.New("inactivity wait completed without a reset or deadline")
}
if step.flow.inactivityExpiration == nil {
Expand All @@ -3876,7 +3824,7 @@ func (step inactivityTimeoutStep) Execute(
expiration := InactivityExpiration{
FlowID: FlowID(ctx.FlowID()),
RunID: RunID(ctx.RunID()),
Deadline: latestDeadline,
Deadline: currentDeadline,
RuntimeMetadata: metadata,
}
if err := step.flow.inactivityExpiration.HandleInactivityExpiration(ctx, expiration); err != nil {
Expand Down
12 changes: 12 additions & 0 deletions internal/agent/tool_recovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package agent
import (
"context"
"errors"
"reflect"
"testing"
"time"

Expand Down Expand Up @@ -177,6 +178,17 @@ func TestRegisteredStepOptionsUseBoundedTimeoutsAndModelSyncDurability(t *testin
}
}

func TestDurableWaitStepOptionsOnlyLockInactivityDeadlineDuringWaitFor(t *testing.T) {
want := &dex.StepOptions{
WaitForLockAttributes: []dex.AttributeLock{
dex.LockAttribute(inactivityDeadlineAttribute),
},
}
if !reflect.DeepEqual(durableWaitStepOptions, want) {
t.Fatalf("durable wait Step options = %+v, want %+v", durableWaitStepOptions, want)
}
}

func TestValidateToolExecutionPolicyRejectsUnknownRunningType(t *testing.T) {
definition := parallelDefinitionForTestOnly("invalid")
definition.RunningType = "sometimes"
Expand Down