test: require parallels agent responses

This commit is contained in:
Peter Steinberger
2026-05-01 14:20:49 +01:00
parent d7ea6d9f8c
commit bae211f72a
7 changed files with 209 additions and 12 deletions

View File

@@ -12,10 +12,12 @@ import {
parseBoolEnv,
parseMode,
parseProvider,
providerIdFromModelId,
repoRoot,
resolveHostIp,
resolveHostPort,
resolveLatestVersion,
resolveParallelsModelTimeoutSeconds,
resolveProviderAuth,
resolveSnapshot,
run,
@@ -687,6 +689,15 @@ rm -rf /root/.openclaw/test-bad-plugin`);
private verifyLocalTurn(): void {
this.guestExec(["openclaw", "models", "set", this.auth.modelId]);
const providerId = providerIdFromModelId(this.auth.modelId) || this.options.provider;
this.guestExec([
"openclaw",
"config",
"set",
`models.providers.${providerId}.timeoutSeconds`,
String(resolveParallelsModelTimeoutSeconds("linux")),
"--strict-json",
]);
this.guestExec([
"openclaw",
"config",
@@ -698,9 +709,38 @@ rm -rf /root/.openclaw/test-bad-plugin`);
this.guestExec(["openclaw", "config", "set", "tools.profile", "minimal"]);
this.prepareAgentWorkspace();
this.guestBash(
`exec /usr/bin/env ${shellQuote(`${this.auth.apiKeyEnv}=${this.auth.apiKeyValue}`)} openclaw agent --local --agent main --session-id parallels-linux-smoke --message ${shellQuote(
"Reply with exact ASCII text OK only.",
)} --thinking minimal --json`,
`agent_ok=false
for attempt in 1 2; do
session_id="parallels-linux-smoke"
if [ "$attempt" -gt 1 ]; then session_id="parallels-linux-smoke-retry-$attempt"; fi
rm -f "$HOME/.openclaw/agents/main/sessions/$session_id.jsonl"
output_file="$(mktemp)"
set +e
/usr/bin/env ${shellQuote(`${this.auth.apiKeyEnv}=${this.auth.apiKeyValue}`)} openclaw agent --local --agent main --session-id "$session_id" --message ${shellQuote(
"Reply with exact ASCII text OK only.",
)} --thinking minimal --json >"$output_file" 2>&1
rc=$?
set -e
cat "$output_file"
if [ "$rc" -ne 0 ]; then
rm -f "$output_file"
exit "$rc"
fi
if grep -Eq '"finalAssistant(Raw|Visible)Text"[[:space:]]*:[[:space:]]*"OK"' "$output_file"; then
agent_ok=true
rm -f "$output_file"
break
fi
rm -f "$output_file"
if [ "$attempt" -lt 2 ]; then
echo "agent turn attempt $attempt finished without OK response; retrying"
sleep 3
fi
done
if [ "$agent_ok" != true ]; then
echo "openclaw agent finished without OK response" >&2
exit 1
fi`,
);
}

View File

@@ -11,9 +11,11 @@ import {
packOpenClaw,
parseMode,
parseProvider,
providerIdFromModelId,
resolveHostIp,
resolveHostPort,
resolveLatestVersion,
resolveParallelsModelTimeoutSeconds,
resolveProviderAuth,
resolveSnapshot,
run,
@@ -971,6 +973,16 @@ exit 1`);
private verifyTurn(): void {
this.guestExec([guestNode, guestOpenClawEntry, "models", "set", this.auth.modelId]);
const providerId = providerIdFromModelId(this.auth.modelId) || this.options.provider;
this.guestExec([
guestNode,
guestOpenClawEntry,
"config",
"set",
`models.providers.${providerId}.timeoutSeconds`,
String(resolveParallelsModelTimeoutSeconds("macos")),
"--strict-json",
]);
this.guestExec([
guestNode,
guestOpenClawEntry,
@@ -983,9 +995,38 @@ exit 1`);
this.guestExec([guestNode, guestOpenClawEntry, "config", "set", "tools.profile", "minimal"]);
this.guestSh(
`${posixAgentWorkspaceScript("Parallels macOS smoke test assistant.")}
exec /usr/bin/env ${shellQuote(`${this.auth.apiKeyEnv}=${this.auth.apiKeyValue}`)} ${guestNode} ${guestOpenClawEntry} agent --local --agent main --session-id parallels-macos-smoke --message ${shellQuote(
"Reply with exact ASCII text OK only.",
)} --thinking minimal --json`,
agent_ok=false
for attempt in 1 2; do
session_id="parallels-macos-smoke"
if [ "$attempt" -gt 1 ]; then session_id="parallels-macos-smoke-retry-$attempt"; fi
rm -f "$HOME/.openclaw/agents/main/sessions/$session_id.jsonl"
output_file="$(mktemp)"
set +e
/usr/bin/env ${shellQuote(`${this.auth.apiKeyEnv}=${this.auth.apiKeyValue}`)} ${guestNode} ${guestOpenClawEntry} agent --local --agent main --session-id "$session_id" --message ${shellQuote(
"Reply with exact ASCII text OK only.",
)} --thinking minimal --json >"$output_file" 2>&1
rc=$?
set -e
cat "$output_file"
if [ "$rc" -ne 0 ]; then
rm -f "$output_file"
exit "$rc"
fi
if grep -Eq '"finalAssistant(Raw|Visible)Text"[[:space:]]*:[[:space:]]*"OK"' "$output_file"; then
agent_ok=true
rm -f "$output_file"
break
fi
rm -f "$output_file"
if [ "$attempt" -lt 2 ]; then
echo "agent turn attempt $attempt finished without OK response; retrying"
sleep 3
fi
done
if [ "$agent_ok" != true ]; then
echo "openclaw agent finished without OK response" >&2
exit 1
fi`,
);
}

View File

@@ -1,7 +1,12 @@
import { posixAgentWorkspaceScript, windowsAgentWorkspaceScript } from "./agent-workspace.ts";
import { shellQuote } from "./host-command.ts";
import { psSingleQuote, windowsOpenClawResolver } from "./powershell.ts";
import type { ProviderAuth } from "./types.ts";
import {
psSingleQuote,
windowsModelProviderTimeoutScript,
windowsOpenClawResolver,
} from "./powershell.ts";
import { providerIdFromModelId, resolveParallelsModelTimeoutSeconds } from "./provider-auth.ts";
import type { Platform, ProviderAuth } from "./types.ts";
export interface NpmUpdateScriptInput {
auth: ProviderAuth;
@@ -9,6 +14,53 @@ export interface NpmUpdateScriptInput {
updateTarget: string;
}
function posixModelProviderTimeoutCommand(
command: string,
modelId: string,
platform: Platform,
): string {
const providerId = providerIdFromModelId(modelId);
if (!providerId) {
return "";
}
return `${command} config set ${shellQuote(
`models.providers.${providerId}.timeoutSeconds`,
)} ${resolveParallelsModelTimeoutSeconds(platform)} --strict-json`;
}
function posixAssertAgentOkScript(command: string, input: NpmUpdateScriptInput, sessionId: string) {
return `agent_ok=false
for attempt in 1 2; do
session_id=${shellQuote(sessionId)}
if [ "$attempt" -gt 1 ]; then session_id=${shellQuote(`${sessionId}-retry`)}"-$attempt"; fi
rm -f "$HOME/.openclaw/agents/main/sessions/$session_id.jsonl"
output_file="$(mktemp)"
set +e
${input.auth.apiKeyEnv}=${shellQuote(input.auth.apiKeyValue)} ${command} agent --local --agent main --session-id "$session_id" --message 'Reply with exact ASCII text OK only.' --thinking minimal --json >"$output_file" 2>&1
rc=$?
set -e
cat "$output_file"
if [ "$rc" -ne 0 ]; then
rm -f "$output_file"
exit "$rc"
fi
if grep -Eq '"finalAssistant(Raw|Visible)Text"[[:space:]]*:[[:space:]]*"OK"' "$output_file"; then
agent_ok=true
rm -f "$output_file"
break
fi
rm -f "$output_file"
if [ "$attempt" -lt 2 ]; then
echo "agent turn attempt $attempt finished without OK response; retrying"
sleep 3
fi
done
if [ "$agent_ok" != true ]; then
echo "openclaw agent finished without OK response" >&2
exit 1
fi`;
}
export function macosUpdateScript(input: NpmUpdateScriptInput): string {
return String.raw`set -euo pipefail
export PATH=/opt/homebrew/bin:/opt/homebrew/opt/node/bin:/opt/homebrew/sbin:/usr/bin:/bin:/usr/sbin:/sbin
@@ -70,10 +122,11 @@ ${posixVersionCheck("/opt/homebrew/bin/openclaw", input.expectedNeedle)}
start_openclaw_gateway
wait_for_gateway
/opt/homebrew/bin/openclaw models set ${shellQuote(input.auth.modelId)}
${posixModelProviderTimeoutCommand("/opt/homebrew/bin/openclaw", input.auth.modelId, "macos")}
/opt/homebrew/bin/openclaw config set agents.defaults.skipBootstrap true --strict-json
/opt/homebrew/bin/openclaw config set tools.profile minimal
${posixAgentWorkspaceScript("Parallels npm update smoke test assistant.")}
${input.auth.apiKeyEnv}=${shellQuote(input.auth.apiKeyValue)} /opt/homebrew/bin/openclaw agent --local --agent main --session-id parallels-npm-update-macos --message 'Reply with exact ASCII text OK only.' --thinking minimal --json`;
${posixAssertAgentOkScript("/opt/homebrew/bin/openclaw", input, "parallels-npm-update-macos")}`;
}
export function windowsUpdateScript(input: NpmUpdateScriptInput): string {
@@ -142,13 +195,32 @@ if ($LASTEXITCODE -ne 0) {
}
Wait-OpenClawGateway
Invoke-OpenClaw models set ${psSingleQuote(input.auth.modelId)}
${windowsModelProviderTimeoutScript(input.auth.modelId)}
Invoke-OpenClaw config set agents.defaults.skipBootstrap true --strict-json
Invoke-OpenClaw config set tools.profile minimal
$sessionPath = Join-Path $env:USERPROFILE '.openclaw\\agents\\main\\sessions\\parallels-npm-update-windows.jsonl'
Remove-Item $sessionPath -Force -ErrorAction SilentlyContinue
${windowsAgentWorkspaceScript("Parallels npm update smoke test assistant.")}
Set-Item -Path ('Env:' + ${psSingleQuote(input.auth.apiKeyEnv)}) -Value ${psSingleQuote(input.auth.apiKeyValue)}
Invoke-OpenClaw agent --local --agent main --session-id parallels-npm-update-windows --message 'Reply with exact ASCII text OK only.' --thinking minimal --json`;
$agentOk = $false
for ($attempt = 1; $attempt -le 2; $attempt++) {
$sessionId = if ($attempt -eq 1) { 'parallels-npm-update-windows' } else { "parallels-npm-update-windows-retry-$attempt" }
$sessionsDir = Join-Path $env:USERPROFILE '.openclaw\\agents\\main\\sessions'
$sessionPath = Join-Path $sessionsDir "$sessionId.jsonl"
Remove-Item $sessionPath -Force -ErrorAction SilentlyContinue
$output = Invoke-OpenClaw agent --local --agent main --session-id $sessionId --message 'Reply with exact ASCII text OK only.' --thinking minimal --json 2>&1
if ($null -ne $output) { $output | ForEach-Object { $_ } }
if ($LASTEXITCODE -ne 0) { throw "agent failed with exit code $LASTEXITCODE" }
if (($output | Out-String) -match '"finalAssistant(Raw|Visible)Text":\\s*"OK"') {
$agentOk = $true
break
}
if ($attempt -lt 2) {
Write-Host "agent turn attempt $attempt finished without OK response; retrying"
Start-Sleep -Seconds 3
}
}
if (-not $agentOk) { throw 'openclaw agent finished without OK response' }`;
}
export function linuxUpdateScript(input: NpmUpdateScriptInput): string {
@@ -207,10 +279,11 @@ ${posixVersionCheck("openclaw", input.expectedNeedle)}
start_openclaw_gateway
wait_for_gateway
openclaw models set ${shellQuote(input.auth.modelId)}
${posixModelProviderTimeoutCommand("openclaw", input.auth.modelId, "linux")}
openclaw config set agents.defaults.skipBootstrap true --strict-json
openclaw config set tools.profile minimal
${posixAgentWorkspaceScript("Parallels npm update smoke test assistant.")}
${input.auth.apiKeyEnv}=${shellQuote(input.auth.apiKeyValue)} openclaw agent --local --agent main --session-id parallels-npm-update-linux --message 'Reply with exact ASCII text OK only.' --thinking minimal --json`;
${posixAssertAgentOkScript("openclaw", input, "parallels-npm-update-linux")}`;
}
function posixVersionCheck(command: string, expectedNeedle: string): string {

View File

@@ -1,3 +1,5 @@
import { providerIdFromModelId, resolveParallelsModelTimeoutSeconds } from "./provider-auth.ts";
export function psSingleQuote(value: string): string {
return `'${value.replaceAll("'", "''")}'`;
}
@@ -12,6 +14,17 @@ export function encodePowerShell(script: string): string {
);
}
export function windowsModelProviderTimeoutScript(modelId: string): string {
const providerId = providerIdFromModelId(modelId);
if (!providerId) {
return "";
}
return `Invoke-OpenClaw config set ${psSingleQuote(
`models.providers.${providerId}.timeoutSeconds`,
)} ${resolveParallelsModelTimeoutSeconds("windows")} --strict-json
if ($LASTEXITCODE -ne 0) { throw "model provider timeout config set failed" }`;
}
export const windowsOpenClawResolver = String.raw`function Resolve-OpenClawCommand {
if ($script:OpenClawResolvedCommand) { return $script:OpenClawResolvedCommand }
$shimCandidates = @()

View File

@@ -72,6 +72,20 @@ export function resolveWindowsProviderAuth(input: {
return { ...auth, modelId: "openai/gpt-4.1-mini" };
}
export function providerIdFromModelId(modelId: string): string {
const providerId = modelId.split("/", 1)[0]?.trim() ?? "";
return /^[A-Za-z0-9_-]+$/u.test(providerId) ? providerId : "";
}
export function resolveParallelsModelTimeoutSeconds(platform?: Platform): number {
const platformEnv =
platform === undefined
? undefined
: process.env[`OPENCLAW_PARALLELS_${platform.toUpperCase()}_MODEL_TIMEOUT_S`];
const raw = Number(platformEnv || process.env.OPENCLAW_PARALLELS_MODEL_TIMEOUT_S || 600);
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 600;
}
export function parseProvider(value: string): Provider {
if (value === "openai" || value === "anthropic" || value === "minimax") {
return value;

View File

@@ -33,7 +33,12 @@ import { WindowsGuest } from "./guest-transports.ts";
import { runSmokeLane, type SmokeLane, type SmokeLaneStatus } from "./lane-runner.ts";
import { waitForVmStatus } from "./parallels-vm.ts";
import { PhaseRunner } from "./phase-runner.ts";
import { encodePowerShell, psSingleQuote, windowsOpenClawResolver } from "./powershell.ts";
import {
encodePowerShell,
psSingleQuote,
windowsModelProviderTimeoutScript,
windowsOpenClawResolver,
} from "./powershell.ts";
import { ensureGuestGit, prepareMinGitZip } from "./windows-git.ts";
interface WindowsOptions {
@@ -887,6 +892,7 @@ if ($LASTEXITCODE -ne 0) { throw "gateway ${action} failed with exit code $LASTE
$PSNativeCommandUseErrorActionPreference = $false
Invoke-OpenClaw models set ${psSingleQuote(this.auth.modelId)}
if ($LASTEXITCODE -ne 0) { throw "models set failed" }
${windowsModelProviderTimeoutScript(this.auth.modelId)}
Invoke-OpenClaw config set agents.defaults.skipBootstrap true --strict-json
if ($LASTEXITCODE -ne 0) { throw "config set failed" }
Invoke-OpenClaw config set tools.profile minimal