2026-01-11 10:20:50 +00:00
#!/usr/bin/env bash
set -euo pipefail
2026-01-30 03:15:10 +01:00
INSTALL_URL = " ${ OPENCLAW_INSTALL_URL :- ${ CLAWDBOT_INSTALL_URL :- https : //openclaw.bot/install.sh } } "
MODELS_MODE = " ${ OPENCLAW_E2E_MODELS :- ${ CLAWDBOT_E2E_MODELS :- both } } " # both|openai|anthropic
INSTALL_TAG = " ${ OPENCLAW_INSTALL_TAG :- ${ CLAWDBOT_INSTALL_TAG :- latest } } "
E2E_PREVIOUS_VERSION = " ${ OPENCLAW_INSTALL_E2E_PREVIOUS :- ${ CLAWDBOT_INSTALL_E2E_PREVIOUS :- } } "
SKIP_PREVIOUS = " ${ OPENCLAW_INSTALL_E2E_SKIP_PREVIOUS :- ${ CLAWDBOT_INSTALL_E2E_SKIP_PREVIOUS :- 0 } } "
2026-01-14 00:56:34 +00:00
OPENAI_API_KEY = " ${ OPENAI_API_KEY :- } "
ANTHROPIC_API_KEY = " ${ ANTHROPIC_API_KEY :- } "
ANTHROPIC_API_TOKEN = " ${ ANTHROPIC_API_TOKEN :- } "
2026-01-11 10:20:50 +00:00
if [ [ " $MODELS_MODE " != "both" && " $MODELS_MODE " != "openai" && " $MODELS_MODE " != "anthropic" ] ] ; then
2026-01-30 03:15:10 +01:00
echo "ERROR: OPENCLAW_E2E_MODELS must be one of: both|openai|anthropic" >& 2
2026-01-11 10:20:50 +00:00
exit 2
fi
if [ [ " $MODELS_MODE " = = "both" ] ] ; then
2026-01-14 00:56:34 +00:00
if [ [ -z " $OPENAI_API_KEY " ] ] ; then
2026-01-30 03:15:10 +01:00
echo "ERROR: OPENCLAW_E2E_MODELS=both requires OPENAI_API_KEY." >& 2
2026-01-11 10:20:50 +00:00
exit 2
fi
2026-01-14 00:56:34 +00:00
if [ [ -z " $ANTHROPIC_API_TOKEN " && -z " $ANTHROPIC_API_KEY " ] ] ; then
2026-01-30 03:15:10 +01:00
echo "ERROR: OPENCLAW_E2E_MODELS=both requires ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY." >& 2
2026-01-14 00:56:34 +00:00
exit 2
fi
elif [ [ " $MODELS_MODE " = = "openai" && -z " $OPENAI_API_KEY " ] ] ; then
2026-01-30 03:15:10 +01:00
echo "ERROR: OPENCLAW_E2E_MODELS=openai requires OPENAI_API_KEY." >& 2
2026-01-11 10:20:50 +00:00
exit 2
2026-01-14 00:56:34 +00:00
elif [ [ " $MODELS_MODE " = = "anthropic" && -z " $ANTHROPIC_API_TOKEN " && -z " $ANTHROPIC_API_KEY " ] ] ; then
2026-01-30 03:15:10 +01:00
echo "ERROR: OPENCLAW_E2E_MODELS=anthropic requires ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY." >& 2
2026-01-11 10:20:50 +00:00
exit 2
fi
echo "==> Resolve npm versions"
2026-01-30 03:15:10 +01:00
EXPECTED_VERSION = " $( npm view " openclaw@ ${ INSTALL_TAG } " version) "
2026-01-18 08:29:56 +00:00
if [ [ -z " $EXPECTED_VERSION " || " $EXPECTED_VERSION " = = "undefined" || " $EXPECTED_VERSION " = = "null" ] ] ; then
2026-01-30 03:15:10 +01:00
echo " ERROR: unable to resolve openclaw@ ${ INSTALL_TAG } version " >& 2
2026-01-18 08:29:56 +00:00
exit 2
fi
2026-01-14 00:38:26 +00:00
if [ [ -n " $E2E_PREVIOUS_VERSION " ] ] ; then
PREVIOUS_VERSION = " $E2E_PREVIOUS_VERSION "
else
PREVIOUS_VERSION = " $( node - <<'NODE'
2026-01-11 10:20:50 +00:00
const { execSync } = require( "node:child_process" ) ;
2026-01-30 03:15:10 +01:00
const versions = JSON.parse( execSync( "npm view openclaw versions --json" , { encoding: "utf8" } ) ) ;
2026-01-11 10:20:50 +00:00
if ( !Array.isArray( versions) || versions.length = = = 0) process.exit( 1) ;
process.stdout.write( versions.length >= 2 ? versions[ versions.length - 2] : versions[ 0] ) ;
NODE
2026-01-14 00:38:26 +00:00
) "
fi
2026-01-18 08:29:56 +00:00
echo " expected= $EXPECTED_VERSION previous= $PREVIOUS_VERSION "
2026-01-11 10:20:50 +00:00
2026-01-14 00:38:26 +00:00
if [ [ " $SKIP_PREVIOUS " = = "1" ] ] ; then
2026-01-30 03:15:10 +01:00
echo "==> Skip preinstall previous (OPENCLAW_INSTALL_E2E_SKIP_PREVIOUS=1)"
2026-01-14 00:38:26 +00:00
else
echo "==> Preinstall previous (forces installer upgrade path; avoids read() prompt)"
2026-01-30 03:15:10 +01:00
npm install -g " openclaw@ ${ PREVIOUS_VERSION } "
2026-01-14 00:38:26 +00:00
fi
2026-01-11 10:20:50 +00:00
echo "==> Run official installer one-liner"
2026-01-18 08:29:56 +00:00
if [ [ " $INSTALL_TAG " = = "beta" ] ] ; then
2026-01-30 03:15:10 +01:00
OPENCLAW_BETA = 1 CLAWDBOT_BETA = 1 curl -fsSL " $INSTALL_URL " | bash
2026-01-18 08:29:56 +00:00
elif [ [ " $INSTALL_TAG " != "latest" ] ] ; then
2026-01-30 03:15:10 +01:00
OPENCLAW_VERSION = " $INSTALL_TAG " CLAWDBOT_VERSION = " $INSTALL_TAG " curl -fsSL " $INSTALL_URL " | bash
2026-01-18 08:29:56 +00:00
else
curl -fsSL " $INSTALL_URL " | bash
fi
2026-01-11 10:20:50 +00:00
echo "==> Verify installed version"
2026-01-30 03:15:10 +01:00
INSTALLED_VERSION = " $( openclaw --version 2>/dev/null | head -n 1 | tr -d '\r' ) "
2026-01-18 08:29:56 +00:00
echo " installed= $INSTALLED_VERSION expected= $EXPECTED_VERSION "
if [ [ " $INSTALLED_VERSION " != " $EXPECTED_VERSION " ] ] ; then
2026-01-30 03:15:10 +01:00
echo " ERROR: expected openclaw@ $EXPECTED_VERSION , got openclaw@ $INSTALLED_VERSION " >& 2
2026-01-11 10:20:50 +00:00
exit 1
fi
set_image_model( ) {
local profile = " $1 "
shift
local candidate
for candidate in " $@ " ; do
2026-01-30 03:15:10 +01:00
if openclaw --profile " $profile " models set-image " $candidate " >/dev/null 2>& 1; then
2026-01-11 10:20:50 +00:00
echo " $candidate "
return 0
fi
done
echo " ERROR: could not set an image model (tried: $* ) " >& 2
return 1
}
set_agent_model( ) {
local profile = " $1 "
local candidate
shift
for candidate in " $@ " ; do
2026-01-30 03:15:10 +01:00
if openclaw --profile " $profile " models set " $candidate " >/dev/null 2>& 1; then
2026-01-11 10:20:50 +00:00
echo " $candidate "
return 0
fi
done
echo " ERROR: could not set agent model (tried: $* ) " >& 2
return 1
}
write_png_lr_rg( ) {
local out = " $1 "
node - <<'NODE' " $out "
const fs = require( "node:fs" ) ;
const zlib = require( "node:zlib" ) ;
const out = process.argv[ 2] ;
const width = 96;
const height = 64;
const crcTable = ( ( ) = > {
const table = new Uint32Array( 256) ;
for ( let i = 0; i < 256; i++) {
let c = i;
for ( let k = 0; k < 8; k++) c = ( c & 1) ? ( 0xedb88320 ^ ( c >>> 1) ) : ( c >>> 1) ;
table[ i] = c >>> 0;
}
return table;
} ) ( ) ;
function crc32( buf) {
let c = 0xffffffff;
for ( let i = 0; i < buf.length; i++) c = crcTable[ ( c ^ buf[ i] ) & 0xff] ^ ( c >>> 8) ;
return ( c ^ 0xffffffff) >>> 0;
}
function chunk( type, data) {
const typeBuf = Buffer.from( type, "ascii" ) ;
const len = Buffer.alloc( 4) ;
len.writeUInt32BE( data.length, 0) ;
const crcBuf = Buffer.alloc( 4) ;
crcBuf.writeUInt32BE( crc32( Buffer.concat( [ typeBuf, data] ) ) , 0) ;
return Buffer.concat( [ len, typeBuf, data, crcBuf] ) ;
}
const sig = Buffer.from( [ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] ) ;
const ihdr = Buffer.alloc( 13) ;
ihdr.writeUInt32BE( width, 0) ;
ihdr.writeUInt32BE( height, 4) ;
ihdr[ 8] = 8; // bit depth
ihdr[ 9] = 2; // color type: truecolor
ihdr[ 10] = 0; // compression
ihdr[ 11] = 0; // filter
ihdr[ 12] = 0; // interlace
const rows = [ ] ;
for ( let y = 0; y < height; y++) {
const row = Buffer.alloc( 1 + width * 3) ;
row[ 0] = 0; // filter: none
for ( let x = 0; x < width; x++) {
const i = 1 + x * 3;
const left = x < width / 2;
row[ i + 0] = left ? 255 : 0;
row[ i + 1] = left ? 0 : 255;
row[ i + 2] = 0;
}
rows.push( row) ;
}
const raw = Buffer.concat( rows) ;
const idat = zlib.deflateSync( raw, { level: 9 } ) ;
const png = Buffer.concat( [
sig,
chunk( "IHDR" , ihdr) ,
chunk( "IDAT" , idat) ,
chunk( "IEND" , Buffer.alloc( 0) ) ,
] ) ;
fs.writeFileSync( out, png) ;
NODE
}
run_agent_turn( ) {
local profile = " $1 "
local session_id = " $2 "
local prompt = " $3 "
local out_json = " $4 "
2026-01-30 03:15:10 +01:00
openclaw --profile " $profile " agent \
2026-01-11 10:20:50 +00:00
--session-id " $session_id " \
--message " $prompt " \
--thinking off \
--json >" $out_json "
}
assert_agent_json_has_text( ) {
local path = " $1 "
node - <<'NODE' " $path "
const fs = require( "node:fs" ) ;
const p = JSON.parse( fs.readFileSync( process.argv[ 2] , "utf8" ) ) ;
const payloads =
Array.isArray( p?.result?.payloads) ? p.result.payloads :
Array.isArray( p?.payloads) ? p.payloads :
[ ] ;
const texts = payloads.map( ( x) = > String( x?.text ?? "" ) .trim( ) ) .filter( Boolean) ;
if ( texts.length = = = 0) process.exit( 1) ;
NODE
}
assert_agent_json_ok( ) {
local json_path = " $1 "
local expect_provider = " $2 "
node - <<'NODE' " $json_path " " $expect_provider "
const fs = require( "node:fs" ) ;
const jsonPath = process.argv[ 2] ;
const expectProvider = process.argv[ 3] ;
const p = JSON.parse( fs.readFileSync( jsonPath, "utf8" ) ) ;
if ( typeof p?.status = = = "string" && p.status != = "ok" && p.status != = "accepted" ) {
console.error( ` ERROR: gateway status = ${ p .status } ` ) ;
process.exit( 1) ;
}
const result = p?.result ?? p;
const payloads = Array.isArray( result?.payloads) ? result.payloads : [ ] ;
const anyError = payloads.some( ( pl) = > pl && pl.isError = = = true ) ;
const combinedText = payloads.map( ( pl) = > String( pl?.text ?? "" ) ) .filter( Boolean) .join( "\n" ) .trim( ) ;
if ( anyError) {
console.error( ` ERROR: agent returned error payload: ${ combinedText } ` ) ;
process.exit( 1) ;
}
if ( /rate_limit_error/i.test( combinedText) || /^429\\ b/.test( combinedText) ) {
console.error( ` ERROR: agent rate limited: ${ combinedText } ` ) ;
process.exit( 1) ;
}
const meta = result?.meta;
const provider =
( typeof meta?.agentMeta?.provider = = = "string" && meta.agentMeta.provider.trim( ) ) ||
( typeof meta?.provider = = = "string" && meta.provider.trim( ) ) ||
"" ;
if ( expectProvider && provider && provider != = expectProvider) {
console.error( ` ERROR: expected provider = ${ expectProvider } , got provider = ${ provider } ` ) ;
process.exit( 1) ;
}
NODE
}
2026-01-18 08:33:45 +00:00
extract_matching_text( ) {
2026-01-11 10:20:50 +00:00
local path = " $1 "
2026-01-18 08:33:45 +00:00
local expected = " $2 "
node - <<'NODE' " $path " " $expected "
2026-01-11 10:20:50 +00:00
const fs = require( "node:fs" ) ;
const p = JSON.parse( fs.readFileSync( process.argv[ 2] , "utf8" ) ) ;
2026-01-18 08:33:45 +00:00
const expected = String( process.argv[ 3] ?? "" ) ;
2026-01-11 10:20:50 +00:00
const payloads =
Array.isArray( p?.result?.payloads) ? p.result.payloads :
Array.isArray( p?.payloads) ? p.payloads :
[ ] ;
2026-01-18 08:33:45 +00:00
const texts = payloads.map( ( x) = > String( x?.text ?? "" ) .trim( ) ) .filter( Boolean) ;
const match = texts.find( ( text) = > text = = = expected) ;
process.stdout.write( match ?? texts[ 0] ?? "" ) ;
2026-01-11 10:20:50 +00:00
NODE
}
assert_session_used_tools( ) {
local jsonl = " $1 "
shift
node - <<'NODE' " $jsonl " " $@ "
const fs = require( "node:fs" ) ;
const jsonl = process.argv[ 2] ;
const required = new Set( process.argv.slice( 3) ) ;
const raw = fs.readFileSync( jsonl, "utf8" ) ;
const lines = raw.split( "\n" ) .map( ( l) = > l.trim( ) ) .filter( Boolean) ;
const seen = new Set( ) ;
const toolTypes = new Set( [
"tool_use" ,
"tool_result" ,
"tool" ,
"tool-call" ,
"tool_call" ,
"tooluse" ,
"tool-use" ,
"toolresult" ,
"tool-result" ,
] ) ;
function walk( node, parent) {
if ( !node) return ;
if ( Array.isArray( node) ) {
for ( const item of node) walk( item, node) ;
return ;
}
if ( typeof node != = "object" ) return ;
const obj = node;
const t = typeof obj.type = = = "string" ? obj.type : null;
if ( t && ( toolTypes.has( t) || /tool/i.test( t) ) ) {
const name =
typeof obj.name = = = "string" ? obj.name :
typeof obj.toolName = = = "string" ? obj.toolName :
typeof obj.tool_name = = = "string" ? obj.tool_name :
( obj.tool && typeof obj.tool.name = = = "string" ) ? obj.tool.name :
null;
if ( name) seen.add( name) ;
}
if ( typeof obj.name = = = "string" && typeof obj.input = = = "object" && obj.input) {
2026-01-12 02:49:55 +00:00
// Many tool-use blocks look like { type: "..." , name: "exec" , input: { ...} }
2026-01-11 10:20:50 +00:00
// but some transcripts omit/rename type.
seen.add( obj.name) ;
}
// OpenAI-ish tool call shapes.
if ( Array.isArray( obj.tool_calls) ) {
for ( const c of obj.tool_calls) {
const fn = c?.function;
if ( fn && typeof fn.name = = = "string" ) seen.add( fn.name) ;
}
}
if ( obj.function && typeof obj.function.name = = = "string" ) seen.add( obj.function.name) ;
for ( const v of Object.values( obj) ) walk( v, obj) ;
}
for ( const line of lines) {
try {
const entry = JSON.parse( line) ;
walk( entry, null) ;
} catch {
// ignore unparsable lines
}
}
const missing = [ ...required] .filter( ( t) = > !seen.has( t) ) ;
if ( missing.length > 0) {
console.error( ` Missing tools in transcript: ${ missing .join( ", " ) } ` ) ;
console.error( ` Seen tools: ${ [...seen].sort().join( ", " ) } ` ) ;
console.error( "Transcript head:" ) ;
console.error( lines.slice( 0, 5) .join( "\n" ) ) ;
process.exit( 1) ;
}
NODE
}
run_profile( ) {
local profile = " $1 "
local port = " $2 "
local workspace = " $3 "
local agent_model_provider = " $4 " # "openai"|"anthropic"
2026-01-16 11:46:56 +00:00
echo " ==> Onboard ( $profile ) "
if [ [ " $agent_model_provider " = = "openai" ] ] ; then
2026-01-30 03:15:10 +01:00
openclaw --profile " $profile " onboard \
2026-01-16 11:46:56 +00:00
--non-interactive \
--accept-risk \
--flow quickstart \
--auth-choice openai-api-key \
--openai-api-key " $OPENAI_API_KEY " \
--gateway-port " $port " \
--gateway-bind loopback \
2026-01-11 10:20:50 +00:00
--gateway-auth token \
--workspace " $workspace " \
--skip-health
2026-01-16 11:46:56 +00:00
elif [ [ -n " $ANTHROPIC_API_TOKEN " ] ] ; then
2026-01-30 03:15:10 +01:00
openclaw --profile " $profile " onboard \
2026-01-16 11:46:56 +00:00
--non-interactive \
--accept-risk \
--flow quickstart \
--auth-choice token \
--token-provider anthropic \
--token " $ANTHROPIC_API_TOKEN " \
--gateway-port " $port " \
2026-01-14 00:56:34 +00:00
--gateway-bind loopback \
--gateway-auth token \
--workspace " $workspace " \
--skip-health
2026-01-16 11:46:56 +00:00
else
2026-01-30 03:15:10 +01:00
openclaw --profile " $profile " onboard \
2026-01-16 11:46:56 +00:00
--non-interactive \
--accept-risk \
--flow quickstart \
--auth-choice apiKey \
--anthropic-api-key " $ANTHROPIC_API_KEY " \
--gateway-port " $port " \
--gateway-bind loopback \
2026-01-11 10:20:50 +00:00
--gateway-auth token \
--workspace " $workspace " \
--skip-health
fi
echo " ==> Verify workspace identity files ( $profile ) "
test -f " $workspace /AGENTS.md "
test -f " $workspace /IDENTITY.md "
test -f " $workspace /USER.md "
test -f " $workspace /SOUL.md "
test -f " $workspace /TOOLS.md "
echo " ==> Configure models ( $profile ) "
local agent_model
local image_model
if [ [ " $agent_model_provider " = = "openai" ] ] ; then
agent_model = " $( set_agent_model " $profile " \
"openai/gpt-4.1-mini" \
"openai/gpt-4.1" \
"openai/gpt-4o-mini" \
"openai/gpt-4o" ) "
image_model = " $( set_image_model " $profile " \
"openai/gpt-4.1" \
"openai/gpt-4o-mini" \
"openai/gpt-4o" \
"openai/gpt-4.1-mini" ) "
else
agent_model = " $( set_agent_model " $profile " \
2026-02-05 16:54:44 -05:00
"anthropic/claude-opus-4-6" \
"claude-opus-4-6" \
2026-01-11 10:20:50 +00:00
"anthropic/claude-opus-4-5" \
"claude-opus-4-5" ) "
image_model = " $( set_image_model " $profile " \
2026-02-05 16:54:44 -05:00
"anthropic/claude-opus-4-6" \
"claude-opus-4-6" \
2026-01-11 10:20:50 +00:00
"anthropic/claude-opus-4-5" \
"claude-opus-4-5" ) "
fi
echo " model= $agent_model "
echo " imageModel= $image_model "
echo " ==> Prepare tool fixtures ( $profile ) "
PROOF_TXT = " $workspace /proof.txt "
PROOF_COPY = " $workspace /copy.txt "
HOSTNAME_TXT = " $workspace /hostname.txt "
IMAGE_PNG = " $workspace /proof.png "
IMAGE_TXT = " $workspace /image.txt "
SESSION_ID = " e2e-tools- ${ profile } "
2026-01-30 03:15:10 +01:00
SESSION_JSONL = " /root/.openclaw- ${ profile } /agents/main/sessions/ ${ SESSION_ID } .jsonl "
2026-01-11 10:20:50 +00:00
PROOF_VALUE = " $( node -e 'console.log(require("node:crypto").randomBytes(16).toString("hex"))' ) "
echo -n " $PROOF_VALUE " >" $PROOF_TXT "
write_png_lr_rg " $IMAGE_PNG "
EXPECTED_HOSTNAME = " $( cat /etc/hostname | tr -d '\r\n' ) "
echo " ==> Start gateway ( $profile ) "
GATEWAY_LOG = " $workspace /gateway.log "
2026-01-30 03:15:10 +01:00
openclaw --profile " $profile " gateway --port " $port " --bind loopback >" $GATEWAY_LOG " 2>& 1 &
2026-01-11 10:20:50 +00:00
GATEWAY_PID = " $! "
cleanup_profile( ) {
if kill -0 " $GATEWAY_PID " 2>/dev/null; then
kill " $GATEWAY_PID " 2>/dev/null || true
wait " $GATEWAY_PID " 2>/dev/null || true
fi
}
trap cleanup_profile EXIT
echo " ==> Wait for health ( $profile ) "
for _ in $( seq 1 60) ; do
2026-01-30 03:15:10 +01:00
if openclaw --profile " $profile " health --timeout 2000 --json >/dev/null 2>& 1; then
2026-01-11 10:20:50 +00:00
break
fi
sleep 0.25
done
2026-01-30 03:15:10 +01:00
openclaw --profile " $profile " health --timeout 10000 --json >/dev/null
2026-01-11 10:20:50 +00:00
echo " ==> Agent turns ( $profile ) "
TURN1_JSON = " /tmp/agent- ${ profile } -1.json "
TURN2_JSON = " /tmp/agent- ${ profile } -2.json "
TURN3_JSON = " /tmp/agent- ${ profile } -3.json "
TURN4_JSON = " /tmp/agent- ${ profile } -4.json "
run_agent_turn " $profile " " $SESSION_ID " \
2026-01-12 02:49:55 +00:00
"Use the read tool (not exec) to read proof.txt. Reply with the exact contents only (no extra whitespace)." \
2026-01-11 10:20:50 +00:00
" $TURN1_JSON "
assert_agent_json_has_text " $TURN1_JSON "
assert_agent_json_ok " $TURN1_JSON " " $agent_model_provider "
local reply1
2026-01-18 08:33:45 +00:00
reply1 = " $( extract_matching_text " $TURN1_JSON " " $PROOF_VALUE " | tr -d '\r\n' ) "
2026-01-11 10:20:50 +00:00
if [ [ " $reply1 " != " $PROOF_VALUE " ] ] ; then
echo " ERROR: agent did not read proof.txt correctly ( $profile ): $reply1 " >& 2
exit 1
fi
local prompt2
2026-01-12 02:49:55 +00:00
prompt2 = $'Use the write tool (not exec) to write exactly this string into copy.txt:\n' " ${ reply1 } " $'\nThen use the read tool (not exec) to read copy.txt and reply with the exact contents only (no extra whitespace).'
2026-01-11 10:20:50 +00:00
run_agent_turn " $profile " " $SESSION_ID " " $prompt2 " " $TURN2_JSON "
assert_agent_json_has_text " $TURN2_JSON "
assert_agent_json_ok " $TURN2_JSON " " $agent_model_provider "
local copy_value
copy_value = " $( cat " $PROOF_COPY " 2>/dev/null | tr -d '\r\n' || true ) "
if [ [ " $copy_value " != " $PROOF_VALUE " ] ] ; then
echo " ERROR: copy.txt did not match proof.txt ( $profile ) " >& 2
exit 1
fi
local reply2
2026-01-18 08:33:45 +00:00
reply2 = " $( extract_matching_text " $TURN2_JSON " " $PROOF_VALUE " | tr -d '\r\n' ) "
2026-01-11 10:20:50 +00:00
if [ [ " $reply2 " != " $PROOF_VALUE " ] ] ; then
echo " ERROR: agent did not read copy.txt correctly ( $profile ): $reply2 " >& 2
exit 1
fi
local prompt3
2026-01-12 02:49:55 +00:00
prompt3 = $'Use the exec tool to run: cat /etc/hostname\nThen use the write tool to write the exact stdout (trim trailing newline) into hostname.txt. Reply with the hostname only.'
2026-01-11 10:20:50 +00:00
run_agent_turn " $profile " " $SESSION_ID " " $prompt3 " " $TURN3_JSON "
assert_agent_json_has_text " $TURN3_JSON "
assert_agent_json_ok " $TURN3_JSON " " $agent_model_provider "
if [ [ " $( cat " $HOSTNAME_TXT " 2>/dev/null | tr -d '\r\n' || true ) " != " $EXPECTED_HOSTNAME " ] ] ; then
echo " ERROR: hostname.txt did not match /etc/hostname ( $profile ) " >& 2
exit 1
fi
run_agent_turn " $profile " " $SESSION_ID " \
"Use the image tool on proof.png. Determine which color is on the left half and which is on the right half. Then use the write tool to write exactly: LEFT=RED RIGHT=GREEN into image.txt. Reply with exactly: LEFT=RED RIGHT=GREEN" \
" $TURN4_JSON "
assert_agent_json_has_text " $TURN4_JSON "
assert_agent_json_ok " $TURN4_JSON " " $agent_model_provider "
if [ [ " $( cat " $IMAGE_TXT " 2>/dev/null | tr -d '\r\n' || true ) " != "LEFT=RED RIGHT=GREEN" ] ] ; then
echo " ERROR: image.txt did not contain expected marker ( $profile ) " >& 2
exit 1
fi
local reply4
2026-01-18 08:33:45 +00:00
reply4 = " $( extract_matching_text " $TURN4_JSON " "LEFT=RED RIGHT=GREEN" ) "
2026-01-11 10:20:50 +00:00
if [ [ " $reply4 " != "LEFT=RED RIGHT=GREEN" ] ] ; then
echo " ERROR: agent reply did not contain expected marker ( $profile ): $reply4 " >& 2
exit 1
fi
echo " ==> Verify tool usage via session transcript ( $profile ) "
# Give the gateway a moment to flush transcripts.
sleep 1
if [ [ ! -f " $SESSION_JSONL " ] ] ; then
echo " ERROR: missing session transcript ( $profile ): $SESSION_JSONL " >& 2
2026-01-30 03:15:10 +01:00
ls -la " /root/.openclaw- ${ profile } /agents/main/sessions " >& 2 || true
2026-01-11 10:20:50 +00:00
exit 1
fi
2026-01-12 02:49:55 +00:00
assert_session_used_tools " $SESSION_JSONL " read write exec image
2026-01-11 10:20:50 +00:00
cleanup_profile
trap - EXIT
}
if [ [ " $MODELS_MODE " = = "openai" || " $MODELS_MODE " = = "both" ] ] ; then
2026-01-30 03:15:10 +01:00
run_profile "e2e-openai" "18789" "/tmp/openclaw-e2e-openai" "openai"
2026-01-11 10:20:50 +00:00
fi
if [ [ " $MODELS_MODE " = = "anthropic" || " $MODELS_MODE " = = "both" ] ] ; then
2026-01-30 03:15:10 +01:00
run_profile "e2e-anthropic" "18799" "/tmp/openclaw-e2e-anthropic" "anthropic"
2026-01-11 10:20:50 +00:00
fi
echo "OK"