2026-02-14 19:55:04 +00:00
import "./reply.directive.directive-behavior.e2e-mocks.js" ;
2026-01-16 20:11:01 +00:00
import fs from "node:fs/promises" ;
2026-01-14 01:08:15 +00:00
import path from "node:path" ;
2026-02-14 19:55:04 +00:00
import { describe , expect , it , vi } from "vitest" ;
import {
installDirectiveBehaviorE2EHooks ,
runEmbeddedPiAgent ,
withTempHome ,
} from "./reply.directive.directive-behavior.e2e-harness.js" ;
2026-01-14 01:08:15 +00:00
import { getReplyFromConfig } from "./reply.js" ;
2026-01-16 20:11:01 +00:00
async function writeSkill ( params : { workspaceDir : string ; name : string ; description : string } ) {
const { workspaceDir , name , description } = params ;
const skillDir = path . join ( workspaceDir , "skills" , name ) ;
await fs . mkdir ( skillDir , { recursive : true } ) ;
await fs . writeFile (
path . join ( skillDir , "SKILL.md" ) ,
` --- \ nname: ${ name } \ ndescription: ${ description } \ n--- \ n \ n# ${ name } \ n ` ,
"utf-8" ,
) ;
}
2026-01-14 01:08:15 +00:00
describe ( "directive behavior" , ( ) = > {
2026-02-14 19:55:04 +00:00
installDirectiveBehaviorE2EHooks ( ) ;
2026-01-14 01:08:15 +00:00
it ( "accepts /thinking xhigh for codex models" , async ( ) = > {
await withTempHome ( async ( home ) = > {
const storePath = path . join ( home , "sessions.json" ) ;
const res = await getReplyFromConfig (
{
Body : "/thinking xhigh" ,
From : "+1004" ,
To : "+2000" ,
2026-01-17 08:27:52 +00:00
CommandAuthorized : true ,
2026-01-14 01:08:15 +00:00
} ,
{ } ,
{
agents : {
defaults : {
model : "openai-codex/gpt-5.2-codex" ,
2026-01-30 03:15:10 +01:00
workspace : path.join ( home , "openclaw" ) ,
2026-01-14 01:08:15 +00:00
} ,
} ,
2026-01-17 08:27:52 +00:00
channels : { whatsapp : { allowFrom : [ "*" ] } } ,
2026-01-14 01:08:15 +00:00
session : { store : storePath } ,
} ,
) ;
2026-01-14 14:31:43 +00:00
const texts = ( Array . isArray ( res ) ? res : [ res ] ) . map ( ( entry ) = > entry ? . text ) . filter ( Boolean ) ;
2026-01-14 01:08:15 +00:00
expect ( texts ) . toContain ( "Thinking level set to xhigh." ) ;
} ) ;
} ) ;
it ( "accepts /thinking xhigh for openai gpt-5.2" , async ( ) = > {
await withTempHome ( async ( home ) = > {
const storePath = path . join ( home , "sessions.json" ) ;
const res = await getReplyFromConfig (
{
Body : "/thinking xhigh" ,
From : "+1004" ,
To : "+2000" ,
2026-01-17 08:27:52 +00:00
CommandAuthorized : true ,
2026-01-14 01:08:15 +00:00
} ,
{ } ,
{
agents : {
defaults : {
model : "openai/gpt-5.2" ,
2026-01-30 03:15:10 +01:00
workspace : path.join ( home , "openclaw" ) ,
2026-01-14 01:08:15 +00:00
} ,
} ,
2026-01-17 08:27:52 +00:00
channels : { whatsapp : { allowFrom : [ "*" ] } } ,
2026-01-14 01:08:15 +00:00
session : { store : storePath } ,
} ,
) ;
2026-01-14 14:31:43 +00:00
const texts = ( Array . isArray ( res ) ? res : [ res ] ) . map ( ( entry ) = > entry ? . text ) . filter ( Boolean ) ;
2026-01-14 01:08:15 +00:00
expect ( texts ) . toContain ( "Thinking level set to xhigh." ) ;
} ) ;
} ) ;
it ( "rejects /thinking xhigh for non-codex models" , async ( ) = > {
await withTempHome ( async ( home ) = > {
const storePath = path . join ( home , "sessions.json" ) ;
const res = await getReplyFromConfig (
{
Body : "/thinking xhigh" ,
From : "+1004" ,
To : "+2000" ,
2026-01-17 08:27:52 +00:00
CommandAuthorized : true ,
2026-01-14 01:08:15 +00:00
} ,
{ } ,
{
agents : {
defaults : {
model : "openai/gpt-4.1-mini" ,
2026-01-30 03:15:10 +01:00
workspace : path.join ( home , "openclaw" ) ,
2026-01-14 01:08:15 +00:00
} ,
} ,
2026-01-17 08:27:52 +00:00
channels : { whatsapp : { allowFrom : [ "*" ] } } ,
2026-01-14 01:08:15 +00:00
session : { store : storePath } ,
} ,
) ;
2026-01-14 14:31:43 +00:00
const texts = ( Array . isArray ( res ) ? res : [ res ] ) . map ( ( entry ) = > entry ? . text ) . filter ( Boolean ) ;
2026-01-14 01:08:15 +00:00
expect ( texts ) . toContain (
2026-02-13 12:39:22 +01:00
'Thinking level "xhigh" is only supported for openai/gpt-5.2, openai-codex/gpt-5.3-codex, openai-codex/gpt-5.3-codex-spark, openai-codex/gpt-5.2-codex, openai-codex/gpt-5.1-codex, github-copilot/gpt-5.2-codex or github-copilot/gpt-5.2.' ,
2026-01-14 01:08:15 +00:00
) ;
} ) ;
} ) ;
it ( "keeps reserved command aliases from matching after trimming" , async ( ) = > {
await withTempHome ( async ( home ) = > {
const res = await getReplyFromConfig (
{
Body : "/help" ,
From : "+1222" ,
To : "+1222" ,
2026-01-17 08:27:52 +00:00
CommandAuthorized : true ,
2026-01-14 01:08:15 +00:00
} ,
{ } ,
{
agents : {
defaults : {
model : "anthropic/claude-opus-4-5" ,
2026-01-30 03:15:10 +01:00
workspace : path.join ( home , "openclaw" ) ,
2026-01-14 01:08:15 +00:00
models : {
"anthropic/claude-opus-4-5" : { alias : " help " } ,
} ,
} ,
} ,
channels : { whatsapp : { allowFrom : [ "*" ] } } ,
session : { store : path.join ( home , "sessions.json" ) } ,
} ,
) ;
const text = Array . isArray ( res ) ? res [ 0 ] ? . text : res?.text ;
expect ( text ) . toContain ( "Help" ) ;
expect ( runEmbeddedPiAgent ) . not . toHaveBeenCalled ( ) ;
} ) ;
} ) ;
2026-01-16 20:11:01 +00:00
it ( "treats skill commands as reserved for model aliases" , async ( ) = > {
await withTempHome ( async ( home ) = > {
2026-01-30 03:15:10 +01:00
const workspace = path . join ( home , "openclaw" ) ;
2026-01-16 20:11:01 +00:00
await writeSkill ( {
workspaceDir : workspace ,
name : "demo-skill" ,
description : "Demo skill" ,
} ) ;
await getReplyFromConfig (
{
Body : "/demo_skill" ,
From : "+1222" ,
To : "+1222" ,
2026-01-17 08:27:52 +00:00
CommandAuthorized : true ,
2026-01-16 20:11:01 +00:00
} ,
{ } ,
{
agents : {
defaults : {
model : "anthropic/claude-opus-4-5" ,
workspace ,
models : {
"anthropic/claude-opus-4-5" : { alias : "demo_skill" } ,
} ,
} ,
} ,
channels : { whatsapp : { allowFrom : [ "*" ] } } ,
session : { store : path.join ( home , "sessions.json" ) } ,
} ,
) ;
expect ( runEmbeddedPiAgent ) . toHaveBeenCalled ( ) ;
const prompt = vi . mocked ( runEmbeddedPiAgent ) . mock . calls [ 0 ] ? . [ 0 ] ? . prompt ? ? "" ;
expect ( prompt ) . toContain ( 'Use the "demo-skill" skill' ) ;
} ) ;
} ) ;
2026-01-14 01:08:15 +00:00
it ( "errors on invalid queue options" , async ( ) = > {
await withTempHome ( async ( home ) = > {
const res = await getReplyFromConfig (
{
Body : "/queue collect debounce:bogus cap:zero drop:maybe" ,
From : "+1222" ,
To : "+1222" ,
2026-01-17 08:27:52 +00:00
CommandAuthorized : true ,
2026-01-14 01:08:15 +00:00
} ,
{ } ,
{
agents : {
defaults : {
model : "anthropic/claude-opus-4-5" ,
2026-01-30 03:15:10 +01:00
workspace : path.join ( home , "openclaw" ) ,
2026-01-14 01:08:15 +00:00
} ,
} ,
channels : { whatsapp : { allowFrom : [ "*" ] } } ,
session : { store : path.join ( home , "sessions.json" ) } ,
} ,
) ;
const text = Array . isArray ( res ) ? res [ 0 ] ? . text : res?.text ;
expect ( text ) . toContain ( "Invalid debounce" ) ;
expect ( text ) . toContain ( "Invalid cap" ) ;
expect ( text ) . toContain ( "Invalid drop policy" ) ;
expect ( runEmbeddedPiAgent ) . not . toHaveBeenCalled ( ) ;
} ) ;
} ) ;
it ( "shows current queue settings when /queue has no arguments" , async ( ) = > {
await withTempHome ( async ( home ) = > {
const res = await getReplyFromConfig (
{
Body : "/queue" ,
From : "+1222" ,
To : "+1222" ,
Provider : "whatsapp" ,
2026-01-17 08:27:52 +00:00
CommandAuthorized : true ,
2026-01-14 01:08:15 +00:00
} ,
{ } ,
{
agents : {
defaults : {
model : "anthropic/claude-opus-4-5" ,
2026-01-30 03:15:10 +01:00
workspace : path.join ( home , "openclaw" ) ,
2026-01-14 01:08:15 +00:00
} ,
} ,
messages : {
queue : {
mode : "collect" ,
debounceMs : 1500 ,
cap : 9 ,
drop : "summarize" ,
} ,
} ,
channels : { whatsapp : { allowFrom : [ "*" ] } } ,
session : { store : path.join ( home , "sessions.json" ) } ,
} ,
) ;
const text = Array . isArray ( res ) ? res [ 0 ] ? . text : res?.text ;
expect ( text ) . toContain (
"Current queue settings: mode=collect, debounce=1500ms, cap=9, drop=summarize." ,
) ;
expect ( text ) . toContain (
"Options: modes steer, followup, collect, steer+backlog, interrupt; debounce:<ms|s|m>, cap:<n>, drop:old|new|summarize." ,
) ;
expect ( runEmbeddedPiAgent ) . not . toHaveBeenCalled ( ) ;
} ) ;
} ) ;
it ( "shows current think level when /think has no argument" , async ( ) = > {
await withTempHome ( async ( home ) = > {
const res = await getReplyFromConfig (
2026-01-17 08:27:52 +00:00
{ Body : "/think" , From : "+1222" , To : "+1222" , CommandAuthorized : true } ,
2026-01-14 01:08:15 +00:00
{ } ,
{
agents : {
defaults : {
model : "anthropic/claude-opus-4-5" ,
2026-01-30 03:15:10 +01:00
workspace : path.join ( home , "openclaw" ) ,
2026-01-14 01:08:15 +00:00
thinkingDefault : "high" ,
} ,
} ,
session : { store : path.join ( home , "sessions.json" ) } ,
} ,
) ;
const text = Array . isArray ( res ) ? res [ 0 ] ? . text : res?.text ;
expect ( text ) . toContain ( "Current thinking level: high" ) ;
expect ( text ) . toContain ( "Options: off, minimal, low, medium, high." ) ;
expect ( runEmbeddedPiAgent ) . not . toHaveBeenCalled ( ) ;
} ) ;
} ) ;
} ) ;