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
13 changes: 10 additions & 3 deletions docs/clawctl-output-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,17 @@ should describe that environment in terms an operator can act on, while
gateway start on standard error. Do nothing for non-zero child exits,
redirected output, non-eligible readiness, or `Starting`, `Unhealthy`, and
`Unknown` gateway states; acknowledge an observed running gateway.
- A failed or unverified automatic gateway start must not change the OpenClaw
exit code. Write a warning to standard error, followed by
- After live gateway narration clears, write one durable outcome line. Unicode
output starts with the crab identity and uses a check mark for a verified
listener, an exclamation mark for a still-starting or unverified launch, and
a cross for a definite stop or handled start failure. Non-Unicode output uses
`[ok]`, `[!]`, or `[x]` without the crab.
- A still-starting or unverified automatic gateway start directs the user to
`clawctl gateway-service status`; do not encourage a second start while an
owned process may still be alive. A definite stop or handled failure writes
`clawctl gateway-service start` on its own line so width-constrained wrapping
cannot break the retry command. Leave the guidance unacknowledged so a later
cannot break the retry command. These advisory outcomes must not change the
OpenClaw exit code. Leave unsuccessful guidance unacknowledged so a later
eligible launch retries. `CLAWCTL_AUTO_GATEWAY_START=0` (or `false`, `no`,
or `off`) restores the start hint.
- Keep a successful `clawctl pwsh` launch silent so the user reaches the shell
Expand Down
124 changes: 109 additions & 15 deletions src/OpenClaw.Launcher/ClawCtlConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,59 @@ internal static Task<GatewayStartResult> NarrateGatewayStartAsync(
bool useColor,
bool narrate,
bool outputIsInteractive,
bool useUnicode,
Func<IProgress<GatewayStartProgress>, Task<GatewayStartResult>> start) =>
NarrateAsync(
output,
useColor,
narrate,
outputIsInteractive,
GatewayStartProgress.Initial,
start);
start,
useUnicode);

internal static void WriteGatewayStartWarning(
internal static void WriteGatewayStartOutcome(
TextWriter output,
GatewayStartResult result,
bool useColor = false,
bool? useUnicode = null)
{
ArgumentNullException.ThrowIfNull(output);
ArgumentNullException.ThrowIfNull(result);

bool unicode = useUnicode ?? SupportsUnicode(output);
(StatusKind kind, string word, string trailing) = result.State switch
{
GatewayState.Running => (
StatusKind.Success,
"running",
$" on {GatewayController.DescribePorts(result.Record)}."),
GatewayState.Starting => (
StatusKind.Warning,
"still starting",
$". {result.Message}"),
GatewayState.Unknown => (
StatusKind.Warning,
"unverified",
$". {result.Message}"),
_ => (
StatusKind.Failure,
"failed",
$". {result.Message}")
};

RenderGatewayOutcome(output, kind, word, trailing, useColor, unicode);
if (result.State is GatewayState.Starting or GatewayState.Unknown)
{
WriteGatewayStatusGuidance(output, useColor, unicode);
}
else if (result.State != GatewayState.Running)
{
WriteGatewayRetryGuidance(output, useColor, unicode);
}
}

internal static void WriteGatewayStartFailure(
TextWriter output,
string detail,
bool useColor = false,
Expand All @@ -140,24 +183,74 @@ internal static void WriteGatewayStartWarning(
ArgumentException.ThrowIfNullOrWhiteSpace(detail);

bool unicode = useUnicode ?? SupportsUnicode(output);
RenderGatewayOutcome(
output,
StatusKind.Failure,
"failed",
$". {detail}",
useColor,
unicode);
WriteGatewayRetryGuidance(output, useColor, unicode);
}

private static void RenderGatewayOutcome(
TextWriter output,
StatusKind kind,
string word,
string trailing,
bool useColor,
bool unicode)
{
Style style = kind switch
{
StatusKind.Success => SuccessStyle,
StatusKind.Warning => WarningStyle,
StatusKind.Failure => FailureStyle,
_ => MutedStyle
};
var paragraph = new Paragraph();
if (unicode)
{
paragraph.Append($"{IdentityMark} ", WarningStyle);
paragraph.Append($"{IdentityMark} ", style);
}

paragraph.Append("Warning:", WarningStyle);
paragraph.Append($" {detail}");
string? mark = Mark(kind, unicode);
if (mark is not null)
{
paragraph.Append(mark, style);
paragraph.Append(" ");
}

paragraph.Append("Gateway: ");
paragraph.Append(word, style);
paragraph.Append(trailing);
Render(output, paragraph, useColor, unicode);
}

// The retry command is rendered separately so the width-constrained
// paragraph above cannot wrap it across lines, which would make it
// unusable to copy.
var retry = new Paragraph();
retry.Append("Retry with ");
retry.Append("clawctl gateway-service start", AccentStyle);
retry.Append(".");
Render(output, retry, useColor, unicode);
private static void WriteGatewayStatusGuidance(
TextWriter output,
bool useColor,
bool unicode)
{
var guidance = new Paragraph();
guidance.Append("Check with ");
guidance.Append("clawctl gateway-service status", AccentStyle);
guidance.Append(".");
Render(output, guidance, useColor, unicode);
}

private static void WriteGatewayRetryGuidance(
TextWriter output,
bool useColor,
bool unicode)
{
// Keep the retry command on its own line so terminal wrapping cannot
// make the next action difficult to copy.
var guidance = new Paragraph();
guidance.Append("Retry with ");
guidance.Append("clawctl gateway-service start", AccentStyle);
guidance.Append(".");
Render(output, guidance, useColor, unicode);
}

internal static void WriteVersion(TextWriter output, bool useColor = false)
Expand Down Expand Up @@ -281,7 +374,8 @@ private static async Task<T> NarrateAsync<T>(
bool narrate,
bool outputIsInteractive,
ClawCtlProgress initial,
Func<IProgress<ClawCtlProgress>, Task<T>> operation)
Func<IProgress<ClawCtlProgress>, Task<T>> operation,
bool? useUnicode = null)
{
ArgumentNullException.ThrowIfNull(output);
ArgumentNullException.ThrowIfNull(initial);
Expand Down Expand Up @@ -311,7 +405,7 @@ await output.WriteLineAsync($"{new string(' ', Indent)}{initial.Message}")
IAnsiConsole console = CreateConsole(
output,
useColor,
SupportsUnicode(output),
useUnicode ?? SupportsUnicode(output),
ResolveWidth(output),
InteractionSupport.Yes);
return await NarrateWithStatusAsync(console, initial, operation)
Expand Down
10 changes: 4 additions & 6 deletions src/OpenClaw.Launcher/Gateway/AgentGatewayGuidance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ private readonly Func<
IProgress<GatewayStartProgress>,
Action<GatewayStartResult>,
Task<GatewayStartResult>> _startGatewayUnderLock;
private readonly Action<TextWriter, GatewayStartResult> _writeGatewayStartOutcome;
private readonly Action<TextWriter, string> _writeGatewayStartFailure;
private readonly Func<string, string?> _readEnvironmentVariable;

Expand All @@ -55,6 +56,7 @@ public AgentGatewayGuidance(
IProgress<GatewayStartProgress>,
Action<GatewayStartResult>,
Task<GatewayStartResult>>? startGateway = null,
Action<TextWriter, GatewayStartResult>? writeGatewayStartOutcome = null,
Action<TextWriter, string>? writeGatewayStartFailure = null,
Func<string, string?>? readEnvironmentVariable = null)
{
Expand All @@ -71,6 +73,7 @@ public AgentGatewayGuidance(
start(new Progress<GatewayStartProgress>()));
_startGatewayUnderLock = startGateway ?? ((_, _) => throw new InvalidOperationException(
"Gateway start is not configured."));
_writeGatewayStartOutcome = writeGatewayStartOutcome ?? ((_, _) => { });
_writeGatewayStartFailure = writeGatewayStartFailure ??
((writer, message) =>
{
Expand Down Expand Up @@ -211,12 +214,7 @@ private async Task StartGatewayAsync(string logonSessionId, TextWriter error)
logonSessionId,
GatewayGuidanceAcknowledgement.GatewayObservedRunning)))
.ConfigureAwait(false);
if (result.State != GatewayState.Running && !result.AlreadyRunning)
{
WriteGatewayStartFailure(
error,
$"Gateway start finished in {result.State} state: {result.Message}");
}
_writeGatewayStartOutcome(error, result);
}
catch (Exception exception) when (
exception is MxcException or SessionException or SessionLaunchException or
Expand Down
19 changes: 18 additions & 1 deletion src/OpenClaw.Launcher/Gateway/AgentPostflight.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,30 @@ public static AgentPostflight Create(
output.UseColor,
interactive,
output.UseLiveRendering,
output.UseUnicode,
start).ConfigureAwait(false);
},
(progress, onRunningUnderLock) => gateway.StartWithLockAlreadyHeldAsync(
runtime.HelperPath,
CancellationToken.None,
progress,
onRunningUnderLock),
(target, result) =>
{
using GatewayOutput output = PrepareGatewayOutput(
target,
interactive,
readEnvironmentVariable,
log,
errorIsProcessConsoleWriter,
errorIsInteractive,
supportsUnicode);
ClawCtlConsole.WriteGatewayStartOutcome(
target,
result,
output.UseColor,
output.UseUnicode);
},
(target, detail) =>
{
using GatewayOutput output = PrepareGatewayOutput(
Expand All @@ -102,7 +119,7 @@ public static AgentPostflight Create(
errorIsProcessConsoleWriter,
errorIsInteractive,
supportsUnicode);
ClawCtlConsole.WriteGatewayStartWarning(
ClawCtlConsole.WriteGatewayStartFailure(
target,
detail,
output.UseColor,
Expand Down
Loading
Loading