Skip to content
Closed
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
24 changes: 14 additions & 10 deletions src/OpenClaw.SetupEngine.UI/Pages/WizardPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ private async Task SendCurrentAnswerAsync(bool skip)
parameters = new { sessionId = _sessionId, answer = new { stepId = _stepId, value = answerValue } };
}

var payload = await _client.SendWizardRequestAsync("wizard.next", parameters, timeoutMs: TimeoutForCurrentStep());
var payload = await _client.SendWizardRequestAsync("wizard.next", parameters, timeoutMs: TimeoutForCurrentStep(answerValue));
if (generation != _operationGeneration)
return;

Expand Down Expand Up @@ -545,16 +545,20 @@ private void UpdateContinueState()
ErrorText.Visibility = Visibility.Collapsed;
}

private int TimeoutForCurrentStep()
private int TimeoutForCurrentStep(object? answerValue)
{
var text = $"{TitleText.Text} {string.Join(' ', MessagePanel.Children.OfType<TextBlock>().Select(t => t.Text))}";
return text.Contains("device", StringComparison.OrdinalIgnoreCase)
|| text.Contains("authorize", StringComparison.OrdinalIgnoreCase)
|| text.Contains("login", StringComparison.OrdinalIgnoreCase)
|| text.Contains("sign in", StringComparison.OrdinalIgnoreCase)
|| text.Contains("oauth", StringComparison.OrdinalIgnoreCase)
? 300_000
: 30_000;
var message = string.Join(' ', MessagePanel.Children.OfType<TextBlock>().Select(t => t.Text));
var answerText = answerValue switch
{
string[] values => string.Join(' ', values),
_ => answerValue?.ToString() ?? ""
};
var selectedValues = GetSelectedOptionValues().ToHashSet(StringComparer.Ordinal);
var selectedOptionText = string.Join(' ', _options
.Where(option => selectedValues.Contains(option.Value))
.Select(option => $"{option.Label} {option.Hint}"));

return WizardSelection.TimeoutForStep(TitleText.Text, message, _stepId, answerText, selectedOptionText);
}

private void ResetInputs()
Expand Down
29 changes: 17 additions & 12 deletions src/OpenClaw.SetupEngine/SetupWizardRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public async Task<StepResult> RunAsync(CancellationToken ct)

try
{
payload = await client.SendWizardRequestAsync("wizard.next", parameters, timeoutMs: TimeoutFor(parsed));
payload = await client.SendWizardRequestAsync("wizard.next", parameters, timeoutMs: TimeoutFor(parsed, answerResult.Answer));
}
catch (Exception ex) when (!ct.IsCancellationRequested && IsRestartLikeWizardDisconnect(ex) && restartAttempts < 2)
{
Expand Down Expand Up @@ -417,16 +417,21 @@ private static bool TryGetConfiguredAnswer(WizardPayload step, Dictionary<string
return false;
}

private static int TimeoutFor(WizardPayload step)
internal static int TimeoutFor(WizardPayload step, string? answer)
{
var text = $"{step.Title} {step.Message}";
return text.Contains("device", StringComparison.OrdinalIgnoreCase)
|| text.Contains("authorize", StringComparison.OrdinalIgnoreCase)
|| text.Contains("login", StringComparison.OrdinalIgnoreCase)
|| text.Contains("sign in", StringComparison.OrdinalIgnoreCase)
|| text.Contains("oauth", StringComparison.OrdinalIgnoreCase)
? 300_000
: 30_000;
var selectedValues = string.IsNullOrWhiteSpace(answer)
? []
: step.StepType == "multiselect"
? SplitMultiSelect(answer)
: [answer];
var selectedValueSet = selectedValues.ToHashSet(StringComparer.Ordinal);
var selectedOptionText = selectedValueSet.Count > 0
? string.Join(' ', step.Options
.Where(option => selectedValueSet.Contains(option.Value))
.Select(option => $"{option.Label} {option.Hint}"))
: "";

return WizardSelection.TimeoutForStep(step.Title, step.Message, step.StepId, answer, selectedOptionText);
}

private static bool IsRestartLikeWizardDisconnect(Exception ex)
Expand Down Expand Up @@ -479,9 +484,9 @@ private sealed record AnswerResolution(bool Success, bool HasAnswer, string Answ
public static AnswerResolution Fail(string error) => new(false, false, "", error);
}

private sealed record WizardOption(string Value, string Label, string Hint);
internal sealed record WizardOption(string Value, string Label, string Hint);

private sealed record WizardPayload(
internal sealed record WizardPayload(
bool IsDone,
string? SessionId,
string StepId,
Expand Down
20 changes: 20 additions & 0 deletions src/OpenClaw.SetupEngine/WizardSelection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ namespace OpenClaw.SetupEngine;

public static class WizardSelection
{
public const int DefaultStepTimeoutMs = 30_000;
public const int SlowStepTimeoutMs = 300_000;

public static bool RequiresSelection(string stepType) => stepType is "select" or "multiselect";
public static bool RequiresAnswer(string stepType) => RequiresSelection(stepType) || stepType == "text";

Expand Down Expand Up @@ -41,4 +44,21 @@ public static bool ShouldDisableContinue(string stepType, IReadOnlyCollection<st

public static bool ShouldDisableContinue(string stepType, string? textInput) =>
stepType == "text" && string.IsNullOrWhiteSpace(textInput);

public static int TimeoutForStep(string? title, string? message, params string?[] additionalText)
{
var text = string.Join(' ', new[] { title, message }.Concat(additionalText).Where(value => !string.IsNullOrWhiteSpace(value)));
return text.Contains("device", StringComparison.OrdinalIgnoreCase)
|| text.Contains("authorize", StringComparison.OrdinalIgnoreCase)
|| text.Contains("login", StringComparison.OrdinalIgnoreCase)
|| text.Contains("sign in", StringComparison.OrdinalIgnoreCase)
|| text.Contains("oauth", StringComparison.OrdinalIgnoreCase)
|| text.Contains("channel", StringComparison.OrdinalIgnoreCase)
|| text.Contains("plugin", StringComparison.OrdinalIgnoreCase)
|| text.Contains("install", StringComparison.OrdinalIgnoreCase)
|| text.Contains("download", StringComparison.OrdinalIgnoreCase)
|| text.Contains("teams", StringComparison.OrdinalIgnoreCase)
? SlowStepTimeoutMs
: DefaultStepTimeoutMs;
}
}
28 changes: 28 additions & 0 deletions tests/OpenClaw.SetupEngine.Tests/SetupWizardRunnerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace OpenClaw.SetupEngine.Tests;

public class SetupWizardRunnerTests
{
[Fact]
public void TimeoutFor_UsesSelectedMultiselectOptionText()
{
var step = new SetupWizardRunner.WizardPayload(
IsDone: false,
SessionId: "session",
StepId: "integration-choice",
StepType: "multiselect",
Title: "Choose integrations",
Message: "Select one or more integrations.",
InitialValue: "",
Sensitive: false,
StepIndex: 0,
TotalSteps: 1,
Options:
[
new SetupWizardRunner.WizardOption("integration_a", "Microsoft Teams", "channel setup"),
new SetupWizardRunner.WizardOption("integration_b", "Matrix", "")
],
Error: null);

Assert.Equal(WizardSelection.SlowStepTimeoutMs, SetupWizardRunner.TimeoutFor(step, "integration_a,integration_b"));
}
}
18 changes: 18 additions & 0 deletions tests/OpenClaw.SetupEngine.Tests/WizardSelectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,22 @@ public void ContinueDisabled_ForEmptyTextInput(string? input, bool expectedDisab
{
Assert.Equal(expectedDisabled, WizardSelection.ShouldDisableContinue("text", input));
}

[Theory]
[InlineData("Authorize device", "", null)]
[InlineData("Choose a channel", "Select where OpenClaw should send messages", null)]
[InlineData("Setup", "Downloading plugin package", null)]
[InlineData("Setup", "Installing integration", null)]
[InlineData("Choose integration", "", "Microsoft Teams")]
[InlineData("Choose integration", "", "msteams")]
public void TimeoutForStep_UsesLongTimeoutForSlowWizardOperations(string title, string message, string? additionalText)
{
Assert.Equal(WizardSelection.SlowStepTimeoutMs, WizardSelection.TimeoutForStep(title, message, additionalText));
}

[Fact]
public void TimeoutForStep_UsesDefaultTimeoutForOrdinaryQuestions()
{
Assert.Equal(WizardSelection.DefaultStepTimeoutMs, WizardSelection.TimeoutForStep("Choose a model", "Select the default model."));
}
}
Loading